From e3106889a8a4c8f0a5ec830f43a25fe3b7735aed Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 3 Feb 2025 23:26:18 +0400 Subject: [PATCH] feat: abstract `EthTransactionValidator` over `ChainSpec` (#14162) --- .../src/commands/debug_cmd/build_block.rs | 15 +-- crates/ethereum/node/src/node.rs | 8 +- crates/net/network/src/test_utils/testnet.rs | 11 +- crates/optimism/evm/src/l1.rs | 35 +++--- crates/optimism/node/src/node.rs | 46 ++++---- crates/optimism/node/src/txpool.rs | 33 ++++-- crates/optimism/rpc/src/eth/receipt.rs | 18 ++- .../storage/provider/src/test_utils/mock.rs | 110 +++++++++++++----- crates/transaction-pool/src/lib.rs | 17 ++- crates/transaction-pool/src/maintain.rs | 4 +- crates/transaction-pool/src/validate/eth.rs | 77 ++++++------ crates/transaction-pool/src/validate/task.rs | 23 ++-- examples/custom-node-components/src/main.rs | 8 +- 13 files changed, 236 insertions(+), 169 deletions(-) diff --git a/bin/reth/src/commands/debug_cmd/build_block.rs b/bin/reth/src/commands/debug_cmd/build_block.rs index 935ae726b..323be8bae 100644 --- a/bin/reth/src/commands/debug_cmd/build_block.rs +++ b/bin/reth/src/commands/debug_cmd/build_block.rs @@ -136,16 +136,11 @@ impl> Command { let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; let blob_store = InMemoryBlobStore::default(); - let validator = - TransactionValidationTaskExecutor::eth_builder(provider_factory.chain_spec()) - .with_head_timestamp(best_block.timestamp) - .kzg_settings(self.kzg_settings()?) - .with_additional_tasks(1) - .build_with_tasks( - blockchain_db.clone(), - ctx.task_executor.clone(), - blob_store.clone(), - ); + let validator = TransactionValidationTaskExecutor::eth_builder(blockchain_db.clone()) + .with_head_timestamp(best_block.timestamp) + .kzg_settings(self.kzg_settings()?) + .with_additional_tasks(1) + .build_with_tasks(ctx.task_executor.clone(), blob_store.clone()); let transaction_pool = reth_transaction_pool::Pool::eth_pool( validator, diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index cbac35022..c85a93b31 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -196,16 +196,12 @@ where let data_dir = ctx.config().datadir(); let pool_config = ctx.pool_config(); let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?; - let validator = TransactionValidationTaskExecutor::eth_builder(ctx.chain_spec()) + let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone()) .with_head_timestamp(ctx.head().timestamp) .kzg_settings(ctx.kzg_settings()?) .with_local_transactions_config(pool_config.local_transactions_config.clone()) .with_additional_tasks(ctx.config().txpool.additional_validation_tasks) - .build_with_tasks( - ctx.provider().clone(), - ctx.task_executor().clone(), - blob_store.clone(), - ); + .build_with_tasks(ctx.task_executor().clone(), blob_store.clone()); let transaction_pool = reth_transaction_pool::Pool::eth_pool(validator, blob_store, pool_config); diff --git a/crates/net/network/src/test_utils/testnet.rs b/crates/net/network/src/test_utils/testnet.rs index 3477387c5..503bf6511 100644 --- a/crates/net/network/src/test_utils/testnet.rs +++ b/crates/net/network/src/test_utils/testnet.rs @@ -10,7 +10,7 @@ use crate::{ }; use futures::{FutureExt, StreamExt}; use pin_project::pin_project; -use reth_chainspec::{ChainSpecProvider, Hardforks, MAINNET}; +use reth_chainspec::{ChainSpecProvider, EthereumHardforks, Hardforks}; use reth_eth_wire::{ protocol::Protocol, DisconnectReason, EthNetworkPrimitives, HelloMessageWithProtocols, }; @@ -175,7 +175,12 @@ where impl Testnet where - C: StateProviderFactory + BlockReaderIdExt + HeaderProvider + Clone + 'static, + C: ChainSpecProvider + + StateProviderFactory + + BlockReaderIdExt + + HeaderProvider + + Clone + + 'static, Pool: TransactionPool, { /// Installs an eth pool on each peer @@ -184,7 +189,6 @@ where let blob_store = InMemoryBlobStore::default(); let pool = TransactionValidationTaskExecutor::eth( peer.client.clone(), - MAINNET.clone(), blob_store.clone(), TokioTaskExecutor::default(), ); @@ -205,7 +209,6 @@ where let blob_store = InMemoryBlobStore::default(); let pool = TransactionValidationTaskExecutor::eth( peer.client.clone(), - MAINNET.clone(), blob_store.clone(), TokioTaskExecutor::default(), ); diff --git a/crates/optimism/evm/src/l1.rs b/crates/optimism/evm/src/l1.rs index 9b8658357..5e35b34f2 100644 --- a/crates/optimism/evm/src/l1.rs +++ b/crates/optimism/evm/src/l1.rs @@ -4,10 +4,9 @@ use crate::OpBlockExecutionError; use alloc::{string::ToString, sync::Arc}; use alloy_consensus::Transaction; use alloy_primitives::{address, b256, hex, Address, Bytes, B256, U256}; -use reth_chainspec::ChainSpec; use reth_execution_errors::BlockExecutionError; use reth_optimism_chainspec::OpChainSpec; -use reth_optimism_forks::OpHardfork; +use reth_optimism_forks::{OpHardfork, OpHardforks}; use reth_primitives_traits::BlockBody; use revm::{ primitives::{Bytecode, HashMap, SpecId}, @@ -186,19 +185,20 @@ pub fn parse_l1_info_tx_ecotone(data: &[u8]) -> Result Result; @@ -206,13 +206,14 @@ pub trait RethL1BlockInfo { /// Computes the data gas cost for an L2 transaction. /// /// ### Takes - /// - `chain_spec`: The [`ChainSpec`] for the node. + /// - `chain_spec`: The chain spec for the node. /// - `timestamp`: The timestamp of the current block. /// - `input`: The calldata of the transaction. fn l1_data_gas( &self, - chain_spec: &ChainSpec, + chain_spec: impl OpHardforks, timestamp: u64, + block_number: u64, input: &[u8], ) -> Result; } @@ -220,8 +221,9 @@ pub trait RethL1BlockInfo { impl RethL1BlockInfo for L1BlockInfo { fn l1_tx_data_fee( &mut self, - chain_spec: &ChainSpec, + chain_spec: impl OpHardforks, timestamp: u64, + block_number: u64, input: &[u8], is_deposit: bool, ) -> Result { @@ -229,13 +231,13 @@ impl RethL1BlockInfo for L1BlockInfo { return Ok(U256::ZERO) } - let spec_id = if chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, timestamp) { + let spec_id = if chain_spec.is_fjord_active_at_timestamp(timestamp) { SpecId::FJORD - } else if chain_spec.is_fork_active_at_timestamp(OpHardfork::Ecotone, timestamp) { + } else if chain_spec.is_ecotone_active_at_timestamp(timestamp) { SpecId::ECOTONE - } else if chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, timestamp) { + } else if chain_spec.is_regolith_active_at_timestamp(timestamp) { SpecId::REGOLITH - } else if chain_spec.is_fork_active_at_timestamp(OpHardfork::Bedrock, timestamp) { + } else if chain_spec.is_bedrock_active_at_block(block_number) { SpecId::BEDROCK } else { return Err(OpBlockExecutionError::L1BlockInfoError { @@ -248,15 +250,16 @@ impl RethL1BlockInfo for L1BlockInfo { fn l1_data_gas( &self, - chain_spec: &ChainSpec, + chain_spec: impl OpHardforks, timestamp: u64, + block_number: u64, input: &[u8], ) -> Result { - let spec_id = if chain_spec.is_fork_active_at_timestamp(OpHardfork::Fjord, timestamp) { + let spec_id = if chain_spec.is_fjord_active_at_timestamp(timestamp) { SpecId::FJORD - } else if chain_spec.is_fork_active_at_timestamp(OpHardfork::Regolith, timestamp) { + } else if chain_spec.is_regolith_active_at_timestamp(timestamp) { SpecId::REGOLITH - } else if chain_spec.is_fork_active_at_timestamp(OpHardfork::Bedrock, timestamp) { + } else if chain_spec.is_bedrock_active_at_block(block_number) { SpecId::BEDROCK } else { return Err(OpBlockExecutionError::L1BlockInfoError { diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index a25f9bf56..55d0d41b2 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -13,7 +13,9 @@ use reth_evm::{ execute::BasicBlockExecutorProvider, ConfigureEvm, ConfigureEvmEnv, ConfigureEvmFor, }; use reth_network::{NetworkConfig, NetworkHandle, NetworkManager, NetworkPrimitives, PeersInfo}; -use reth_node_api::{AddOnsContext, FullNodeComponents, NodeAddOns, PrimitivesTy, TxTy}; +use reth_node_api::{ + AddOnsContext, FullNodeComponents, NodeAddOns, NodePrimitives, PrimitivesTy, TxTy, +}; use reth_node_builder::{ components::{ ComponentsBuilder, ConsensusBuilder, ExecutorBuilder, NetworkBuilder, @@ -26,6 +28,7 @@ use reth_node_builder::{ use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::OpBeaconConsensus; use reth_optimism_evm::{BasicOpReceiptBuilder, OpEvmConfig, OpExecutionStrategyFactory}; +use reth_optimism_forks::OpHardforks; use reth_optimism_payload_builder::{ builder::OpPayloadTransactions, config::{OpBuilderConfig, OpDAConfig}, @@ -357,7 +360,12 @@ pub struct OpPoolBuilder { impl PoolBuilder for OpPoolBuilder where - Node: FullNodeTypes>, + Node: FullNodeTypes< + Types: NodeTypes< + ChainSpec: OpHardforks, + Primitives: NodePrimitives, + >, + >, { type Pool = OpTransactionPool; @@ -366,24 +374,22 @@ where let data_dir = ctx.config().datadir(); let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?; - let validator = TransactionValidationTaskExecutor::eth_builder(Arc::new( - ctx.chain_spec().inner.clone(), - )) - .no_eip4844() - .with_head_timestamp(ctx.head().timestamp) - .kzg_settings(ctx.kzg_settings()?) - .with_additional_tasks( - pool_config_overrides - .additional_validation_tasks - .unwrap_or_else(|| ctx.config().txpool.additional_validation_tasks), - ) - .build_with_tasks(ctx.provider().clone(), ctx.task_executor().clone(), blob_store.clone()) - .map(|validator| { - OpTransactionValidator::new(validator) - // In --dev mode we can't require gas fees because we're unable to decode - // the L1 block info - .require_l1_data_gas_fee(!ctx.config().dev.dev) - }); + let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone()) + .no_eip4844() + .with_head_timestamp(ctx.head().timestamp) + .kzg_settings(ctx.kzg_settings()?) + .with_additional_tasks( + pool_config_overrides + .additional_validation_tasks + .unwrap_or_else(|| ctx.config().txpool.additional_validation_tasks), + ) + .build_with_tasks(ctx.task_executor().clone(), blob_store.clone()) + .map(|validator| { + OpTransactionValidator::new(validator) + // In --dev mode we can't require gas fees because we're unable to decode + // the L1 block info + .require_l1_data_gas_fee(!ctx.config().dev.dev) + }); let transaction_pool = reth_transaction_pool::Pool::new( validator, diff --git a/crates/optimism/node/src/txpool.rs b/crates/optimism/node/src/txpool.rs index 31b1f6708..fd8a4f1ec 100644 --- a/crates/optimism/node/src/txpool.rs +++ b/crates/optimism/node/src/txpool.rs @@ -6,16 +6,16 @@ use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{Address, TxHash, TxKind, U256}; use op_alloy_consensus::OpTypedTransaction; use parking_lot::RwLock; -use reth_chainspec::ChainSpec; use reth_node_api::{Block, BlockBody}; use reth_optimism_evm::RethL1BlockInfo; -use reth_optimism_primitives::{OpBlock, OpTransactionSigned}; +use reth_optimism_forks::OpHardforks; +use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::{ transaction::TransactionConversionError, GotExpected, InvalidTransactionError, Recovered, SealedBlock, }; use reth_primitives_traits::SignedTransaction; -use reth_provider::{BlockReaderIdExt, StateProviderFactory}; +use reth_provider::{BlockReaderIdExt, ChainSpecProvider, StateProviderFactory}; use reth_revm::L1BlockInfo; use reth_transaction_pool::{ CoinbaseTipOrdering, EthBlobTransactionSidecar, EthPoolTransaction, EthPooledTransaction, @@ -242,7 +242,10 @@ pub struct OpTransactionValidator { impl OpTransactionValidator { /// Returns the configured chain spec - pub fn chain_spec(&self) -> &Arc { + pub fn chain_spec(&self) -> Arc + where + Client: ChainSpecProvider, + { self.inner.chain_spec() } @@ -256,6 +259,11 @@ impl OpTransactionValidator { self.block_info.timestamp.load(Ordering::Relaxed) } + /// Returns the current block number. + fn block_number(&self) -> u64 { + self.block_info.number.load(Ordering::Relaxed) + } + /// Whether to ensure that the transaction's sender has enough balance to also cover the L1 gas /// fee. pub fn require_l1_data_gas_fee(self, require_l1_data_gas_fee: bool) -> Self { @@ -271,7 +279,7 @@ impl OpTransactionValidator { impl OpTransactionValidator where - Client: StateProviderFactory + BlockReaderIdExt, + Client: ChainSpecProvider + StateProviderFactory + BlockReaderIdExt, Tx: EthPoolTransaction, { /// Create a new [`OpTransactionValidator`]. @@ -284,6 +292,7 @@ where // so that we will accept txs into the pool before the first block if block.header().number() == 0 { this.block_info.timestamp.store(block.header().timestamp(), Ordering::Relaxed); + this.block_info.number.store(block.header().number(), Ordering::Relaxed); } else { this.update_l1_block_info(block.header(), block.body().transactions().first()); } @@ -309,6 +318,7 @@ where T: Transaction, { self.block_info.timestamp.store(header.timestamp(), Ordering::Relaxed); + self.block_info.number.store(header.number(), Ordering::Relaxed); if let Some(Ok(cost_addition)) = tx.map(reth_optimism_evm::extract_l1_info_from_tx) { *self.block_info.l1_block_info.write() = cost_addition; @@ -357,6 +367,7 @@ where let cost_addition = match l1_block_info.l1_tx_data_fee( self.chain_spec(), self.block_timestamp(), + self.block_number(), &encoded, false, ) { @@ -404,7 +415,7 @@ where impl TransactionValidator for OpTransactionValidator where - Client: StateProviderFactory + BlockReaderIdExt, + Client: ChainSpecProvider + StateProviderFactory + BlockReaderIdExt, Tx: EthPoolTransaction, { type Transaction = Tx; @@ -443,6 +454,8 @@ pub struct OpL1BlockInfo { l1_block_info: RwLock, /// Current block timestamp. timestamp: AtomicU64, + /// Current block number. + number: AtomicU64, } #[cfg(test)] @@ -451,7 +464,7 @@ mod tests { use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{PrimitiveSignature as Signature, TxKind, U256}; use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; - use reth_chainspec::MAINNET; + use reth_optimism_chainspec::OP_MAINNET; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use reth_provider::test_utils::MockEthProvider; @@ -461,11 +474,11 @@ mod tests { }; #[test] fn validate_optimism_transaction() { - let client = MockEthProvider::default(); - let validator = EthTransactionValidatorBuilder::new(MAINNET.clone()) + let client = MockEthProvider::default().with_chain_spec(OP_MAINNET.clone()); + let validator = EthTransactionValidatorBuilder::new(client) .no_shanghai() .no_cancun() - .build(client, InMemoryBlobStore::default()); + .build(InMemoryBlobStore::default()); let validator = OpTransactionValidator::new(validator); let origin = TransactionOrigin::External; diff --git a/crates/optimism/rpc/src/eth/receipt.rs b/crates/optimism/rpc/src/eth/receipt.rs index cd37a8d24..7f12d2595 100644 --- a/crates/optimism/rpc/src/eth/receipt.rs +++ b/crates/optimism/rpc/src/eth/receipt.rs @@ -59,6 +59,8 @@ where /// deposit transaction. #[derive(Debug, Clone)] pub struct OpReceiptFieldsBuilder { + /// Block number. + pub block_number: u64, /// Block timestamp. pub block_timestamp: u64, /// The L1 fee for transaction. @@ -87,8 +89,9 @@ pub struct OpReceiptFieldsBuilder { impl OpReceiptFieldsBuilder { /// Returns a new builder. - pub const fn new(block_timestamp: u64) -> Self { + pub const fn new(block_timestamp: u64, block_number: u64) -> Self { Self { + block_number, block_timestamp, l1_fee: None, l1_data_gas: None, @@ -110,18 +113,19 @@ impl OpReceiptFieldsBuilder { l1_block_info: &mut revm::L1BlockInfo, ) -> Result { let raw_tx = tx.encoded_2718(); + let block_number = self.block_number; let timestamp = self.block_timestamp; self.l1_fee = Some( l1_block_info - .l1_tx_data_fee(chain_spec, timestamp, &raw_tx, tx.is_deposit()) + .l1_tx_data_fee(chain_spec, timestamp, block_number, &raw_tx, tx.is_deposit()) .map_err(|_| OpEthApiError::L1BlockFeeError)? .saturating_to(), ); self.l1_data_gas = Some( l1_block_info - .l1_data_gas(chain_spec, timestamp, &raw_tx) + .l1_data_gas(chain_spec, timestamp, block_number, &raw_tx) .map_err(|_| OpEthApiError::L1BlockGasError)? .saturating_add(l1_block_info.l1_fee_overhead.unwrap_or_default()) .saturating_to(), @@ -154,6 +158,7 @@ impl OpReceiptFieldsBuilder { /// Builds the [`OpTransactionReceiptFields`] object. pub const fn build(self) -> OpTransactionReceiptFields { let Self { + block_number: _, // used to compute other fields block_timestamp: _, // used to compute other fields l1_fee, l1_data_gas: l1_gas_used, @@ -202,6 +207,7 @@ impl OpReceiptBuilder { l1_block_info: &mut revm::L1BlockInfo, ) -> Result { let timestamp = meta.timestamp; + let block_number = meta.block_number; let core_receipt = build_receipt(transaction, meta, receipt, all_receipts, None, |receipt_with_bloom| { match receipt { @@ -222,7 +228,7 @@ impl OpReceiptBuilder { } })?; - let op_receipt_fields = OpReceiptFieldsBuilder::new(timestamp) + let op_receipt_fields = OpReceiptFieldsBuilder::new(timestamp, block_number) .l1_block_info(chain_spec, transaction, l1_block_info)? .build(); @@ -304,7 +310,7 @@ mod test { // test assert!(OP_MAINNET.is_fjord_active_at_timestamp(BLOCK_124665056_TIMESTAMP)); - let receipt_meta = OpReceiptFieldsBuilder::new(BLOCK_124665056_TIMESTAMP) + let receipt_meta = OpReceiptFieldsBuilder::new(BLOCK_124665056_TIMESTAMP, 124665056) .l1_block_info(&OP_MAINNET, &tx_1, &mut l1_block_info) .expect("should parse revm l1 info") .build(); @@ -370,7 +376,7 @@ mod test { let tx = hex!("02f86c8221058034839a4ae283021528942f16386bb37709016023232523ff6d9daf444be380841249c58bc080a001b927eda2af9b00b52a57be0885e0303c39dd2831732e14051c2336470fd468a0681bf120baf562915841a48601c2b54a6742511e535cf8f71c95115af7ff63bd"); let tx_1 = OpTransactionSigned::decode_2718(&mut &tx[..]).unwrap(); - let receipt_meta = OpReceiptFieldsBuilder::new(1730216981) + let receipt_meta = OpReceiptFieldsBuilder::new(1730216981, 21713817) .l1_block_info(&BASE_MAINNET, &tx_1, &mut l1_block_info) .expect("should parse revm l1 info") .build(); diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index b3b9f420b..47e54dfca 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -15,7 +15,7 @@ use alloy_primitives::{ Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, TxHash, TxNumber, B256, U256, }; use parking_lot::Mutex; -use reth_chainspec::{ChainInfo, ChainSpec}; +use reth_chainspec::{ChainInfo, EthChainSpec}; use reth_db::mock::{DatabaseMock, TxMock}; use reth_db_api::models::{AccountBeforeTx, StoredBlockBodyIndices}; use reth_execution_types::ExecutionOutcome; @@ -43,8 +43,8 @@ use std::{ }; /// A mock implementation for Provider interfaces. -#[derive(Debug, Clone)] -pub struct MockEthProvider { +#[derive(Debug)] +pub struct MockEthProvider { /// Local block store pub blocks: Arc>>>, /// Local header store @@ -57,6 +57,18 @@ pub struct MockEthProvider { pub state_roots: Arc>>, } +impl Clone for MockEthProvider { + fn clone(&self) -> Self { + Self { + blocks: self.blocks.clone(), + headers: self.headers.clone(), + accounts: self.accounts.clone(), + chain_spec: self.chain_spec.clone(), + state_roots: self.state_roots.clone(), + } + } +} + impl MockEthProvider { /// Create a new, empty instance pub fn new() -> Self { @@ -68,7 +80,9 @@ impl MockEthProvider { state_roots: Default::default(), } } +} +impl MockEthProvider { /// Add block to local block store pub fn add_block(&self, hash: B256, block: Block) { self.add_header(hash, block.header.clone()); @@ -111,6 +125,17 @@ impl MockEthProvider { pub fn add_state_root(&self, state_root: B256) { self.state_roots.lock().push(state_root); } + + /// Set chain spec. + pub fn with_chain_spec(self, chain_spec: C) -> MockEthProvider { + MockEthProvider { + blocks: self.blocks, + headers: self.headers, + accounts: self.accounts, + chain_spec: Arc::new(chain_spec), + state_roots: self.state_roots, + } + } } impl Default for MockEthProvider { @@ -162,16 +187,20 @@ pub struct MockNode; impl NodeTypes for MockNode { type Primitives = EthPrimitives; - type ChainSpec = ChainSpec; + type ChainSpec = reth_chainspec::ChainSpec; type StateCommitment = MerklePatriciaTrie; type Storage = EthStorage; } -impl StateCommitmentProvider for MockEthProvider { +impl StateCommitmentProvider + for MockEthProvider +{ type StateCommitment = ::StateCommitment; } -impl DatabaseProviderFactory for MockEthProvider { +impl DatabaseProviderFactory + for MockEthProvider +{ type DB = DatabaseMock; type Provider = DatabaseProvider; type ProviderRW = DatabaseProvider; @@ -185,7 +214,7 @@ impl DatabaseProviderFactory for MockEthProvider { } } -impl HeaderProvider for MockEthProvider { +impl HeaderProvider for MockEthProvider { type Header = Header; fn header(&self, block_hash: &BlockHash) -> ProviderResult> { @@ -244,7 +273,9 @@ impl HeaderProvider for MockEthProvider { } } -impl ChainSpecProvider for MockEthProvider { +impl ChainSpecProvider + for MockEthProvider +{ type ChainSpec = ChainSpec; fn chain_spec(&self) -> Arc { @@ -252,7 +283,9 @@ impl ChainSpecProvider for MockEthProvider { } } -impl TransactionsProvider for MockEthProvider { +impl TransactionsProvider + for MockEthProvider +{ type Transaction = T; fn transaction_id(&self, tx_hash: TxHash) -> ProviderResult> { @@ -391,7 +424,7 @@ impl TransactionsProvider for MockEthProvider { } } -impl ReceiptProvider for MockEthProvider { +impl ReceiptProvider for MockEthProvider { type Receipt = Receipt; fn receipt(&self, _id: TxNumber) -> ProviderResult> { @@ -414,9 +447,12 @@ impl ReceiptProvider for MockEthProvider { } } -impl ReceiptProviderIdExt for MockEthProvider {} +impl ReceiptProviderIdExt + for MockEthProvider +{ +} -impl BlockHashReader for MockEthProvider { +impl BlockHashReader for MockEthProvider { fn block_hash(&self, number: u64) -> ProviderResult> { let lock = self.blocks.lock(); @@ -440,7 +476,7 @@ impl BlockHashReader for MockEthProvider { } } -impl BlockNumReader for MockEthProvider { +impl BlockNumReader for MockEthProvider { fn chain_info(&self) -> ProviderResult { let best_block_number = self.best_block_number()?; let lock = self.headers.lock(); @@ -471,7 +507,7 @@ impl BlockNumReader for MockEthProvider { } } -impl BlockIdReader for MockEthProvider { +impl BlockIdReader for MockEthProvider { fn pending_block_num_hash(&self) -> ProviderResult> { Ok(None) } @@ -485,7 +521,7 @@ impl BlockIdReader for MockEthProvider { } } -impl BlockReader for MockEthProvider { +impl BlockReader for MockEthProvider { type Block = Block; fn find_block_by_hash( @@ -559,7 +595,9 @@ impl BlockReader for MockEthProvider { } } -impl BlockReaderIdExt for MockEthProvider { +impl BlockReaderIdExt + for MockEthProvider +{ fn block_by_id(&self, id: BlockId) -> ProviderResult>> { match id { BlockId::Number(num) => self.block_by_number_or_tag(num), @@ -586,13 +624,15 @@ impl BlockReaderIdExt for MockEthProvider { } } -impl AccountReader for MockEthProvider { +impl AccountReader for MockEthProvider { fn basic_account(&self, address: &Address) -> ProviderResult> { Ok(self.accounts.lock().get(address).cloned().map(|a| a.account)) } } -impl StageCheckpointReader for MockEthProvider { +impl StageCheckpointReader + for MockEthProvider +{ fn get_stage_checkpoint(&self, _id: StageId) -> ProviderResult> { Ok(None) } @@ -606,7 +646,7 @@ impl StageCheckpointReader for MockEthProvider { } } -impl StateRootProvider for MockEthProvider { +impl StateRootProvider for MockEthProvider { fn state_root(&self, _state: HashedPostState) -> ProviderResult { Ok(self.state_roots.lock().pop().unwrap_or_default()) } @@ -632,7 +672,9 @@ impl StateRootProvider for MockEthProvider { } } -impl StorageRootProvider for MockEthProvider { +impl StorageRootProvider + for MockEthProvider +{ fn storage_root( &self, _address: Address, @@ -660,7 +702,7 @@ impl StorageRootProvider for MockEthProvider { } } -impl StateProofProvider for MockEthProvider { +impl StateProofProvider for MockEthProvider { fn proof( &self, _input: TrieInput, @@ -687,13 +729,17 @@ impl StateProofProvider for MockEthProvider { } } -impl HashedPostStateProvider for MockEthProvider { +impl HashedPostStateProvider + for MockEthProvider +{ fn hashed_post_state(&self, _state: &revm::db::BundleState) -> HashedPostState { HashedPostState::default() } } -impl StateProvider for MockEthProvider { +impl StateProvider + for MockEthProvider +{ fn storage( &self, account: Address, @@ -716,7 +762,9 @@ impl StateProvider for MockEthProvider { } } -impl StateProviderFactory for MockEthProvider { +impl StateProviderFactory + for MockEthProvider +{ fn latest(&self) -> ProviderResult { Ok(Box::new(self.clone())) } @@ -768,7 +816,9 @@ impl StateProviderFactory for MockEthProvider { } } -impl WithdrawalsProvider for MockEthProvider { +impl WithdrawalsProvider + for MockEthProvider +{ fn withdrawals_by_block( &self, _id: BlockHashOrNumber, @@ -778,13 +828,15 @@ impl WithdrawalsProvider for MockEthProvider { } } -impl OmmersProvider for MockEthProvider { +impl OmmersProvider for MockEthProvider { fn ommers(&self, _id: BlockHashOrNumber) -> ProviderResult>> { Ok(None) } } -impl BlockBodyIndicesProvider for MockEthProvider { +impl BlockBodyIndicesProvider + for MockEthProvider +{ fn block_body_indices(&self, _num: u64) -> ProviderResult> { Ok(None) } @@ -796,7 +848,7 @@ impl BlockBodyIndicesProvider for MockEthProvider { } } -impl ChangeSetReader for MockEthProvider { +impl ChangeSetReader for MockEthProvider { fn account_block_changeset( &self, _block_number: BlockNumber, @@ -805,7 +857,7 @@ impl ChangeSetReader for MockEthProvider { } } -impl StateReader for MockEthProvider { +impl StateReader for MockEthProvider { type Receipt = Receipt; fn get_state(&self, _block: BlockNumber) -> ProviderResult> { diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 19f44df3b..1ecfdf53b 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -82,12 +82,14 @@ //! use reth_chainspec::MAINNET; //! use reth_storage_api::StateProviderFactory; //! use reth_tasks::TokioTaskExecutor; +//! use reth_chainspec::ChainSpecProvider; //! use reth_transaction_pool::{TransactionValidationTaskExecutor, Pool, TransactionPool}; //! use reth_transaction_pool::blobstore::InMemoryBlobStore; -//! async fn t(client: C) where C: StateProviderFactory + Clone + 'static{ +//! use reth_chainspec::EthereumHardforks; +//! async fn t(client: C) where C: ChainSpecProvider + StateProviderFactory + Clone + 'static{ //! let blob_store = InMemoryBlobStore::default(); //! let pool = Pool::eth_pool( -//! TransactionValidationTaskExecutor::eth(client, MAINNET.clone(), blob_store.clone(), TokioTaskExecutor::default()), +//! TransactionValidationTaskExecutor::eth(client, blob_store.clone(), TokioTaskExecutor::default()), //! blob_store, //! Default::default(), //! ); @@ -126,7 +128,7 @@ //! let manager = TaskManager::new(rt.handle().clone()); //! let executor = manager.executor(); //! let pool = Pool::eth_pool( -//! TransactionValidationTaskExecutor::eth(client.clone(), MAINNET.clone(), blob_store.clone(), executor.clone()), +//! TransactionValidationTaskExecutor::eth(client.clone(), blob_store.clone(), executor.clone()), //! blob_store, //! Default::default(), //! ); @@ -174,6 +176,7 @@ use crate::{identifier::TransactionId, pool::PoolInner}; use alloy_eips::eip4844::{BlobAndProofV1, BlobTransactionSidecar}; use alloy_primitives::{Address, TxHash, B256, U256}; use aquamarine as _; +use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; use reth_eth_wire_types::HandleMempoolData; use reth_execution_types::ChangedAccount; use reth_primitives::Recovered; @@ -280,7 +283,8 @@ where impl EthTransactionPool where - Client: StateProviderFactory + Clone + 'static, + Client: + ChainSpecProvider + StateProviderFactory + Clone + 'static, S: BlobStore, { /// Returns a new [`Pool`] that uses the default [`TransactionValidationTaskExecutor`] when @@ -292,15 +296,16 @@ where /// use reth_chainspec::MAINNET; /// use reth_storage_api::StateProviderFactory; /// use reth_tasks::TokioTaskExecutor; + /// use reth_chainspec::ChainSpecProvider; /// use reth_transaction_pool::{ /// blobstore::InMemoryBlobStore, Pool, TransactionValidationTaskExecutor, /// }; - /// # fn t(client: C) where C: StateProviderFactory + Clone + 'static { + /// use reth_chainspec::EthereumHardforks; + /// # fn t(client: C) where C: ChainSpecProvider + StateProviderFactory + Clone + 'static { /// let blob_store = InMemoryBlobStore::default(); /// let pool = Pool::eth_pool( /// TransactionValidationTaskExecutor::eth( /// client, - /// MAINNET.clone(), /// blob_store.clone(), /// TokioTaskExecutor::default(), /// ), diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 84d1545ab..8a6ead756 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -677,7 +677,6 @@ mod tests { }; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::{hex, U256}; - use reth_chainspec::MAINNET; use reth_fs_util as fs; use reth_primitives::{PooledTransaction, TransactionSigned}; use reth_provider::test_utils::{ExtendedAccount, MockEthProvider}; @@ -706,8 +705,7 @@ mod tests { let sender = hex!("1f9090aaE28b8a3dCeaDf281B0F12828e676c326").into(); provider.add_account(sender, ExtendedAccount::new(42, U256::MAX)); let blob_store = InMemoryBlobStore::default(); - let validator = EthTransactionValidatorBuilder::new(MAINNET.clone()) - .build(provider, blob_store.clone()); + let validator = EthTransactionValidatorBuilder::new(provider).build(blob_store.clone()); let txpool = Pool::new( validator.clone(), diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index d659c5930..9f77d913b 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -22,7 +22,7 @@ use alloy_eips::{ eip1559::ETHEREUM_BLOCK_GAS_LIMIT, eip4844::{env_settings::EnvKzgSettings, MAX_BLOBS_PER_BLOCK}, }; -use reth_chainspec::{ChainSpec, EthereumHardforks}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_primitives::{InvalidTransactionError, SealedBlock}; use reth_primitives_traits::{Block, GotExpected}; use reth_storage_api::{StateProvider, StateProviderFactory}; @@ -45,8 +45,11 @@ pub struct EthTransactionValidator { impl EthTransactionValidator { /// Returns the configured chain spec - pub fn chain_spec(&self) -> &Arc { - &self.inner.chain_spec + pub fn chain_spec(&self) -> Arc + where + Client: ChainSpecProvider, + { + self.client().chain_spec() } /// Returns the configured client @@ -57,7 +60,7 @@ impl EthTransactionValidator { impl EthTransactionValidator where - Client: StateProviderFactory, + Client: ChainSpecProvider + StateProviderFactory, Tx: EthPoolTransaction, { /// Validates a single transaction. @@ -86,7 +89,7 @@ where impl TransactionValidator for EthTransactionValidator where - Client: StateProviderFactory, + Client: ChainSpecProvider + StateProviderFactory, Tx: EthPoolTransaction, { type Transaction = Tx; @@ -130,8 +133,6 @@ where /// And adheres to the configured [`LocalTransactionConfig`]. #[derive(Debug)] pub(crate) struct EthTransactionValidatorInner { - /// Spec of the chain - chain_spec: Arc, /// This type fetches account info from the db client: Client, /// Blobstore used for fetching re-injected blob transactions. @@ -162,18 +163,23 @@ pub(crate) struct EthTransactionValidatorInner { // === impl EthTransactionValidatorInner === -impl EthTransactionValidatorInner { +impl EthTransactionValidatorInner { /// Returns the configured chain id pub(crate) fn chain_id(&self) -> u64 { - self.chain_spec.chain().id() + self.client.chain_spec().chain().id() } } impl EthTransactionValidatorInner where - Client: StateProviderFactory, + Client: ChainSpecProvider + StateProviderFactory, Tx: EthPoolTransaction, { + /// Returns the configured chain spec + fn chain_spec(&self) -> Arc { + self.client.chain_spec() + } + /// Validates a single transaction using an optional cached state provider. /// If no provider is passed, a new one will be created. This allows reusing /// the same provider across multiple txs. @@ -509,15 +515,15 @@ where fn on_new_head_block(&self, new_tip_block: &T) { // update all forks - if self.chain_spec.is_cancun_active_at_timestamp(new_tip_block.timestamp()) { + if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) { self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed); } - if self.chain_spec.is_shanghai_active_at_timestamp(new_tip_block.timestamp()) { + if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) { self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed); } - if self.chain_spec.is_prague_active_at_timestamp(new_tip_block.timestamp()) { + if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) { self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed); } @@ -531,8 +537,8 @@ where /// A builder for [`TransactionValidationTaskExecutor`] #[derive(Debug)] -pub struct EthTransactionValidatorBuilder { - chain_spec: Arc, +pub struct EthTransactionValidatorBuilder { + client: Client, /// Fork indicator whether we are in the Shanghai stage. shanghai: bool, /// Fork indicator whether we are in the Cancun hardfork. @@ -564,8 +570,8 @@ pub struct EthTransactionValidatorBuilder { max_tx_input_bytes: usize, } -impl EthTransactionValidatorBuilder { - /// Creates a new builder for the given [`ChainSpec`] +impl EthTransactionValidatorBuilder { + /// Creates a new builder for the given client /// /// By default this assumes the network is on the `Cancun` hardfork and the following /// transactions are allowed: @@ -573,10 +579,10 @@ impl EthTransactionValidatorBuilder { /// - EIP-2718 /// - EIP-1559 /// - EIP-4844 - pub fn new(chain_spec: Arc) -> Self { + pub fn new(client: Client) -> Self { Self { block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT.into(), - chain_spec, + client, minimum_priority_fee: None, additional_tasks: 1, kzg_settings: EnvKzgSettings::Default, @@ -696,10 +702,13 @@ impl EthTransactionValidatorBuilder { /// Configures validation rules based on the head block's timestamp. /// /// For example, whether the Shanghai and Cancun hardfork is activated at launch. - pub fn with_head_timestamp(mut self, timestamp: u64) -> Self { - self.cancun = self.chain_spec.is_cancun_active_at_timestamp(timestamp); - self.shanghai = self.chain_spec.is_shanghai_active_at_timestamp(timestamp); - self.prague = self.chain_spec.is_prague_active_at_timestamp(timestamp); + pub fn with_head_timestamp(mut self, timestamp: u64) -> Self + where + Client: ChainSpecProvider, + { + self.cancun = self.client.chain_spec().is_cancun_active_at_timestamp(timestamp); + self.shanghai = self.client.chain_spec().is_shanghai_active_at_timestamp(timestamp); + self.prague = self.client.chain_spec().is_prague_active_at_timestamp(timestamp); self } @@ -718,16 +727,12 @@ impl EthTransactionValidatorBuilder { } /// Builds a the [`EthTransactionValidator`] without spawning validator tasks. - pub fn build( - self, - client: Client, - blob_store: S, - ) -> EthTransactionValidator + pub fn build(self, blob_store: S) -> EthTransactionValidator where S: BlobStore, { let Self { - chain_spec, + client, shanghai, cancun, prague, @@ -750,7 +755,6 @@ impl EthTransactionValidatorBuilder { }; let inner = EthTransactionValidatorInner { - chain_spec, client, eip2718, eip1559, @@ -775,9 +779,8 @@ impl EthTransactionValidatorBuilder { /// The validator will spawn `additional_tasks` additional tasks for validation. /// /// By default this will spawn 1 additional task. - pub fn build_with_tasks( + pub fn build_with_tasks( self, - client: Client, tasks: T, blob_store: S, ) -> TransactionValidationTaskExecutor> @@ -786,7 +789,7 @@ impl EthTransactionValidatorBuilder { S: BlobStore, { let additional_tasks = self.additional_tasks; - let validator = self.build(client, blob_store); + let validator = self.build(blob_store); let (tx, task) = ValidationTask::new(); @@ -882,7 +885,6 @@ mod tests { }; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::{hex, U256}; - use reth_chainspec::MAINNET; use reth_primitives::{transaction::SignedTransactionIntoRecoveredExt, PooledTransaction}; use reth_provider::test_utils::{ExtendedAccount, MockEthProvider}; @@ -915,8 +917,7 @@ mod tests { ExtendedAccount::new(transaction.nonce(), U256::MAX), ); let blob_store = InMemoryBlobStore::default(); - let validator = EthTransactionValidatorBuilder::new(MAINNET.clone()) - .build(provider, blob_store.clone()); + let validator = EthTransactionValidatorBuilder::new(provider).build(blob_store.clone()); let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone()); @@ -943,9 +944,9 @@ mod tests { ); let blob_store = InMemoryBlobStore::default(); - let validator = EthTransactionValidatorBuilder::new(MAINNET.clone()) + let validator = EthTransactionValidatorBuilder::new(provider) .set_block_gas_limit(1_000_000) // tx gas limit is 1_015_288 - .build(provider, blob_store.clone()); + .build(blob_store.clone()); let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone()); diff --git a/crates/transaction-pool/src/validate/task.rs b/crates/transaction-pool/src/validate/task.rs index 22cc84bd9..b630ea7ef 100644 --- a/crates/transaction-pool/src/validate/task.rs +++ b/crates/transaction-pool/src/validate/task.rs @@ -7,7 +7,6 @@ use crate::{ TransactionValidator, }; use futures_util::{lock::Mutex, StreamExt}; -use reth_chainspec::ChainSpec; use reth_primitives::SealedBlock; use reth_primitives_traits::Block; use reth_tasks::TaskSpawner; @@ -93,8 +92,8 @@ pub struct TransactionValidationTaskExecutor { impl TransactionValidationTaskExecutor<()> { /// Convenience method to create a [`EthTransactionValidatorBuilder`] - pub fn eth_builder(chain_spec: Arc) -> EthTransactionValidatorBuilder { - EthTransactionValidatorBuilder::new(chain_spec) + pub fn eth_builder(client: Client) -> EthTransactionValidatorBuilder { + EthTransactionValidatorBuilder::new(client) } } @@ -112,23 +111,18 @@ impl TransactionValidationTaskExecutor { } impl TransactionValidationTaskExecutor> { - /// Creates a new instance for the given [`ChainSpec`] + /// Creates a new instance for the given client /// /// This will spawn a single validation tasks that performs the actual validation. /// See [`TransactionValidationTaskExecutor::eth_with_additional_tasks`] - pub fn eth( - client: Client, - chain_spec: Arc, - blob_store: S, - tasks: T, - ) -> Self + pub fn eth(client: Client, blob_store: S, tasks: T) -> Self where T: TaskSpawner, { - Self::eth_with_additional_tasks(client, chain_spec, blob_store, tasks, 0) + Self::eth_with_additional_tasks(client, blob_store, tasks, 0) } - /// Creates a new instance for the given [`ChainSpec`] + /// Creates a new instance for the given client /// /// By default this will enable support for: /// - shanghai @@ -139,7 +133,6 @@ impl TransactionValidationTaskExecutor( client: Client, - chain_spec: Arc, blob_store: S, tasks: T, num_additional_tasks: usize, @@ -147,9 +140,9 @@ impl TransactionValidationTaskExecutor(client, tasks, blob_store) + .build_with_tasks::(tasks, blob_store) } } diff --git a/examples/custom-node-components/src/main.rs b/examples/custom-node-components/src/main.rs index 7924aabd8..0408c75cc 100644 --- a/examples/custom-node-components/src/main.rs +++ b/examples/custom-node-components/src/main.rs @@ -55,15 +55,11 @@ where async fn build_pool(self, ctx: &BuilderContext) -> eyre::Result { let data_dir = ctx.config().datadir(); let blob_store = InMemoryBlobStore::default(); - let validator = TransactionValidationTaskExecutor::eth_builder(ctx.chain_spec()) + let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone()) .with_head_timestamp(ctx.head().timestamp) .kzg_settings(ctx.kzg_settings()?) .with_additional_tasks(ctx.config().txpool.additional_validation_tasks) - .build_with_tasks( - ctx.provider().clone(), - ctx.task_executor().clone(), - blob_store.clone(), - ); + .build_with_tasks(ctx.task_executor().clone(), blob_store.clone()); let transaction_pool = reth_transaction_pool::Pool::eth_pool(validator, blob_store, self.pool_config);