diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 3d1624a0c2a9..d40f0f897589 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -25,7 +25,7 @@ use alloy_eips::{ use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_primitives::{InvalidTransactionError, SealedBlock}; use reth_primitives_traits::{BlockBody, GotExpected}; -use reth_storage_api::{AccountReader, StateProviderFactory}; +use reth_storage_api::{StateProvider, StateProviderFactory}; use reth_tasks::TaskSpawner; use std::{ marker::PhantomData, @@ -80,7 +80,7 @@ where &self, transactions: Vec<(TransactionOrigin, Tx)>, ) -> Vec<TransactionValidationOutcome<Tx>> { - transactions.into_iter().map(|(origin, tx)| self.validate_one(origin, tx)).collect() + self.inner.validate_batch(transactions) } } @@ -175,11 +175,14 @@ where Client: StateProviderFactory, Tx: EthPoolTransaction, { - /// Validates a single transaction. - fn validate_one( + /// 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. + fn validate_one_with_provider( &self, origin: TransactionOrigin, mut transaction: Tx, + maybe_state: &mut Option<Box<dyn StateProvider>>, ) -> TransactionValidationOutcome<Tx> { // Checks for tx_type match transaction.tx_type() { @@ -349,11 +352,22 @@ where } } - let account = match self - .client - .latest() - .and_then(|state| state.basic_account(transaction.sender())) - { + // If we don't have a state provider yet, fetch the latest state + if maybe_state.is_none() { + match self.client.latest() { + Ok(new_state) => { + *maybe_state = Some(new_state); + } + Err(err) => { + return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err)) + } + } + } + + let state = maybe_state.as_deref().expect("provider is set"); + + // Use provider to get account info + let account = match state.basic_account(transaction.sender()) { Ok(account) => account.unwrap_or_default(), Err(err) => { return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err)) @@ -368,11 +382,7 @@ where // transactions. if account.has_bytecode() { let is_eip7702 = if self.fork_tracker.is_prague_activated() { - match self - .client - .latest() - .and_then(|state| state.bytecode_by_hash(account.get_bytecode_hash())) - { + match state.bytecode_by_hash(account.get_bytecode_hash()) { Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(), Err(err) => { return TransactionValidationOutcome::Error( @@ -479,6 +489,28 @@ where } } + /// Validates a single transaction. + fn validate_one( + &self, + origin: TransactionOrigin, + transaction: Tx, + ) -> TransactionValidationOutcome<Tx> { + let mut provider = None; + self.validate_one_with_provider(origin, transaction, &mut provider) + } + + /// Validates all given transactions. + fn validate_batch( + &self, + transactions: Vec<(TransactionOrigin, Tx)>, + ) -> Vec<TransactionValidationOutcome<Tx>> { + let mut provider = None; + transactions + .into_iter() + .map(|(origin, tx)| self.validate_one_with_provider(origin, tx, &mut provider)) + .collect() + } + fn on_new_head_block<T: BlockHeader>(&self, new_tip_block: &T) { // update all forks if self.chain_spec.is_cancun_active_at_timestamp(new_tip_block.timestamp()) {