Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Gas Implementation #639

Merged
merged 20 commits into from
Aug 21, 2020
Merged
13 changes: 12 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion blockchain/blocks/src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ pub struct BlockHeader {
/// aggregate signature of miner in block
#[builder(default)]
bls_aggregate: Option<Signature>,
/// aggregate signature of miner in block
/// the base fee of the parent block
#[builder(default)]
parent_base_fee: TokenAmount,
// CACHE
Expand Down Expand Up @@ -290,6 +290,10 @@ impl BlockHeader {
// Cache should be initialized, otherwise will return default Cid
&self.cached_cid
}
/// Getter for BlockHeader parent_base_fee
pub fn parent_base_fee(&self) -> &BigInt {
&self.parent_base_fee
}
/// Getter for BlockHeader fork_signal
pub fn fork_signal(&self) -> u64 {
self.fork_signal
Expand Down
75 changes: 75 additions & 0 deletions blockchain/chain/src/store/base_fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use blocks::Tipset;
use ipld_blockstore::BlockStore;
use message::Message;
use num_bigint::BigInt;
use std::convert::From;

pub const BLOCK_GAS_LIMIT: i64 = 10_000_000_000;
pub const BLOCK_GAS_TARGET: i64 = (BLOCK_GAS_LIMIT / 2) as i64;
pub const BASE_FEE_MAX_CHANGE_DENOM: i64 = 8; // 12.5%;
pub const INITIAL_BASE_FEE: i64 = 100000000; // Genesis base fee
pub const MINIMUM_BASE_FEE: i64 = 100;

fn compute_next_base_fee(base_fee: &BigInt, gas_limit_used: i64, no_of_blocks: usize) -> BigInt {
let delta = gas_limit_used / no_of_blocks as i64 - BLOCK_GAS_TARGET;
let mut change = base_fee * BigInt::from(delta);
change /= BLOCK_GAS_TARGET;
change /= BASE_FEE_MAX_CHANGE_DENOM;
ec2 marked this conversation as resolved.
Show resolved Hide resolved
let mut next_base_fee = base_fee + change;
if next_base_fee < BigInt::from(MINIMUM_BASE_FEE) {
ec2 marked this conversation as resolved.
Show resolved Hide resolved
next_base_fee = BigInt::from(MINIMUM_BASE_FEE);
}
next_base_fee
}
ec2 marked this conversation as resolved.
Show resolved Hide resolved

pub fn compute_base_fee<DB>(db: &DB, ts: &Tipset) -> Result<BigInt, crate::Error>
where
DB: BlockStore,
{
let mut total_limit = 0;
for b in ts.blocks() {
let (msg1, msg2) = crate::block_messages(db, &b)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be nice to have a function like block_messages which returns ChainMessage because this is one of the cases where we don't care if it is a signed or unsigned message that is returned. We are just worrying about the gas_limit on that particular message.

Edit : This is not necessary to change btw. Was just a comment in passing

for m in msg1 {
total_limit += m.gas_limit();
}
for m in msg2 {
total_limit += m.gas_limit();
}
}
ec2 marked this conversation as resolved.
Show resolved Hide resolved
let parent_base_fee = ts.blocks()[0].parent_base_fee();
ec2 marked this conversation as resolved.
Show resolved Hide resolved
Ok(compute_next_base_fee(
parent_base_fee,
total_limit,
ts.blocks().len(),
))
}

#[cfg(test)]
mod tests {
use super::*;

fn construct_tests() -> Vec<(i64, i64, usize, i64)> {
// (base_fee, limit_used, no_of_blocks, output)
let mut cases = Vec::new();
cases.push((100_000_000, 0, 1, 87_500_000));
cases.push((100_000_000, 0, 5, 87_500_000));
cases.push((100_000_000, BLOCK_GAS_TARGET, 1, 100_000_000));
cases.push((100_000_000, BLOCK_GAS_TARGET * 2, 2, 100_000_000));
cases.push((100_000_000, BLOCK_GAS_LIMIT * 2, 2, 112_500_000));
cases.push((100_000_000, BLOCK_GAS_LIMIT * 15 / 10, 2, 106_250_000));
cases
}

#[test]
fn run_base_fee_tests() {
let cases = construct_tests();

for case in cases {
let output = compute_next_base_fee(&case.0.into(), case.1, case.2);
assert_eq!(BigInt::from(case.3), output);
}
}
}
2 changes: 2 additions & 0 deletions blockchain/chain/src/store/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

pub mod base_fee;
mod chain_store;
mod errors;
mod tip_index;

pub use self::base_fee::*;
pub use self::chain_store::*;
pub use self::errors::*;
pub use self::tip_index::*;
15 changes: 14 additions & 1 deletion blockchain/chain_sync/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,19 @@ where
Err(err) => error_vec.push(err.to_string()),
}

// base fee check
let base_fee = chain::compute_base_fee(self.chain_store.db.as_ref(), &parent_tipset)
.map_err(|e| {
Error::Validation(format!("Could not compute base fee: {}", e.to_string()))
})?;
if &base_fee != block.header().parent_base_fee() {
error_vec.push(format!(
"base fee doesnt match: {} (header), {} (computed)",
block.header().parent_base_fee(),
base_fee
));
}

let slash = self
.state_manager
.is_miner_slashed(header.miner_address(), &parent_tipset.parent_state())
Expand Down Expand Up @@ -1096,7 +1109,7 @@ mod tests {
let (bls, secp) = construct_messages();

let expected_root =
Cid::from_raw_cid("bafy2bzacebx7t56l6urh4os4kzar5asc5hmbhl7so6sfkzcgpjforkwylmqxa")
Cid::from_raw_cid("bafy2bzaceasssikoiintnok7f3sgnekfifarzobyr3r4f25sgxmn23q4c35ic")
.unwrap();

let root = compute_msg_meta(cs.chain_store.blockstore(), &[bls], &[secp]).unwrap();
Expand Down
27 changes: 20 additions & 7 deletions blockchain/message_pool/src/msgpool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,16 @@ impl MsgSet {
}
if let Some(exms) = self.msgs.get(&m.sequence()) {
if m.cid()? != exms.cid()? {
let gas_price = exms.message().gas_price();
let premium = exms.message().gas_premium();
let rbf_num = BigInt::from(RBF_NUM);
let rbf_denom = BigInt::from(RBF_DENOM);
let min_price = gas_price.clone() + ((gas_price * &rbf_num) / rbf_denom) + 1u8;
if m.message().gas_price() <= &min_price {
warn!("mesage gas price is below min gas price");
let min_price = premium + ((premium * &rbf_num) / rbf_denom) + 1u8;
if m.message().gas_premium() <= &min_price {
warn!("message gas price is below min gas price");
return Err(Error::GasPriceTooLow);
}
} else {
warn!("try to add message with duplicate sequence");
warn!("try to add message with duplicate sequence increase gas premium");
return Err(Error::DuplicateSequence);
}
}
Expand Down Expand Up @@ -91,6 +91,8 @@ pub trait Provider {
fn messages_for_tipset(&self, h: &Tipset) -> Result<Vec<UnsignedMessage>, Error>;
/// Return a tipset given the tipset keys from the ChainStore
fn load_tipset(&self, tsk: &TipsetKeys) -> Result<Tipset, Error>;
/// Computes the base fee
fn chain_compute_base_fee(&self, ts: &Tipset) -> Result<BigInt, Error>;
}

/// This is the mpool provider struct that will let us access and add messages to messagepool.
Expand Down Expand Up @@ -154,6 +156,9 @@ where
fn load_tipset(&self, tsk: &TipsetKeys) -> Result<Tipset, Error> {
self.cs.tipset_from_keys(tsk).map_err(|err| err.into())
}
fn chain_compute_base_fee(&self, ts: &Tipset) -> Result<BigInt, Error> {
chain::compute_base_fee(self.cs.blockstore(), ts).map_err(|err| err.into())
}
}

/// This is the Provider implementation that will be used for the mpool RPC
Expand Down Expand Up @@ -219,6 +224,9 @@ where
let ts = chain::tipset_from_keys(self.db.as_ref(), tsk)?;
Ok(ts)
}
fn chain_compute_base_fee(&self, ts: &Tipset) -> Result<BigInt, Error> {
chain::compute_base_fee(self.db.as_ref(), ts).map_err(|err| err.into())
}
}

