From 210323bb301ed6fe6aa2dc59aa8625a34f87b14b Mon Sep 17 00:00:00 2001 From: rose2221 Date: Mon, 9 Jun 2025 00:11:26 +0530 Subject: [PATCH 01/44] added-feature --- crates/chainspec/src/api.rs | 16 ++++++++++-- crates/chainspec/tests/base_fee.rs | 26 +++++++++++++++++++ crates/ethereum/evm/src/lib.rs | 9 +++---- crates/optimism/chainspec/src/lib.rs | 12 +++------ .../optimism/consensus/src/validation/mod.rs | 11 ++------ crates/optimism/evm/src/lib.rs | 7 +++-- crates/rpc/rpc-builder/src/lib.rs | 7 +++-- crates/rpc/rpc-eth-api/src/core.rs | 4 +++ crates/rpc/rpc-eth-api/src/helpers/fee.rs | 13 +++------- crates/rpc/rpc/src/eth/core.rs | 10 +++---- crates/transaction-pool/src/maintain.rs | 5 ++-- 11 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 crates/chainspec/tests/base_fee.rs diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 79cf2233582..8677f1b0fcd 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -1,19 +1,20 @@ use crate::{ChainSpec, DepositContract}; use alloc::{boxed::Box, vec::Vec}; use alloy_chains::Chain; -use alloy_consensus::Header; +use alloy_consensus::{BlockHeader, Header}; use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_genesis::Genesis; use alloy_primitives::{B256, U256}; use core::fmt::{Debug, Display}; use reth_ethereum_forks::EthereumHardforks; use reth_network_peers::NodeRecord; +use reth_primitives_traits::AlloyBlockHeader; /// Trait representing type configuring a chain spec. #[auto_impl::auto_impl(&, Arc)] pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// The header type of the network. - type Header; + type Header: BlockHeader + AlloyBlockHeader; /// Returns the [`Chain`] object this spec targets. fn chain(&self) -> Chain; @@ -65,6 +66,17 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// Returns the final total difficulty if the Paris hardfork is known. fn final_paris_total_difficulty(&self) -> Option; + + /// Calculate the EIP-1559 base fee for the next block from the given parent header. + fn next_block_base_fee(&self, parent: &H) -> u64 + where + Self: Sized, + H: BlockHeader + AlloyBlockHeader, + { + parent + .next_block_base_fee(self.base_fee_params_at_timestamp(parent.timestamp())) + .unwrap_or_default() + } } impl EthChainSpec for ChainSpec { diff --git a/crates/chainspec/tests/base_fee.rs b/crates/chainspec/tests/base_fee.rs new file mode 100644 index 00000000000..e5ab42de477 --- /dev/null +++ b/crates/chainspec/tests/base_fee.rs @@ -0,0 +1,26 @@ +use reth_chainspec::{ChainSpec, EthChainSpec}; + +use alloy_consensus::{BlockHeader, Header}; +use alloy_eips::eip1559::{BaseFeeParams, INITIAL_BASE_FEE}; +fn parent_header() -> Header { + Header { + gas_used: 15_000_000, + gas_limit: 30_000_000, + base_fee_per_gas: Some(INITIAL_BASE_FEE), + timestamp: 1_000, + ..Default::default() + } +} + +#[test] +fn default_chain_spec_base_fee_matches_formula() { + let spec = ChainSpec::default(); + let parent = parent_header(); + + let expected = parent + .next_block_base_fee(spec.base_fee_params_at_timestamp(parent.timestamp)) + .unwrap_or_default(); + + let got = spec.next_block_base_fee(&parent); + assert_eq!(expected, got, "Base fee calculation does not match expected value"); +} diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index ad77ae74ea4..9f1deea5f8b 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -212,10 +212,7 @@ where BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice } }); - let mut basefee = parent.next_block_base_fee( - self.chain_spec().base_fee_params_at_timestamp(attributes.timestamp), - ); - + let mut basefee = chain_spec.next_block_base_fee(&parent); let mut gas_limit = attributes.gas_limit; // If we are on the London fork boundary, we need to multiply the parent's gas limit by the @@ -231,7 +228,7 @@ where gas_limit *= elasticity_multiplier as u64; // set the base fee to the initial base fee from the EIP-1559 spec - basefee = Some(INITIAL_BASE_FEE) + basefee = INITIAL_BASE_FEE as u64; } let block_env = BlockEnv { @@ -242,7 +239,7 @@ where prevrandao: Some(attributes.prev_randao), gas_limit, // calculate basefee based on parent block's gas usage - basefee: basefee.unwrap_or_default(), + basefee, // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; diff --git a/crates/optimism/chainspec/src/lib.rs b/crates/optimism/chainspec/src/lib.rs index e0d94bcd367..957f4a2bafd 100644 --- a/crates/optimism/chainspec/src/lib.rs +++ b/crates/optimism/chainspec/src/lib.rs @@ -734,9 +734,7 @@ mod tests { genesis.hash_slow(), b256!("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd") ); - let base_fee = genesis - .next_block_base_fee(BASE_MAINNET.base_fee_params_at_timestamp(genesis.timestamp)) - .unwrap(); + let base_fee = BASE_MAINNET.next_block_base_fee(&genesis); // assert_eq!(base_fee, 980000000); } @@ -748,9 +746,7 @@ mod tests { genesis.hash_slow(), b256!("0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4") ); - let base_fee = genesis - .next_block_base_fee(BASE_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp)) - .unwrap(); + let base_fee = BASE_SEPOLIA.next_block_base_fee(&genesis); // assert_eq!(base_fee, 980000000); } @@ -762,9 +758,7 @@ mod tests { genesis.hash_slow(), b256!("0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d") ); - let base_fee = genesis - .next_block_base_fee(OP_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp)) - .unwrap(); + let base_fee = OP_SEPOLIA.next_block_base_fee(&genesis); // assert_eq!(base_fee, 980000000); } diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index 1432d0ca37a..bce75b5b05e 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -200,9 +200,7 @@ pub fn next_block_base_fee( if chain_spec.is_holocene_active_at_timestamp(parent.timestamp()) { Ok(decode_holocene_base_fee(chain_spec, parent, timestamp)?) } else { - Ok(parent - .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(timestamp)) - .unwrap_or_default()) + Ok(chain_spec.next_block_base_fee(&parent)) } } @@ -253,12 +251,7 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 0); - assert_eq!( - base_fee.unwrap(), - parent - .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0)) - .unwrap_or_default() - ); + assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent)); } #[test] diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 523bd49de79..b2dcaade016 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -22,7 +22,6 @@ use op_revm::{OpSpecId, OpTransaction}; use reth_chainspec::EthChainSpec; use reth_evm::{ConfigureEvm, EvmEnv}; use reth_optimism_chainspec::OpChainSpec; -use reth_optimism_consensus::next_block_base_fee; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{DepositReceipt, OpPrimitives}; use reth_primitives_traits::{NodePrimitives, SealedBlock, SealedHeader, SignedTransaction}; @@ -101,7 +100,7 @@ impl OpEvmConfig impl ConfigureEvm for OpEvmConfig where - ChainSpec: EthChainSpec + OpHardforks, + ChainSpec: EthChainSpec
+ OpHardforks, N: NodePrimitives< Receipt = R::Receipt, SignedTx = R::Transaction, @@ -177,7 +176,7 @@ where ) .or_else(|| (spec_id.into_eth_spec().is_enabled_in(SpecId::CANCUN)).then_some(0)) .map(|gas| BlobExcessGasAndPrice::new(gas, false)); - + let spec = self.chain_spec(); let block_env = BlockEnv { number: parent.number() + 1, beneficiary: attributes.suggested_fee_recipient, @@ -186,7 +185,7 @@ where prevrandao: Some(attributes.prev_randao), gas_limit: attributes.gas_limit, // calculate basefee based on parent block's gas usage - basefee: next_block_base_fee(self.chain_spec(), parent, attributes.timestamp)?, + basefee: spec.next_block_base_fee(parent), // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index d0623ea4a94..35c13b2d5b0 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -22,6 +22,7 @@ use crate::{auth::AuthRpcModule, error::WsHttpSamePortError, metrics::RpcRequestMetrics}; use alloy_provider::{fillers::RecommendedFillers, Provider, ProviderBuilder}; use core::marker::PhantomData; +pub use cors::CorsDomainError; use error::{ConflictingModules, RpcError, ServerKind}; use http::{header::AUTHORIZATION, HeaderMap}; use jsonrpsee::{ @@ -49,8 +50,8 @@ use reth_rpc_eth_api::{ use reth_rpc_eth_types::{EthConfig, EthSubscriptionIdProvider}; use reth_rpc_layer::{AuthLayer, Claims, CompressionLayer, JwtAuthValidator, JwtSecret}; use reth_storage_api::{ - AccountReader, BlockReader, BlockReaderIdExt, ChangeSetReader, FullRpcProvider, ProviderBlock, - StateProviderFactory, + AccountReader, BlockReader, BlockReaderIdExt, ChangeSetReader, FullRpcProvider, HeaderProvider, + ProviderBlock, StateProviderFactory, }; use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::{noop::NoopTransactionPool, PoolTransaction, TransactionPool}; @@ -65,8 +66,6 @@ use std::{ use tower::Layer; use tower_http::cors::CorsLayer; -pub use cors::CorsDomainError; - // re-export for convenience pub use jsonrpsee::server::ServerBuilder; use jsonrpsee::server::ServerConfigBuilder; diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index 2a3e361729c..0e0c5ec832f 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -31,6 +31,10 @@ pub trait FullEthApiServer: RpcHeader, > + FullEthApi + Clone +// where +// ::Provider: ChainSpecProvider + HeaderProvider
, <::Provider as ChainSpecProvider>::ChainSpec: +// EthChainSpec
, { } diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index da354181aff..7e5861f1fb8 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -155,8 +155,8 @@ pub trait EthFees: LoadFee { // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the // next block - base_fee_per_gas - .push(last_entry.next_block_base_fee(self.provider().chain_spec()) as u128); + let spec = self.provider().chain_spec(); + base_fee_per_gas.push(last_entry.next_block_base_fee(&spec) as u128); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); } else { @@ -209,13 +209,8 @@ pub trait EthFees: LoadFee { // newest block" // // The unwrap is safe since we checked earlier that we got at least 1 header. - let last_header = headers.last().expect("is present"); - base_fee_per_gas.push( - last_header.next_block_base_fee( - self.provider() - .chain_spec() - .base_fee_params_at_timestamp(last_header.timestamp())).unwrap_or_default() as u128 - ); + let last_header = headers.last().expect("is present");let spec = self.provider().chain_spec(); + base_fee_per_gas.push(spec.next_block_base_fee(last_header.header()) as u128); // Same goes for the `base_fee_per_blob_gas`: // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index fe1f8bdcd4c..4099290000a 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -470,7 +470,7 @@ mod tests { use jsonrpsee_types::error::INVALID_PARAMS_CODE; use rand::Rng; use reth_chain_state::CanonStateSubscriptions; - use reth_chainspec::{BaseFeeParams, ChainSpec, ChainSpecProvider}; + use reth_chainspec::{ChainSpec, ChainSpecProvider, EthChainSpec}; use reth_ethereum_primitives::TransactionSigned; use reth_evm_ethereum::EthEvmConfig; use reth_network_api::noop::NoopNetwork; @@ -582,11 +582,9 @@ mod tests { // Add final base fee (for the next block outside of the request) let last_header = last_header.unwrap(); - base_fees_per_gas.push(BaseFeeParams::ethereum().next_block_base_fee( - last_header.gas_used, - last_header.gas_limit, - last_header.base_fee_per_gas.unwrap_or_default(), - ) as u128); + let spec = mock_provider.chain_spec(); + let fee = spec.next_block_base_fee(&last_header); + base_fees_per_gas.push(fee as u128); let eth_api = build_test_eth_api(mock_provider); diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 1c6a4a52a89..261b9e36c8d 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -132,14 +132,13 @@ pub async fn maintain_transaction_pool( // ensure the pool points to latest state if let Ok(Some(latest)) = client.header_by_number_or_tag(BlockNumberOrTag::Latest) { let latest = SealedHeader::seal_slow(latest); + let header = latest.header(); let chain_spec = client.chain_spec(); let info = BlockInfo { block_gas_limit: latest.gas_limit(), last_seen_block_hash: latest.hash(), last_seen_block_number: latest.number(), - pending_basefee: latest - .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(latest.timestamp())) - .unwrap_or_default(), + pending_basefee: chain_spec.next_block_base_fee(header), pending_blob_fee: latest .maybe_next_block_blob_fee(chain_spec.blob_params_at_timestamp(latest.timestamp())), }; From 33469e14357c6f39a7b4dde455f0443907d99fd3 Mon Sep 17 00:00:00 2001 From: Rose Jethani <101273941+rose2221@users.noreply.github.com> Date: Mon, 9 Jun 2025 00:59:34 +0530 Subject: [PATCH 02/44] Update crates/rpc/rpc-eth-api/src/helpers/fee.rs Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 7e5861f1fb8..9d2e5242c09 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -209,7 +209,8 @@ pub trait EthFees: LoadFee { // newest block" // // The unwrap is safe since we checked earlier that we got at least 1 header. - let last_header = headers.last().expect("is present");let spec = self.provider().chain_spec(); + let last_header = headers.last().expect("is present"); + let spec = self.provider().chain_spec(); base_fee_per_gas.push(spec.next_block_base_fee(last_header.header()) as u128); // Same goes for the `base_fee_per_blob_gas`: From 3868f41a6268a28c56ab00ffccd65705d39bde5a Mon Sep 17 00:00:00 2001 From: rose2221 Date: Tue, 10 Jun 2025 18:09:46 +0530 Subject: [PATCH 03/44] files changed --- crates/chainspec/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 8677f1b0fcd..973a6a0e41f 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -14,7 +14,7 @@ use reth_primitives_traits::AlloyBlockHeader; #[auto_impl::auto_impl(&, Arc)] pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// The header type of the network. - type Header: BlockHeader + AlloyBlockHeader; + type Header: AlloyBlockHeader; /// Returns the [`Chain`] object this spec targets. fn chain(&self) -> Chain; From 3a4deffc019bd79e6b52ca364278b9bed5d9eb91 Mon Sep 17 00:00:00 2001 From: rose2221 Date: Sun, 15 Jun 2025 18:37:33 +0530 Subject: [PATCH 04/44] fixed errors --- crates/chainspec/tests/base_fee.rs | 4 ++-- crates/consensus/common/src/validation.rs | 12 ++--------- crates/ethereum/node/tests/e2e/rpc.rs | 11 +++------- .../optimism/consensus/src/validation/mod.rs | 7 +------ crates/rpc/rpc-builder/src/lib.rs | 4 ++-- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 21 +++++++++++++------ crates/rpc/rpc-eth-types/src/fee_history.rs | 12 +---------- crates/transaction-pool/src/maintain.rs | 12 ++--------- 8 files changed, 28 insertions(+), 55 deletions(-) diff --git a/crates/chainspec/tests/base_fee.rs b/crates/chainspec/tests/base_fee.rs index e5ab42de477..d14cee1e291 100644 --- a/crates/chainspec/tests/base_fee.rs +++ b/crates/chainspec/tests/base_fee.rs @@ -1,7 +1,7 @@ +use alloy_consensus::Header; +use alloy_eips::eip1559::INITIAL_BASE_FEE; use reth_chainspec::{ChainSpec, EthChainSpec}; -use alloy_consensus::{BlockHeader, Header}; -use alloy_eips::eip1559::{BaseFeeParams, INITIAL_BASE_FEE}; fn parent_header() -> Header { Header { gas_used: 15_000_000, diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index b3e75677b1f..3d22435d5e7 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -3,7 +3,7 @@ use alloy_consensus::{ constants::MAXIMUM_EXTRA_DATA_SIZE, BlockHeader as _, EMPTY_OMMER_ROOT_HASH, }; -use alloy_eips::{calc_next_block_base_fee, eip4844::DATA_GAS_PER_BLOB, eip7840::BlobParams}; +use alloy_eips::{eip4844::DATA_GAS_PER_BLOB, eip7840::BlobParams}; use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks}; use reth_consensus::ConsensusError; use reth_primitives_traits::{ @@ -266,15 +266,7 @@ pub fn validate_against_parent_eip1559_base_fee< { alloy_eips::eip1559::INITIAL_BASE_FEE } else { - // This BaseFeeMissing will not happen as previous blocks are checked to have - // them. - let base_fee = parent.base_fee_per_gas().ok_or(ConsensusError::BaseFeeMissing)?; - calc_next_block_base_fee( - parent.gas_used(), - parent.gas_limit(), - base_fee, - chain_spec.base_fee_params_at_timestamp(header.timestamp()), - ) + chain_spec.next_block_base_fee(parent) }; if expected_base_fee != base_fee { return Err(ConsensusError::BaseFeeDiff(GotExpected { diff --git a/crates/ethereum/node/tests/e2e/rpc.rs b/crates/ethereum/node/tests/e2e/rpc.rs index 57462fbfc6d..f1c070a83a8 100644 --- a/crates/ethereum/node/tests/e2e/rpc.rs +++ b/crates/ethereum/node/tests/e2e/rpc.rs @@ -1,5 +1,5 @@ use crate::utils::eth_payload_attributes; -use alloy_eips::{calc_next_block_base_fee, eip2718::Encodable2718}; +use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{Address, B256, U256}; use alloy_provider::{network::EthereumWallet, Provider, ProviderBuilder, SendableTx}; use alloy_rpc_types_beacon::relay::{ @@ -9,7 +9,7 @@ use alloy_rpc_types_beacon::relay::{ use alloy_rpc_types_engine::{BlobsBundleV1, ExecutionPayloadV3}; use alloy_rpc_types_eth::TransactionRequest; use rand::{rngs::StdRng, Rng, SeedableRng}; -use reth_chainspec::{ChainSpecBuilder, MAINNET}; +use reth_chainspec::{ChainSpecBuilder, EthChainSpec, MAINNET}; use reth_e2e_test_utils::setup_engine; use reth_node_ethereum::EthereumNode; use reth_payload_primitives::BuiltPayload; @@ -98,12 +98,7 @@ async fn test_fee_history() -> eyre::Result<()> { .unwrap() .header; for block in (latest_block + 2 - block_count)..=latest_block { - let expected_base_fee = calc_next_block_base_fee( - prev_header.gas_used, - prev_header.gas_limit, - prev_header.base_fee_per_gas.unwrap(), - chain_spec.base_fee_params_at_block(block), - ); + let expected_base_fee = chain_spec.next_block_base_fee(&prev_header); let header = provider.get_block_by_number(block.into()).await?.unwrap().header; diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index bce75b5b05e..2c86dd24f3d 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -266,12 +266,7 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 1800000005); - assert_eq!( - base_fee.unwrap(), - parent - .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0)) - .unwrap_or_default() - ); + assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent)); } #[test] diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 35c13b2d5b0..af88e374274 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -50,8 +50,8 @@ use reth_rpc_eth_api::{ use reth_rpc_eth_types::{EthConfig, EthSubscriptionIdProvider}; use reth_rpc_layer::{AuthLayer, Claims, CompressionLayer, JwtAuthValidator, JwtSecret}; use reth_storage_api::{ - AccountReader, BlockReader, BlockReaderIdExt, ChangeSetReader, FullRpcProvider, HeaderProvider, - ProviderBlock, StateProviderFactory, + AccountReader, BlockReader, BlockReaderIdExt, ChangeSetReader, FullRpcProvider, ProviderBlock, + StateProviderFactory, }; use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::{noop::NoopTransactionPool, PoolTransaction, TransactionPool}; diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 9d2e5242c09..7631ec873d3 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -5,17 +5,16 @@ use crate::FromEthApiError; use alloy_consensus::BlockHeader; use alloy_eips::eip7840::BlobParams; use alloy_primitives::U256; -use alloy_rpc_types_eth::{BlockNumberOrTag, FeeHistory}; +use alloy_rpc_types_eth::{BlockId, BlockNumberOrTag, FeeHistory}; use futures::Future; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; -use reth_primitives_traits::BlockBody; +use reth_primitives_traits::{Block, BlockBody}; use reth_rpc_eth_types::{ fee_history::calculate_reward_percentiles_for_block, EthApiError, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, RpcInvalidTransactionError, }; -use reth_storage_api::{BlockIdReader, BlockReaderIdExt, HeaderProvider}; +use reth_storage_api::{BlockIdReader, BlockReader, BlockReaderIdExt, HeaderProvider}; use tracing::debug; - /// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the /// `eth_` namespace. pub trait EthFees: LoadFee { @@ -156,7 +155,16 @@ pub trait EthFees: LoadFee { // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the // next block let spec = self.provider().chain_spec(); - base_fee_per_gas.push(last_entry.next_block_base_fee(&spec) as u128); + let block = self + .provider() + .block_by_hash(last_entry.header_hash) + .map_err(|e| EthApiError::Internal(e.into()))? // convert ProviderError → RethError here + .ok_or_else(|| { + EthApiError::HeaderNotFound(BlockId::Hash(last_entry.header_hash.into())) + })?; + + let fee = EthChainSpec::next_block_base_fee(&spec, block.header()); + base_fee_per_gas.push(fee as u128); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); } else { @@ -211,7 +219,8 @@ pub trait EthFees: LoadFee { // The unwrap is safe since we checked earlier that we got at least 1 header. let last_header = headers.last().expect("is present"); let spec = self.provider().chain_spec(); - base_fee_per_gas.push(spec.next_block_base_fee(last_header.header()) as u128); + let fee = EthChainSpec::next_block_base_fee(&spec, last_header.header()); + base_fee_per_gas.push(fee as u128); // Same goes for the `base_fee_per_blob_gas`: // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 0425a38629b..c9692cd357c 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -7,7 +7,7 @@ use std::{ }; use alloy_consensus::{BlockHeader, Transaction, TxReceipt}; -use alloy_eips::{eip1559::calc_next_block_base_fee, eip7840::BlobParams}; +use alloy_eips::eip7840::BlobParams; use alloy_primitives::B256; use alloy_rpc_types_eth::TxGasAndReward; use futures::{ @@ -393,16 +393,6 @@ impl FeeHistoryEntry { } } - /// Returns the base fee for the next block according to the EIP-1559 spec. - pub fn next_block_base_fee(&self, chain_spec: impl EthChainSpec) -> u64 { - calc_next_block_base_fee( - self.gas_used, - self.gas_limit, - self.base_fee_per_gas, - chain_spec.base_fee_params_at_timestamp(self.timestamp), - ) - } - /// Returns the blob fee for the next block according to the EIP-4844 spec. /// /// Returns `None` if `excess_blob_gas` is None. diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 261b9e36c8d..bab110fab49 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -316,12 +316,7 @@ 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 - .header() - .next_block_base_fee( - chain_spec.base_fee_params_at_timestamp(new_tip.timestamp()), - ) - .unwrap_or_default(); + let pending_block_base_fee = chain_spec.next_block_base_fee(new_tip.header()); let pending_block_blob_fee = new_tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(new_tip.timestamp()), ); @@ -422,10 +417,7 @@ 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 - .header() - .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(tip.timestamp())) - .unwrap_or_default(); + let pending_block_base_fee = chain_spec.next_block_base_fee(tip.header()); let pending_block_blob_fee = tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(tip.timestamp()), ); From d726bb6ac28f3cc4532f002172875013315f0104 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 06:31:30 +1000 Subject: [PATCH 05/44] fix: correct timestamp usage in centralized base fee calculation This fixes critical backward compatibility issues in the base fee centralization: - Update EthChainSpec::next_block_base_fee to accept target_timestamp parameter - Use correct timestamps for base fee parameter fetching: - Consensus validation: header.timestamp() (target block timestamp) - EVM: attributes.timestamp (target block timestamp) - RPC fee history: parent timestamp (for unknown future block) - Transaction pool: current tip timestamp (for unknown future block) - E2E tests: current block timestamp The original implementation was using parent.timestamp() which could cause consensus failures when base fee parameters change between blocks. --- crates/chainspec/src/api.rs | 7 +++++-- crates/chainspec/tests/base_fee.rs | 7 +++++-- crates/consensus/common/src/validation.rs | 2 +- crates/ethereum/evm/src/lib.rs | 2 +- crates/ethereum/node/tests/e2e/rpc.rs | 3 +-- crates/optimism/chainspec/src/lib.rs | 6 +++--- crates/optimism/consensus/src/validation/mod.rs | 6 +++--- crates/optimism/evm/src/lib.rs | 2 +- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 8 ++++++-- crates/rpc/rpc/src/eth/core.rs | 2 +- crates/transaction-pool/src/maintain.rs | 8 +++++--- 11 files changed, 32 insertions(+), 21 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 973a6a0e41f..86e2421c692 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -68,13 +68,16 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { fn final_paris_total_difficulty(&self) -> Option; /// Calculate the EIP-1559 base fee for the next block from the given parent header. - fn next_block_base_fee(&self, parent: &H) -> u64 + /// + /// The `target_timestamp` should be the timestamp of the block being calculated for, + /// not the parent block timestamp. + fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 where Self: Sized, H: BlockHeader + AlloyBlockHeader, { parent - .next_block_base_fee(self.base_fee_params_at_timestamp(parent.timestamp())) + .next_block_base_fee(self.base_fee_params_at_timestamp(target_timestamp)) .unwrap_or_default() } } diff --git a/crates/chainspec/tests/base_fee.rs b/crates/chainspec/tests/base_fee.rs index d14cee1e291..d61189003e4 100644 --- a/crates/chainspec/tests/base_fee.rs +++ b/crates/chainspec/tests/base_fee.rs @@ -17,10 +17,13 @@ fn default_chain_spec_base_fee_matches_formula() { let spec = ChainSpec::default(); let parent = parent_header(); + // For testing, assume next block has timestamp 12 seconds later + let next_timestamp = parent.timestamp + 12; + let expected = parent - .next_block_base_fee(spec.base_fee_params_at_timestamp(parent.timestamp)) + .next_block_base_fee(spec.base_fee_params_at_timestamp(next_timestamp)) .unwrap_or_default(); - let got = spec.next_block_base_fee(&parent); + let got = spec.next_block_base_fee(&parent, next_timestamp); assert_eq!(expected, got, "Base fee calculation does not match expected value"); } diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 3d22435d5e7..574a1cd07a6 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -266,7 +266,7 @@ pub fn validate_against_parent_eip1559_base_fee< { alloy_eips::eip1559::INITIAL_BASE_FEE } else { - chain_spec.next_block_base_fee(parent) + chain_spec.next_block_base_fee(parent, header.timestamp()) }; if expected_base_fee != base_fee { return Err(ConsensusError::BaseFeeDiff(GotExpected { diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 46bfc886df6..f45d5b15ece 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -214,7 +214,7 @@ where BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice } }); - let mut basefee = chain_spec.next_block_base_fee(&parent); + let mut basefee = chain_spec.next_block_base_fee(&parent, attributes.timestamp); let mut gas_limit = attributes.gas_limit; // If we are on the London fork boundary, we need to multiply the parent's gas limit by the diff --git a/crates/ethereum/node/tests/e2e/rpc.rs b/crates/ethereum/node/tests/e2e/rpc.rs index f1c070a83a8..94070c397a8 100644 --- a/crates/ethereum/node/tests/e2e/rpc.rs +++ b/crates/ethereum/node/tests/e2e/rpc.rs @@ -98,9 +98,8 @@ async fn test_fee_history() -> eyre::Result<()> { .unwrap() .header; for block in (latest_block + 2 - block_count)..=latest_block { - let expected_base_fee = chain_spec.next_block_base_fee(&prev_header); - let header = provider.get_block_by_number(block.into()).await?.unwrap().header; + let expected_base_fee = chain_spec.next_block_base_fee(&prev_header, header.timestamp); assert_eq!(header.base_fee_per_gas.unwrap(), expected_base_fee); assert_eq!( diff --git a/crates/optimism/chainspec/src/lib.rs b/crates/optimism/chainspec/src/lib.rs index 957f4a2bafd..035da4506f3 100644 --- a/crates/optimism/chainspec/src/lib.rs +++ b/crates/optimism/chainspec/src/lib.rs @@ -734,7 +734,7 @@ mod tests { genesis.hash_slow(), b256!("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd") ); - let base_fee = BASE_MAINNET.next_block_base_fee(&genesis); + let base_fee = BASE_MAINNET.next_block_base_fee(&genesis, genesis.timestamp); // assert_eq!(base_fee, 980000000); } @@ -746,7 +746,7 @@ mod tests { genesis.hash_slow(), b256!("0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4") ); - let base_fee = BASE_SEPOLIA.next_block_base_fee(&genesis); + let base_fee = BASE_SEPOLIA.next_block_base_fee(&genesis, genesis.timestamp); // assert_eq!(base_fee, 980000000); } @@ -758,7 +758,7 @@ mod tests { genesis.hash_slow(), b256!("0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d") ); - let base_fee = OP_SEPOLIA.next_block_base_fee(&genesis); + let base_fee = OP_SEPOLIA.next_block_base_fee(&genesis, genesis.timestamp); // assert_eq!(base_fee, 980000000); } diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index 2c86dd24f3d..9418b6305e6 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -200,7 +200,7 @@ pub fn next_block_base_fee( if chain_spec.is_holocene_active_at_timestamp(parent.timestamp()) { Ok(decode_holocene_base_fee(chain_spec, parent, timestamp)?) } else { - Ok(chain_spec.next_block_base_fee(&parent)) + Ok(chain_spec.next_block_base_fee(&parent, timestamp)) } } @@ -251,7 +251,7 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 0); - assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent)); + assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent, 0)); } #[test] @@ -266,7 +266,7 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 1800000005); - assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent)); + assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent, 1800000005)); } #[test] diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index b2dcaade016..5850d42c520 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -185,7 +185,7 @@ where prevrandao: Some(attributes.prev_randao), gas_limit: attributes.gas_limit, // calculate basefee based on parent block's gas usage - basefee: spec.next_block_base_fee(parent), + basefee: spec.next_block_base_fee(parent, attributes.timestamp), // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index b8352107888..295a0972344 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -161,7 +161,11 @@ pub trait EthFees: LoadFee { EthApiError::HeaderNotFound(BlockId::Hash(last_entry.header_hash.into())) })?; - let fee = EthChainSpec::next_block_base_fee(&spec, block.header()); + let fee = EthChainSpec::next_block_base_fee( + &spec, + block.header(), + block.header().timestamp(), + ); base_fee_per_gas.push(fee as u128); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); @@ -217,7 +221,7 @@ pub trait EthFees: LoadFee { // The unwrap is safe since we checked earlier that we got at least 1 header. let last_header = headers.last().expect("is present"); let spec = self.provider().chain_spec(); - let fee = EthChainSpec::next_block_base_fee(&spec, last_header.header()); + let fee = EthChainSpec::next_block_base_fee(&spec, last_header.header(), last_header.header().timestamp()); base_fee_per_gas.push(fee as u128); // Same goes for the `base_fee_per_blob_gas`: diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index f9f28b25fa7..24b0a1510ff 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -583,7 +583,7 @@ mod tests { // Add final base fee (for the next block outside of the request) let last_header = last_header.unwrap(); let spec = mock_provider.chain_spec(); - let fee = spec.next_block_base_fee(&last_header); + let fee = spec.next_block_base_fee(&last_header, last_header.timestamp); base_fees_per_gas.push(fee as u128); let eth_api = build_test_eth_api(mock_provider); diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index bab110fab49..018087d7d15 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -138,7 +138,7 @@ pub async fn maintain_transaction_pool( block_gas_limit: latest.gas_limit(), last_seen_block_hash: latest.hash(), last_seen_block_number: latest.number(), - pending_basefee: chain_spec.next_block_base_fee(header), + pending_basefee: chain_spec.next_block_base_fee(header, latest.timestamp()), pending_blob_fee: latest .maybe_next_block_blob_fee(chain_spec.blob_params_at_timestamp(latest.timestamp())), }; @@ -316,7 +316,8 @@ 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 = chain_spec.next_block_base_fee(new_tip.header()); + let pending_block_base_fee = + chain_spec.next_block_base_fee(new_tip.header(), new_tip.timestamp()); let pending_block_blob_fee = new_tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(new_tip.timestamp()), ); @@ -417,7 +418,8 @@ pub async fn maintain_transaction_pool( let chain_spec = client.chain_spec(); // fees for the next block: `tip+1` - let pending_block_base_fee = chain_spec.next_block_base_fee(tip.header()); + let pending_block_base_fee = + chain_spec.next_block_base_fee(tip.header(), tip.timestamp()); let pending_block_blob_fee = tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(tip.timestamp()), ); From c389d02205a44554b63c05325c9e662e64f7ad21 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 06:46:59 +1000 Subject: [PATCH 06/44] fix: address clippy warnings in base fee centralization - Remove duplicate trait bounds in EthChainSpec - Add documentation for base fee test module - Remove unnecessary cast for INITIAL_BASE_FEE --- crates/chainspec/src/api.rs | 4 ++-- crates/chainspec/tests/base_fee.rs | 2 ++ crates/ethereum/evm/src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 86e2421c692..192d5c1b891 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -1,7 +1,7 @@ use crate::{ChainSpec, DepositContract}; use alloc::{boxed::Box, vec::Vec}; use alloy_chains::Chain; -use alloy_consensus::{BlockHeader, Header}; +use alloy_consensus::Header; use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_genesis::Genesis; use alloy_primitives::{B256, U256}; @@ -74,7 +74,7 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 where Self: Sized, - H: BlockHeader + AlloyBlockHeader, + H: AlloyBlockHeader, { parent .next_block_base_fee(self.base_fee_params_at_timestamp(target_timestamp)) diff --git a/crates/chainspec/tests/base_fee.rs b/crates/chainspec/tests/base_fee.rs index d61189003e4..1d91f4d78e1 100644 --- a/crates/chainspec/tests/base_fee.rs +++ b/crates/chainspec/tests/base_fee.rs @@ -1,3 +1,5 @@ +//! Tests for centralized base fee calculation functionality. + use alloy_consensus::Header; use alloy_eips::eip1559::INITIAL_BASE_FEE; use reth_chainspec::{ChainSpec, EthChainSpec}; diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index f45d5b15ece..20aed2a5065 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -230,7 +230,7 @@ where gas_limit *= elasticity_multiplier as u64; // set the base fee to the initial base fee from the EIP-1559 spec - basefee = INITIAL_BASE_FEE as u64; + basefee = INITIAL_BASE_FEE; } let block_env = BlockEnv { From 4066c5baec9d51bbf6e77aea934b336c62b94978 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 07:02:52 +1000 Subject: [PATCH 07/44] refactor: minimize diff and follow reth patterns - Move base fee test to existing test module instead of separate file - Remove unnecessary trait bound from EthChainSpec::Header - Maintain backward compatibility by keeping original trait signature This reduces the diff size and follows reth's testing conventions. --- crates/chainspec/src/api.rs | 2 +- crates/chainspec/src/lib.rs | 30 +++++++++++++++++++++++++++++ crates/chainspec/tests/base_fee.rs | 31 ------------------------------ 3 files changed, 31 insertions(+), 32 deletions(-) delete mode 100644 crates/chainspec/tests/base_fee.rs diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 192d5c1b891..2d3b62d1f07 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -14,7 +14,7 @@ use reth_primitives_traits::AlloyBlockHeader; #[auto_impl::auto_impl(&, Arc)] pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// The header type of the network. - type Header: AlloyBlockHeader; + type Header; /// Returns the [`Chain`] object this spec targets. fn chain(&self) -> Chain; diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index d140bf88bee..cc0421cfc06 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -145,4 +145,34 @@ mod tests { let chain: Chain = NamedChain::Holesky.into(); assert_eq!(s, chain.public_dns_network_protocol().unwrap().as_str()); } + + #[test] + fn test_centralized_base_fee_calculation() { + use crate::{ChainSpec, EthChainSpec}; + use alloy_consensus::Header; + use alloy_eips::eip1559::INITIAL_BASE_FEE; + + fn parent_header() -> Header { + Header { + gas_used: 15_000_000, + gas_limit: 30_000_000, + base_fee_per_gas: Some(INITIAL_BASE_FEE), + timestamp: 1_000, + ..Default::default() + } + } + + let spec = ChainSpec::default(); + let parent = parent_header(); + + // For testing, assume next block has timestamp 12 seconds later + let next_timestamp = parent.timestamp + 12; + + let expected = parent + .next_block_base_fee(spec.base_fee_params_at_timestamp(next_timestamp)) + .unwrap_or_default(); + + let got = spec.next_block_base_fee(&parent, next_timestamp); + assert_eq!(expected, got, "Base fee calculation does not match expected value"); + } } diff --git a/crates/chainspec/tests/base_fee.rs b/crates/chainspec/tests/base_fee.rs deleted file mode 100644 index 1d91f4d78e1..00000000000 --- a/crates/chainspec/tests/base_fee.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Tests for centralized base fee calculation functionality. - -use alloy_consensus::Header; -use alloy_eips::eip1559::INITIAL_BASE_FEE; -use reth_chainspec::{ChainSpec, EthChainSpec}; - -fn parent_header() -> Header { - Header { - gas_used: 15_000_000, - gas_limit: 30_000_000, - base_fee_per_gas: Some(INITIAL_BASE_FEE), - timestamp: 1_000, - ..Default::default() - } -} - -#[test] -fn default_chain_spec_base_fee_matches_formula() { - let spec = ChainSpec::default(); - let parent = parent_header(); - - // For testing, assume next block has timestamp 12 seconds later - let next_timestamp = parent.timestamp + 12; - - let expected = parent - .next_block_base_fee(spec.base_fee_params_at_timestamp(next_timestamp)) - .unwrap_or_default(); - - let got = spec.next_block_base_fee(&parent, next_timestamp); - assert_eq!(expected, got, "Base fee calculation does not match expected value"); -} From 96e35f7cf3eb976d3e47da109cf3b6cf82534cb5 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 07:09:46 +1000 Subject: [PATCH 08/44] fix: remove commented code and restore proper test logic - Remove leftover commented trait bounds in RPC core - Fix circular test assertions in optimism base fee tests - Restore proper test logic that compares against direct calculation This ensures tests actually validate the centralized calculation works correctly. --- crates/optimism/consensus/src/validation/mod.rs | 14 ++++++++++++-- crates/rpc/rpc-eth-api/src/core.rs | 4 ---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index 9418b6305e6..aedab4be6b0 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -251,7 +251,12 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 0); - assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent, 0)); + assert_eq!( + base_fee.unwrap(), + parent + .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0)) + .unwrap_or_default() + ); } #[test] @@ -266,7 +271,12 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 1800000005); - assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent, 1800000005)); + assert_eq!( + base_fee.unwrap(), + parent + .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(1800000005)) + .unwrap_or_default() + ); } #[test] diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index 0e0c5ec832f..2a3e361729c 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -31,10 +31,6 @@ pub trait FullEthApiServer: RpcHeader, > + FullEthApi + Clone -// where -// ::Provider: ChainSpecProvider + HeaderProvider
, <::Provider as ChainSpecProvider>::ChainSpec: -// EthChainSpec
, { } From 3581cb7d3a2b36f3512bfc47dc70e826537d883a Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 07:15:43 +1000 Subject: [PATCH 09/44] fix: restore Self: Sized constraint for trait object compatibility The Self: Sized constraint is required to make EthChainSpec dyn-safe, as it is used as a trait object in the ExEx system. Removing this constraint breaks compilation due to the generic method. --- crates/chainspec/src/api.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 2d3b62d1f07..06aa92d7d9a 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -73,7 +73,6 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// not the parent block timestamp. fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 where - Self: Sized, H: AlloyBlockHeader, { parent From 974466a5b74b0c6b636eed29362414ee0259d43b Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 07:18:07 +1000 Subject: [PATCH 10/44] Update api.rs --- crates/chainspec/src/api.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 06aa92d7d9a..2d3b62d1f07 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -73,6 +73,7 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// not the parent block timestamp. fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 where + Self: Sized, H: AlloyBlockHeader, { parent From 7046d8cdc48dd82fcc59977cdff955e8496f63a8 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 07:58:36 +1000 Subject: [PATCH 11/44] revert --- crates/optimism/consensus/src/validation/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index aedab4be6b0..6ed3b375e06 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -274,7 +274,7 @@ mod tests { assert_eq!( base_fee.unwrap(), parent - .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(1800000005)) + .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0)) .unwrap_or_default() ); } From ff89a49ab5764018db1237eb734813ad8597977d Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 08:03:52 +1000 Subject: [PATCH 12/44] Remove excessive trait bound --- crates/optimism/evm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 5850d42c520..05b85e0df8e 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -100,7 +100,7 @@ impl OpEvmConfig impl ConfigureEvm for OpEvmConfig where - ChainSpec: EthChainSpec
+ OpHardforks, + ChainSpec: EthChainSpec + OpHardforks, N: NodePrimitives< Receipt = R::Receipt, SignedTx = R::Transaction, From 87740a34ceabd6959e2978145ca84c64b38fae81 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 08:12:07 +1000 Subject: [PATCH 13/44] Update lib.rs --- crates/optimism/evm/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 05b85e0df8e..24699c430d1 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -176,7 +176,6 @@ where ) .or_else(|| (spec_id.into_eth_spec().is_enabled_in(SpecId::CANCUN)).then_some(0)) .map(|gas| BlobExcessGasAndPrice::new(gas, false)); - let spec = self.chain_spec(); let block_env = BlockEnv { number: parent.number() + 1, beneficiary: attributes.suggested_fee_recipient, @@ -185,7 +184,7 @@ where prevrandao: Some(attributes.prev_randao), gas_limit: attributes.gas_limit, // calculate basefee based on parent block's gas usage - basefee: spec.next_block_base_fee(parent, attributes.timestamp), + basefee: self.chain_spec().next_block_base_fee(parent, attributes.timestamp), // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; From 9c036728febf4ba2ba790e26559d1375b35b426d Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 08:16:02 +1000 Subject: [PATCH 14/44] revert change --- crates/rpc/rpc-builder/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index af88e374274..d0623ea4a94 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -22,7 +22,6 @@ use crate::{auth::AuthRpcModule, error::WsHttpSamePortError, metrics::RpcRequestMetrics}; use alloy_provider::{fillers::RecommendedFillers, Provider, ProviderBuilder}; use core::marker::PhantomData; -pub use cors::CorsDomainError; use error::{ConflictingModules, RpcError, ServerKind}; use http::{header::AUTHORIZATION, HeaderMap}; use jsonrpsee::{ @@ -66,6 +65,8 @@ use std::{ use tower::Layer; use tower_http::cors::CorsLayer; +pub use cors::CorsDomainError; + // re-export for convenience pub use jsonrpsee::server::ServerBuilder; use jsonrpsee::server::ServerConfigBuilder; From 58f2ae93a4ce3d95a22251e9f316e9f154d753f5 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 08:38:22 +1000 Subject: [PATCH 15/44] refactor: simplify fee history base fee calculation Replace complex block fetching with direct header-based calculation using minimal Header struct. This eliminates unnecessary database lookups and error handling while maintaining the same functionality. - Add next_block_base_fee method to FeeHistoryEntry - Remove redundant block fetching in fee history calculation - Clean up unused imports --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 26 +++++---------------- crates/rpc/rpc-eth-types/src/fee_history.rs | 19 ++++++++++++++- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 295a0972344..54c27d98efe 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -5,15 +5,15 @@ use crate::FromEthApiError; use alloy_consensus::BlockHeader; use alloy_eips::eip7840::BlobParams; use alloy_primitives::U256; -use alloy_rpc_types_eth::{BlockId, BlockNumberOrTag, FeeHistory}; +use alloy_rpc_types_eth::{BlockNumberOrTag, FeeHistory}; use futures::Future; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; -use reth_primitives_traits::{Block, BlockBody}; +use reth_primitives_traits::BlockBody; use reth_rpc_eth_types::{ fee_history::calculate_reward_percentiles_for_block, EthApiError, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, RpcInvalidTransactionError, }; -use reth_storage_api::{BlockIdReader, BlockReader, BlockReaderIdExt, HeaderProvider}; +use reth_storage_api::{BlockIdReader, BlockReaderIdExt, HeaderProvider}; use tracing::debug; /// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the /// `eth_` namespace. @@ -152,20 +152,7 @@ pub trait EthFees: LoadFee { // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the // next block - let spec = self.provider().chain_spec(); - let block = self - .provider() - .block_by_hash(last_entry.header_hash) - .map_err(|e| EthApiError::Internal(e.into()))? // convert ProviderError → RethError here - .ok_or_else(|| { - EthApiError::HeaderNotFound(BlockId::Hash(last_entry.header_hash.into())) - })?; - - let fee = EthChainSpec::next_block_base_fee( - &spec, - block.header(), - block.header().timestamp(), - ); + let fee = last_entry.next_block_base_fee(self.provider().chain_spec()); base_fee_per_gas.push(fee as u128); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); @@ -220,9 +207,8 @@ pub trait EthFees: LoadFee { // // The unwrap is safe since we checked earlier that we got at least 1 header. let last_header = headers.last().expect("is present"); - let spec = self.provider().chain_spec(); - let fee = EthChainSpec::next_block_base_fee(&spec, last_header.header(), last_header.header().timestamp()); - base_fee_per_gas.push(fee as u128); + let fee = self.provider().chain_spec().next_block_base_fee(last_header.header(), last_header.header().timestamp()); + base_fee_per_gas.push(fee as u128); // Same goes for the `base_fee_per_blob_gas`: // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 014113fa33f..6b099e22e3d 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -6,7 +6,7 @@ use std::{ sync::{atomic::Ordering::SeqCst, Arc}, }; -use alloy_consensus::{BlockHeader, Transaction, TxReceipt}; +use alloy_consensus::{BlockHeader, Header, Transaction, TxReceipt}; use alloy_eips::eip7840::BlobParams; use alloy_primitives::B256; use alloy_rpc_types_eth::TxGasAndReward; @@ -415,4 +415,21 @@ impl FeeHistoryEntry { Some(self.blob_params?.next_block_excess_blob_gas(excess_blob_gas, self.blob_gas_used?)) }) } + + /// Calculate the base fee for the next block according to the EIP-1559 spec. + pub fn next_block_base_fee(&self, chain_spec: impl EthChainSpec) -> u64 { + // Create a minimal header with the required fields + let parent_header = Header { + gas_used: self.gas_used, + gas_limit: self.gas_limit, + base_fee_per_gas: Some(self.base_fee_per_gas), + timestamp: self.timestamp, + ..Default::default() + }; + + // Use the chain spec to get the appropriate base fee params and calculate next fee + parent_header + .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(self.timestamp)) + .unwrap_or_default() + } } From b8f0046dd8a69e0348e8f11282e7a800568276e0 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 08:41:09 +1000 Subject: [PATCH 16/44] refactor: use chainspec.next_block_base_fee directly Replace manual base fee parameter lookup with direct chainspec method call, making the code cleaner and more consistent with the centralized base fee calculation approach. --- crates/rpc/rpc-eth-types/src/fee_history.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 6b099e22e3d..89171d1fb2b 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -427,9 +427,7 @@ impl FeeHistoryEntry { ..Default::default() }; - // Use the chain spec to get the appropriate base fee params and calculate next fee - parent_header - .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(self.timestamp)) - .unwrap_or_default() + // Use the chain spec's centralized base fee calculation + chain_spec.next_block_base_fee(&parent_header, self.timestamp) } } From 26c66d5a8703d8353553900d64981e7958fe7688 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:03:41 +1000 Subject: [PATCH 17/44] lint --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 4 ++-- crates/rpc/rpc-eth-types/src/fee_history.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 54c27d98efe..d89151fda3c 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -152,8 +152,8 @@ pub trait EthFees: LoadFee { // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the // next block - let fee = last_entry.next_block_base_fee(self.provider().chain_spec()); - base_fee_per_gas.push(fee as u128); + base_fee_per_gas + .push(last_entry.next_block_base_fee(self.provider().chain_spec()) as u128); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); } else { diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 89171d1fb2b..c9095483eb1 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -426,7 +426,7 @@ impl FeeHistoryEntry { timestamp: self.timestamp, ..Default::default() }; - + // Use the chain spec's centralized base fee calculation chain_spec.next_block_base_fee(&parent_header, self.timestamp) } From cb1a7416306910b17e2c08288dc8814b576b7463 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:05:45 +1000 Subject: [PATCH 18/44] lint --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index d89151fda3c..6f0b9c79b58 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -207,9 +207,7 @@ pub trait EthFees: LoadFee { // // The unwrap is safe since we checked earlier that we got at least 1 header. let last_header = headers.last().expect("is present"); - let fee = self.provider().chain_spec().next_block_base_fee(last_header.header(), last_header.header().timestamp()); - base_fee_per_gas.push(fee as u128); - + base_fee_per_gas.push(self.provider().chain_spec().next_block_base_fee(last_header.header(), last_header.header().timestamp()) as u128); // Same goes for the `base_fee_per_blob_gas`: // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. base_fee_per_blob_gas.push( From 329f90767a85184306052c29407879741d07c585 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:08:18 +1000 Subject: [PATCH 19/44] Update fee.rs --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 6f0b9c79b58..8e61ee100b7 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -207,7 +207,10 @@ pub trait EthFees: LoadFee { // // The unwrap is safe since we checked earlier that we got at least 1 header. let last_header = headers.last().expect("is present"); - base_fee_per_gas.push(self.provider().chain_spec().next_block_base_fee(last_header.header(), last_header.header().timestamp()) as u128); + base_fee_per_gas.push( + self.provider(). + chain_spec(). + next_block_base_fee(last_header.header(), last_header.header().timestamp()) as u128); // Same goes for the `base_fee_per_blob_gas`: // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. base_fee_per_blob_gas.push( From e9d8b783068b4af0aaf0cd0813eef9093a878956 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:11:02 +1000 Subject: [PATCH 20/44] Update fee_history.rs --- crates/rpc/rpc-eth-types/src/fee_history.rs | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index c9095483eb1..f6d8d942b22 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -397,6 +397,21 @@ impl FeeHistoryEntry { } } + /// Calculate the base fee for the next block according to the EIP-1559 spec. + pub fn next_block_base_fee(&self, chain_spec: impl EthChainSpec) -> u64 { + // Create a minimal header with the required fields + let parent_header = Header { + gas_used: self.gas_used, + gas_limit: self.gas_limit, + base_fee_per_gas: Some(self.base_fee_per_gas), + timestamp: self.timestamp, + ..Default::default() + }; + + // Use the chain spec's centralized base fee calculation + chain_spec.next_block_base_fee(&parent_header, self.timestamp) + } + /// Returns the blob fee for the next block according to the EIP-4844 spec. /// /// Returns `None` if `excess_blob_gas` is None. @@ -415,19 +430,4 @@ impl FeeHistoryEntry { Some(self.blob_params?.next_block_excess_blob_gas(excess_blob_gas, self.blob_gas_used?)) }) } - - /// Calculate the base fee for the next block according to the EIP-1559 spec. - pub fn next_block_base_fee(&self, chain_spec: impl EthChainSpec) -> u64 { - // Create a minimal header with the required fields - let parent_header = Header { - gas_used: self.gas_used, - gas_limit: self.gas_limit, - base_fee_per_gas: Some(self.base_fee_per_gas), - timestamp: self.timestamp, - ..Default::default() - }; - - // Use the chain spec's centralized base fee calculation - chain_spec.next_block_base_fee(&parent_header, self.timestamp) - } } From 4e0332d9b949f5075164918ab809e87bc61863a8 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:13:42 +1000 Subject: [PATCH 21/44] perf: optimize chain_spec access in fee history Store chain_spec in variable to avoid multiple provider calls in the same function scope, improving performance by reducing redundant method calls. --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 8e61ee100b7..7c8d3a0ae03 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -152,8 +152,8 @@ pub trait EthFees: LoadFee { // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the // next block - base_fee_per_gas - .push(last_entry.next_block_base_fee(self.provider().chain_spec()) as u128); + let chain_spec = self.provider().chain_spec(); + base_fee_per_gas.push(last_entry.next_block_base_fee(&chain_spec) as u128); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); } else { @@ -165,13 +165,12 @@ pub trait EthFees: LoadFee { return Err(EthApiError::InvalidBlockRange.into()) } - + let chain_spec = self.provider().chain_spec(); for header in &headers { base_fee_per_gas.push(header.base_fee_per_gas().unwrap_or_default() as u128); gas_used_ratio.push(header.gas_used() as f64 / header.gas_limit() as f64); - let blob_params = self.provider() - .chain_spec() + let blob_params = chain_spec .blob_params_at_timestamp(header.timestamp()) .unwrap_or_else(BlobParams::cancun); @@ -208,15 +207,13 @@ pub trait EthFees: LoadFee { // The unwrap is safe since we checked earlier that we got at least 1 header. let last_header = headers.last().expect("is present"); base_fee_per_gas.push( - self.provider(). - chain_spec(). - next_block_base_fee(last_header.header(), last_header.header().timestamp()) as u128); + chain_spec.next_block_base_fee(last_header.header(), last_header.header().timestamp()) as u128); // Same goes for the `base_fee_per_blob_gas`: // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. base_fee_per_blob_gas.push( last_header .maybe_next_block_blob_fee( - self.provider().chain_spec().blob_params_at_timestamp(last_header.timestamp()) + chain_spec.blob_params_at_timestamp(last_header.timestamp()) ).unwrap_or_default() ); }; From c83d2dcff4194cb2923f13ec46a2cb5369704c19 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:21:00 +1000 Subject: [PATCH 22/44] Update core.rs --- crates/rpc/rpc/src/eth/core.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index 24b0a1510ff..70320ed02d5 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -583,8 +583,8 @@ mod tests { // Add final base fee (for the next block outside of the request) let last_header = last_header.unwrap(); let spec = mock_provider.chain_spec(); - let fee = spec.next_block_base_fee(&last_header, last_header.timestamp); - base_fees_per_gas.push(fee as u128); + base_fees_per_gas + .push(spec.next_block_base_fee(&last_header, last_header.timestamp) as u128); let eth_api = build_test_eth_api(mock_provider); From 7a2f43feefb981d5a004f874748eb8f89a49a8f1 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:23:18 +1000 Subject: [PATCH 23/44] Update maintain.rs --- crates/transaction-pool/src/maintain.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 018087d7d15..133ce8fc4f1 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -132,13 +132,12 @@ pub async fn maintain_transaction_pool( // ensure the pool points to latest state if let Ok(Some(latest)) = client.header_by_number_or_tag(BlockNumberOrTag::Latest) { let latest = SealedHeader::seal_slow(latest); - let header = latest.header(); let chain_spec = client.chain_spec(); let info = BlockInfo { block_gas_limit: latest.gas_limit(), last_seen_block_hash: latest.hash(), last_seen_block_number: latest.number(), - pending_basefee: chain_spec.next_block_base_fee(header, latest.timestamp()), + pending_basefee: chain_spec.next_block_base_fee(latest.header(), latest.timestamp()), pending_blob_fee: latest .maybe_next_block_blob_fee(chain_spec.blob_params_at_timestamp(latest.timestamp())), }; From 8e4c8e42695b27063e6a27a12a1d0f8f49c38819 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:27:55 +1000 Subject: [PATCH 24/44] Update fee_history.rs --- crates/rpc/rpc-eth-types/src/fee_history.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index f6d8d942b22..862a43c5275 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -400,6 +400,7 @@ impl FeeHistoryEntry { /// Calculate the base fee for the next block according to the EIP-1559 spec. pub fn next_block_base_fee(&self, chain_spec: impl EthChainSpec) -> u64 { // Create a minimal header with the required fields + // Custom overrides of next_block_base_fee that use more than these fields are not supported here. let parent_header = Header { gas_used: self.gas_used, gas_limit: self.gas_limit, From 9630cdd0831891f80e5ebf9aeae0bf85b0246b6a Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:34:50 +1000 Subject: [PATCH 25/44] style: improve code formatting and remove excessive comments - Remove verbose comments that don't add value - Fix line length formatting to meet Reth standards - Apply consistent code style via rustfmt --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 4 +++- crates/rpc/rpc-eth-types/src/fee_history.rs | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 7c8d3a0ae03..19b3d797d10 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -207,7 +207,9 @@ pub trait EthFees: LoadFee { // The unwrap is safe since we checked earlier that we got at least 1 header. let last_header = headers.last().expect("is present"); base_fee_per_gas.push( - chain_spec.next_block_base_fee(last_header.header(), last_header.header().timestamp()) as u128); + chain_spec + .next_block_base_fee(last_header.header(), last_header.header().timestamp()) as u128, + ); // Same goes for the `base_fee_per_blob_gas`: // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. base_fee_per_blob_gas.push( diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 862a43c5275..9ead77f6823 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -399,8 +399,6 @@ impl FeeHistoryEntry { /// Calculate the base fee for the next block according to the EIP-1559 spec. pub fn next_block_base_fee(&self, chain_spec: impl EthChainSpec) -> u64 { - // Create a minimal header with the required fields - // Custom overrides of next_block_base_fee that use more than these fields are not supported here. let parent_header = Header { gas_used: self.gas_used, gas_limit: self.gas_limit, @@ -409,7 +407,6 @@ impl FeeHistoryEntry { ..Default::default() }; - // Use the chain spec's centralized base fee calculation chain_spec.next_block_base_fee(&parent_header, self.timestamp) } From 1128f98ea405f0498c79291640eda9aad437f2d6 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:42:54 +1000 Subject: [PATCH 26/44] docs: clarify header field limitations for next_block_base_fee Add clear warning to implementers that some callers (like fee_history) pass minimal headers with only gas_used, gas_limit, base_fee_per_gas, and timestamp populated. This prevents implementers from relying on additional header fields that may not be available. --- crates/chainspec/src/api.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 2d3b62d1f07..88b5800dc54 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -71,6 +71,10 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// /// The `target_timestamp` should be the timestamp of the block being calculated for, /// not the parent block timestamp. + /// + /// Caution: Implementers must only use gas_used, gas_limit, base_fee_per_gas, and timestamp + /// from the parent header. Some callers (like fee_history) pass minimal headers that only + /// populate these fields, with all other fields set to default values. fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 where Self: Sized, From 97d69d130072ae1d251813f13ea14e90f32ef189 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:48:52 +1000 Subject: [PATCH 27/44] lint --- crates/chainspec/src/api.rs | 4 ++-- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 88b5800dc54..84e839edf50 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -72,8 +72,8 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// The `target_timestamp` should be the timestamp of the block being calculated for, /// not the parent block timestamp. /// - /// Caution: Implementers must only use gas_used, gas_limit, base_fee_per_gas, and timestamp - /// from the parent header. Some callers (like fee_history) pass minimal headers that only + /// Caution: Implementers must only use gas_used, gas_limit, base_fee_per_gas, and timestamp + /// from the parent header. Some callers (like fee_history) pass minimal headers that only /// populate these fields, with all other fields set to default values. fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 where diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 19b3d797d10..4255d37588a 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -152,8 +152,8 @@ pub trait EthFees: LoadFee { // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the // next block - let chain_spec = self.provider().chain_spec(); - base_fee_per_gas.push(last_entry.next_block_base_fee(&chain_spec) as u128); + base_fee_per_gas + .push(last_entry.next_block_base_fee(self.provider().chain_spec()) as u128); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); } else { From 171e60a1d28cef9dbbb116d93386d47f5256d5f4 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 09:55:39 +1000 Subject: [PATCH 28/44] refactor: update Optimism tests to use centralized base fee calculation Update test assertions to use chain_spec.next_block_base_fee instead of manual header.next_block_base_fee calls for consistency with the centralized approach. --- crates/optimism/consensus/src/validation/mod.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index 6ed3b375e06..b7037ca8804 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -253,9 +253,7 @@ mod tests { let base_fee = next_block_base_fee(&op_chain_spec, &parent, 0); assert_eq!( base_fee.unwrap(), - parent - .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0)) - .unwrap_or_default() + op_chain_spec.next_block_base_fee(&parent, 0) ); } @@ -273,9 +271,7 @@ mod tests { let base_fee = next_block_base_fee(&op_chain_spec, &parent, 1800000005); assert_eq!( base_fee.unwrap(), - parent - .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0)) - .unwrap_or_default() + op_chain_spec.next_block_base_fee(&parent, 0) ); } From 8d07fb2e96df1a808de1507074194b6edb547669 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 10:03:38 +1000 Subject: [PATCH 29/44] fix: add backticks to code items in documentation Fix clippy doc_markdown warnings by adding backticks around code items in the caution documentation for next_block_base_fee method. --- crates/chainspec/src/api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 84e839edf50..cb3e8e317dc 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -72,8 +72,8 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// The `target_timestamp` should be the timestamp of the block being calculated for, /// not the parent block timestamp. /// - /// Caution: Implementers must only use gas_used, gas_limit, base_fee_per_gas, and timestamp - /// from the parent header. Some callers (like fee_history) pass minimal headers that only + /// Caution: Implementers must only use `gas_used`, `gas_limit`, `base_fee_per_gas`, and `timestamp` + /// from the parent header. Some callers (like `fee_history`) pass minimal headers that only /// populate these fields, with all other fields set to default values. fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 where From 709861ff19b3db53189e60c90b307f7e0a2ab415 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 10:10:35 +1000 Subject: [PATCH 30/44] lint --- crates/chainspec/src/api.rs | 6 +++--- crates/optimism/consensus/src/validation/mod.rs | 10 ++-------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index cb3e8e317dc..b21cf9a8cbf 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -72,9 +72,9 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// The `target_timestamp` should be the timestamp of the block being calculated for, /// not the parent block timestamp. /// - /// Caution: Implementers must only use `gas_used`, `gas_limit`, `base_fee_per_gas`, and `timestamp` - /// from the parent header. Some callers (like `fee_history`) pass minimal headers that only - /// populate these fields, with all other fields set to default values. + /// Caution: Implementers must only use `gas_used`, `gas_limit`, `base_fee_per_gas`, and + /// `timestamp` from the parent header. Some callers (like `fee_history`) pass minimal + /// headers that only populate these fields, with all other fields set to default values. fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 where Self: Sized, diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index b7037ca8804..2f5791604bf 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -251,10 +251,7 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 0); - assert_eq!( - base_fee.unwrap(), - op_chain_spec.next_block_base_fee(&parent, 0) - ); + assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent, 0)); } #[test] @@ -269,10 +266,7 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 1800000005); - assert_eq!( - base_fee.unwrap(), - op_chain_spec.next_block_base_fee(&parent, 0) - ); + assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent, 0)); } #[test] From 3abe2f4eaccc21283de85cc9880c7e0941e7581e Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 10:12:07 +1000 Subject: [PATCH 31/44] Update api.rs --- crates/chainspec/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index b21cf9a8cbf..df523c3f5c2 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -69,7 +69,7 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// Calculate the EIP-1559 base fee for the next block from the given parent header. /// - /// The `target_timestamp` should be the timestamp of the block being calculated for, + /// The `target_timestamp` should generally be the timestamp of the block being calculated for, /// not the parent block timestamp. /// /// Caution: Implementers must only use `gas_used`, `gas_limit`, `base_fee_per_gas`, and From 464a097b5335ec614bf8e00505c17d6b3e115d14 Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 19 Jun 2025 11:11:41 +1000 Subject: [PATCH 32/44] Update fee.rs --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 4255d37588a..17f806514d1 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -15,6 +15,7 @@ use reth_rpc_eth_types::{ }; use reth_storage_api::{BlockIdReader, BlockReaderIdExt, HeaderProvider}; use tracing::debug; + /// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the /// `eth_` namespace. pub trait EthFees: LoadFee { From 893122db75f3cabe03dbed03c12143ecd06c348d Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 20 Jun 2025 19:21:13 +1000 Subject: [PATCH 33/44] docs: simplify next_block_base_fee documentation Link to AlloyBlockHeader::next_block_base_fee instead of duplicating the implementation details in the trait documentation. --- crates/chainspec/src/api.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index df523c3f5c2..d9d3979cefb 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -67,14 +67,7 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// Returns the final total difficulty if the Paris hardfork is known. fn final_paris_total_difficulty(&self) -> Option; - /// Calculate the EIP-1559 base fee for the next block from the given parent header. - /// - /// The `target_timestamp` should generally be the timestamp of the block being calculated for, - /// not the parent block timestamp. - /// - /// Caution: Implementers must only use `gas_used`, `gas_limit`, `base_fee_per_gas`, and - /// `timestamp` from the parent header. Some callers (like `fee_history`) pass minimal - /// headers that only populate these fields, with all other fields set to default values. + /// See [`AlloyBlockHeader::next_block_base_fee`]. fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 where Self: Sized, From bafe1038aa47beafef45f17bfcac33f4e9f2ef2f Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 20 Jun 2025 19:49:42 +1000 Subject: [PATCH 34/44] refactor: change next_block_base_fee to take individual gas fields Instead of taking a full header, next_block_base_fee now takes: - parent_gas_used: u64 - parent_gas_limit: u64 - parent_base_fee_per_gas: u64 - timestamp: u64 This simplifies the API and avoids creating minimal headers just to pass gas fields, as noted in PR feedback. Removes unnecessary helper method from FeeHistoryEntry since it's no longer needed. --- crates/chainspec/src/api.rs | 24 ++++++++++------- crates/chainspec/src/lib.rs | 7 ++++- crates/consensus/common/src/validation.rs | 7 ++++- crates/ethereum/evm/src/lib.rs | 7 ++++- crates/ethereum/node/tests/e2e/rpc.rs | 7 ++++- crates/optimism/chainspec/src/lib.rs | 21 ++++++++++++--- .../optimism/consensus/src/validation/mod.rs | 27 ++++++++++++++++--- crates/optimism/evm/src/lib.rs | 7 ++++- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 16 ++++++++--- crates/rpc/rpc-eth-types/src/fee_history.rs | 13 --------- crates/rpc/rpc/src/eth/core.rs | 8 ++++-- crates/transaction-pool/src/maintain.rs | 23 ++++++++++++---- 12 files changed, 122 insertions(+), 45 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index d9d3979cefb..ba5e16245ac 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -2,13 +2,12 @@ use crate::{ChainSpec, DepositContract}; use alloc::{boxed::Box, vec::Vec}; use alloy_chains::Chain; use alloy_consensus::Header; -use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams}; +use alloy_eips::{calc_next_block_base_fee, eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_genesis::Genesis; use alloy_primitives::{B256, U256}; use core::fmt::{Debug, Display}; use reth_ethereum_forks::EthereumHardforks; use reth_network_peers::NodeRecord; -use reth_primitives_traits::AlloyBlockHeader; /// Trait representing type configuring a chain spec. #[auto_impl::auto_impl(&, Arc)] @@ -68,14 +67,19 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { fn final_paris_total_difficulty(&self) -> Option; /// See [`AlloyBlockHeader::next_block_base_fee`]. - fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> u64 - where - Self: Sized, - H: AlloyBlockHeader, - { - parent - .next_block_base_fee(self.base_fee_params_at_timestamp(target_timestamp)) - .unwrap_or_default() + fn next_block_base_fee( + &self, + parent_gas_used: u64, + parent_gas_limit: u64, + parent_base_fee_per_gas: u64, + timestamp: u64, + ) -> u64 { + calc_next_block_base_fee( + parent_gas_used, + parent_gas_limit, + parent_base_fee_per_gas, + self.base_fee_params_at_timestamp(timestamp), + ) } } diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index cc0421cfc06..617c3a25516 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -172,7 +172,12 @@ mod tests { .next_block_base_fee(spec.base_fee_params_at_timestamp(next_timestamp)) .unwrap_or_default(); - let got = spec.next_block_base_fee(&parent, next_timestamp); + let got = spec.next_block_base_fee( + parent.gas_used, + parent.gas_limit, + parent.base_fee_per_gas.unwrap_or_default(), + next_timestamp, + ); assert_eq!(expected, got, "Base fee calculation does not match expected value"); } } diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 574a1cd07a6..e650fd6736e 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -266,7 +266,12 @@ pub fn validate_against_parent_eip1559_base_fee< { alloy_eips::eip1559::INITIAL_BASE_FEE } else { - chain_spec.next_block_base_fee(parent, header.timestamp()) + chain_spec.next_block_base_fee( + parent.gas_used(), + parent.gas_limit(), + parent.base_fee_per_gas().unwrap_or_default(), + header.timestamp(), + ) }; if expected_base_fee != base_fee { return Err(ConsensusError::BaseFeeDiff(GotExpected { diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 20aed2a5065..a27f6623822 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -214,7 +214,12 @@ where BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice } }); - let mut basefee = chain_spec.next_block_base_fee(&parent, attributes.timestamp); + let mut basefee = chain_spec.next_block_base_fee( + parent.gas_used, + parent.gas_limit, + parent.base_fee_per_gas.unwrap_or_default(), + attributes.timestamp, + ); let mut gas_limit = attributes.gas_limit; // If we are on the London fork boundary, we need to multiply the parent's gas limit by the diff --git a/crates/ethereum/node/tests/e2e/rpc.rs b/crates/ethereum/node/tests/e2e/rpc.rs index 94070c397a8..ac36ea3c279 100644 --- a/crates/ethereum/node/tests/e2e/rpc.rs +++ b/crates/ethereum/node/tests/e2e/rpc.rs @@ -99,7 +99,12 @@ async fn test_fee_history() -> eyre::Result<()> { .header; for block in (latest_block + 2 - block_count)..=latest_block { let header = provider.get_block_by_number(block.into()).await?.unwrap().header; - let expected_base_fee = chain_spec.next_block_base_fee(&prev_header, header.timestamp); + let expected_base_fee = chain_spec.next_block_base_fee( + prev_header.gas_used, + prev_header.gas_limit, + prev_header.base_fee_per_gas.unwrap_or_default(), + header.timestamp, + ); assert_eq!(header.base_fee_per_gas.unwrap(), expected_base_fee); assert_eq!( diff --git a/crates/optimism/chainspec/src/lib.rs b/crates/optimism/chainspec/src/lib.rs index 035da4506f3..c51dd3de41a 100644 --- a/crates/optimism/chainspec/src/lib.rs +++ b/crates/optimism/chainspec/src/lib.rs @@ -734,7 +734,12 @@ mod tests { genesis.hash_slow(), b256!("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd") ); - let base_fee = BASE_MAINNET.next_block_base_fee(&genesis, genesis.timestamp); + let base_fee = BASE_MAINNET.next_block_base_fee( + genesis.gas_used, + genesis.gas_limit, + genesis.base_fee_per_gas.unwrap_or_default(), + genesis.timestamp, + ); // assert_eq!(base_fee, 980000000); } @@ -746,7 +751,12 @@ mod tests { genesis.hash_slow(), b256!("0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4") ); - let base_fee = BASE_SEPOLIA.next_block_base_fee(&genesis, genesis.timestamp); + let base_fee = BASE_SEPOLIA.next_block_base_fee( + genesis.gas_used, + genesis.gas_limit, + genesis.base_fee_per_gas.unwrap_or_default(), + genesis.timestamp, + ); // assert_eq!(base_fee, 980000000); } @@ -758,7 +768,12 @@ mod tests { genesis.hash_slow(), b256!("0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d") ); - let base_fee = OP_SEPOLIA.next_block_base_fee(&genesis, genesis.timestamp); + let base_fee = OP_SEPOLIA.next_block_base_fee( + genesis.gas_used, + genesis.gas_limit, + genesis.base_fee_per_gas.unwrap_or_default(), + genesis.timestamp, + ); // assert_eq!(base_fee, 980000000); } diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index 2f5791604bf..a8804e63bff 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -200,7 +200,12 @@ pub fn next_block_base_fee( if chain_spec.is_holocene_active_at_timestamp(parent.timestamp()) { Ok(decode_holocene_base_fee(chain_spec, parent, timestamp)?) } else { - Ok(chain_spec.next_block_base_fee(&parent, timestamp)) + Ok(chain_spec.next_block_base_fee( + parent.gas_used(), + parent.gas_limit(), + parent.base_fee_per_gas().unwrap_or_default(), + timestamp, + )) } } @@ -251,7 +256,15 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 0); - assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent, 0)); + assert_eq!( + base_fee.unwrap(), + op_chain_spec.next_block_base_fee( + parent.gas_used, + parent.gas_limit, + parent.base_fee_per_gas.unwrap_or_default(), + 0, + ) + ); } #[test] @@ -266,7 +279,15 @@ mod tests { ..Default::default() }; let base_fee = next_block_base_fee(&op_chain_spec, &parent, 1800000005); - assert_eq!(base_fee.unwrap(), op_chain_spec.next_block_base_fee(&parent, 0)); + assert_eq!( + base_fee.unwrap(), + op_chain_spec.next_block_base_fee( + parent.gas_used, + parent.gas_limit, + parent.base_fee_per_gas.unwrap_or_default(), + 0, + ) + ); } #[test] diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 24699c430d1..023023a9058 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -184,7 +184,12 @@ where prevrandao: Some(attributes.prev_randao), gas_limit: attributes.gas_limit, // calculate basefee based on parent block's gas usage - basefee: self.chain_spec().next_block_base_fee(parent, attributes.timestamp), + basefee: self.chain_spec().next_block_base_fee( + parent.gas_used(), + parent.gas_limit(), + parent.base_fee_per_gas().unwrap_or_default(), + attributes.timestamp, + ), // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 17f806514d1..75cb3cec4e9 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -153,8 +153,12 @@ pub trait EthFees: LoadFee { // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the // next block - base_fee_per_gas - .push(last_entry.next_block_base_fee(self.provider().chain_spec()) as u128); + base_fee_per_gas.push(self.provider().chain_spec().next_block_base_fee( + last_entry.gas_used, + last_entry.gas_limit, + last_entry.base_fee_per_gas, + last_entry.timestamp, + ) as u128); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); } else { @@ -208,8 +212,12 @@ pub trait EthFees: LoadFee { // The unwrap is safe since we checked earlier that we got at least 1 header. let last_header = headers.last().expect("is present"); base_fee_per_gas.push( - chain_spec - .next_block_base_fee(last_header.header(), last_header.header().timestamp()) as u128, + chain_spec.next_block_base_fee( + last_header.gas_used(), + last_header.gas_limit(), + last_header.base_fee_per_gas().unwrap_or_default(), + last_header.timestamp(), + ) as u128, ); // Same goes for the `base_fee_per_blob_gas`: // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 9ead77f6823..44be9045786 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -397,19 +397,6 @@ impl FeeHistoryEntry { } } - /// Calculate the base fee for the next block according to the EIP-1559 spec. - pub fn next_block_base_fee(&self, chain_spec: impl EthChainSpec) -> u64 { - let parent_header = Header { - gas_used: self.gas_used, - gas_limit: self.gas_limit, - base_fee_per_gas: Some(self.base_fee_per_gas), - timestamp: self.timestamp, - ..Default::default() - }; - - chain_spec.next_block_base_fee(&parent_header, self.timestamp) - } - /// Returns the blob fee for the next block according to the EIP-4844 spec. /// /// Returns `None` if `excess_blob_gas` is None. diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index 70320ed02d5..f310dda6f47 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -583,8 +583,12 @@ mod tests { // Add final base fee (for the next block outside of the request) let last_header = last_header.unwrap(); let spec = mock_provider.chain_spec(); - base_fees_per_gas - .push(spec.next_block_base_fee(&last_header, last_header.timestamp) as u128); + base_fees_per_gas.push(spec.next_block_base_fee( + last_header.gas_used, + last_header.gas_limit, + last_header.base_fee_per_gas.unwrap_or_default(), + last_header.timestamp, + ) as u128); let eth_api = build_test_eth_api(mock_provider); diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 133ce8fc4f1..6c79ec30114 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -137,7 +137,12 @@ pub async fn maintain_transaction_pool( block_gas_limit: latest.gas_limit(), last_seen_block_hash: latest.hash(), last_seen_block_number: latest.number(), - pending_basefee: chain_spec.next_block_base_fee(latest.header(), latest.timestamp()), + pending_basefee: chain_spec.next_block_base_fee( + latest.gas_used(), + latest.gas_limit(), + latest.base_fee_per_gas().unwrap_or_default(), + latest.timestamp(), + ), pending_blob_fee: latest .maybe_next_block_blob_fee(chain_spec.blob_params_at_timestamp(latest.timestamp())), }; @@ -315,8 +320,12 @@ 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 = - chain_spec.next_block_base_fee(new_tip.header(), new_tip.timestamp()); + let pending_block_base_fee = chain_spec.next_block_base_fee( + new_tip.gas_used(), + new_tip.gas_limit(), + new_tip.base_fee_per_gas().unwrap_or_default(), + new_tip.timestamp(), + ); let pending_block_blob_fee = new_tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(new_tip.timestamp()), ); @@ -417,8 +426,12 @@ pub async fn maintain_transaction_pool( let chain_spec = client.chain_spec(); // fees for the next block: `tip+1` - let pending_block_base_fee = - chain_spec.next_block_base_fee(tip.header(), tip.timestamp()); + let pending_block_base_fee = chain_spec.next_block_base_fee( + tip.gas_used(), + tip.gas_limit(), + tip.base_fee_per_gas().unwrap_or_default(), + tip.timestamp(), + ); let pending_block_blob_fee = tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(tip.timestamp()), ); From 992996e59f8f138ba879708c46f4a0e30642facf Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 20 Jun 2025 20:00:15 +1000 Subject: [PATCH 35/44] fix: improve error handling and clean up imports - Use proper error handling instead of unwrap_or_default for base fee - Remove unused Header import from fee_history.rs --- crates/consensus/common/src/validation.rs | 3 ++- crates/rpc/rpc-eth-types/src/fee_history.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index e650fd6736e..4bccb3a8b4d 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -266,10 +266,11 @@ pub fn validate_against_parent_eip1559_base_fee< { alloy_eips::eip1559::INITIAL_BASE_FEE } else { + let base_fee = parent.base_fee_per_gas().ok_or(ConsensusError::BaseFeeMissing)?; chain_spec.next_block_base_fee( parent.gas_used(), parent.gas_limit(), - parent.base_fee_per_gas().unwrap_or_default(), + base_fee, header.timestamp(), ) }; diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 44be9045786..014113fa33f 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -6,7 +6,7 @@ use std::{ sync::{atomic::Ordering::SeqCst, Arc}, }; -use alloy_consensus::{BlockHeader, Header, Transaction, TxReceipt}; +use alloy_consensus::{BlockHeader, Transaction, TxReceipt}; use alloy_eips::eip7840::BlobParams; use alloy_primitives::B256; use alloy_rpc_types_eth::TxGasAndReward; From 3cf3618d23b46bfdf9c0db385922a43c8853c850 Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 20 Jun 2025 20:09:24 +1000 Subject: [PATCH 36/44] docs: add clarifying comment about BaseFeeMissing error Explains that the BaseFeeMissing error for parent blocks won't occur in practice since previous blocks are validated to have base fees. --- crates/consensus/common/src/validation.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 4bccb3a8b4d..f0c61b36720 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -266,6 +266,8 @@ pub fn validate_against_parent_eip1559_base_fee< { alloy_eips::eip1559::INITIAL_BASE_FEE } else { + // This BaseFeeMissing will not happen as previous blocks are checked to have + // them. let base_fee = parent.base_fee_per_gas().ok_or(ConsensusError::BaseFeeMissing)?; chain_spec.next_block_base_fee( parent.gas_used(), From 3bc1c01b45006003db1b381c0defd832892da362 Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 20 Jun 2025 20:14:55 +1000 Subject: [PATCH 37/44] test: use unwrap() instead of unwrap_or_default() for base fee In e2e tests, we expect base_fee_per_gas to always be present, so using unwrap() provides better error reporting if it's missing. --- crates/ethereum/node/tests/e2e/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ethereum/node/tests/e2e/rpc.rs b/crates/ethereum/node/tests/e2e/rpc.rs index ac36ea3c279..71be71f5974 100644 --- a/crates/ethereum/node/tests/e2e/rpc.rs +++ b/crates/ethereum/node/tests/e2e/rpc.rs @@ -102,7 +102,7 @@ async fn test_fee_history() -> eyre::Result<()> { let expected_base_fee = chain_spec.next_block_base_fee( prev_header.gas_used, prev_header.gas_limit, - prev_header.base_fee_per_gas.unwrap_or_default(), + prev_header.base_fee_per_gas.unwrap(), header.timestamp, ); From 92795dd0dcc3421cf325c826ac4a6bb3b923a8b8 Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 20 Jun 2025 20:24:57 +1000 Subject: [PATCH 38/44] revert: use original next_block_base_fee function for optimism Revert back to using the optimism-specific next_block_base_fee function which handles Holocene hardfork logic and proper error propagation with ?, matching the original behavior before the trait signature changes. --- crates/optimism/evm/src/lib.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 023023a9058..f38b7e43a04 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -17,6 +17,8 @@ use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; use alloy_op_evm::{block::receipt_builder::OpReceiptBuilder, OpBlockExecutionCtx}; use alloy_primitives::U256; use core::fmt::Debug; +use reth_optimism_consensus::next_block_base_fee; + use op_alloy_consensus::EIP1559ParamError; use op_revm::{OpSpecId, OpTransaction}; use reth_chainspec::EthChainSpec; @@ -184,12 +186,7 @@ where prevrandao: Some(attributes.prev_randao), gas_limit: attributes.gas_limit, // calculate basefee based on parent block's gas usage - basefee: self.chain_spec().next_block_base_fee( - parent.gas_used(), - parent.gas_limit(), - parent.base_fee_per_gas().unwrap_or_default(), - attributes.timestamp, - ), + basefee: next_block_base_fee(self.chain_spec(), parent, attributes.timestamp)?, // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; From 7beacfcaaaa2b39808a68ea8ccc50c2f8bac3f07 Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 20 Jun 2025 20:27:58 +1000 Subject: [PATCH 39/44] style: clean up imports and formatting - Move import to maintain alphabetical order - Add blank line for better readability --- crates/optimism/evm/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index f38b7e43a04..523bd49de79 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -17,13 +17,12 @@ use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; use alloy_op_evm::{block::receipt_builder::OpReceiptBuilder, OpBlockExecutionCtx}; use alloy_primitives::U256; use core::fmt::Debug; -use reth_optimism_consensus::next_block_base_fee; - use op_alloy_consensus::EIP1559ParamError; use op_revm::{OpSpecId, OpTransaction}; use reth_chainspec::EthChainSpec; use reth_evm::{ConfigureEvm, EvmEnv}; use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_consensus::next_block_base_fee; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{DepositReceipt, OpPrimitives}; use reth_primitives_traits::{NodePrimitives, SealedBlock, SealedHeader, SignedTransaction}; @@ -178,6 +177,7 @@ where ) .or_else(|| (spec_id.into_eth_spec().is_enabled_in(SpecId::CANCUN)).then_some(0)) .map(|gas| BlobExcessGasAndPrice::new(gas, false)); + let block_env = BlockEnv { number: parent.number() + 1, beneficiary: attributes.suggested_fee_recipient, From da5313747e0e09943a6ebbeeb403145fb10eec89 Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 20 Jun 2025 20:33:42 +1000 Subject: [PATCH 40/44] docs: fix broken documentation link Replace broken AlloyBlockHeader::next_block_base_fee reference with calc_next_block_base_fee which is the actual function used in the implementation. --- crates/chainspec/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index ba5e16245ac..848c703b7a5 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -66,7 +66,7 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// Returns the final total difficulty if the Paris hardfork is known. fn final_paris_total_difficulty(&self) -> Option; - /// See [`AlloyBlockHeader::next_block_base_fee`]. + /// See [`calc_next_block_base_fee`]. fn next_block_base_fee( &self, parent_gas_used: u64, From d35816aff693e04a3f747d2d06752757b7766253 Mon Sep 17 00:00:00 2001 From: Rez Date: Sat, 21 Jun 2025 11:32:45 +1000 Subject: [PATCH 41/44] feat: add parent_timestamp parameter to next_block_base_fee Add parent_timestamp parameter to the next_block_base_fee method signature to enable time-based validations and hardfork transition logic that may require both parent and target block timestamps. Changes: - Updated EthChainSpec trait method signature - Updated all callers to provide parent timestamp - Maintained backward compatibility of calculation logic - Parent timestamp now available for future chain-specific implementations The parent_timestamp parameter is currently unused in the base implementation but provides the foundation for more sophisticated base fee calculations. --- crates/chainspec/src/api.rs | 5 +++-- crates/chainspec/src/lib.rs | 1 + crates/consensus/common/src/validation.rs | 1 + crates/ethereum/evm/src/lib.rs | 1 + crates/ethereum/node/tests/e2e/rpc.rs | 1 + crates/optimism/chainspec/src/lib.rs | 3 +++ crates/optimism/consensus/src/validation/mod.rs | 3 +++ crates/rpc/rpc-eth-api/src/helpers/fee.rs | 2 ++ crates/rpc/rpc/src/eth/core.rs | 1 + crates/transaction-pool/src/maintain.rs | 3 +++ 10 files changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 848c703b7a5..416f50af0bf 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -72,13 +72,14 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { parent_gas_used: u64, parent_gas_limit: u64, parent_base_fee_per_gas: u64, - timestamp: u64, + _parent_timestamp: u64, + target_timestamp: u64, ) -> u64 { calc_next_block_base_fee( parent_gas_used, parent_gas_limit, parent_base_fee_per_gas, - self.base_fee_params_at_timestamp(timestamp), + self.base_fee_params_at_timestamp(target_timestamp), ) } } diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index 617c3a25516..cd071496f1f 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -176,6 +176,7 @@ mod tests { parent.gas_used, parent.gas_limit, parent.base_fee_per_gas.unwrap_or_default(), + parent.timestamp, next_timestamp, ); assert_eq!(expected, got, "Base fee calculation does not match expected value"); diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index f0c61b36720..b6dce50f466 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -273,6 +273,7 @@ pub fn validate_against_parent_eip1559_base_fee< parent.gas_used(), parent.gas_limit(), base_fee, + parent.timestamp(), header.timestamp(), ) }; diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index a27f6623822..375fadb245a 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -218,6 +218,7 @@ where parent.gas_used, parent.gas_limit, parent.base_fee_per_gas.unwrap_or_default(), + parent.timestamp, attributes.timestamp, ); let mut gas_limit = attributes.gas_limit; diff --git a/crates/ethereum/node/tests/e2e/rpc.rs b/crates/ethereum/node/tests/e2e/rpc.rs index 71be71f5974..3312b719523 100644 --- a/crates/ethereum/node/tests/e2e/rpc.rs +++ b/crates/ethereum/node/tests/e2e/rpc.rs @@ -103,6 +103,7 @@ async fn test_fee_history() -> eyre::Result<()> { prev_header.gas_used, prev_header.gas_limit, prev_header.base_fee_per_gas.unwrap(), + prev_header.timestamp, header.timestamp, ); diff --git a/crates/optimism/chainspec/src/lib.rs b/crates/optimism/chainspec/src/lib.rs index c51dd3de41a..ec168414236 100644 --- a/crates/optimism/chainspec/src/lib.rs +++ b/crates/optimism/chainspec/src/lib.rs @@ -739,6 +739,7 @@ mod tests { genesis.gas_limit, genesis.base_fee_per_gas.unwrap_or_default(), genesis.timestamp, + genesis.timestamp, ); // assert_eq!(base_fee, 980000000); @@ -756,6 +757,7 @@ mod tests { genesis.gas_limit, genesis.base_fee_per_gas.unwrap_or_default(), genesis.timestamp, + genesis.timestamp, ); // assert_eq!(base_fee, 980000000); @@ -773,6 +775,7 @@ mod tests { genesis.gas_limit, genesis.base_fee_per_gas.unwrap_or_default(), genesis.timestamp, + genesis.timestamp, ); // assert_eq!(base_fee, 980000000); diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index a8804e63bff..533042e933b 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -204,6 +204,7 @@ pub fn next_block_base_fee( parent.gas_used(), parent.gas_limit(), parent.base_fee_per_gas().unwrap_or_default(), + parent.timestamp(), timestamp, )) } @@ -262,6 +263,7 @@ mod tests { parent.gas_used, parent.gas_limit, parent.base_fee_per_gas.unwrap_or_default(), + parent.timestamp, 0, ) ); @@ -285,6 +287,7 @@ mod tests { parent.gas_used, parent.gas_limit, parent.base_fee_per_gas.unwrap_or_default(), + parent.timestamp, 0, ) ); diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 75cb3cec4e9..f552bc371e8 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -158,6 +158,7 @@ pub trait EthFees: LoadFee { last_entry.gas_limit, last_entry.base_fee_per_gas, last_entry.timestamp, + last_entry.timestamp, ) as u128); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); @@ -217,6 +218,7 @@ pub trait EthFees: LoadFee { last_header.gas_limit(), last_header.base_fee_per_gas().unwrap_or_default(), last_header.timestamp(), + last_header.timestamp(), ) as u128, ); // Same goes for the `base_fee_per_blob_gas`: diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index f310dda6f47..74cc9a60bfd 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -588,6 +588,7 @@ mod tests { last_header.gas_limit, last_header.base_fee_per_gas.unwrap_or_default(), last_header.timestamp, + last_header.timestamp, ) as u128); let eth_api = build_test_eth_api(mock_provider); diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 6c79ec30114..4849489c3e3 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -142,6 +142,7 @@ pub async fn maintain_transaction_pool( latest.gas_limit(), latest.base_fee_per_gas().unwrap_or_default(), latest.timestamp(), + latest.timestamp(), ), pending_blob_fee: latest .maybe_next_block_blob_fee(chain_spec.blob_params_at_timestamp(latest.timestamp())), @@ -325,6 +326,7 @@ pub async fn maintain_transaction_pool( new_tip.gas_limit(), new_tip.base_fee_per_gas().unwrap_or_default(), new_tip.timestamp(), + new_tip.timestamp(), ); let pending_block_blob_fee = new_tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(new_tip.timestamp()), @@ -431,6 +433,7 @@ pub async fn maintain_transaction_pool( tip.gas_limit(), tip.base_fee_per_gas().unwrap_or_default(), tip.timestamp(), + tip.timestamp(), ); let pending_block_blob_fee = tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(tip.timestamp()), From e76da1cd5bcd8939006c7bda1ed41e5efa20ed18 Mon Sep 17 00:00:00 2001 From: Rez Date: Mon, 23 Jun 2025 07:23:51 +1000 Subject: [PATCH 42/44] feat: adopt alloy-consensus pattern for next_block_base_fee Centralizes base fee calculation through EthChainSpec trait while following alloy-consensus pattern of returning Option instead of u64. Changes: - Update EthChainSpec::next_block_base_fee to return Option - Add Self: Sized constraint to maintain trait object compatibility - Use header parameter approach instead of individual fields - Update all callers to handle Option return with unwrap_or_default() - Maintain backward compatibility for Optimism-specific logic - Preserve original variable patterns to minimize diff The Option return type indicates absence of EIP-1559 support (None = no base fee), matching the pattern used in alloy-consensus crate. --- crates/chainspec/src/api.rs | 25 +++++++--------- crates/chainspec/src/lib.rs | 8 +---- crates/consensus/common/src/validation.rs | 13 ++------ crates/ethereum/evm/src/lib.rs | 13 +++----- crates/ethereum/node/tests/e2e/rpc.rs | 9 ++---- crates/optimism/chainspec/src/lib.rs | 24 ++------------- .../optimism/consensus/src/validation/mod.rs | 24 ++------------- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 28 ++++++++--------- crates/rpc/rpc/src/eth/core.rs | 11 +++---- crates/transaction-pool/src/maintain.rs | 30 ++++++------------- 10 files changed, 54 insertions(+), 131 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 416f50af0bf..c21f60dbd6c 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -1,7 +1,7 @@ use crate::{ChainSpec, DepositContract}; use alloc::{boxed::Box, vec::Vec}; use alloy_chains::Chain; -use alloy_consensus::Header; +use alloy_consensus::{BlockHeader, Header}; use alloy_eips::{calc_next_block_base_fee, eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_genesis::Genesis; use alloy_primitives::{B256, U256}; @@ -67,20 +67,17 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { fn final_paris_total_difficulty(&self) -> Option; /// See [`calc_next_block_base_fee`]. - fn next_block_base_fee( - &self, - parent_gas_used: u64, - parent_gas_limit: u64, - parent_base_fee_per_gas: u64, - _parent_timestamp: u64, - target_timestamp: u64, - ) -> u64 { - calc_next_block_base_fee( - parent_gas_used, - parent_gas_limit, - parent_base_fee_per_gas, + fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> Option + where + Self: Sized, + H: BlockHeader, + { + Some(calc_next_block_base_fee( + parent.gas_used(), + parent.gas_limit(), + parent.base_fee_per_gas()?, self.base_fee_params_at_timestamp(target_timestamp), - ) + )) } } diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index cd071496f1f..5ba42529399 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -172,13 +172,7 @@ mod tests { .next_block_base_fee(spec.base_fee_params_at_timestamp(next_timestamp)) .unwrap_or_default(); - let got = spec.next_block_base_fee( - parent.gas_used, - parent.gas_limit, - parent.base_fee_per_gas.unwrap_or_default(), - parent.timestamp, - next_timestamp, - ); + let got = spec.next_block_base_fee(&parent, next_timestamp).unwrap_or_default(); assert_eq!(expected, got, "Base fee calculation does not match expected value"); } } diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index b6dce50f466..1d2ee7bc871 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -266,16 +266,9 @@ pub fn validate_against_parent_eip1559_base_fee< { alloy_eips::eip1559::INITIAL_BASE_FEE } else { - // This BaseFeeMissing will not happen as previous blocks are checked to have - // them. - let base_fee = parent.base_fee_per_gas().ok_or(ConsensusError::BaseFeeMissing)?; - chain_spec.next_block_base_fee( - parent.gas_used(), - parent.gas_limit(), - base_fee, - parent.timestamp(), - header.timestamp(), - ) + chain_spec + .next_block_base_fee(parent, header.timestamp()) + .ok_or(ConsensusError::BaseFeeMissing)? }; if expected_base_fee != base_fee { return Err(ConsensusError::BaseFeeDiff(GotExpected { diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 375fadb245a..25a9ce67338 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -214,13 +214,8 @@ where BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice } }); - let mut basefee = chain_spec.next_block_base_fee( - parent.gas_used, - parent.gas_limit, - parent.base_fee_per_gas.unwrap_or_default(), - parent.timestamp, - attributes.timestamp, - ); + let mut basefee = chain_spec.next_block_base_fee(parent, attributes.timestamp); + let mut gas_limit = attributes.gas_limit; // If we are on the London fork boundary, we need to multiply the parent's gas limit by the @@ -236,7 +231,7 @@ where gas_limit *= elasticity_multiplier as u64; // set the base fee to the initial base fee from the EIP-1559 spec - basefee = INITIAL_BASE_FEE; + basefee = Some(INITIAL_BASE_FEE) } let block_env = BlockEnv { @@ -247,7 +242,7 @@ where prevrandao: Some(attributes.prev_randao), gas_limit, // calculate basefee based on parent block's gas usage - basefee, + basefee: basefee.unwrap_or_default(), // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; diff --git a/crates/ethereum/node/tests/e2e/rpc.rs b/crates/ethereum/node/tests/e2e/rpc.rs index 3312b719523..ea49d8b3c8e 100644 --- a/crates/ethereum/node/tests/e2e/rpc.rs +++ b/crates/ethereum/node/tests/e2e/rpc.rs @@ -99,13 +99,8 @@ async fn test_fee_history() -> eyre::Result<()> { .header; for block in (latest_block + 2 - block_count)..=latest_block { let header = provider.get_block_by_number(block.into()).await?.unwrap().header; - let expected_base_fee = chain_spec.next_block_base_fee( - prev_header.gas_used, - prev_header.gas_limit, - prev_header.base_fee_per_gas.unwrap(), - prev_header.timestamp, - header.timestamp, - ); + let expected_base_fee = + chain_spec.next_block_base_fee(&prev_header, header.timestamp).unwrap(); assert_eq!(header.base_fee_per_gas.unwrap(), expected_base_fee); assert_eq!( diff --git a/crates/optimism/chainspec/src/lib.rs b/crates/optimism/chainspec/src/lib.rs index ec168414236..19c76c6043c 100644 --- a/crates/optimism/chainspec/src/lib.rs +++ b/crates/optimism/chainspec/src/lib.rs @@ -734,13 +734,7 @@ mod tests { genesis.hash_slow(), b256!("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd") ); - let base_fee = BASE_MAINNET.next_block_base_fee( - genesis.gas_used, - genesis.gas_limit, - genesis.base_fee_per_gas.unwrap_or_default(), - genesis.timestamp, - genesis.timestamp, - ); + let base_fee = BASE_MAINNET.next_block_base_fee(genesis, genesis.timestamp).unwrap(); // assert_eq!(base_fee, 980000000); } @@ -752,13 +746,7 @@ mod tests { genesis.hash_slow(), b256!("0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4") ); - let base_fee = BASE_SEPOLIA.next_block_base_fee( - genesis.gas_used, - genesis.gas_limit, - genesis.base_fee_per_gas.unwrap_or_default(), - genesis.timestamp, - genesis.timestamp, - ); + let base_fee = BASE_SEPOLIA.next_block_base_fee(genesis, genesis.timestamp).unwrap(); // assert_eq!(base_fee, 980000000); } @@ -770,13 +758,7 @@ mod tests { genesis.hash_slow(), b256!("0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d") ); - let base_fee = OP_SEPOLIA.next_block_base_fee( - genesis.gas_used, - genesis.gas_limit, - genesis.base_fee_per_gas.unwrap_or_default(), - genesis.timestamp, - genesis.timestamp, - ); + let base_fee = OP_SEPOLIA.next_block_base_fee(genesis, genesis.timestamp).unwrap(); // assert_eq!(base_fee, 980000000); } diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index 533042e933b..bb0756c6fee 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -200,13 +200,7 @@ pub fn next_block_base_fee( if chain_spec.is_holocene_active_at_timestamp(parent.timestamp()) { Ok(decode_holocene_base_fee(chain_spec, parent, timestamp)?) } else { - Ok(chain_spec.next_block_base_fee( - parent.gas_used(), - parent.gas_limit(), - parent.base_fee_per_gas().unwrap_or_default(), - parent.timestamp(), - timestamp, - )) + Ok(chain_spec.next_block_base_fee(&parent, timestamp).unwrap_or_default()) } } @@ -259,13 +253,7 @@ mod tests { let base_fee = next_block_base_fee(&op_chain_spec, &parent, 0); assert_eq!( base_fee.unwrap(), - op_chain_spec.next_block_base_fee( - parent.gas_used, - parent.gas_limit, - parent.base_fee_per_gas.unwrap_or_default(), - parent.timestamp, - 0, - ) + op_chain_spec.next_block_base_fee(&parent, 0).unwrap_or_default() ); } @@ -283,13 +271,7 @@ mod tests { let base_fee = next_block_base_fee(&op_chain_spec, &parent, 1800000005); assert_eq!( base_fee.unwrap(), - op_chain_spec.next_block_base_fee( - parent.gas_used, - parent.gas_limit, - parent.base_fee_per_gas.unwrap_or_default(), - parent.timestamp, - 0, - ) + op_chain_spec.next_block_base_fee(&parent, 0).unwrap_or_default() ); } diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index f552bc371e8..385b6e969ce 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -153,13 +153,17 @@ pub trait EthFees: LoadFee { // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the // next block - base_fee_per_gas.push(self.provider().chain_spec().next_block_base_fee( - last_entry.gas_used, - last_entry.gas_limit, - last_entry.base_fee_per_gas, - last_entry.timestamp, - last_entry.timestamp, - ) as u128); + let last_header = self + .provider() + .header(&last_entry.header_hash) + .map_err(Self::Error::from_eth_err)? + .ok_or(EthApiError::HeaderNotFound(last_entry.header_hash.into()))?; + base_fee_per_gas.push( + self.provider() + .chain_spec() + .next_block_base_fee(&last_header, last_entry.timestamp) + .unwrap_or_default() as u128, + ); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); } else { @@ -213,13 +217,9 @@ pub trait EthFees: LoadFee { // The unwrap is safe since we checked earlier that we got at least 1 header. let last_header = headers.last().expect("is present"); base_fee_per_gas.push( - chain_spec.next_block_base_fee( - last_header.gas_used(), - last_header.gas_limit(), - last_header.base_fee_per_gas().unwrap_or_default(), - last_header.timestamp(), - last_header.timestamp(), - ) as u128, + chain_spec + .next_block_base_fee(last_header.header(), last_header.timestamp()) + .unwrap_or_default() as u128, ); // Same goes for the `base_fee_per_blob_gas`: // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index 74cc9a60bfd..5acf584156a 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -583,13 +583,10 @@ mod tests { // Add final base fee (for the next block outside of the request) let last_header = last_header.unwrap(); let spec = mock_provider.chain_spec(); - base_fees_per_gas.push(spec.next_block_base_fee( - last_header.gas_used, - last_header.gas_limit, - last_header.base_fee_per_gas.unwrap_or_default(), - last_header.timestamp, - last_header.timestamp, - ) as u128); + base_fees_per_gas.push( + spec.next_block_base_fee(&last_header, last_header.timestamp).unwrap_or_default() + as u128, + ); let eth_api = build_test_eth_api(mock_provider); diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 4849489c3e3..e6982c70f13 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -137,13 +137,9 @@ pub async fn maintain_transaction_pool( block_gas_limit: latest.gas_limit(), last_seen_block_hash: latest.hash(), last_seen_block_number: latest.number(), - pending_basefee: chain_spec.next_block_base_fee( - latest.gas_used(), - latest.gas_limit(), - latest.base_fee_per_gas().unwrap_or_default(), - latest.timestamp(), - latest.timestamp(), - ), + pending_basefee: chain_spec + .next_block_base_fee(latest.header(), latest.timestamp()) + .unwrap_or_default(), pending_blob_fee: latest .maybe_next_block_blob_fee(chain_spec.blob_params_at_timestamp(latest.timestamp())), }; @@ -321,13 +317,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 = chain_spec.next_block_base_fee( - new_tip.gas_used(), - new_tip.gas_limit(), - new_tip.base_fee_per_gas().unwrap_or_default(), - new_tip.timestamp(), - new_tip.timestamp(), - ); + let pending_block_base_fee = chain_spec + .next_block_base_fee(new_tip.header(), new_tip.timestamp()) + .unwrap_or_default(); let pending_block_blob_fee = new_tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(new_tip.timestamp()), ); @@ -428,13 +420,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 = chain_spec.next_block_base_fee( - tip.gas_used(), - tip.gas_limit(), - tip.base_fee_per_gas().unwrap_or_default(), - tip.timestamp(), - tip.timestamp(), - ); + let pending_block_base_fee = chain_spec + .next_block_base_fee(tip.header(), tip.timestamp()) + .unwrap_or_default(); let pending_block_blob_fee = tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(tip.timestamp()), ); From 9e94ca2a5becf1ef566be93ab1d5f0e50d94c1a3 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 24 Jun 2025 05:40:05 +1000 Subject: [PATCH 43/44] feat: store full headers in FeeHistoryEntry to eliminate database lookups This change implements a performance optimization suggested in PR #16927 by storing complete Header objects in FeeHistoryEntry instead of individual fields. This eliminates the need for additional database header lookups when calculating next block base fees. Key changes: - Modified FeeHistoryEntry to store full Header instead of individual gas fields - Updated FeeHistoryEntry::new() to accept any Block with BlockHeader (generic) - Updated fee history construction to use stored headers directly - Eliminated database lookup in fee calculation path (line 157-160 in fee.rs) - Followed Reth patterns for direct field access over accessor methods Performance impact: - Eliminates one database query per fee history request - Increases memory usage by ~720KB for typical cache (acceptable trade-off) - Removes potential failure point from missing headers in database Technical implementation: - Single FeeHistoryEntry::new() method now handles all header types via BlockHeader trait - Manual Header construction ensures compatibility across Ethereum, Optimism, and other chains - Maintains full backward compatibility with existing API --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 10 +-- crates/rpc/rpc-eth-types/src/fee_history.rs | 74 ++++++++++++--------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 385b6e969ce..9cc7fc61b82 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -136,7 +136,8 @@ pub trait EthFees: LoadFee { } for entry in &fee_entries { - base_fee_per_gas.push(entry.base_fee_per_gas as u128); + base_fee_per_gas + .push(entry.header.base_fee_per_gas.unwrap_or_default() as u128); gas_used_ratio.push(entry.gas_used_ratio); base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default()); blob_gas_used_ratio.push(entry.blob_gas_used_ratio); @@ -153,15 +154,10 @@ pub trait EthFees: LoadFee { // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the // next block - let last_header = self - .provider() - .header(&last_entry.header_hash) - .map_err(Self::Error::from_eth_err)? - .ok_or(EthApiError::HeaderNotFound(last_entry.header_hash.into()))?; base_fee_per_gas.push( self.provider() .chain_spec() - .next_block_base_fee(&last_header, last_entry.timestamp) + .next_block_base_fee(&last_entry.header, last_entry.header.timestamp) .unwrap_or_default() as u128, ); diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 014113fa33f..e5fd43ef336 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -6,9 +6,8 @@ use std::{ sync::{atomic::Ordering::SeqCst, Arc}, }; -use alloy_consensus::{BlockHeader, Transaction, TxReceipt}; +use alloy_consensus::{BlockHeader, Header, Transaction, TxReceipt}; use alloy_eips::eip7840::BlobParams; -use alloy_primitives::B256; use alloy_rpc_types_eth::TxGasAndReward; use futures::{ future::{Fuse, FusedFuture}, @@ -74,6 +73,7 @@ impl FeeHistoryCache { async fn insert_blocks<'a, I, B, R, C>(&self, blocks: I, chain_spec: &C) where B: Block + 'a, + B::Header: BlockHeader, R: TxReceipt + 'a, I: IntoIterator, &'a [R])>, C: EthChainSpec, @@ -89,8 +89,8 @@ impl FeeHistoryCache { ); fee_history_entry.rewards = calculate_reward_percentiles_for_block( &percentiles, - fee_history_entry.gas_used, - fee_history_entry.base_fee_per_gas, + fee_history_entry.header.gas_used, + fee_history_entry.header.base_fee_per_gas.unwrap_or_default(), block.body().transactions(), receipts, ) @@ -337,8 +337,8 @@ where /// A cached entry for a block's fee history. #[derive(Debug, Clone)] pub struct FeeHistoryEntry { - /// The base fee per gas for this block. - pub base_fee_per_gas: u64, + /// The full block header. + pub header: Header, /// Gas used ratio this block. pub gas_used_ratio: f64, /// The base per blob gas for EIP-4844. @@ -349,21 +349,8 @@ pub struct FeeHistoryEntry { /// Calculated as the ratio of blob gas used and the available blob data gas per block. /// Will be zero if no blob gas was used or pre EIP-4844. pub blob_gas_used_ratio: f64, - /// The excess blob gas of the block. - pub excess_blob_gas: Option, - /// The total amount of blob gas consumed by the transactions within the block, - /// added in EIP-4844 - pub blob_gas_used: Option, - /// Gas used by this block. - pub gas_used: u64, - /// Gas limit by this block. - pub gas_limit: u64, - /// Hash of the block. - pub header_hash: B256, /// Approximated rewards for the configured percentiles. pub rewards: Vec, - /// The timestamp of the block. - pub timestamp: u64, /// Blob parameters for this block. pub blob_params: Option, } @@ -372,12 +359,38 @@ impl FeeHistoryEntry { /// Creates a new entry from a sealed block. /// /// Note: This does not calculate the rewards for the block. - pub fn new(block: &SealedBlock, blob_params: Option) -> Self { + pub fn new(block: &SealedBlock, blob_params: Option) -> Self + where + B: Block, + B::Header: BlockHeader, + { + let header = block.header(); Self { - base_fee_per_gas: block.header().base_fee_per_gas().unwrap_or_default(), - gas_used_ratio: block.header().gas_used() as f64 / block.header().gas_limit() as f64, - base_fee_per_blob_gas: block - .header() + header: Header { + parent_hash: header.parent_hash(), + ommers_hash: header.ommers_hash(), + beneficiary: header.beneficiary(), + state_root: header.state_root(), + transactions_root: header.transactions_root(), + receipts_root: header.receipts_root(), + withdrawals_root: header.withdrawals_root(), + logs_bloom: header.logs_bloom(), + difficulty: header.difficulty(), + number: header.number(), + gas_limit: header.gas_limit(), + gas_used: header.gas_used(), + timestamp: header.timestamp(), + mix_hash: header.mix_hash().unwrap_or_default(), + nonce: header.nonce().unwrap_or_default(), + base_fee_per_gas: header.base_fee_per_gas(), + blob_gas_used: header.blob_gas_used(), + excess_blob_gas: header.excess_blob_gas(), + parent_beacon_block_root: header.parent_beacon_block_root(), + requests_hash: header.requests_hash(), + extra_data: Default::default(), + }, + gas_used_ratio: header.gas_used() as f64 / header.gas_limit() as f64, + base_fee_per_blob_gas: header .excess_blob_gas() .and_then(|excess_blob_gas| Some(blob_params?.calc_blob_fee(excess_blob_gas))), blob_gas_used_ratio: block.body().blob_gas_used() as f64 / @@ -386,13 +399,7 @@ impl FeeHistoryEntry { .map(|params| params.max_blob_gas_per_block()) .unwrap_or(alloy_eips::eip4844::MAX_DATA_GAS_PER_BLOCK_DENCUN) as f64, - excess_blob_gas: block.header().excess_blob_gas(), - blob_gas_used: block.header().blob_gas_used(), - gas_used: block.header().gas_used(), - header_hash: block.hash(), - gas_limit: block.header().gas_limit(), rewards: Vec::new(), - timestamp: block.header().timestamp(), blob_params, } } @@ -411,8 +418,11 @@ impl FeeHistoryEntry { /// /// Returns a `None` if no excess blob gas is set, no EIP-4844 support pub fn next_block_excess_blob_gas(&self) -> Option { - self.excess_blob_gas.and_then(|excess_blob_gas| { - Some(self.blob_params?.next_block_excess_blob_gas(excess_blob_gas, self.blob_gas_used?)) + self.header.excess_blob_gas.and_then(|excess_blob_gas| { + Some( + self.blob_params? + .next_block_excess_blob_gas(excess_blob_gas, self.header.blob_gas_used?), + ) }) } } From e44ef72acc117d3d0977045e41724fb249134a45 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 24 Jun 2025 08:11:57 +1000 Subject: [PATCH 44/44] feat: make FeeHistoryCache and FeeHistoryEntry generic over header type Make both FeeHistoryCache and FeeHistoryEntry generic over header type H to support different blockchain header implementations while maintaining backward compatibility through default type parameters. This enables alternative blockchain implementations (like Optimism, Polygon, BSC) to use their own header types with custom fields while reusing the same fee history infrastructure. The changes maintain type safety by ensuring cache and entry types are compatible only when using the same header type. Changes: - FeeHistoryCache with generic header support - FeeHistoryEntry with improved generic constructor - Updated all trait definitions and implementations to use ProviderHeader - Added proper type constraints in builders to ensure header type consistency - Maintained backward compatibility through default type parameters --- crates/optimism/rpc/src/eth/mod.rs | 2 +- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 14 +++-- crates/rpc/rpc-eth-types/src/fee_history.rs | 67 ++++++++------------- crates/rpc/rpc/src/eth/builder.rs | 14 ++++- crates/rpc/rpc/src/eth/core.rs | 11 ++-- crates/rpc/rpc/src/eth/helpers/fees.rs | 4 +- crates/rpc/rpc/src/eth/helpers/state.rs | 5 +- 7 files changed, 57 insertions(+), 60 deletions(-) diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index 7d37873a4d4..b9e679b92d8 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -239,7 +239,7 @@ where } #[inline] - fn fee_history_cache(&self) -> &FeeHistoryCache { + fn fee_history_cache(&self) -> &FeeHistoryCache> { self.inner.eth_api.fee_history_cache() } diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 9cc7fc61b82..3ad83c6102d 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -13,7 +13,7 @@ use reth_rpc_eth_types::{ fee_history::calculate_reward_percentiles_for_block, EthApiError, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, RpcInvalidTransactionError, }; -use reth_storage_api::{BlockIdReader, BlockReaderIdExt, HeaderProvider}; +use reth_storage_api::{BlockIdReader, BlockReaderIdExt, HeaderProvider, ProviderHeader}; use tracing::debug; /// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the @@ -137,7 +137,7 @@ pub trait EthFees: LoadFee { for entry in &fee_entries { base_fee_per_gas - .push(entry.header.base_fee_per_gas.unwrap_or_default() as u128); + .push(entry.header.base_fee_per_gas().unwrap_or_default() as u128); gas_used_ratio.push(entry.gas_used_ratio); base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default()); blob_gas_used_ratio.push(entry.blob_gas_used_ratio); @@ -157,7 +157,7 @@ pub trait EthFees: LoadFee { base_fee_per_gas.push( self.provider() .chain_spec() - .next_block_base_fee(&last_entry.header, last_entry.header.timestamp) + .next_block_base_fee(&last_entry.header, last_entry.header.timestamp()) .unwrap_or_default() as u128, ); @@ -240,7 +240,11 @@ pub trait EthFees: LoadFee { /// Approximates reward at a given percentile for a specific block /// Based on the configured resolution - fn approximate_percentile(&self, entry: &FeeHistoryEntry, requested_percentile: f64) -> u128 { + fn approximate_percentile( + &self, + entry: &FeeHistoryEntry>, + requested_percentile: f64, + ) -> u128 { let resolution = self.fee_history_cache().resolution(); let rounded_percentile = (requested_percentile * resolution as f64).round() / resolution as f64; @@ -268,7 +272,7 @@ where /// Returns a handle for reading fee history data from memory. /// /// Data access in default (L1) trait method implementations. - fn fee_history_cache(&self) -> &FeeHistoryCache; + fn fee_history_cache(&self) -> &FeeHistoryCache>; /// Returns the gas price if it is set, otherwise fetches a suggested gas price for legacy /// transactions. diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index e5fd43ef336..011099bf053 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -28,11 +28,14 @@ use super::{EthApiError, EthStateCache}; /// /// Purpose for this is to provide cached data for `eth_feeHistory`. #[derive(Debug, Clone)] -pub struct FeeHistoryCache { - inner: Arc, +pub struct FeeHistoryCache { + inner: Arc>, } -impl FeeHistoryCache { +impl FeeHistoryCache +where + H: BlockHeader + Clone, +{ /// Creates new `FeeHistoryCache` instance, initialize it with the more recent data, set bounds pub fn new(config: FeeHistoryCacheConfig) -> Self { let inner = FeeHistoryCacheInner { @@ -72,8 +75,7 @@ impl FeeHistoryCache { /// Insert block data into the cache. async fn insert_blocks<'a, I, B, R, C>(&self, blocks: I, chain_spec: &C) where - B: Block + 'a, - B::Header: BlockHeader, + B: Block
+ 'a, R: TxReceipt + 'a, I: IntoIterator, &'a [R])>, C: EthChainSpec, @@ -83,14 +85,14 @@ impl FeeHistoryCache { let percentiles = self.predefined_percentiles(); // Insert all new blocks and calculate approximated rewards for (block, receipts) in blocks { - let mut fee_history_entry = FeeHistoryEntry::new( + let mut fee_history_entry = FeeHistoryEntry::::new( block, chain_spec.blob_params_at_timestamp(block.header().timestamp()), ); fee_history_entry.rewards = calculate_reward_percentiles_for_block( &percentiles, - fee_history_entry.header.gas_used, - fee_history_entry.header.base_fee_per_gas.unwrap_or_default(), + fee_history_entry.header.gas_used(), + fee_history_entry.header.base_fee_per_gas().unwrap_or_default(), block.body().transactions(), receipts, ) @@ -142,7 +144,7 @@ impl FeeHistoryCache { &self, start_block: u64, end_block: u64, - ) -> Option> { + ) -> Option>> { if end_block < start_block { // invalid range, return None return None @@ -198,7 +200,7 @@ impl Default for FeeHistoryCacheConfig { /// Container type for shared state in [`FeeHistoryCache`] #[derive(Debug)] -struct FeeHistoryCacheInner { +struct FeeHistoryCacheInner { /// Stores the lower bound of the cache lower_bound: AtomicU64, /// Stores the upper bound of the cache @@ -207,13 +209,13 @@ struct FeeHistoryCacheInner { /// and max number of blocks config: FeeHistoryCacheConfig, /// Stores the entries of the cache - entries: tokio::sync::RwLock>, + entries: tokio::sync::RwLock>>, } /// Awaits for new chain events and directly inserts them into the cache so they're available /// immediately before they need to be fetched from disk. pub async fn fee_history_cache_new_blocks_task( - fee_history_cache: FeeHistoryCache, + fee_history_cache: FeeHistoryCache, mut events: St, provider: Provider, cache: EthStateCache, @@ -222,6 +224,7 @@ pub async fn fee_history_cache_new_blocks_task( Provider: BlockReaderIdExt + ChainSpecProvider + 'static, N: NodePrimitives, + N::BlockHeader: BlockHeader + Clone, { // We're listening for new blocks emitted when the node is in live sync. // If the node transitions to stage sync, we need to fetch the missing blocks @@ -336,9 +339,9 @@ where /// A cached entry for a block's fee history. #[derive(Debug, Clone)] -pub struct FeeHistoryEntry { +pub struct FeeHistoryEntry { /// The full block header. - pub header: Header, + pub header: H, /// Gas used ratio this block. pub gas_used_ratio: f64, /// The base per blob gas for EIP-4844. @@ -355,40 +358,20 @@ pub struct FeeHistoryEntry { pub blob_params: Option, } -impl FeeHistoryEntry { +impl FeeHistoryEntry +where + H: BlockHeader + Clone, +{ /// Creates a new entry from a sealed block. /// /// Note: This does not calculate the rewards for the block. pub fn new(block: &SealedBlock, blob_params: Option) -> Self where - B: Block, - B::Header: BlockHeader, + B: Block
, { let header = block.header(); Self { - header: Header { - parent_hash: header.parent_hash(), - ommers_hash: header.ommers_hash(), - beneficiary: header.beneficiary(), - state_root: header.state_root(), - transactions_root: header.transactions_root(), - receipts_root: header.receipts_root(), - withdrawals_root: header.withdrawals_root(), - logs_bloom: header.logs_bloom(), - difficulty: header.difficulty(), - number: header.number(), - gas_limit: header.gas_limit(), - gas_used: header.gas_used(), - timestamp: header.timestamp(), - mix_hash: header.mix_hash().unwrap_or_default(), - nonce: header.nonce().unwrap_or_default(), - base_fee_per_gas: header.base_fee_per_gas(), - blob_gas_used: header.blob_gas_used(), - excess_blob_gas: header.excess_blob_gas(), - parent_beacon_block_root: header.parent_beacon_block_root(), - requests_hash: header.requests_hash(), - extra_data: Default::default(), - }, + header: block.header().clone(), gas_used_ratio: header.gas_used() as f64 / header.gas_limit() as f64, base_fee_per_blob_gas: header .excess_blob_gas() @@ -418,10 +401,10 @@ impl FeeHistoryEntry { /// /// Returns a `None` if no excess blob gas is set, no EIP-4844 support pub fn next_block_excess_blob_gas(&self) -> Option { - self.header.excess_blob_gas.and_then(|excess_blob_gas| { + self.header.excess_blob_gas().and_then(|excess_blob_gas| { Some( self.blob_params? - .next_block_excess_blob_gas(excess_blob_gas, self.header.blob_gas_used?), + .next_block_excess_blob_gas(excess_blob_gas, self.header.blob_gas_used()?), ) }) } diff --git a/crates/rpc/rpc/src/eth/builder.rs b/crates/rpc/rpc/src/eth/builder.rs index dbc7af09d0b..732ae1edf11 100644 --- a/crates/rpc/rpc/src/eth/builder.rs +++ b/crates/rpc/rpc/src/eth/builder.rs @@ -160,7 +160,11 @@ where + StateProviderFactory + ChainSpecProvider + CanonStateSubscriptions< - Primitives: NodePrimitives, + Primitives: NodePrimitives< + Block = Provider::Block, + Receipt = Provider::Receipt, + BlockHeader = Provider::Header, + >, > + Clone + Unpin + 'static, @@ -188,7 +192,7 @@ where let gas_oracle = gas_oracle.unwrap_or_else(|| { GasPriceOracle::new(provider.clone(), gas_oracle_config, eth_cache.clone()) }); - let fee_history_cache = FeeHistoryCache::new(fee_history_cache_config); + let fee_history_cache = FeeHistoryCache::::new(fee_history_cache_config); let new_canonical_blocks = provider.canonical_state_stream(); let fhc = fee_history_cache.clone(); let cache = eth_cache.clone(); @@ -232,7 +236,11 @@ where Provider: BlockReaderIdExt + StateProviderFactory + CanonStateSubscriptions< - Primitives: NodePrimitives, + Primitives: NodePrimitives< + Block = Provider::Block, + Receipt = Provider::Receipt, + BlockHeader = Provider::Header, + >, > + ChainSpecProvider + Clone + Unpin diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index 5acf584156a..59b8fb4fa2b 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -19,7 +19,8 @@ use reth_rpc_eth_types::{ EthApiError, EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock, }; use reth_storage_api::{ - BlockReader, BlockReaderIdExt, NodePrimitivesProvider, ProviderBlock, ProviderReceipt, + BlockReader, BlockReaderIdExt, NodePrimitivesProvider, ProviderBlock, ProviderHeader, + ProviderReceipt, }; use reth_tasks::{ pool::{BlockingTaskGuard, BlockingTaskPool}, @@ -127,7 +128,7 @@ where max_simulate_blocks: u64, eth_proof_window: u64, blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, + fee_history_cache: FeeHistoryCache>, evm_config: EvmConfig, proof_permits: usize, ) -> Self { @@ -276,7 +277,7 @@ pub struct EthApiInner { /// A pool dedicated to CPU heavy blocking tasks. blocking_task_pool: BlockingTaskPool, /// Cache for block fees history - fee_history_cache: FeeHistoryCache, + fee_history_cache: FeeHistoryCache>, /// The type that defines how to configure the EVM evm_config: EvmConfig, @@ -303,7 +304,7 @@ where max_simulate_blocks: u64, eth_proof_window: u64, blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, + fee_history_cache: FeeHistoryCache>, evm_config: EvmConfig, task_spawner: Box, proof_permits: usize, @@ -411,7 +412,7 @@ where /// Returns a handle to the fee history cache. #[inline] - pub const fn fee_history_cache(&self) -> &FeeHistoryCache { + pub const fn fee_history_cache(&self) -> &FeeHistoryCache> { &self.fee_history_cache } diff --git a/crates/rpc/rpc/src/eth/helpers/fees.rs b/crates/rpc/rpc/src/eth/helpers/fees.rs index 9ee8b9702be..ddefc6ec9ff 100644 --- a/crates/rpc/rpc/src/eth/helpers/fees.rs +++ b/crates/rpc/rpc/src/eth/helpers/fees.rs @@ -3,7 +3,7 @@ use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_rpc_eth_api::helpers::{EthFees, LoadBlock, LoadFee}; use reth_rpc_eth_types::{FeeHistoryCache, GasPriceOracle}; -use reth_storage_api::{BlockReader, BlockReaderIdExt, StateProviderFactory}; +use reth_storage_api::{BlockReader, BlockReaderIdExt, ProviderHeader, StateProviderFactory}; use crate::EthApi; @@ -27,7 +27,7 @@ where } #[inline] - fn fee_history_cache(&self) -> &FeeHistoryCache { + fn fee_history_cache(&self) -> &FeeHistoryCache> { self.inner.fee_history_cache() } } diff --git a/crates/rpc/rpc/src/eth/helpers/state.rs b/crates/rpc/rpc/src/eth/helpers/state.rs index 19b857fa986..90c9e32c64d 100644 --- a/crates/rpc/rpc/src/eth/helpers/state.rs +++ b/crates/rpc/rpc/src/eth/helpers/state.rs @@ -36,6 +36,7 @@ where #[cfg(test)] mod tests { use super::*; + use alloy_consensus::Header; use alloy_eips::eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M; use alloy_primitives::{Address, StorageKey, StorageValue, U256}; use reth_evm_ethereum::EthEvmConfig; @@ -67,7 +68,7 @@ mod tests { DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), - FeeHistoryCache::new(FeeHistoryCacheConfig::default()), + FeeHistoryCache::
::new(FeeHistoryCacheConfig::default()), evm_config, DEFAULT_PROOF_PERMITS, ) @@ -93,7 +94,7 @@ mod tests { DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_ETH_PROOF_WINDOW + 1, BlockingTaskPool::build().expect("failed to build tracing pool"), - FeeHistoryCache::new(FeeHistoryCacheConfig::default()), + FeeHistoryCache::
::new(FeeHistoryCacheConfig::default()), evm_config, DEFAULT_PROOF_PERMITS, )