Skip to content

Commit

Permalink
Dynamic Gas Implementation (#639)
Browse files Browse the repository at this point in the history
* finally fixed compiler stuff

* accidentally committed some files

* tests

* some gas api

* add gas api

* base fee validation in sync

* connect to rpc

* fix clippy

* add license

* Cleanup and fix tests

* Cleanup bigint clones

* suggestions

* update basefee

* dont duplicate base fee computations

* fix more suggestions

* lint

Co-authored-by: austinabell <austinabell8@gmail.com>
  • Loading branch information
ec2 and austinabell authored Aug 21, 2020
1 parent 12ea58c commit bbd20cc
Show file tree
Hide file tree
Showing 22 changed files with 674 additions and 84 deletions.
12 changes: 12 additions & 0 deletions 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
1 change: 1 addition & 0 deletions blockchain/chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ byteorder = "1.3.4"
beacon = { path = "../beacon" }
flo_stream = "0.4.0"
address = { package = "forest_address", path = "../../vm/address" }
lazy_static = "1.4"

[dev-dependencies]
multihash = "0.10.0"
Expand Down
3 changes: 3 additions & 0 deletions blockchain/chain/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

#[macro_use]
extern crate lazy_static;

mod store;

pub use self::store::*;
100 changes: 100 additions & 0 deletions blockchain/chain/src/store/base_fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use blocks::Tipset;
use encoding::Cbor;
use ipld_blockstore::BlockStore;
use message::Message;
use num_bigint::BigInt;
use std::collections::HashSet;

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 PACKING_EFFICIENCY_DENOM: i64 = 5;
pub const PACKING_EFFICIENCY_NUM: i64 = 4;
lazy_static! {
/// Cbor bytes of an empty array serialized.
pub static ref MINIMUM_BASE_FEE: BigInt = 100.into();

}

fn compute_next_base_fee(base_fee: &BigInt, gas_limit_used: i64, no_of_blocks: usize) -> BigInt {
let mut delta = (PACKING_EFFICIENCY_DENOM * gas_limit_used
/ (no_of_blocks as i64 * PACKING_EFFICIENCY_NUM))
- BLOCK_GAS_TARGET;
// cap change at 12.5% (BaseFeeMaxChangeDenom) by capping delta
if delta > BLOCK_GAS_TARGET {
delta = BLOCK_GAS_TARGET
}
if delta < -BLOCK_GAS_TARGET {
delta = -BLOCK_GAS_TARGET
}

let change: BigInt = ((base_fee * delta) / BLOCK_GAS_TARGET) / BASE_FEE_MAX_CHANGE_DENOM;
let mut next_base_fee = base_fee + change;
if next_base_fee < *MINIMUM_BASE_FEE {
next_base_fee = MINIMUM_BASE_FEE.clone();
}
next_base_fee
}

pub fn compute_base_fee<DB>(db: &DB, ts: &Tipset) -> Result<BigInt, crate::Error>
where
DB: BlockStore,
{
let mut total_limit = 0;
let mut seen = HashSet::new();

for b in ts.blocks() {
let (msg1, msg2) = crate::block_messages(db, &b)?;
for m in msg1 {
let m_cid = m.cid()?;
if !seen.contains(&m_cid) {
total_limit += m.gas_limit();
seen.insert(m_cid);
}
}
for m in msg2 {
let m_cid = m.cid()?;
if !seen.contains(&m_cid) {
total_limit += m.gas_limit();
seen.insert(m_cid);
}
}
}
let parent_base_fee = ts.blocks()[0].parent_base_fee();
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, 103_125_000));
cases.push((100_000_000, BLOCK_GAS_TARGET * 2, 2, 103_125_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, 110_937_500));
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 @@ -1098,7 +1111,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

0 comments on commit bbd20cc

Please sign in to comment.