/// This is the main MessagePool struct
Expand Down Expand Up @@ -468,7 +476,7 @@ where

/// Return a Vector of signed messages for a given from address. This vector will be sorted by
/// each messsage's sequence. If no corresponding messages found, return None result type
async fn pending_for(&self, a: &Address) -> Option<Vec<SignedMessage>> {
pub async fn pending_for(&self, a: &Address) -> Option<Vec<SignedMessage>> {
let pending = self.pending.read().await;
let mset = pending.get(a)?;
if mset.msgs.is_empty() {
Expand Down Expand Up @@ -504,14 +512,15 @@ where

/// Return gas price estimate this has been translated from lotus, a more smart implementation will
/// most likely need to be implemented
pub fn estimate_gas_price(
pub fn estimate_gas_premium(
&self,
nblocksincl: u64,
_sender: Address,
_gas_limit: u64,
_tsk: TipsetKeys,
) -> Result<BigInt, Error> {
// TODO possibly come up with a smarter way to estimate the gas price
// TODO a smarter way exists now
let min_gas_price = 0;
match nblocksincl {
0 => Ok(BigInt::from(min_gas_price + 2)),
Expand Down Expand Up @@ -827,6 +836,10 @@ pub mod test_provider {
}
Err(Errors::InvalidToAddr)
}

fn chain_compute_base_fee(&self, _ts: &Tipset) -> Result<BigInt, Error> {
Ok(100.into())
}
}

pub fn create_header(weight: u64, parent_bz: &[u8], cached_bytes: &[u8]) -> BlockHeader {
Expand Down
Loading