diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index e2dfc9c91..01035e35d 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -254,11 +254,13 @@ impl StorageInner { transactions: &Vec, chain_spec: Arc, ) -> Header { + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); + // check previous block for base fee let base_fee_per_gas = self .headers .get(&self.best_block) - .and_then(|parent| parent.next_block_base_fee(chain_spec.base_fee_params)); + .and_then(|parent| parent.next_block_base_fee(chain_spec.base_fee_params(timestamp))); let mut header = Header { parent_hash: self.best_hash, @@ -273,7 +275,7 @@ impl StorageInner { number: self.best_block + 1, gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, gas_used: 0, - timestamp: SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(), + timestamp, mix_hash: Default::default(), nonce: 0, base_fee_per_gas, diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 8ba4e535f..a2fce109e 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -263,7 +263,8 @@ fn check_gas_limit( // By consensus, gas_limit is multiplied by elasticity (*2) on // on exact block that hardfork happens. if chain_spec.fork(Hardfork::London).transitions_at_block(child.number) { - parent_gas_limit = parent.gas_limit * chain_spec.base_fee_params.elasticity_multiplier; + parent_gas_limit = + parent.gas_limit * chain_spec.base_fee_params(child.timestamp).elasticity_multiplier; } if child.gas_limit > parent_gas_limit { @@ -336,7 +337,7 @@ pub fn validate_header_regarding_parent( } else { // This BaseFeeMissing will not happen as previous blocks are checked to have them. parent - .next_block_base_fee(chain_spec.base_fee_params) + .next_block_base_fee(chain_spec.base_fee_params(child.timestamp)) .ok_or(ConsensusError::BaseFeeMissing)? }; if expected_base_fee != base_fee { diff --git a/crates/payload/builder/src/payload.rs b/crates/payload/builder/src/payload.rs index 919c0705a..57c4c82b0 100644 --- a/crates/payload/builder/src/payload.rs +++ b/crates/payload/builder/src/payload.rs @@ -251,7 +251,9 @@ impl PayloadBuilderAttributes { gas_limit: U256::from(parent.gas_limit), // calculate basefee based on parent block's gas usage basefee: U256::from( - parent.next_block_base_fee(chain_spec.base_fee_params).unwrap_or_default(), + parent + .next_block_base_fee(chain_spec.base_fee_params(self.timestamp)) + .unwrap_or_default(), ), // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, diff --git a/crates/primitives/src/chain/mod.rs b/crates/primitives/src/chain/mod.rs index c78d365fd..ac016b4f1 100644 --- a/crates/primitives/src/chain/mod.rs +++ b/crates/primitives/src/chain/mod.rs @@ -13,8 +13,9 @@ use strum::{AsRefStr, EnumCount, EnumIter, EnumString, EnumVariantNames}; // The chain spec module. mod spec; pub use spec::{ - AllGenesisFormats, BaseFeeParams, ChainSpec, ChainSpecBuilder, DisplayHardforks, ForkCondition, - ForkTimestamps, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, + AllGenesisFormats, BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, + DisplayHardforks, ForkBaseFeeParams, ForkCondition, ForkTimestamps, DEV, GOERLI, HOLESKY, + MAINNET, SEPOLIA, }; #[cfg(feature = "optimism")] diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 746b043cd..a1d5d96a8 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -63,7 +63,7 @@ pub static MAINNET: Lazy> = Lazy::new(|| { 11052984, b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"), )), - base_fee_params: BaseFeeParams::ethereum(), + base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 3500, snapshot_block_interval: 500_000, } @@ -106,7 +106,7 @@ pub static GOERLI: Lazy> = Lazy::new(|| { 4367322, b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"), )), - base_fee_params: BaseFeeParams::ethereum(), + base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 1700, snapshot_block_interval: 1_000_000, } @@ -153,8 +153,7 @@ pub static SEPOLIA: Lazy> = Lazy::new(|| { 1273020, b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"), )), - - base_fee_params: BaseFeeParams::ethereum(), + base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 1700, snapshot_block_interval: 1_000_000, } @@ -196,7 +195,7 @@ pub static HOLESKY: Lazy> = Lazy::new(|| { 0, b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"), )), - base_fee_params: BaseFeeParams::ethereum(), + base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 1700, snapshot_block_interval: 1_000_000, } @@ -236,6 +235,7 @@ pub static DEV: Lazy> = Lazy::new(|| { ), (Hardfork::Shanghai, ForkCondition::Timestamp(0)), ]), + base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), deposit_contract: None, // TODO: do we even have? ..Default::default() } @@ -277,7 +277,13 @@ pub static OP_GOERLI: Lazy> = Lazy::new(|| { (Hardfork::Shanghai, ForkCondition::Timestamp(1699981200)), (Hardfork::Canyon, ForkCondition::Timestamp(1699981200)), ]), - base_fee_params: BaseFeeParams::optimism(), + base_fee_params: BaseFeeParamsKind::Variable( + vec![ + (Hardfork::London, BaseFeeParams::optimism_goerli()), + (Hardfork::Canyon, BaseFeeParams::optimism_goerli_canyon()), + ] + .into(), + ), prune_delete_limit: 1700, snapshot_block_interval: 1_000_000, ..Default::default() @@ -320,7 +326,13 @@ pub static BASE_GOERLI: Lazy> = Lazy::new(|| { (Hardfork::Shanghai, ForkCondition::Timestamp(1699981200)), (Hardfork::Canyon, ForkCondition::Timestamp(1699981200)), ]), - base_fee_params: BaseFeeParams::optimism_goerli(), + base_fee_params: BaseFeeParamsKind::Variable( + vec![ + (Hardfork::London, BaseFeeParams::optimism_goerli()), + (Hardfork::Canyon, BaseFeeParams::optimism_goerli_canyon()), + ] + .into(), + ), prune_delete_limit: 1700, snapshot_block_interval: 1_000_000, ..Default::default() @@ -361,7 +373,13 @@ pub static BASE_MAINNET: Lazy> = Lazy::new(|| { (Hardfork::Bedrock, ForkCondition::Block(0)), (Hardfork::Regolith, ForkCondition::Timestamp(0)), ]), - base_fee_params: BaseFeeParams::optimism(), + base_fee_params: BaseFeeParamsKind::Variable( + vec![ + (Hardfork::London, BaseFeeParams::optimism()), + (Hardfork::Canyon, BaseFeeParams::optimism_canyon()), + ] + .into(), + ), prune_delete_limit: 1700, snapshot_block_interval: 1_000_000, ..Default::default() @@ -369,6 +387,41 @@ pub static BASE_MAINNET: Lazy> = Lazy::new(|| { .into() }); +/// A wrapper around [BaseFeeParams] that allows for specifying constant or dynamic EIP-1559 +/// parameters based on the active [Hardfork]. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum BaseFeeParamsKind { + /// Constant [BaseFeeParams]; used for chains that don't have dynamic EIP-1559 parameters + Constant(BaseFeeParams), + /// Variable [BaseFeeParams]; used for chains that have dynamic EIP-1559 parameters like + /// Optimism + Variable(ForkBaseFeeParams), +} + +impl From for BaseFeeParamsKind { + fn from(params: BaseFeeParams) -> Self { + BaseFeeParamsKind::Constant(params) + } +} + +impl From for BaseFeeParamsKind { + fn from(params: ForkBaseFeeParams) -> Self { + BaseFeeParamsKind::Variable(params) + } +} + +/// A type alias to a vector of tuples of [Hardfork] and [BaseFeeParams], sorted by [Hardfork] +/// activation order. This is used to specify dynamic EIP-1559 parameters for chains like Optimism. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ForkBaseFeeParams(Vec<(Hardfork, BaseFeeParams)>); + +impl From> for ForkBaseFeeParams { + fn from(params: Vec<(Hardfork, BaseFeeParams)>) -> Self { + ForkBaseFeeParams(params) + } +} + /// BaseFeeParams contains the config parameters that control block base fee computation #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] pub struct BaseFeeParams { @@ -398,6 +451,17 @@ impl BaseFeeParams { } } + /// Get the base fee parameters for optimism goerli (post Canyon) + #[cfg(feature = "optimism")] + pub const fn optimism_goerli_canyon() -> BaseFeeParams { + BaseFeeParams { + max_change_denominator: + crate::constants::OP_GOERLI_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, + elasticity_multiplier: + crate::constants::OP_GOERLI_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, + } + } + /// Get the base fee parameters for optimism mainnet #[cfg(feature = "optimism")] pub const fn optimism() -> BaseFeeParams { @@ -408,6 +472,17 @@ impl BaseFeeParams { crate::constants::OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, } } + + /// Get the base fee parameters for optimism mainnet (post Canyon) + #[cfg(feature = "optimism")] + pub const fn optimism_canyon() -> BaseFeeParams { + BaseFeeParams { + max_change_denominator: + crate::constants::OP_MAINNET_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON, + elasticity_multiplier: + crate::constants::OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER, + } + } } /// An Ethereum chain specification. @@ -450,7 +525,7 @@ pub struct ChainSpec { pub deposit_contract: Option, /// The parameters that configure how a block's base fee is computed - pub base_fee_params: BaseFeeParams, + pub base_fee_params: BaseFeeParamsKind, /// The delete limit for pruner, per block. In the actual pruner run it will be multiplied by /// the amount of blocks between pruner runs to account for the difference in amount of new @@ -472,7 +547,7 @@ impl Default for ChainSpec { fork_timestamps: Default::default(), hardforks: Default::default(), deposit_contract: Default::default(), - base_fee_params: BaseFeeParams::ethereum(), + base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: MAINNET.prune_delete_limit, snapshot_block_interval: Default::default(), } @@ -559,6 +634,25 @@ impl ChainSpec { (self.fork(Hardfork::London).active_at_block(0)).then_some(genesis_base_fee) } + /// Get the [BaseFeeParams] for the chain at the given timestamp. + pub fn base_fee_params(&self, timestamp: u64) -> BaseFeeParams { + match self.base_fee_params { + BaseFeeParamsKind::Constant(bf_params) => bf_params, + BaseFeeParamsKind::Variable(ForkBaseFeeParams { 0: ref bf_params }) => { + // Walk through the base fee params configuration in reverse order, and return the + // first one that corresponds to a hardfork that is active at the + // given timestamp. + for (fork, params) in bf_params.iter().rev() { + if self.is_fork_active_at_timestamp(*fork, timestamp) { + return *params + } + } + + bf_params.first().map(|(_, params)| *params).unwrap_or(BaseFeeParams::ethereum()) + } + } + } + /// Get the hash of the genesis block. pub fn genesis_hash(&self) -> B256 { if let Some(hash) = self.genesis_hash { diff --git a/crates/primitives/src/constants/mod.rs b/crates/primitives/src/constants/mod.rs index 2e3c691d9..9d2b31855 100644 --- a/crates/primitives/src/constants/mod.rs +++ b/crates/primitives/src/constants/mod.rs @@ -67,6 +67,11 @@ pub const EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 2; #[cfg(feature = "optimism")] pub const OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 50; +/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism Canyon +/// hardfork. +#[cfg(feature = "optimism")] +pub const OP_MAINNET_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON: u64 = 250; + /// Base fee max change denominator for Optimism Mainnet as defined in the Optimism /// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. #[cfg(feature = "optimism")] @@ -77,6 +82,11 @@ pub const OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 6; #[cfg(feature = "optimism")] pub const OP_GOERLI_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 50; +/// Base fee max change denominator for Optimism Goerli as defined in the Optimism Canyon +/// hardfork. +#[cfg(feature = "optimism")] +pub const OP_GOERLI_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_CANYON: u64 = 250; + /// Base fee max change denominator for Optimism Goerli as defined in the Optimism /// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc. #[cfg(feature = "optimism")] diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 63ad9c14a..2f40f2a19 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -55,9 +55,9 @@ pub use block::{ }; pub use bytes::{self, Buf, BufMut, BytesMut}; pub use chain::{ - AllGenesisFormats, BaseFeeParams, Chain, ChainInfo, ChainSpec, ChainSpecBuilder, - DisplayHardforks, ForkCondition, ForkTimestamps, NamedChain, DEV, GOERLI, HOLESKY, MAINNET, - SEPOLIA, + AllGenesisFormats, BaseFeeParams, BaseFeeParamsKind, Chain, ChainInfo, ChainSpec, + ChainSpecBuilder, DisplayHardforks, ForkBaseFeeParams, ForkCondition, ForkTimestamps, + NamedChain, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, }; #[cfg(feature = "optimism")] pub use chain::{BASE_GOERLI, BASE_MAINNET, OP_GOERLI}; diff --git a/crates/rpc/rpc/src/eth/api/fees.rs b/crates/rpc/rpc/src/eth/api/fees.rs index 481a8d01a..f8d435871 100644 --- a/crates/rpc/rpc/src/eth/api/fees.rs +++ b/crates/rpc/rpc/src/eth/api/fees.rs @@ -130,11 +130,18 @@ where } } let last_entry = fee_entries.last().expect("is not empty"); + + let last_entry_timestamp = self + .provider() + .header_by_hash_or_number(last_entry.header_hash.into())? + .map(|h| h.timestamp) + .unwrap_or_default(); + base_fee_per_gas.push(U256::from(calculate_next_block_base_fee( last_entry.gas_used, last_entry.gas_limit, last_entry.base_fee_per_gas, - self.provider().chain_spec().base_fee_params, + self.provider().chain_spec().base_fee_params(last_entry_timestamp), ))); } else { // read the requested header range @@ -183,7 +190,7 @@ where last_header.gas_used, last_header.gas_limit, last_header.base_fee_per_gas.unwrap_or_default(), - self.provider().chain_spec().base_fee_params, + self.provider().chain_spec().base_fee_params(last_header.timestamp), ))); }; diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index 96830b9f3..7e2cd00f8 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -277,7 +277,8 @@ where latest.timestamp += 12; // base fee of the child block let chain_spec = self.provider().chain_spec(); - latest.base_fee_per_gas = latest.next_block_base_fee(chain_spec.base_fee_params); + latest.base_fee_per_gas = + latest.next_block_base_fee(chain_spec.base_fee_params(latest.timestamp)); PendingBlockEnvOrigin::DerivedFromLatest(latest) }; diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 4f834de4d..5bd8a985f 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -92,7 +92,7 @@ pub async fn maintain_transaction_pool( last_seen_block_hash: latest.hash, last_seen_block_number: latest.number, pending_basefee: latest - .next_block_base_fee(chain_spec.base_fee_params) + .next_block_base_fee(chain_spec.base_fee_params(latest.timestamp + 12)) .unwrap_or_default(), pending_blob_fee: latest.next_block_blob_fee(), }; @@ -238,8 +238,9 @@ pub async fn maintain_transaction_pool( let chain_spec = client.chain_spec(); // fees for the next block: `new_tip+1` - let pending_block_base_fee = - new_tip.next_block_base_fee(chain_spec.base_fee_params).unwrap_or_default(); + let pending_block_base_fee = new_tip + .next_block_base_fee(chain_spec.base_fee_params(new_tip.timestamp + 12)) + .unwrap_or_default(); let pending_block_blob_fee = new_tip.next_block_blob_fee(); // we know all changed account in the new chain @@ -342,8 +343,9 @@ pub async fn maintain_transaction_pool( let chain_spec = client.chain_spec(); // fees for the next block: `tip+1` - let pending_block_base_fee = - tip.next_block_base_fee(chain_spec.base_fee_params).unwrap_or_default(); + let pending_block_base_fee = tip + .next_block_base_fee(chain_spec.base_fee_params(tip.timestamp + 12)) + .unwrap_or_default(); let pending_block_blob_fee = tip.next_block_blob_fee(); let first_block = blocks.first();