diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index e9679e2666e..2551e7b9a2e 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -9,9 +9,11 @@ use crate::{ metrics::TxPoolValidationMetrics, traits::TransactionOrigin, validate::{ValidTransaction, ValidationTask, MAX_INIT_CODE_BYTE_SIZE}, - EthBlobTransactionSidecar, EthPoolTransaction, LocalTransactionConfig, - TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator, + Address, BlobTransactionSidecarVariant, EthBlobTransactionSidecar, EthPoolTransaction, + LocalTransactionConfig, TransactionValidationOutcome, TransactionValidationTaskExecutor, + TransactionValidator, }; + use alloy_consensus::{ constants::{ EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, @@ -25,10 +27,10 @@ use alloy_eips::{ }; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_primitives_traits::{ - constants::MAX_TX_GAS_LIMIT_OSAKA, transaction::error::InvalidTransactionError, Block, + constants::MAX_TX_GAS_LIMIT_OSAKA, transaction::error::InvalidTransactionError, Account, Block, GotExpected, SealedBlock, }; -use reth_storage_api::{AccountInfoReader, StateProviderFactory}; +use reth_storage_api::{AccountInfoReader, BytecodeReader, StateProviderFactory}; use reth_tasks::TaskSpawner; use std::{ marker::PhantomData, @@ -154,6 +156,47 @@ where ) -> TransactionValidationOutcome { self.inner.validate_one_with_provider(origin, transaction, state) } + + /// Validates that the sender’s account has valid or no bytecode. + pub fn validate_sender_bytecode( + &self, + transaction: &Tx, + account: &Account, + state: &dyn AccountInfoReader, + ) -> Result, TransactionValidationOutcome> { + self.inner.validate_sender_bytecode(transaction, account, state) + } + + /// Checks if the transaction nonce is valid. + pub fn validate_sender_nonce( + &self, + transaction: &Tx, + account: &Account, + ) -> Result<(), InvalidPoolTransactionError> { + self.inner.validate_sender_nonce(transaction, account) + } + + /// Ensures the sender has sufficient account balance. + pub fn validate_sender_balance( + &self, + transaction: &Tx, + account: &Account, + ) -> Result<(), InvalidPoolTransactionError> { + self.inner.validate_sender_balance(transaction, account) + } + + /// Validates EIP-4844 blob sidecar data and returns the extracted sidecar, if any. + pub fn validate_eip4844( + &self, + transaction: &mut Tx, + ) -> Result, InvalidPoolTransactionError> { + self.inner.validate_eip4844(transaction) + } + + /// Returns the recovered authorities for the given transaction + pub fn recover_authorities(&self, transaction: &Tx) -> std::option::Option> { + self.inner.recover_authorities(transaction) + } } impl TransactionValidator for EthTransactionValidator @@ -574,21 +617,70 @@ where } }; + // check for bytecode + match self.validate_sender_bytecode(&transaction, &account, &state) { + Err(outcome) => return outcome, + Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err), + _ => {} + }; + + // Checks for nonce + if let Err(err) = self.validate_sender_nonce(&transaction, &account) { + return TransactionValidationOutcome::Invalid(transaction, err) + } + + // checks for max cost not exceedng account_balance + if let Err(err) = self.validate_sender_balance(&transaction, &account) { + return TransactionValidationOutcome::Invalid(transaction, err) + } + + // heavy blob tx validation + let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) { + Err(err) => return TransactionValidationOutcome::Invalid(transaction, err), + Ok(sidecar) => sidecar, + }; + + let authorities = self.recover_authorities(&transaction); + // Return the valid transaction + TransactionValidationOutcome::Valid { + balance: account.balance, + state_nonce: account.nonce, + bytecode_hash: account.bytecode_hash, + transaction: ValidTransaction::new(transaction, maybe_blob_sidecar), + // by this point assume all external transactions should be propagated + propagate: match origin { + TransactionOrigin::External => true, + TransactionOrigin::Local => { + self.local_transactions_config.propagate_local_transactions + } + TransactionOrigin::Private => false, + }, + authorities, + } + } + + /// Validates that the sender’s account has valid or no bytecode. + fn validate_sender_bytecode( + &self, + transaction: &Tx, + sender: &Account, + state: impl BytecodeReader, + ) -> Result, TransactionValidationOutcome> { // Unless Prague is active, the signer account shouldn't have bytecode. // // If Prague is active, only EIP-7702 bytecode is allowed for the sender. // // Any other case means that the account is not an EOA, and should not be able to send // transactions. - if let Some(code_hash) = &account.bytecode_hash { + if let Some(code_hash) = &sender.bytecode_hash { let is_eip7702 = if self.fork_tracker.is_prague_activated() { match state.bytecode_by_hash(code_hash) { Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(), Err(err) => { - return TransactionValidationOutcome::Error( + return Err(TransactionValidationOutcome::Error( *transaction.hash(), Box::new(err), - ) + )) } } } else { @@ -596,38 +688,53 @@ where }; if !is_eip7702 { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::SignerAccountHasBytecode.into(), - ) + return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into())) } } + Ok(Ok(())) + } + /// Checks if the transaction nonce is valid. + fn validate_sender_nonce( + &self, + transaction: &Tx, + sender: &Account, + ) -> Result<(), InvalidPoolTransactionError> { let tx_nonce = transaction.nonce(); - // Checks for nonce - if tx_nonce < account.nonce { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::NonceNotConsistent { tx: tx_nonce, state: account.nonce } - .into(), - ) + if tx_nonce < sender.nonce { + return Err(InvalidTransactionError::NonceNotConsistent { + tx: tx_nonce, + state: sender.nonce, + } + .into()) } + Ok(()) + } + /// Ensures the sender has sufficient account balance. + fn validate_sender_balance( + &self, + transaction: &Tx, + sender: &Account, + ) -> Result<(), InvalidPoolTransactionError> { let cost = transaction.cost(); - // Checks for max cost - if !self.disable_balance_check && cost > &account.balance { + if !self.disable_balance_check && cost > &sender.balance { let expected = *cost; - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::InsufficientFunds( - GotExpected { got: account.balance, expected }.into(), - ) - .into(), + return Err(InvalidTransactionError::InsufficientFunds( + GotExpected { got: sender.balance, expected }.into(), ) + .into()) } + Ok(()) + } + /// Validates EIP-4844 blob sidecar data and returns the extracted sidecar, if any. + fn validate_eip4844( + &self, + transaction: &mut Tx, + ) -> Result, InvalidPoolTransactionError> { let mut maybe_blob_sidecar = None; // heavy blob tx validation @@ -636,25 +743,19 @@ where match transaction.take_blob() { EthBlobTransactionSidecar::None => { // this should not happen - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::TxTypeNotSupported.into(), - ) + return Err(InvalidTransactionError::TxTypeNotSupported.into()) } EthBlobTransactionSidecar::Missing => { // This can happen for re-injected blob transactions (on re-org), since the blob // is stripped from the transaction and not included in a block. // check if the blob is in the store, if it's included we previously validated // it and inserted it - if matches!(self.blob_store.contains(*transaction.hash()), Ok(true)) { + if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) { // validated transaction is already in the store } else { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Eip4844( - Eip4844PoolTransactionError::MissingEip4844BlobSidecar, - ), - ) + return Err(InvalidPoolTransactionError::Eip4844( + Eip4844PoolTransactionError::MissingEip4844BlobSidecar, + )) } } EthBlobTransactionSidecar::Present(sidecar) => { @@ -662,30 +763,21 @@ where if self.fork_tracker.is_osaka_activated() { if sidecar.is_eip4844() { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Eip4844( - Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka, - ), - ) + return Err(InvalidPoolTransactionError::Eip4844( + Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka, + )) } } else if sidecar.is_eip7594() { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Eip4844( - Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka, - ), - ) + return Err(InvalidPoolTransactionError::Eip4844( + Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka, + )) } // validate the blob if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Eip4844( - Eip4844PoolTransactionError::InvalidEip4844Blob(err), - ), - ) + return Err(InvalidPoolTransactionError::Eip4844( + Eip4844PoolTransactionError::InvalidEip4844Blob(err), + )) } // Record the duration of successful blob validation as histogram self.validation_metrics.blob_validation_duration.record(now.elapsed()); @@ -694,26 +786,14 @@ where } } } + Ok(maybe_blob_sidecar) + } - let authorities = transaction.authorization_list().map(|auths| { - auths.iter().flat_map(|auth| auth.recover_authority()).collect::>() - }); - // Return the valid transaction - TransactionValidationOutcome::Valid { - balance: account.balance, - state_nonce: account.nonce, - bytecode_hash: account.bytecode_hash, - transaction: ValidTransaction::new(transaction, maybe_blob_sidecar), - // by this point assume all external transactions should be propagated - propagate: match origin { - TransactionOrigin::External => true, - TransactionOrigin::Local => { - self.local_transactions_config.propagate_local_transactions - } - TransactionOrigin::Private => false, - }, - authorities, - } + /// Returns the recovered authorities for the given transaction + fn recover_authorities(&self, transaction: &Tx) -> std::option::Option> { + transaction + .authorization_list() + .map(|auths| auths.iter().flat_map(|auth| auth.recover_authority()).collect::>()) } /// Validates all given transactions.