Skip to main content

reth_transaction_pool/validate/
eth.rs

1//! Ethereum transaction validator.
2
3use super::constants::DEFAULT_MAX_TX_INPUT_BYTES;
4use crate::{
5    blobstore::{BlobStore, PooledBlobSidecar},
6    error::{
7        Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
8    },
9    metrics::TxPoolValidationMetrics,
10    traits::TransactionOrigin,
11    validate::ValidTransaction,
12    Address, EthBlobTransactionSidecar, EthPoolTransaction, LocalTransactionConfig,
13    TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator,
14};
15
16use alloy_consensus::{
17    constants::{
18        EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
19        KECCAK_EMPTY, LEGACY_TX_TYPE_ID,
20    },
21    BlockHeader,
22};
23use alloy_eips::{
24    eip4844::env_settings::EnvKzgSettings, eip7840::BlobParams, merge::SLOT_DURATION_SECS, BlockId,
25};
26use alloy_primitives::U256;
27use alloy_rlp::Encodable;
28use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
29use reth_evm::ConfigureEvm;
30use reth_primitives_traits::{
31    transaction::error::InvalidTransactionError, Account, BlockTy, GotExpected, HeaderTy,
32    SealedBlock,
33};
34use reth_storage_api::{
35    errors::ProviderError, AccountInfoReader, BlockReaderIdExt, BytecodeReader, StateProviderBox,
36    StateProviderFactory,
37};
38use reth_tasks::Runtime;
39use revm::context_interface::Cfg;
40use std::{
41    fmt,
42    marker::PhantomData,
43    sync::{
44        atomic::{AtomicBool, AtomicU64, AtomicUsize},
45        Arc,
46    },
47    time::{Instant, SystemTime},
48};
49
50/// Additional stateless validation function signature.
51///
52/// Receives the transaction origin and a reference to the transaction. Returns `Ok(())` if the
53/// transaction passes or `Err` to reject it.
54pub type StatelessValidationFn<T> =
55    Arc<dyn Fn(TransactionOrigin, &T) -> Result<(), InvalidPoolTransactionError> + Send + Sync>;
56
57/// Additional stateful validation function signature.
58///
59/// Receives the transaction origin, a reference to the transaction, and an account state reader.
60/// Returns `Ok(())` if the transaction passes or `Err` to reject it.
61pub type StatefulValidationFn<T> = Arc<
62    dyn Fn(TransactionOrigin, &T, &dyn AccountInfoReader) -> Result<(), InvalidPoolTransactionError>
63        + Send
64        + Sync,
65>;
66
67/// A [`TransactionValidator`] implementation that validates ethereum transaction.
68///
69/// It supports all known ethereum transaction types:
70/// - Legacy
71/// - EIP-2718
72/// - EIP-1559
73/// - EIP-4844
74/// - EIP-7702
75///
76/// And enforces additional constraints such as:
77/// - Maximum transaction size
78/// - Maximum gas limit
79///
80/// And adheres to the configured [`LocalTransactionConfig`].
81pub struct EthTransactionValidator<Client, T, Evm> {
82    /// This type fetches account info from the db
83    client: Client,
84    /// The chain ID transactions must use.
85    chain_id: u64,
86    /// Blobstore used for fetching re-injected blob transactions.
87    blob_store: Box<dyn BlobStore>,
88    /// tracks activated forks relevant for transaction validation
89    fork_tracker: ForkTracker,
90    /// Fork indicator whether we are using EIP-2718 type transactions.
91    eip2718: bool,
92    /// Fork indicator whether we are using EIP-1559 type transactions.
93    eip1559: bool,
94    /// Fork indicator whether we are using EIP-4844 blob transactions.
95    eip4844: bool,
96    /// Fork indicator whether we are using EIP-7702 type transactions.
97    eip7702: bool,
98    /// The current max gas limit
99    block_gas_limit: AtomicU64,
100    /// The current tx fee cap limit in wei locally submitted into the pool.
101    tx_fee_cap: Option<u128>,
102    /// Minimum priority fee to enforce for acceptance into the pool.
103    ///
104    /// For legacy and EIP-2930 transactions the gas price is used as the priority fee.
105    minimum_priority_fee: Option<u128>,
106    /// Stores the setup and parameters needed for validating KZG proofs.
107    kzg_settings: EnvKzgSettings,
108    /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.
109    local_transactions_config: LocalTransactionConfig,
110    /// Maximum size in bytes a single transaction can have in order to be accepted into the pool.
111    max_tx_input_bytes: usize,
112    /// Maximum gas limit for individual transactions
113    max_tx_gas_limit: Option<u64>,
114    /// Disable balance checks during transaction validation
115    disable_balance_check: bool,
116    /// EVM configuration for fetching execution limits
117    evm_config: Evm,
118    /// Marker for the transaction type
119    _marker: PhantomData<T>,
120    /// Metrics for tsx pool validation
121    validation_metrics: TxPoolValidationMetrics,
122    /// Bitmap of custom transaction types that are allowed.
123    other_tx_types: U256,
124    /// Whether EIP-7594 blob sidecars are accepted.
125    /// When false, EIP-7594 (v1) sidecars are always rejected and EIP-4844 (v0) sidecars
126    /// are always accepted, regardless of Osaka fork activation.
127    eip7594: bool,
128    /// Optional additional stateless validation check applied at the end of
129    /// [`validate_stateless`](Self::validate_stateless).
130    additional_stateless_validation: Option<StatelessValidationFn<T>>,
131    /// Optional additional stateful validation check applied at the end of
132    /// [`validate_stateful`](Self::validate_stateful).
133    additional_stateful_validation: Option<StatefulValidationFn<T>>,
134}
135
136impl<Client, Tx, Evm> fmt::Debug for EthTransactionValidator<Client, Tx, Evm> {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        f.debug_struct("EthTransactionValidator")
139            .field("fork_tracker", &self.fork_tracker)
140            .field("eip2718", &self.eip2718)
141            .field("eip1559", &self.eip1559)
142            .field("eip4844", &self.eip4844)
143            .field("eip7702", &self.eip7702)
144            .field("block_gas_limit", &self.block_gas_limit)
145            .field("tx_fee_cap", &self.tx_fee_cap)
146            .field("minimum_priority_fee", &self.minimum_priority_fee)
147            .field("max_tx_input_bytes", &self.max_tx_input_bytes)
148            .field("max_tx_gas_limit", &self.max_tx_gas_limit)
149            .field("disable_balance_check", &self.disable_balance_check)
150            .field("eip7594", &self.eip7594)
151            .field(
152                "additional_stateless_validation",
153                &self.additional_stateless_validation.as_ref().map(|_| "..."),
154            )
155            .field(
156                "additional_stateful_validation",
157                &self.additional_stateful_validation.as_ref().map(|_| "..."),
158            )
159            .finish()
160    }
161}
162
163impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm> {
164    /// Returns the configured chain spec
165    pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
166    where
167        Client: ChainSpecProvider,
168    {
169        self.client().chain_spec()
170    }
171
172    /// Returns the configured chain id
173    pub const fn chain_id(&self) -> u64 {
174        self.chain_id
175    }
176
177    /// Returns the configured client
178    pub const fn client(&self) -> &Client {
179        &self.client
180    }
181
182    /// Returns the tracks activated forks relevant for transaction validation
183    pub const fn fork_tracker(&self) -> &ForkTracker {
184        &self.fork_tracker
185    }
186
187    /// Returns the EVM config used for transaction validation.
188    pub const fn evm_config(&self) -> &Evm {
189        &self.evm_config
190    }
191
192    /// Returns if there are EIP-2718 type transactions
193    pub const fn eip2718(&self) -> bool {
194        self.eip2718
195    }
196
197    /// Returns if there are EIP-1559 type transactions
198    pub const fn eip1559(&self) -> bool {
199        self.eip1559
200    }
201
202    /// Returns if there are EIP-4844 blob transactions
203    pub const fn eip4844(&self) -> bool {
204        self.eip4844
205    }
206
207    /// Returns if there are EIP-7702 type transactions
208    pub const fn eip7702(&self) -> bool {
209        self.eip7702
210    }
211
212    /// Returns the current tx fee cap limit in wei locally submitted into the pool
213    pub const fn tx_fee_cap(&self) -> &Option<u128> {
214        &self.tx_fee_cap
215    }
216
217    /// Returns the minimum priority fee to enforce for acceptance into the pool
218    pub const fn minimum_priority_fee(&self) -> &Option<u128> {
219        &self.minimum_priority_fee
220    }
221
222    /// Returns the setup and parameters needed for validating KZG proofs.
223    pub const fn kzg_settings(&self) -> &EnvKzgSettings {
224        &self.kzg_settings
225    }
226
227    /// Returns the config to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions..
228    pub const fn local_transactions_config(&self) -> &LocalTransactionConfig {
229        &self.local_transactions_config
230    }
231
232    /// Returns the maximum size in bytes a single transaction can have in order to be accepted into
233    /// the pool.
234    pub const fn max_tx_input_bytes(&self) -> usize {
235        self.max_tx_input_bytes
236    }
237
238    /// Returns whether balance checks are disabled for this validator.
239    pub const fn disable_balance_check(&self) -> bool {
240        self.disable_balance_check
241    }
242
243    /// Sets an additional stateless validation check that is applied at the end of
244    /// [`validate_stateless`](Self::validate_stateless).
245    ///
246    /// The check receives the transaction origin and a reference to the transaction, and
247    /// should return `Ok(())` if the transaction is valid or
248    /// `Err(InvalidPoolTransactionError)` to reject it.
249    ///
250    /// # Example
251    ///
252    /// ```ignore
253    /// use reth_transaction_pool::{error::InvalidPoolTransactionError, TransactionOrigin};
254    ///
255    /// let mut validator = builder.build(blob_store);
256    /// // Reject external transactions with input data exceeding 1KB
257    /// validator.set_additional_stateless_validation(|origin, tx| {
258    ///     if origin.is_external() && tx.input().len() > 1024 {
259    ///         return Err(InvalidPoolTransactionError::OversizedData {
260    ///             size: tx.input().len(),
261    ///             limit: 1024,
262    ///         });
263    ///     }
264    ///     Ok(())
265    /// });
266    /// ```
267    pub fn set_additional_stateless_validation<F>(&mut self, f: F)
268    where
269        F: Fn(TransactionOrigin, &Tx) -> Result<(), InvalidPoolTransactionError>
270            + Send
271            + Sync
272            + 'static,
273    {
274        self.additional_stateless_validation = Some(Arc::new(f));
275    }
276
277    /// Sets the additional stateless validation check from an already shared
278    /// [`StatelessValidationFn`].
279    ///
280    /// This is useful when the same hook is shared across multiple validators, avoiding an extra
281    /// allocation compared to
282    /// [`set_additional_stateless_validation`](Self::set_additional_stateless_validation).
283    pub fn set_additional_stateless_validation_fn(&mut self, f: StatelessValidationFn<Tx>) {
284        self.additional_stateless_validation = Some(f);
285    }
286
287    /// Sets or clears the additional stateless validation check from an optional
288    /// [`StatelessValidationFn`].
289    ///
290    /// Passing `None` removes any previously configured check.
291    pub fn set_additional_stateless_validation_fn_opt(
292        &mut self,
293        f: Option<StatelessValidationFn<Tx>>,
294    ) {
295        self.additional_stateless_validation = f;
296    }
297
298    /// Sets an additional stateful validation check that is applied at the end of
299    /// [`validate_stateful`](Self::validate_stateful).
300    ///
301    /// The check receives the transaction origin, a reference to the transaction, and the
302    /// account state reader, and should return `Ok(())` if the transaction is valid or
303    /// `Err(InvalidPoolTransactionError)` to reject it.
304    ///
305    /// # Example
306    ///
307    /// ```ignore
308    /// use reth_transaction_pool::{error::InvalidPoolTransactionError, TransactionOrigin};
309    ///
310    /// let mut validator = builder.build(blob_store);
311    /// // Reject transactions from accounts with zero balance
312    /// validator.set_additional_stateful_validation(|origin, tx, state| {
313    ///     let account = state.basic_account(tx.sender_ref())?.unwrap_or_default();
314    ///     if account.balance.is_zero() {
315    ///         return Err(InvalidPoolTransactionError::Other(Box::new(
316    ///             std::io::Error::new(std::io::ErrorKind::Other, "zero balance"),
317    ///         )));
318    ///     }
319    ///     Ok(())
320    /// });
321    /// ```
322    pub fn set_additional_stateful_validation<F>(&mut self, f: F)
323    where
324        F: Fn(
325                TransactionOrigin,
326                &Tx,
327                &dyn AccountInfoReader,
328            ) -> Result<(), InvalidPoolTransactionError>
329            + Send
330            + Sync
331            + 'static,
332    {
333        self.additional_stateful_validation = Some(Arc::new(f));
334    }
335
336    /// Sets the additional stateful validation check from an already shared
337    /// [`StatefulValidationFn`].
338    ///
339    /// This is useful when the same hook is shared across multiple validators, avoiding an extra
340    /// allocation compared to
341    /// [`set_additional_stateful_validation`](Self::set_additional_stateful_validation).
342    pub fn set_additional_stateful_validation_fn(&mut self, f: StatefulValidationFn<Tx>) {
343        self.additional_stateful_validation = Some(f);
344    }
345
346    /// Sets or clears the additional stateful validation check from an optional
347    /// [`StatefulValidationFn`].
348    ///
349    /// Passing `None` removes any previously configured check.
350    pub fn set_additional_stateful_validation_fn_opt(
351        &mut self,
352        f: Option<StatefulValidationFn<Tx>>,
353    ) {
354        self.additional_stateful_validation = f;
355    }
356}
357
358impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm>
359where
360    Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
361    Tx: EthPoolTransaction,
362    Evm: ConfigureEvm,
363{
364    /// Returns the current max gas limit
365    pub fn block_gas_limit(&self) -> u64 {
366        self.max_gas_limit()
367    }
368
369    /// Validates a single transaction.
370    ///
371    /// See also [`TransactionValidator::validate_transaction`]
372    pub fn validate_one(
373        &self,
374        origin: TransactionOrigin,
375        transaction: Tx,
376    ) -> TransactionValidationOutcome<Tx> {
377        let mut state: Option<StateProviderBox> = None;
378        self.validate_one_with_provider(origin, transaction, &mut state, || self.client.latest())
379    }
380
381    /// Validates a single transaction with the provided state provider.
382    ///
383    /// This allows reusing the same provider across multiple transaction validations,
384    /// which can improve performance when validating many transactions.
385    ///
386    /// If `state` is `None`, a new state provider will be created.
387    pub fn validate_one_with_state(
388        &self,
389        origin: TransactionOrigin,
390        transaction: Tx,
391        state: &mut Option<Box<dyn AccountInfoReader + Send>>,
392    ) -> TransactionValidationOutcome<Tx> {
393        self.validate_one_with_provider(origin, transaction, state, || {
394            self.client.latest().map(|state| Box::new(state) as Box<dyn AccountInfoReader + Send>)
395        })
396    }
397
398    /// Validates a single transaction using an optional cached state provider.
399    /// If no provider is passed, a new one will be created. This allows reusing
400    /// the same provider across multiple txs.
401    fn validate_one_with_provider<P, F>(
402        &self,
403        origin: TransactionOrigin,
404        transaction: Tx,
405        maybe_state: &mut Option<P>,
406        state_provider: F,
407    ) -> TransactionValidationOutcome<Tx>
408    where
409        P: AccountInfoReader,
410        F: FnOnce() -> Result<P, ProviderError>,
411    {
412        match self.validate_stateless(origin, &transaction) {
413            Ok(()) => {
414                // stateless checks passed, pass transaction down stateful validation pipeline
415                // If we don't have a state provider yet, fetch the latest state
416                if maybe_state.is_none() {
417                    match state_provider() {
418                        Ok(new_state) => {
419                            *maybe_state = Some(new_state);
420                        }
421                        Err(err) => {
422                            return TransactionValidationOutcome::Error(
423                                *transaction.hash(),
424                                Box::new(err),
425                            )
426                        }
427                    }
428                }
429
430                let state = maybe_state.as_ref().expect("provider is set");
431
432                self.validate_stateful(origin, transaction, state)
433            }
434            Err(err) => TransactionValidationOutcome::Invalid(transaction, err),
435        }
436    }
437
438    /// Validates a single transaction against the given state provider, performing both
439    /// [stateless](Self::validate_stateless) and [stateful](Self::validate_stateful) checks.
440    pub fn validate_one_with_state_provider(
441        &self,
442        origin: TransactionOrigin,
443        transaction: Tx,
444        state: impl AccountInfoReader,
445    ) -> TransactionValidationOutcome<Tx> {
446        if let Err(err) = self.validate_stateless(origin, &transaction) {
447            return TransactionValidationOutcome::Invalid(transaction, err);
448        }
449        self.validate_stateful(origin, transaction, state)
450    }
451
452    /// Validates a single transaction without requiring any state access (stateless checks only).
453    ///
454    /// Checks tx type support, nonce bounds, size limits, gas limits, fee constraints, chain ID,
455    /// intrinsic gas, and blob tx pre-checks.
456    pub fn validate_stateless(
457        &self,
458        origin: TransactionOrigin,
459        transaction: &Tx,
460    ) -> Result<(), InvalidPoolTransactionError> {
461        // Checks for tx_type
462        match transaction.ty() {
463            // Accept only legacy transactions until EIP-2718/2930 activates
464            EIP2930_TX_TYPE_ID if !self.eip2718 => {
465                return Err(InvalidTransactionError::Eip2930Disabled.into())
466            }
467            // Reject dynamic fee transactions until EIP-1559 activates.
468            EIP1559_TX_TYPE_ID if !self.eip1559 => {
469                return Err(InvalidTransactionError::Eip1559Disabled.into())
470            }
471            // Reject blob transactions.
472            EIP4844_TX_TYPE_ID if !self.eip4844 => {
473                return Err(InvalidTransactionError::Eip4844Disabled.into())
474            }
475            // Reject EIP-7702 transactions.
476            EIP7702_TX_TYPE_ID if !self.eip7702 => {
477                return Err(InvalidTransactionError::Eip7702Disabled.into())
478            }
479            // Accept known transaction types when their respective fork is active
480            LEGACY_TX_TYPE_ID | EIP2930_TX_TYPE_ID | EIP1559_TX_TYPE_ID | EIP4844_TX_TYPE_ID |
481            EIP7702_TX_TYPE_ID => {}
482
483            ty if !self.other_tx_types.bit(ty as usize) => {
484                return Err(InvalidTransactionError::TxTypeNotSupported.into())
485            }
486
487            _ => {}
488        };
489
490        // Reject transactions with a nonce equal to U64::max according to EIP-2681
491        if transaction.requires_nonce_bound_check() && transaction.nonce() == u64::MAX {
492            return Err(InvalidPoolTransactionError::Eip2681)
493        }
494
495        // Reject transactions over defined size to prevent DOS attacks
496        if transaction.is_eip4844() {
497            // Since blob transactions are pulled instead of pushed, and only the consensus data is
498            // kept in memory while the sidecar is cached on disk, there is no critical limit that
499            // should be enforced. Still, enforcing some cap on the dynamic transaction data. blob
500            // txs also must be executable right away when they enter the pool.
501            let tx_size = transaction.input().len().saturating_add(
502                transaction
503                    .access_list()
504                    .map(|access_list| access_list.length())
505                    .unwrap_or_default(),
506            );
507            if tx_size > self.max_tx_input_bytes {
508                self.validation_metrics.rejected_oversized_data.increment(1);
509                return Err(InvalidPoolTransactionError::OversizedData {
510                    size: tx_size,
511                    limit: self.max_tx_input_bytes,
512                })
513            }
514        } else {
515            // ensure the size of the non-blob transaction
516            let tx_size = transaction.encoded_length();
517            if tx_size > self.max_tx_input_bytes {
518                self.validation_metrics.rejected_oversized_data.increment(1);
519                return Err(InvalidPoolTransactionError::OversizedData {
520                    size: tx_size,
521                    limit: self.max_tx_input_bytes,
522                })
523            }
524        }
525
526        // Check whether the init code size has been exceeded.
527        if self.fork_tracker.is_shanghai_activated() {
528            let max_initcode_size =
529                self.fork_tracker.max_initcode_size.load(std::sync::atomic::Ordering::Relaxed);
530            transaction.ensure_max_init_code_size(max_initcode_size)?;
531        }
532
533        // Checks for gas limit
534        let transaction_gas_limit = transaction.gas_limit();
535        let block_gas_limit = self.max_gas_limit();
536        if transaction_gas_limit > block_gas_limit {
537            return Err(InvalidPoolTransactionError::ExceedsGasLimit(
538                transaction_gas_limit,
539                block_gas_limit,
540            ))
541        }
542
543        // Check individual transaction gas limit if configured
544        if let Some(max_tx_gas_limit) = self.max_tx_gas_limit &&
545            transaction_gas_limit > max_tx_gas_limit
546        {
547            return Err(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
548                transaction_gas_limit,
549                max_tx_gas_limit,
550            ))
551        }
552
553        // Ensure max_priority_fee_per_gas (if EIP1559) is less than max_fee_per_gas if any.
554        if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
555            self.validation_metrics.rejected_tip_above_fee_cap.increment(1);
556            return Err(InvalidTransactionError::TipAboveFeeCap.into())
557        }
558
559        // determine whether the transaction should be treated as local
560        let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
561
562        // Ensure max possible transaction fee doesn't exceed configured transaction fee cap.
563        // Only for transactions locally submitted for acceptance into the pool.
564        if is_local {
565            match self.tx_fee_cap {
566                Some(0) | None => {} // Skip if cap is 0 or None
567                Some(tx_fee_cap_wei) => {
568                    let max_tx_fee_wei = transaction.cost().saturating_sub(transaction.value());
569                    if max_tx_fee_wei > tx_fee_cap_wei {
570                        self.validation_metrics.rejected_exceeds_fee_cap.increment(1);
571                        return Err(InvalidPoolTransactionError::ExceedsFeeCap {
572                            max_tx_fee_wei: max_tx_fee_wei.saturating_to(),
573                            tx_fee_cap_wei,
574                        })
575                    }
576                }
577            }
578        }
579
580        // Drop transactions with a priority fee lower than the configured minimum for acceptance
581        // into the pool. This applies to all transaction types: legacy and EIP-2930 transactions
582        // use their gas price as the priority fee.
583        if let Some(minimum_priority_fee) = self.minimum_priority_fee &&
584            transaction.priority_fee_or_price() < minimum_priority_fee
585        {
586            self.validation_metrics.rejected_priority_fee_below_minimum.increment(1);
587            return Err(InvalidPoolTransactionError::PriorityFeeBelowMinimum {
588                minimum_priority_fee,
589            })
590        }
591
592        // Checks for chainid
593        if let Some(chain_id) = transaction.chain_id() &&
594            chain_id != self.chain_id()
595        {
596            return Err(InvalidTransactionError::ChainIdMismatch.into())
597        }
598
599        if transaction.is_eip7702() {
600            // Prague fork is required for 7702 txs
601            if !self.fork_tracker.is_prague_activated() {
602                return Err(InvalidTransactionError::TxTypeNotSupported.into())
603            }
604
605            if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
606                return Err(Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into())
607            }
608        }
609
610        ensure_intrinsic_gas(transaction, &self.fork_tracker).inspect_err(|err| {
611            if matches!(err, InvalidPoolTransactionError::IntrinsicGasTooLow) {
612                self.validation_metrics.rejected_intrinsic_gas_too_low.increment(1);
613            } else {
614                self.validation_metrics.rejected_intrinsic_gas_too_high.increment(1);
615            }
616        })?;
617
618        // light blob tx pre-checks
619        if transaction.is_eip4844() {
620            // Cancun fork is required for blob txs
621            if !self.fork_tracker.is_cancun_activated() {
622                return Err(InvalidTransactionError::TxTypeNotSupported.into())
623            }
624
625            let blob_count = transaction.blob_count().unwrap_or(0);
626            if blob_count == 0 {
627                // no blobs
628                self.validation_metrics.invalid_4844.increment(1);
629                return Err(InvalidPoolTransactionError::Eip4844(
630                    Eip4844PoolTransactionError::NoEip4844Blobs,
631                ))
632            }
633
634            let max_blob_count = self.fork_tracker.max_blob_count();
635            if blob_count > max_blob_count {
636                self.validation_metrics.invalid_4844.increment(1);
637                return Err(InvalidPoolTransactionError::Eip4844(
638                    Eip4844PoolTransactionError::TooManyEip4844Blobs {
639                        have: blob_count,
640                        permitted: max_blob_count,
641                    },
642                ))
643            }
644        }
645
646        // Transaction gas limit validation (EIP-7825 for Osaka+)
647        let tx_gas_limit_cap =
648            self.fork_tracker.tx_gas_limit_cap.load(std::sync::atomic::Ordering::Relaxed);
649        if tx_gas_limit_cap > 0 && transaction.gas_limit() > tx_gas_limit_cap {
650            return Err(InvalidTransactionError::GasLimitTooHigh.into())
651        }
652
653        // Run additional stateless validation if configured
654        if let Some(check) = &self.additional_stateless_validation {
655            check(origin, transaction)?;
656        }
657
658        Ok(())
659    }
660
661    /// Validates a single transaction against the given state (stateful checks only).
662    ///
663    /// Checks sender account balance, nonce, bytecode, and validates blob sidecars. The
664    /// transaction must have already passed [`validate_stateless`](Self::validate_stateless).
665    pub fn validate_stateful<P>(
666        &self,
667        origin: TransactionOrigin,
668        mut transaction: Tx,
669        state: P,
670    ) -> TransactionValidationOutcome<Tx>
671    where
672        P: AccountInfoReader,
673    {
674        // Use provider to get account info
675        let account = match state.basic_account(transaction.sender_ref()) {
676            Ok(account) => account.unwrap_or_default(),
677            Err(err) => {
678                return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
679            }
680        };
681
682        // check for bytecode
683        match self.validate_sender_bytecode(&transaction, &account, &state) {
684            Err(outcome) => return outcome,
685            Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err),
686            _ => {}
687        };
688
689        // Checks for nonce
690        if transaction.requires_nonce_check() &&
691            let Err(err) = self.validate_sender_nonce(&transaction, &account)
692        {
693            return TransactionValidationOutcome::Invalid(transaction, err)
694        }
695
696        // checks for max cost not exceedng account_balance
697        if let Err(err) = self.validate_sender_balance(&transaction, &account) {
698            return TransactionValidationOutcome::Invalid(transaction, err)
699        }
700
701        // heavy blob tx validation
702        let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) {
703            Err(err) => return TransactionValidationOutcome::Invalid(transaction, err),
704            Ok(sidecar) => sidecar,
705        };
706
707        // Run additional stateful validation if configured
708        if let Some(check) = &self.additional_stateful_validation &&
709            let Err(err) = check(origin, &transaction, &state)
710        {
711            return TransactionValidationOutcome::Invalid(transaction, err)
712        }
713
714        let authorities = self.recover_authorities(&transaction);
715        // Return the valid transaction
716        TransactionValidationOutcome::Valid {
717            balance: account.balance,
718            state_nonce: account.nonce,
719            bytecode_hash: account.bytecode_hash,
720            transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
721            // by this point assume all external transactions should be propagated
722            propagate: match origin {
723                TransactionOrigin::External => true,
724                TransactionOrigin::Local => {
725                    self.local_transactions_config.propagate_local_transactions
726                }
727                TransactionOrigin::Private => false,
728            },
729            authorities,
730        }
731    }
732
733    /// Validates that the sender’s account has valid or no bytecode.
734    pub fn validate_sender_bytecode(
735        &self,
736        transaction: &Tx,
737        sender: &Account,
738        state: impl BytecodeReader,
739    ) -> Result<Result<(), InvalidPoolTransactionError>, TransactionValidationOutcome<Tx>> {
740        // Unless Prague is active, the signer account shouldn't have bytecode.
741        //
742        // If Prague is active, only EIP-7702 bytecode is allowed for the sender.
743        //
744        // Any other case means that the account is not an EOA, and should not be able to send
745        // transactions.
746        if let Some(code_hash) = &sender.bytecode_hash &&
747            *code_hash != KECCAK_EMPTY
748        {
749            let is_eip7702 = if self.fork_tracker.is_prague_activated() {
750                match state.bytecode_by_hash(code_hash) {
751                    Ok(bytecode) => bytecode.is_some_and(|b| b.is_eip7702()),
752                    Err(err) => {
753                        return Err(TransactionValidationOutcome::Error(
754                            *transaction.hash(),
755                            Box::new(err),
756                        ))
757                    }
758                }
759            } else {
760                false
761            };
762
763            if !is_eip7702 {
764                return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into()))
765            }
766        }
767        Ok(Ok(()))
768    }
769
770    /// Checks if the transaction nonce is valid.
771    pub fn validate_sender_nonce(
772        &self,
773        transaction: &Tx,
774        sender: &Account,
775    ) -> Result<(), InvalidPoolTransactionError> {
776        let tx_nonce = transaction.nonce();
777
778        if tx_nonce < sender.nonce {
779            return Err(InvalidTransactionError::NonceNotConsistent {
780                tx: tx_nonce,
781                state: sender.nonce,
782            }
783            .into())
784        }
785        Ok(())
786    }
787
788    /// Ensures the sender has sufficient account balance.
789    pub fn validate_sender_balance(
790        &self,
791        transaction: &Tx,
792        sender: &Account,
793    ) -> Result<(), InvalidPoolTransactionError> {
794        let cost = transaction.cost();
795
796        if !self.disable_balance_check && cost > &sender.balance {
797            let expected = *cost;
798            return Err(InvalidTransactionError::InsufficientFunds(
799                GotExpected { got: sender.balance, expected }.into(),
800            )
801            .into())
802        }
803        Ok(())
804    }
805
806    /// Validates EIP-4844 blob sidecar data and returns the extracted sidecar, if any.
807    pub fn validate_eip4844(
808        &self,
809        transaction: &mut Tx,
810    ) -> Result<Option<PooledBlobSidecar>, InvalidPoolTransactionError> {
811        let mut maybe_blob_sidecar = None;
812
813        // heavy blob tx validation
814        if transaction.is_eip4844() {
815            // extract the blob from the transaction
816            match transaction.take_blob() {
817                EthBlobTransactionSidecar::None => {
818                    // this should not happen
819                    self.validation_metrics.invalid_4844.increment(1);
820                    return Err(InvalidTransactionError::TxTypeNotSupported.into())
821                }
822                EthBlobTransactionSidecar::Missing => {
823                    // This can happen for re-injected blob transactions (on re-org), since the blob
824                    // is stripped from the transaction and not included in a block.
825                    // check if the blob is in the store, if it's included we previously validated
826                    // it and inserted it
827                    if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) {
828                        // validated transaction is already in the store
829                    } else {
830                        self.validation_metrics.invalid_4844.increment(1);
831                        return Err(InvalidPoolTransactionError::Eip4844(
832                            Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
833                        ))
834                    }
835                }
836                EthBlobTransactionSidecar::Present(sidecar) => {
837                    let now = Instant::now();
838
839                    // EIP-7594 sidecar version handling
840                    if self.eip7594 {
841                        // Standard Ethereum behavior
842                        if self.fork_tracker.is_osaka_activated() {
843                            if sidecar.is_eip4844() {
844                                self.validation_metrics.invalid_4844.increment(1);
845                                return Err(InvalidPoolTransactionError::Eip4844(
846                                    Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
847                                ))
848                            }
849                        } else if sidecar.is_eip7594() && !self.allow_7594_sidecars() {
850                            self.validation_metrics.invalid_4844.increment(1);
851                            return Err(InvalidPoolTransactionError::Eip4844(
852                                Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
853                            ))
854                        }
855                    } else {
856                        // EIP-7594 disabled: always reject v1 sidecars, accept v0
857                        if sidecar.is_eip7594() {
858                            self.validation_metrics.invalid_4844.increment(1);
859                            return Err(InvalidPoolTransactionError::Eip4844(
860                                Eip4844PoolTransactionError::Eip7594SidecarDisallowed,
861                            ))
862                        }
863                    }
864
865                    // validate the blob
866                    if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
867                        self.validation_metrics.invalid_4844.increment(1);
868                        return Err(InvalidPoolTransactionError::Eip4844(
869                            Eip4844PoolTransactionError::InvalidEip4844Blob(err),
870                        ))
871                    }
872                    // Record the duration of successful blob validation as histogram
873                    self.validation_metrics.blob_validation_duration.record(now.elapsed());
874                    // store the extracted blob
875                    maybe_blob_sidecar = Some(sidecar);
876                }
877            }
878        }
879        Ok(maybe_blob_sidecar)
880    }
881
882    /// Returns recovered authorities from authorizations for this chain or all chains.
883    fn recover_authorities(&self, transaction: &Tx) -> std::option::Option<Vec<Address>> {
884        transaction.authorization_list().map(|auths| {
885            let chain_id = U256::from(self.chain_id());
886            auths
887                .iter()
888                .filter(|auth| auth.chain_id().is_zero() || auth.chain_id() == &chain_id)
889                .flat_map(|auth| auth.recover_authority())
890                .collect()
891        })
892    }
893
894    /// Validates all given transactions.
895    fn validate_batch(
896        &self,
897        transactions: impl IntoIterator<Item = (TransactionOrigin, Tx)>,
898    ) -> Vec<TransactionValidationOutcome<Tx>> {
899        let mut provider: Option<StateProviderBox> = None;
900        transactions
901            .into_iter()
902            .map(|(origin, tx)| {
903                self.validate_one_with_provider(origin, tx, &mut provider, || self.client.latest())
904            })
905            .collect()
906    }
907
908    /// Validates all given transactions with origin.
909    fn validate_batch_with_origin(
910        &self,
911        origin: TransactionOrigin,
912        transactions: impl IntoIterator<Item = Tx> + Send,
913    ) -> Vec<TransactionValidationOutcome<Tx>> {
914        let mut provider: Option<StateProviderBox> = None;
915        transactions
916            .into_iter()
917            .map(|tx| {
918                self.validate_one_with_provider(origin, tx, &mut provider, || self.client.latest())
919            })
920            .collect()
921    }
922
923    fn on_new_head_block(&self, new_tip_block: &HeaderTy<Evm::Primitives>) {
924        // update all forks
925        if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
926            self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
927        }
928
929        if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
930            self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
931        }
932
933        if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
934            self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
935        }
936
937        if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
938            self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
939        }
940
941        if self.chain_spec().is_amsterdam_active_at_timestamp(new_tip_block.timestamp()) {
942            self.fork_tracker.amsterdam.store(true, std::sync::atomic::Ordering::Relaxed);
943        }
944
945        self.fork_tracker
946            .tip_timestamp
947            .store(new_tip_block.timestamp(), std::sync::atomic::Ordering::Relaxed);
948
949        if let Some(blob_params) =
950            self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
951        {
952            self.fork_tracker
953                .max_blob_count
954                .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed);
955        }
956
957        self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
958
959        // Get EVM limits from evm_config.evm_env()
960        let evm_env = self
961            .evm_config
962            .evm_env(new_tip_block)
963            .expect("evm_env should not fail for executed block");
964
965        self.fork_tracker
966            .max_initcode_size
967            .store(evm_env.cfg_env.max_initcode_size(), std::sync::atomic::Ordering::Relaxed);
968        let (tx_gas_limit_cap, tx_regular_gas_cap) = tx_gas_caps(&evm_env.cfg_env);
969        self.fork_tracker
970            .tx_gas_limit_cap
971            .store(tx_gas_limit_cap, std::sync::atomic::Ordering::Relaxed);
972        self.fork_tracker
973            .tx_regular_gas_cap
974            .store(tx_regular_gas_cap, std::sync::atomic::Ordering::Relaxed);
975    }
976
977    fn max_gas_limit(&self) -> u64 {
978        self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
979    }
980
981    /// Returns whether EIP-7594 sidecars are allowed
982    fn allow_7594_sidecars(&self) -> bool {
983        let tip_timestamp = self.fork_tracker.tip_timestamp();
984
985        // If next block is Osaka, allow 7594 sidecars
986        if self
987            .chain_spec()
988            .is_osaka_active_at_timestamp(tip_timestamp.saturating_add(SLOT_DURATION_SECS))
989        {
990            true
991        } else if self
992            .chain_spec()
993            .is_osaka_active_at_timestamp(tip_timestamp.saturating_add(2 * SLOT_DURATION_SECS))
994        {
995            let current_timestamp =
996                SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
997
998            // Allow after 4 seconds into last non-Osaka slot
999            current_timestamp >= tip_timestamp.saturating_add(4)
1000        } else {
1001            false
1002        }
1003    }
1004}
1005
1006impl<Client, Tx, Evm> TransactionValidator for EthTransactionValidator<Client, Tx, Evm>
1007where
1008    Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
1009    Tx: EthPoolTransaction,
1010    Evm: ConfigureEvm,
1011{
1012    type Transaction = Tx;
1013    type Block = BlockTy<Evm::Primitives>;
1014
1015    async fn validate_transaction(
1016        &self,
1017        origin: TransactionOrigin,
1018        transaction: Self::Transaction,
1019    ) -> TransactionValidationOutcome<Self::Transaction> {
1020        self.validate_one(origin, transaction)
1021    }
1022
1023    async fn validate_transactions(
1024        &self,
1025        transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
1026            + Send,
1027    ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
1028        self.validate_batch(transactions)
1029    }
1030
1031    async fn validate_transactions_with_origin(
1032        &self,
1033        origin: TransactionOrigin,
1034        transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
1035    ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
1036        self.validate_batch_with_origin(origin, transactions)
1037    }
1038
1039    fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
1040        Self::on_new_head_block(self, new_tip_block.header())
1041    }
1042}
1043
1044/// A builder for [`EthTransactionValidator`] and [`TransactionValidationTaskExecutor`]
1045#[derive(Debug)]
1046pub struct EthTransactionValidatorBuilder<Client, Evm> {
1047    client: Client,
1048    /// The chain ID transactions must use.
1049    chain_id: u64,
1050    /// The EVM configuration to use for validation.
1051    evm_config: Evm,
1052    /// Fork indicator whether we are in the Shanghai stage.
1053    shanghai: bool,
1054    /// Fork indicator whether we are in the Cancun hardfork.
1055    cancun: bool,
1056    /// Fork indicator whether we are in the Prague hardfork.
1057    prague: bool,
1058    /// Fork indicator whether we are in the Osaka hardfork.
1059    osaka: bool,
1060    /// Fork indicator whether we are in the Amsterdam hardfork.
1061    amsterdam: bool,
1062    /// Timestamp of the tip block.
1063    tip_timestamp: u64,
1064    /// Max blob count at the block's timestamp.
1065    max_blob_count: u64,
1066    /// Whether using EIP-2718 type transactions is allowed
1067    eip2718: bool,
1068    /// Whether using EIP-1559 type transactions is allowed
1069    eip1559: bool,
1070    /// Whether using EIP-4844 type transactions is allowed
1071    eip4844: bool,
1072    /// Whether using EIP-7702 type transactions is allowed
1073    eip7702: bool,
1074    /// The current max gas limit
1075    block_gas_limit: AtomicU64,
1076    /// The current tx fee cap limit in wei locally submitted into the pool.
1077    tx_fee_cap: Option<u128>,
1078    /// Minimum priority fee to enforce for acceptance into the pool.
1079    ///
1080    /// For legacy and EIP-2930 transactions the gas price is used as the priority fee.
1081    minimum_priority_fee: Option<u128>,
1082    /// Determines how many additional tasks to spawn
1083    ///
1084    /// Default is 1
1085    additional_tasks: usize,
1086
1087    /// Stores the setup and parameters needed for validating KZG proofs.
1088    kzg_settings: EnvKzgSettings,
1089    /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.
1090    local_transactions_config: LocalTransactionConfig,
1091    /// Max size in bytes of a single transaction allowed
1092    max_tx_input_bytes: usize,
1093    /// Maximum gas limit for individual transactions
1094    max_tx_gas_limit: Option<u64>,
1095    /// Disable balance checks during transaction validation
1096    disable_balance_check: bool,
1097    /// Bitmap of custom transaction types that are allowed.
1098    other_tx_types: U256,
1099    /// Cached max initcode size from EVM config
1100    max_initcode_size: usize,
1101    /// Cached transaction gas limit cap from EVM config (0 = no cap)
1102    tx_gas_limit_cap: u64,
1103    /// Cached EIP-8037 intrinsic regular gas cap from EVM config (0 = no cap).
1104    tx_regular_gas_cap: u64,
1105    /// Whether EIP-7594 blob sidecars are accepted.
1106    /// When false, EIP-7594 (v1) sidecars are always rejected and EIP-4844 (v0) sidecars
1107    /// are always accepted, regardless of Osaka fork activation.
1108    eip7594: bool,
1109}
1110
1111impl<Client, Evm> EthTransactionValidatorBuilder<Client, Evm> {
1112    /// Creates a new builder for the given client and EVM config
1113    ///
1114    /// By default this assumes the network is on the `Prague` hardfork and the following
1115    /// transactions are allowed:
1116    ///  - Legacy
1117    ///  - EIP-2718
1118    ///  - EIP-1559
1119    ///  - EIP-4844
1120    ///  - EIP-7702
1121    pub fn new(client: Client, evm_config: Evm) -> Self
1122    where
1123        Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
1124            + BlockReaderIdExt<Header = HeaderTy<Evm::Primitives>>,
1125        Evm: ConfigureEvm,
1126    {
1127        let chain_spec = client.chain_spec();
1128        let tip = client
1129            .header_by_id(BlockId::latest())
1130            .expect("failed to fetch latest header")
1131            .expect("latest header is not found");
1132        let evm_env =
1133            evm_config.evm_env(&tip).expect("evm_env should not fail for existing blocks");
1134        let (tx_gas_limit_cap, tx_regular_gas_cap) = tx_gas_caps(&evm_env.cfg_env);
1135
1136        Self {
1137            block_gas_limit: tip.gas_limit().into(),
1138            client,
1139            chain_id: chain_spec.chain().id(),
1140            evm_config,
1141            minimum_priority_fee: None,
1142            additional_tasks: 1,
1143            kzg_settings: EnvKzgSettings::Default,
1144            local_transactions_config: Default::default(),
1145            max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
1146            tx_fee_cap: Some(1e18 as u128),
1147            max_tx_gas_limit: None,
1148            // by default all transaction types are allowed
1149            eip2718: true,
1150            eip1559: true,
1151            eip4844: true,
1152            eip7702: true,
1153
1154            shanghai: chain_spec.is_shanghai_active_at_timestamp(tip.timestamp()),
1155            cancun: chain_spec.is_cancun_active_at_timestamp(tip.timestamp()),
1156            prague: chain_spec.is_prague_active_at_timestamp(tip.timestamp()),
1157            osaka: chain_spec.is_osaka_active_at_timestamp(tip.timestamp()),
1158            amsterdam: chain_spec.is_amsterdam_active_at_timestamp(tip.timestamp()),
1159
1160            tip_timestamp: tip.timestamp(),
1161
1162            max_blob_count: chain_spec
1163                .blob_params_at_timestamp(tip.timestamp())
1164                .unwrap_or_else(BlobParams::prague)
1165                .max_blobs_per_tx,
1166
1167            // balance checks are enabled by default
1168            disable_balance_check: false,
1169
1170            // no custom transaction types by default
1171            other_tx_types: U256::ZERO,
1172
1173            tx_gas_limit_cap,
1174            tx_regular_gas_cap,
1175            max_initcode_size: evm_env.cfg_env.max_initcode_size(),
1176
1177            // EIP-7594 sidecars are accepted by default (standard Ethereum behavior)
1178            eip7594: true,
1179        }
1180    }
1181
1182    /// Disables the Cancun fork.
1183    pub const fn no_cancun(self) -> Self {
1184        self.set_cancun(false)
1185    }
1186
1187    /// Whether to allow exemptions for local transaction exemptions.
1188    pub fn with_local_transactions_config(
1189        mut self,
1190        local_transactions_config: LocalTransactionConfig,
1191    ) -> Self {
1192        self.local_transactions_config = local_transactions_config;
1193        self
1194    }
1195
1196    /// Set the Cancun fork.
1197    pub const fn set_cancun(mut self, cancun: bool) -> Self {
1198        self.cancun = cancun;
1199        self
1200    }
1201
1202    /// Disables the Shanghai fork.
1203    pub const fn no_shanghai(self) -> Self {
1204        self.set_shanghai(false)
1205    }
1206
1207    /// Set the Shanghai fork.
1208    pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
1209        self.shanghai = shanghai;
1210        self
1211    }
1212
1213    /// Disables the Prague fork.
1214    pub const fn no_prague(self) -> Self {
1215        self.set_prague(false)
1216    }
1217
1218    /// Set the Prague fork.
1219    pub const fn set_prague(mut self, prague: bool) -> Self {
1220        self.prague = prague;
1221        self
1222    }
1223
1224    /// Disables the Osaka fork.
1225    pub const fn no_osaka(self) -> Self {
1226        self.set_osaka(false)
1227    }
1228
1229    /// Set the Osaka fork.
1230    pub const fn set_osaka(mut self, osaka: bool) -> Self {
1231        self.osaka = osaka;
1232        self
1233    }
1234
1235    /// Disables the Amsterdam fork.
1236    pub const fn no_amsterdam(self) -> Self {
1237        self.set_amsterdam(false)
1238    }
1239
1240    /// Set the Amsterdam fork.
1241    pub const fn set_amsterdam(mut self, amsterdam: bool) -> Self {
1242        self.amsterdam = amsterdam;
1243        self
1244    }
1245
1246    /// Disables the support for EIP-2718 transactions.
1247    pub const fn no_eip2718(self) -> Self {
1248        self.set_eip2718(false)
1249    }
1250
1251    /// Set the support for EIP-2718 transactions.
1252    pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
1253        self.eip2718 = eip2718;
1254        self
1255    }
1256
1257    /// Disables the support for EIP-1559 transactions.
1258    pub const fn no_eip1559(self) -> Self {
1259        self.set_eip1559(false)
1260    }
1261
1262    /// Set the support for EIP-1559 transactions.
1263    pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
1264        self.eip1559 = eip1559;
1265        self
1266    }
1267
1268    /// Disables the support for EIP-4844 transactions.
1269    pub const fn no_eip4844(self) -> Self {
1270        self.set_eip4844(false)
1271    }
1272
1273    /// Set the support for EIP-4844 transactions.
1274    pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
1275        self.eip4844 = eip4844;
1276        self
1277    }
1278
1279    /// Disables the support for EIP-7702 transactions.
1280    pub const fn no_eip7702(self) -> Self {
1281        self.set_eip7702(false)
1282    }
1283
1284    /// Set the support for EIP-7702 transactions.
1285    pub const fn set_eip7702(mut self, eip7702: bool) -> Self {
1286        self.eip7702 = eip7702;
1287        self
1288    }
1289
1290    /// Disables EIP-7594 blob sidecar support.
1291    ///
1292    /// When disabled, EIP-7594 (v1) blob sidecars are always rejected and EIP-4844 (v0)
1293    /// sidecars are always accepted, regardless of Osaka fork activation.
1294    ///
1295    /// Use this for chains that do not adopt EIP-7594 (`PeerDAS`).
1296    pub const fn no_eip7594(self) -> Self {
1297        self.set_eip7594(false)
1298    }
1299
1300    /// Set EIP-7594 blob sidecar support.
1301    ///
1302    /// When true (default), standard Ethereum behavior applies: v0 sidecars before Osaka,
1303    /// v1 sidecars after Osaka. When false, v1 sidecars are always rejected.
1304    pub const fn set_eip7594(mut self, eip7594: bool) -> Self {
1305        self.eip7594 = eip7594;
1306        self
1307    }
1308
1309    /// Sets the [`EnvKzgSettings`] to use for validating KZG proofs.
1310    pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
1311        self.kzg_settings = kzg_settings;
1312        self
1313    }
1314
1315    /// Sets a minimum priority fee that's enforced for acceptance into the pool.
1316    ///
1317    /// This applies to all transaction types. For legacy and EIP-2930 transactions the gas price
1318    /// is used as the priority fee.
1319    pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: Option<u128>) -> Self {
1320        self.minimum_priority_fee = minimum_priority_fee;
1321        self
1322    }
1323
1324    /// Sets the number of additional tasks to spawn.
1325    pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
1326        self.additional_tasks = additional_tasks;
1327        self
1328    }
1329
1330    /// Sets a max size in bytes of a single transaction allowed into the pool
1331    pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
1332        self.max_tx_input_bytes = max_tx_input_bytes;
1333        self
1334    }
1335
1336    /// Sets the block gas limit
1337    ///
1338    /// Transactions with a gas limit greater than this will be rejected.
1339    pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
1340        self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
1341        self
1342    }
1343
1344    /// Sets the block gas limit
1345    ///
1346    /// Transactions with a gas limit greater than this will be rejected.
1347    pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
1348        self.tx_fee_cap = Some(tx_fee_cap);
1349        self
1350    }
1351
1352    /// Sets the maximum gas limit for individual transactions
1353    pub const fn with_max_tx_gas_limit(mut self, max_tx_gas_limit: Option<u64>) -> Self {
1354        self.max_tx_gas_limit = max_tx_gas_limit;
1355        self
1356    }
1357
1358    /// Disables balance checks during transaction validation
1359    pub const fn disable_balance_check(mut self) -> Self {
1360        self.disable_balance_check = true;
1361        self
1362    }
1363
1364    /// Adds a custom transaction type to the validator.
1365    pub const fn with_custom_tx_type(mut self, tx_type: u8) -> Self {
1366        self.other_tx_types.set_bit(tx_type as usize, true);
1367        self
1368    }
1369
1370    /// Builds a the [`EthTransactionValidator`] without spawning validator tasks.
1371    pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx, Evm>
1372    where
1373        S: BlobStore,
1374    {
1375        let Self {
1376            client,
1377            chain_id,
1378            evm_config,
1379            shanghai,
1380            cancun,
1381            prague,
1382            osaka,
1383            amsterdam,
1384            tip_timestamp,
1385            eip2718,
1386            eip1559,
1387            eip4844,
1388            eip7702,
1389            block_gas_limit,
1390            tx_fee_cap,
1391            minimum_priority_fee,
1392            kzg_settings,
1393            local_transactions_config,
1394            max_tx_input_bytes,
1395            max_tx_gas_limit,
1396            disable_balance_check,
1397            max_blob_count,
1398            additional_tasks: _,
1399            other_tx_types,
1400            max_initcode_size,
1401            tx_gas_limit_cap,
1402            tx_regular_gas_cap,
1403            eip7594,
1404        } = self;
1405
1406        let fork_tracker = ForkTracker {
1407            shanghai: AtomicBool::new(shanghai),
1408            cancun: AtomicBool::new(cancun),
1409            prague: AtomicBool::new(prague),
1410            osaka: AtomicBool::new(osaka),
1411            amsterdam: AtomicBool::new(amsterdam),
1412            tip_timestamp: AtomicU64::new(tip_timestamp),
1413            max_blob_count: AtomicU64::new(max_blob_count),
1414            max_initcode_size: AtomicUsize::new(max_initcode_size),
1415            tx_gas_limit_cap: AtomicU64::new(tx_gas_limit_cap),
1416            tx_regular_gas_cap: AtomicU64::new(tx_regular_gas_cap),
1417        };
1418
1419        EthTransactionValidator {
1420            client,
1421            chain_id,
1422            eip2718,
1423            eip1559,
1424            fork_tracker,
1425            eip4844,
1426            eip7702,
1427            block_gas_limit,
1428            tx_fee_cap,
1429            minimum_priority_fee,
1430            blob_store: Box::new(blob_store),
1431            kzg_settings,
1432            local_transactions_config,
1433            max_tx_input_bytes,
1434            max_tx_gas_limit,
1435            disable_balance_check,
1436            evm_config,
1437            _marker: Default::default(),
1438            validation_metrics: TxPoolValidationMetrics::default(),
1439            other_tx_types,
1440            eip7594,
1441            additional_stateless_validation: None,
1442            additional_stateful_validation: None,
1443        }
1444    }
1445
1446    /// Builds a [`EthTransactionValidator`] and spawns validation tasks via the
1447    /// [`TransactionValidationTaskExecutor`]
1448    ///
1449    /// The validator will spawn `additional_tasks` additional tasks for validation.
1450    ///
1451    /// By default this will spawn 1 additional task.
1452    pub fn build_with_tasks<Tx, S>(
1453        self,
1454        tasks: Runtime,
1455        blob_store: S,
1456    ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx, Evm>>
1457    where
1458        S: BlobStore,
1459    {
1460        let additional_tasks = self.additional_tasks;
1461        let validator = self.build::<Tx, S>(blob_store);
1462        TransactionValidationTaskExecutor::spawn(validator, &tasks, additional_tasks)
1463    }
1464}
1465
1466/// Keeps track of whether certain forks are activated
1467#[derive(Debug)]
1468pub struct ForkTracker {
1469    /// Tracks if shanghai is activated at the block's timestamp.
1470    pub shanghai: AtomicBool,
1471    /// Tracks if cancun is activated at the block's timestamp.
1472    pub cancun: AtomicBool,
1473    /// Tracks if prague is activated at the block's timestamp.
1474    pub prague: AtomicBool,
1475    /// Tracks if osaka is activated at the block's timestamp.
1476    pub osaka: AtomicBool,
1477    /// Tracks if amsterdam is activated at the block's timestamp.
1478    pub amsterdam: AtomicBool,
1479    /// Tracks max blob count per transaction at the block's timestamp.
1480    pub max_blob_count: AtomicU64,
1481    /// Tracks the timestamp of the tip block.
1482    pub tip_timestamp: AtomicU64,
1483    /// Cached max initcode size from EVM config
1484    pub max_initcode_size: AtomicUsize,
1485    /// Cached transaction gas limit cap from EVM config (0 = no cap)
1486    pub tx_gas_limit_cap: AtomicU64,
1487    /// Cached EIP-8037 intrinsic regular gas cap from EVM config (0 = no cap).
1488    pub tx_regular_gas_cap: AtomicU64,
1489}
1490
1491impl ForkTracker {
1492    /// Returns `true` if Shanghai fork is activated.
1493    pub fn is_shanghai_activated(&self) -> bool {
1494        self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1495    }
1496
1497    /// Returns `true` if Cancun fork is activated.
1498    pub fn is_cancun_activated(&self) -> bool {
1499        self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1500    }
1501
1502    /// Returns `true` if Prague fork is activated.
1503    pub fn is_prague_activated(&self) -> bool {
1504        self.prague.load(std::sync::atomic::Ordering::Relaxed)
1505    }
1506
1507    /// Returns `true` if Osaka fork is activated.
1508    pub fn is_osaka_activated(&self) -> bool {
1509        self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1510    }
1511
1512    /// Returns `true` if Amsterdam fork is activated.
1513    pub fn is_amsterdam_activated(&self) -> bool {
1514        self.amsterdam.load(std::sync::atomic::Ordering::Relaxed)
1515    }
1516
1517    /// Returns the timestamp of the tip block.
1518    pub fn tip_timestamp(&self) -> u64 {
1519        self.tip_timestamp.load(std::sync::atomic::Ordering::Relaxed)
1520    }
1521
1522    /// Returns the max allowed blob count per transaction.
1523    pub fn max_blob_count(&self) -> u64 {
1524        self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1525    }
1526}
1527
1528/// Ensures that gas limit of the transaction exceeds the intrinsic gas of the transaction, and
1529/// that its intrinsic regular gas fits the EIP-8037 regular gas cap.
1530///
1531/// Caution: This only checks past the Merge hardfork.
1532pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1533    transaction: &T,
1534    fork_tracker: &ForkTracker,
1535) -> Result<(), InvalidPoolTransactionError> {
1536    use revm::primitives::hardfork::SpecId;
1537    let spec_id = if fork_tracker.is_amsterdam_activated() {
1538        SpecId::AMSTERDAM
1539    } else if fork_tracker.is_prague_activated() {
1540        SpecId::PRAGUE
1541    } else if fork_tracker.is_shanghai_activated() {
1542        SpecId::SHANGHAI
1543    } else {
1544        SpecId::MERGE
1545    };
1546
1547    // EIP-2780 replaces the flat intrinsic base cost with a decomposed one that depends on
1548    // `tx.to` and `tx.value`.
1549    let eip2780 = fork_tracker.is_amsterdam_activated().then(|| {
1550        revm::context_interface::cfg::gas_params::Eip2780TxInfo {
1551            value: transaction.value(),
1552            // Self-transfer: a `Call` whose recipient is the sender itself.
1553            is_self_transfer: transaction.kind().to() == Some(&transaction.sender()),
1554        }
1555    });
1556
1557    let gas = revm::interpreter::gas::calculate_initial_tx_gas(
1558        spec_id,
1559        transaction.input(),
1560        transaction.is_create(),
1561        transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1562        transaction.access_list().map(|l| l.storage_keys_count()).unwrap_or_default() as u64,
1563        transaction.authorization_count().unwrap_or_default(),
1564        eip2780,
1565    );
1566
1567    let gas_limit = transaction.gas_limit();
1568    // EIP-8037: `tx.gas` may exceed the cap, but intrinsic regular gas and the calldata floor may
1569    // not, otherwise the transaction can never be included.
1570    let regular_gas_cap =
1571        fork_tracker.tx_regular_gas_cap.load(std::sync::atomic::Ordering::Relaxed);
1572    if gas_limit < gas.initial_total_gas() || gas_limit < gas.floor_gas() {
1573        Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1574    } else if regular_gas_cap > 0 &&
1575        gas.initial_regular_gas().max(gas.floor_gas()) > regular_gas_cap
1576    {
1577        // `GasTooHigh` is not a bad transaction, so peers that still relay these, such as nodes
1578        // without this check, are not penalized.
1579        Err(InvalidTransactionError::GasTooHigh.into())
1580    } else {
1581        Ok(())
1582    }
1583}
1584
1585/// Returns the pool's `(tx_gas_limit_cap, tx_regular_gas_cap)` for the given EVM config, where 0
1586/// disables the respective check.
1587///
1588/// EIP-8037 lets `tx.gas` exceed the EIP-7825 cap because state gas is drawn from a reservoir
1589/// above it, but the cap still bounds intrinsic regular gas and the calldata floor.
1590fn tx_gas_caps(cfg: &impl Cfg) -> (u64, u64) {
1591    let cap = cfg.tx_gas_limit_cap();
1592    if cfg.is_amsterdam_eip8037_enabled() {
1593        (0, cap)
1594    } else {
1595        (cap, 0)
1596    }
1597}
1598
1599#[cfg(test)]
1600mod tests {
1601    use super::*;
1602    use crate::{
1603        blobstore::InMemoryBlobStore, error::PoolErrorKind, test_utils::TransactionBuilder,
1604        traits::PoolTransaction, CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1605    };
1606    use alloy_consensus::Transaction;
1607    use alloy_eips::{
1608        eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M,
1609        eip2718::{Decodable2718, Encodable2718},
1610        eip2930::{AccessList, AccessListItem},
1611        eip7702::Authorization,
1612    };
1613    use alloy_primitives::{hex, Address, Bytes, Signature, B256, U256};
1614    use reth_chainspec::{ChainSpecBuilder, MAINNET};
1615    use reth_ethereum_primitives::PooledTransactionVariant;
1616    use reth_evm_ethereum::EthEvmConfig;
1617    use reth_primitives_traits::SignedTransaction;
1618    use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1619    use revm::primitives::eip3860::MAX_INITCODE_SIZE;
1620
1621    fn test_evm_config() -> EthEvmConfig {
1622        EthEvmConfig::mainnet()
1623    }
1624
1625    /// Mainnet with a 30M genesis gas limit instead of 5,000, since the validator takes its block
1626    /// gas limit from the latest header.
1627    fn mock_provider() -> MockEthProvider {
1628        mock_provider_with_gas_limit(ETHEREUM_BLOCK_GAS_LIMIT_30M)
1629    }
1630
1631    fn mock_provider_with_gas_limit(gas_limit: u64) -> MockEthProvider {
1632        let mut genesis = MAINNET.genesis.clone();
1633        genesis.gas_limit = gas_limit;
1634        MockEthProvider::default()
1635            .with_chain_spec(ChainSpecBuilder::mainnet().genesis(genesis).build())
1636            .with_genesis_block()
1637    }
1638
1639    fn get_transaction() -> EthPooledTransaction {
1640        let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1641
1642        let data = hex::decode(raw).unwrap();
1643        let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1644
1645        EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1646    }
1647
1648    fn eip1559_tx(
1649        to: Address,
1650        sender: Address,
1651        value: u64,
1652        gas_limit: u64,
1653    ) -> EthPooledTransaction {
1654        let tx = alloy_consensus::TxEip1559 {
1655            chain_id: 1,
1656            nonce: 0,
1657            gas_limit,
1658            max_fee_per_gas: 1,
1659            max_priority_fee_per_gas: 0,
1660            to: to.into(),
1661            value: U256::from(value),
1662            ..Default::default()
1663        };
1664        pooled_eip1559_tx(tx, sender)
1665    }
1666
1667    fn pooled_eip1559_tx(tx: alloy_consensus::TxEip1559, sender: Address) -> EthPooledTransaction {
1668        let signed = reth_ethereum_primitives::TransactionSigned::new_unhashed(
1669            tx.into(),
1670            alloy_primitives::Signature::test_signature(),
1671        );
1672        EthPooledTransaction::new(
1673            alloy_consensus::transaction::Recovered::new_unchecked(signed, sender),
1674            200,
1675        )
1676    }
1677
1678    fn amsterdam_fork_tracker() -> ForkTracker {
1679        ForkTracker {
1680            shanghai: true.into(),
1681            cancun: true.into(),
1682            prague: true.into(),
1683            osaka: true.into(),
1684            amsterdam: true.into(),
1685            tip_timestamp: 0.into(),
1686            max_blob_count: 0.into(),
1687            max_initcode_size: AtomicUsize::new(MAX_INITCODE_SIZE),
1688            tx_gas_limit_cap: AtomicU64::new(0),
1689            tx_regular_gas_cap: AtomicU64::new(0),
1690        }
1691    }
1692
1693    /// EIP-2780 replaces the flat 21k intrinsic base with a decomposed one: 12k base, plus a cold
1694    /// account access for `tx.to` and a transfer charge when `tx.value` is non-zero, with a
1695    /// carve-out for self-transfers.
1696    #[test]
1697    fn intrinsic_gas_eip2780() {
1698        let sender = Address::repeat_byte(1);
1699        let recipient = Address::repeat_byte(2);
1700
1701        let amsterdam = amsterdam_fork_tracker;
1702        let pre_amsterdam = || ForkTracker { amsterdam: false.into(), ..amsterdam() };
1703
1704        // Self-transfer: base cost only (12k), where pre-Amsterdam it pays the flat 21k.
1705        let self_transfer = eip1559_tx(sender, sender, 1, 15_000);
1706        assert!(ensure_intrinsic_gas(&self_transfer, &amsterdam()).is_ok());
1707        assert!(ensure_intrinsic_gas(&self_transfer, &pre_amsterdam()).is_err());
1708
1709        // Zero-value call to another account: base + cold account access (15k).
1710        let zero_value = eip1559_tx(recipient, sender, 0, 15_000);
1711        assert!(ensure_intrinsic_gas(&zero_value, &amsterdam()).is_ok());
1712        assert!(
1713            ensure_intrinsic_gas(&eip1559_tx(recipient, sender, 0, 14_999), &amsterdam()).is_err()
1714        );
1715
1716        // Value transfer to another account: base + cold access + transfer log + value cost (21k).
1717        assert!(
1718            ensure_intrinsic_gas(&eip1559_tx(recipient, sender, 1, 15_000), &amsterdam()).is_err()
1719        );
1720        assert!(
1721            ensure_intrinsic_gas(&eip1559_tx(recipient, sender, 1, 21_000), &amsterdam()).is_ok()
1722        );
1723    }
1724
1725    /// EIP-8037: `tx.gas` may exceed the cap, but intrinsic regular gas and the calldata floor may
1726    /// not.
1727    #[test]
1728    fn intrinsic_regular_gas_cap_eip8037() {
1729        let cap = revm::primitives::eip7825::TX_GAS_LIMIT_CAP;
1730        let fork_tracker =
1731            ForkTracker { tx_regular_gas_cap: AtomicU64::new(cap), ..amsterdam_fork_tracker() };
1732        let is_too_high = |res| {
1733            matches!(
1734                res,
1735                Err(InvalidPoolTransactionError::Consensus(InvalidTransactionError::GasTooHigh))
1736            )
1737        };
1738        let tx = |tx| {
1739            pooled_eip1559_tx(
1740                alloy_consensus::TxEip1559 {
1741                    chain_id: 1,
1742                    gas_limit: 2 * cap,
1743                    max_fee_per_gas: 1,
1744                    to: Address::repeat_byte(2).into(),
1745                    ..tx
1746                },
1747                Address::repeat_byte(1),
1748            )
1749        };
1750
1751        // Zero-value call with `addresses` access list entries: 15,000 + 4,180 intrinsic regular
1752        // gas per address.
1753        let access_list_tx = |addresses: u64| {
1754            tx(alloy_consensus::TxEip1559 {
1755                access_list: AccessList(
1756                    (0..addresses)
1757                        .map(|i| AccessListItem {
1758                            address: Address::left_padding_from(&i.to_be_bytes()),
1759                            storage_keys: vec![],
1760                        })
1761                        .collect(),
1762                ),
1763                ..Default::default()
1764            })
1765        };
1766        // 16,776,800 intrinsic regular gas fits the cap.
1767        assert!(ensure_intrinsic_gas(&access_list_tx(4_010), &fork_tracker).is_ok());
1768        // 16,780,980 exceeds it.
1769        assert!(is_too_high(ensure_intrinsic_gas(&access_list_tx(4_011), &fork_tracker)));
1770
1771        // Zero-value call with `len` non-zero calldata bytes: the calldata floor (15,000 + 64 per
1772        // byte) exceeds the cap while intrinsic regular gas (15,000 + 16 per byte) stays below it.
1773        let calldata_tx = |len: usize| {
1774            tx(alloy_consensus::TxEip1559 { input: vec![1; len].into(), ..Default::default() })
1775        };
1776        // 16,777,176 floor gas fits the cap.
1777        assert!(ensure_intrinsic_gas(&calldata_tx(261_909), &fork_tracker).is_ok());
1778        // 16,777,240 exceeds it.
1779        assert!(is_too_high(ensure_intrinsic_gas(&calldata_tx(261_910), &fork_tracker)));
1780
1781        // Without the regular gas cap (pre EIP-8037), only the gas limit is checked.
1782        let no_cap = amsterdam_fork_tracker();
1783        assert!(ensure_intrinsic_gas(&access_list_tx(4_011), &no_cap).is_ok());
1784        assert!(ensure_intrinsic_gas(&calldata_tx(261_910), &no_cap).is_ok());
1785    }
1786
1787    // <https://github.com/paradigmxyz/reth/issues/5178>
1788    #[tokio::test]
1789    async fn validate_transaction() {
1790        let transaction = get_transaction();
1791        let mut fork_tracker = ForkTracker {
1792            shanghai: false.into(),
1793            cancun: false.into(),
1794            prague: false.into(),
1795            osaka: false.into(),
1796            amsterdam: false.into(),
1797            tip_timestamp: 0.into(),
1798            max_blob_count: 0.into(),
1799            max_initcode_size: AtomicUsize::new(MAX_INITCODE_SIZE),
1800            tx_gas_limit_cap: AtomicU64::new(0),
1801            tx_regular_gas_cap: AtomicU64::new(0),
1802        };
1803
1804        let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1805        assert!(res.is_ok());
1806
1807        fork_tracker.shanghai = true.into();
1808        let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1809        assert!(res.is_ok());
1810
1811        let provider = mock_provider();
1812        provider.add_account(
1813            transaction.sender(),
1814            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1815        );
1816        let blob_store = InMemoryBlobStore::default();
1817        let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1818            .build(blob_store.clone());
1819
1820        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1821
1822        assert!(outcome.is_valid());
1823
1824        let pool =
1825            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1826
1827        let res = pool.add_external_transaction(transaction.clone()).await;
1828        assert!(res.is_ok());
1829        let tx = pool.get(transaction.hash());
1830        assert!(tx.is_some());
1831    }
1832
1833    #[test]
1834    fn accepts_sender_with_empty_bytecode() {
1835        let transaction = get_transaction();
1836        let provider = mock_provider();
1837        provider.add_account(
1838            transaction.sender(),
1839            ExtendedAccount::new(transaction.nonce(), U256::MAX).with_bytecode(Bytes::new()),
1840        );
1841        let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1842            .build(InMemoryBlobStore::default());
1843
1844        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1845
1846        assert!(outcome.is_valid());
1847    }
1848
1849    #[test]
1850    fn validates_nonce_bound() {
1851        let provider = mock_provider();
1852        let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1853            .build(InMemoryBlobStore::default());
1854        let transaction = |nonce| {
1855            EthPooledTransaction::try_from_consensus(
1856                TransactionBuilder::default()
1857                    .chain_id(validator.chain_id())
1858                    .nonce(nonce)
1859                    .gas_limit(21_000)
1860                    .to(Address::ZERO)
1861                    .into_eip1559()
1862                    .try_into_recovered()
1863                    .unwrap(),
1864            )
1865            .unwrap()
1866        };
1867
1868        assert!(validator
1869            .validate_stateless(TransactionOrigin::External, &transaction(u64::MAX - 1))
1870            .is_ok());
1871        assert!(matches!(
1872            validator.validate_stateless(TransactionOrigin::External, &transaction(u64::MAX)),
1873            Err(InvalidPoolTransactionError::Eip2681)
1874        ));
1875    }
1876
1877    #[test]
1878    fn validates_configured_chain_id() {
1879        let provider = mock_provider();
1880        let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1881            .build(InMemoryBlobStore::default());
1882        let transaction = |chain_id| {
1883            EthPooledTransaction::try_from_consensus(
1884                TransactionBuilder::default()
1885                    .chain_id(chain_id)
1886                    .gas_limit(21_000)
1887                    .to(Address::ZERO)
1888                    .into_eip1559()
1889                    .try_into_recovered()
1890                    .unwrap(),
1891            )
1892            .unwrap()
1893        };
1894
1895        assert!(validator
1896            .validate_stateless(TransactionOrigin::External, &transaction(validator.chain_id()))
1897            .is_ok());
1898        assert!(matches!(
1899            validator.validate_stateless(
1900                TransactionOrigin::External,
1901                &transaction(validator.chain_id() + 1)
1902            ),
1903            Err(InvalidPoolTransactionError::Consensus(InvalidTransactionError::ChainIdMismatch))
1904        ));
1905    }
1906
1907    #[test]
1908    fn only_recovers_authorities_for_matching_chain_id() {
1909        let validator = EthTransactionValidatorBuilder::new(mock_provider(), test_evm_config())
1910            .build(InMemoryBlobStore::default());
1911        let auth = |chain_id, address| {
1912            Authorization { chain_id, address, nonce: 0 }.into_signed(Signature::test_signature())
1913        };
1914        let universal = auth(U256::ZERO, Address::repeat_byte(1));
1915        let local = auth(U256::from(validator.chain_id()), Address::repeat_byte(2));
1916        let foreign = auth(U256::from(validator.chain_id() + 1), Address::repeat_byte(3));
1917        let oversized = auth(U256::from(u64::MAX) + U256::from(1), Address::repeat_byte(4));
1918        let expected =
1919            vec![universal.recover_authority().unwrap(), local.recover_authority().unwrap()];
1920        let tx = alloy_consensus::TxEip7702 {
1921            chain_id: validator.chain_id(),
1922            authorization_list: vec![universal, foreign, local, oversized],
1923            ..Default::default()
1924        };
1925        let signed = reth_ethereum_primitives::TransactionSigned::new_unhashed(
1926            tx.into(),
1927            Signature::test_signature(),
1928        );
1929        let transaction = EthPooledTransaction::new(
1930            alloy_consensus::transaction::Recovered::new_unchecked(signed, Address::ZERO),
1931            200,
1932        );
1933
1934        assert_eq!(validator.recover_authorities(&transaction), Some(expected));
1935    }
1936
1937    // <https://github.com/paradigmxyz/reth/issues/8550>
1938    #[tokio::test]
1939    async fn invalid_on_gas_limit_too_high() {
1940        let transaction = get_transaction();
1941
1942        let provider = MockEthProvider::default().with_genesis_block();
1943        provider.add_account(
1944            transaction.sender(),
1945            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1946        );
1947
1948        let blob_store = InMemoryBlobStore::default();
1949        let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1950            .set_block_gas_limit(1_000_000) // tx gas limit is 1_015_288
1951            .build(blob_store.clone());
1952
1953        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1954
1955        assert!(outcome.is_invalid());
1956
1957        let pool =
1958            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1959
1960        let res = pool.add_external_transaction(transaction.clone()).await;
1961        assert!(res.is_err());
1962        assert!(matches!(
1963            res.unwrap_err().kind,
1964            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1965                1_015_288, 1_000_000
1966            ))
1967        ));
1968        let tx = pool.get(transaction.hash());
1969        assert!(tx.is_none());
1970    }
1971
1972    #[tokio::test]
1973    async fn invalid_on_fee_cap_exceeded() {
1974        let transaction = get_transaction();
1975        let provider = mock_provider();
1976        provider.add_account(
1977            transaction.sender(),
1978            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1979        );
1980
1981        let blob_store = InMemoryBlobStore::default();
1982        let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1983            .set_tx_fee_cap(100) // 100 wei cap
1984            .build(blob_store.clone());
1985
1986        let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1987        assert!(outcome.is_invalid());
1988
1989        if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1990            assert!(matches!(
1991                err,
1992                InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1993                if (max_tx_fee_wei > tx_fee_cap_wei)
1994            ));
1995        }
1996
1997        let pool =
1998            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1999        let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
2000        assert!(res.is_err());
2001        assert!(matches!(
2002            res.unwrap_err().kind,
2003            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
2004        ));
2005        let tx = pool.get(transaction.hash());
2006        assert!(tx.is_none());
2007    }
2008
2009    #[tokio::test]
2010    async fn valid_on_zero_fee_cap() {
2011        let transaction = get_transaction();
2012        let provider = mock_provider();
2013        provider.add_account(
2014            transaction.sender(),
2015            ExtendedAccount::new(transaction.nonce(), U256::MAX),
2016        );
2017
2018        let blob_store = InMemoryBlobStore::default();
2019        let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2020            .set_tx_fee_cap(0) // no cap
2021            .build(blob_store);
2022
2023        let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
2024        assert!(outcome.is_valid());
2025    }
2026
2027    #[tokio::test]
2028    async fn valid_on_normal_fee_cap() {
2029        let transaction = get_transaction();
2030        let provider = mock_provider();
2031        provider.add_account(
2032            transaction.sender(),
2033            ExtendedAccount::new(transaction.nonce(), U256::MAX),
2034        );
2035
2036        let blob_store = InMemoryBlobStore::default();
2037        let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2038            .set_tx_fee_cap(2e18 as u128) // 2 ETH cap
2039            .build(blob_store);
2040
2041        let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
2042        assert!(outcome.is_valid());
2043    }
2044
2045    #[tokio::test]
2046    async fn invalid_on_max_tx_gas_limit_exceeded() {
2047        let transaction = get_transaction();
2048        let provider = mock_provider();
2049        provider.add_account(
2050            transaction.sender(),
2051            ExtendedAccount::new(transaction.nonce(), U256::MAX),
2052        );
2053
2054        let blob_store = InMemoryBlobStore::default();
2055        let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2056            .with_max_tx_gas_limit(Some(500_000)) // Set limit lower than transaction gas limit (1_015_288)
2057            .build(blob_store.clone());
2058
2059        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
2060        assert!(outcome.is_invalid());
2061
2062        let pool =
2063            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
2064
2065        let res = pool.add_external_transaction(transaction.clone()).await;
2066        assert!(res.is_err());
2067        assert!(matches!(
2068            res.unwrap_err().kind,
2069            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
2070                1_015_288, 500_000
2071            ))
2072        ));
2073        let tx = pool.get(transaction.hash());
2074        assert!(tx.is_none());
2075    }
2076
2077    #[tokio::test]
2078    async fn valid_on_max_tx_gas_limit_disabled() {
2079        let transaction = get_transaction();
2080        let provider = mock_provider();
2081        provider.add_account(
2082            transaction.sender(),
2083            ExtendedAccount::new(transaction.nonce(), U256::MAX),
2084        );
2085
2086        let blob_store = InMemoryBlobStore::default();
2087        let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2088            .with_max_tx_gas_limit(None) // disabled
2089            .build(blob_store);
2090
2091        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2092        assert!(outcome.is_valid());
2093    }
2094
2095    #[tokio::test]
2096    async fn valid_on_max_tx_gas_limit_within_limit() {
2097        let transaction = get_transaction();
2098        let provider = mock_provider();
2099        provider.add_account(
2100            transaction.sender(),
2101            ExtendedAccount::new(transaction.nonce(), U256::MAX),
2102        );
2103
2104        let blob_store = InMemoryBlobStore::default();
2105        let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2106            .with_max_tx_gas_limit(Some(2_000_000)) // Set limit higher than transaction gas limit (1_015_288)
2107            .build(blob_store);
2108
2109        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2110        assert!(outcome.is_valid());
2111    }
2112
2113    // Helper function to set up common test infrastructure for priority fee tests
2114    fn setup_priority_fee_test() -> (EthPooledTransaction, MockEthProvider) {
2115        let transaction = get_transaction();
2116        let provider = mock_provider();
2117        provider.add_account(
2118            transaction.sender(),
2119            ExtendedAccount::new(transaction.nonce(), U256::MAX),
2120        );
2121        (transaction, provider)
2122    }
2123
2124    // Helper function to create a validator with minimum priority fee
2125    fn create_validator_with_minimum_fee(
2126        provider: MockEthProvider,
2127        minimum_priority_fee: Option<u128>,
2128        local_config: Option<LocalTransactionConfig>,
2129    ) -> EthTransactionValidator<MockEthProvider, EthPooledTransaction, EthEvmConfig> {
2130        let blob_store = InMemoryBlobStore::default();
2131        let mut builder = EthTransactionValidatorBuilder::new(provider, test_evm_config())
2132            .with_minimum_priority_fee(minimum_priority_fee);
2133
2134        if let Some(config) = local_config {
2135            builder = builder.with_local_transactions_config(config);
2136        }
2137
2138        builder.build(blob_store)
2139    }
2140
2141    #[tokio::test]
2142    async fn invalid_on_priority_fee_lower_than_configured_minimum() {
2143        let (transaction, provider) = setup_priority_fee_test();
2144
2145        // Verify the test transaction is a dynamic fee transaction
2146        assert!(transaction.is_dynamic_fee());
2147
2148        // Set minimum priority fee to be double the transaction's priority fee
2149        let minimum_priority_fee =
2150            transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
2151
2152        let validator =
2153            create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2154
2155        for origin in
2156            [TransactionOrigin::External, TransactionOrigin::Local, TransactionOrigin::Private]
2157        {
2158            let outcome = validator.validate_one(origin, transaction.clone());
2159            assert!(matches!(
2160                outcome,
2161                TransactionValidationOutcome::Invalid(
2162                    _,
2163                    InvalidPoolTransactionError::PriorityFeeBelowMinimum {
2164                        minimum_priority_fee: min_fee
2165                    }
2166                ) if min_fee == minimum_priority_fee
2167            ));
2168        }
2169
2170        // Local submission is the path used by `eth_sendRawTransaction`.
2171        let blob_store = InMemoryBlobStore::default();
2172        let pool =
2173            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
2174
2175        let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
2176        assert!(res.is_err());
2177        assert!(matches!(
2178            res.unwrap_err().kind,
2179            PoolErrorKind::InvalidTransaction(
2180                InvalidPoolTransactionError::PriorityFeeBelowMinimum { .. }
2181            )
2182        ));
2183        let tx = pool.get(transaction.hash());
2184        assert!(tx.is_none());
2185    }
2186
2187    #[tokio::test]
2188    async fn valid_on_priority_fee_equal_to_minimum() {
2189        let (transaction, provider) = setup_priority_fee_test();
2190
2191        // Set minimum priority fee equal to transaction's priority fee
2192        let tx_priority_fee =
2193            transaction.max_priority_fee_per_gas().expect("priority fee is expected");
2194        let validator = create_validator_with_minimum_fee(provider, Some(tx_priority_fee), None);
2195
2196        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2197        assert!(outcome.is_valid());
2198    }
2199
2200    #[tokio::test]
2201    async fn valid_on_priority_fee_above_minimum() {
2202        let (transaction, provider) = setup_priority_fee_test();
2203
2204        // Set minimum priority fee below transaction's priority fee
2205        let tx_priority_fee =
2206            transaction.max_priority_fee_per_gas().expect("priority fee is expected");
2207        let minimum_priority_fee = tx_priority_fee / 2; // Half of transaction's priority fee
2208
2209        let validator =
2210            create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2211
2212        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2213        assert!(outcome.is_valid());
2214    }
2215
2216    #[tokio::test]
2217    async fn valid_on_minimum_priority_fee_disabled() {
2218        let (transaction, provider) = setup_priority_fee_test();
2219
2220        // No minimum priority fee set (default is None)
2221        let validator = create_validator_with_minimum_fee(provider, None, None);
2222
2223        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2224        assert!(outcome.is_valid());
2225    }
2226
2227    #[tokio::test]
2228    async fn priority_fee_validation_applies_to_private_transactions() {
2229        let (transaction, provider) = setup_priority_fee_test();
2230
2231        // Set minimum priority fee to be double the transaction's priority fee
2232        let minimum_priority_fee =
2233            transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
2234
2235        let validator =
2236            create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2237
2238        // Private transactions are also subject to minimum priority fee validation
2239        // because they are not considered "local" by default unless specifically configured
2240        let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
2241        assert!(outcome.is_invalid());
2242
2243        if let TransactionValidationOutcome::Invalid(_, err) = outcome {
2244            assert!(matches!(
2245                err,
2246                InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
2247                if min_fee == minimum_priority_fee
2248            ));
2249        }
2250    }
2251
2252    #[tokio::test]
2253    async fn valid_on_local_config_exempts_private_transactions() {
2254        let (transaction, provider) = setup_priority_fee_test();
2255
2256        // Set minimum priority fee to be double the transaction's priority fee
2257        let minimum_priority_fee =
2258            transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
2259
2260        // Configure local transactions to include all private transactions
2261        let local_config =
2262            LocalTransactionConfig { propagate_local_transactions: true, ..Default::default() };
2263
2264        let validator = create_validator_with_minimum_fee(
2265            provider,
2266            Some(minimum_priority_fee),
2267            Some(local_config),
2268        );
2269
2270        // With appropriate local config, the behavior depends on the local transaction logic
2271        // This test documents the current behavior - private transactions are still validated
2272        // unless the sender is specifically whitelisted in local_transactions_config
2273        let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
2274        assert!(outcome.is_invalid()); // Still invalid because sender not in whitelist
2275    }
2276
2277    /// Returns a legacy and an EIP-2930 transaction with the given gas price, and a provider
2278    /// holding a funded sender account for both.
2279    fn setup_gas_price_txs(gas_price: u128) -> ([EthPooledTransaction; 2], MockEthProvider) {
2280        let sender = Address::repeat_byte(1);
2281        let to = Address::repeat_byte(2);
2282
2283        let legacy = alloy_consensus::TxLegacy {
2284            chain_id: Some(1),
2285            gas_price,
2286            gas_limit: 21_000,
2287            to: to.into(),
2288            ..Default::default()
2289        };
2290        let eip2930 = alloy_consensus::TxEip2930 {
2291            chain_id: 1,
2292            gas_price,
2293            gas_limit: 21_000,
2294            to: to.into(),
2295            ..Default::default()
2296        };
2297
2298        let txs = [legacy.into(), eip2930.into()].map(|tx: alloy_consensus::TypedTransaction| {
2299            let signed = reth_ethereum_primitives::TransactionSigned::new_unhashed(
2300                tx.into(),
2301                alloy_primitives::Signature::test_signature(),
2302            );
2303            EthPooledTransaction::new(
2304                alloy_consensus::transaction::Recovered::new_unchecked(signed, sender),
2305                200,
2306            )
2307        });
2308
2309        let provider = mock_provider();
2310        provider.add_account(sender, ExtendedAccount::new(0, U256::MAX));
2311
2312        (txs, provider)
2313    }
2314
2315    #[tokio::test]
2316    async fn invalid_on_gas_price_lower_than_configured_minimum() {
2317        let gas_price = 1_000_000_000;
2318        let minimum_priority_fee = gas_price + 1;
2319        let (txs, provider) = setup_gas_price_txs(gas_price);
2320        let validator =
2321            create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2322
2323        // Legacy and EIP-2930 transactions use their gas price as the priority fee.
2324        for transaction in txs {
2325            assert!(!transaction.is_dynamic_fee());
2326
2327            for origin in
2328                [TransactionOrigin::External, TransactionOrigin::Local, TransactionOrigin::Private]
2329            {
2330                let outcome = validator.validate_one(origin, transaction.clone());
2331                assert!(matches!(
2332                    outcome,
2333                    TransactionValidationOutcome::Invalid(
2334                        _,
2335                        InvalidPoolTransactionError::PriorityFeeBelowMinimum {
2336                            minimum_priority_fee: min_fee
2337                        }
2338                    ) if min_fee == minimum_priority_fee
2339                ));
2340            }
2341        }
2342    }
2343
2344    #[tokio::test]
2345    async fn valid_on_gas_price_at_or_above_minimum() {
2346        let gas_price = 1_000_000_000;
2347        let (txs, provider) = setup_gas_price_txs(gas_price);
2348
2349        for minimum_priority_fee in [gas_price, gas_price / 2] {
2350            let validator = create_validator_with_minimum_fee(
2351                provider.clone(),
2352                Some(minimum_priority_fee),
2353                None,
2354            );
2355
2356            for transaction in txs.clone() {
2357                let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2358                assert!(outcome.is_valid());
2359            }
2360        }
2361    }
2362
2363    #[tokio::test]
2364    async fn valid_on_gas_price_with_minimum_priority_fee_disabled() {
2365        let (txs, provider) = setup_gas_price_txs(1);
2366        let validator = create_validator_with_minimum_fee(provider, None, None);
2367
2368        for transaction in txs {
2369            let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2370            assert!(outcome.is_valid());
2371        }
2372    }
2373
2374    #[test]
2375    fn reject_oversized_tx() {
2376        let mut transaction = get_transaction();
2377        transaction.encoded_length = DEFAULT_MAX_TX_INPUT_BYTES + 1;
2378        let provider = MockEthProvider::default().with_genesis_block();
2379
2380        // No minimum priority fee set (default is None)
2381        let validator = create_validator_with_minimum_fee(provider, None, None);
2382
2383        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2384        let invalid = outcome.as_invalid().unwrap();
2385        assert!(invalid.is_oversized());
2386    }
2387
2388    #[test]
2389    fn reject_blob_tx_with_oversized_access_list() {
2390        let max_tx_input_bytes = 512;
2391        let provider = MockEthProvider::default().with_genesis_block();
2392        let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
2393            .with_max_tx_input_bytes(max_tx_input_bytes)
2394            .build(InMemoryBlobStore::default());
2395
2396        let blob_tx_with_access_list = |storage_keys: usize| {
2397            let access_list = AccessList(vec![AccessListItem {
2398                address: Address::random(),
2399                storage_keys: (0..storage_keys).map(|_| B256::random()).collect(),
2400            }]);
2401            let tx = TransactionBuilder::default()
2402                .access_list(access_list)
2403                .into_eip4844()
2404                .try_into_recovered()
2405                .unwrap();
2406            let encoded_length = tx.encode_2718_len();
2407            EthPooledTransaction::new(tx, encoded_length)
2408        };
2409
2410        let is_oversized = |tx: &EthPooledTransaction| {
2411            matches!(
2412                validator.validate_stateless(TransactionOrigin::External, tx),
2413                Err(InvalidPoolTransactionError::OversizedData { .. })
2414            )
2415        };
2416
2417        let small = blob_tx_with_access_list(1);
2418        assert!(!is_oversized(&small));
2419
2420        let large = blob_tx_with_access_list(64);
2421        assert!(large.input().is_empty());
2422        assert!(is_oversized(&large));
2423    }
2424
2425    #[tokio::test]
2426    async fn valid_with_disabled_balance_check() {
2427        let transaction = get_transaction();
2428        let provider = mock_provider();
2429
2430        // Set account with 0 balance
2431        provider.add_account(
2432            transaction.sender(),
2433            ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
2434        );
2435
2436        // Validate with balance check enabled
2437        let validator =
2438            EthTransactionValidatorBuilder::new(provider.clone(), EthEvmConfig::mainnet())
2439                .build(InMemoryBlobStore::default());
2440
2441        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
2442        let expected_cost = *transaction.cost();
2443        if let TransactionValidationOutcome::Invalid(_, err) = outcome {
2444            assert!(matches!(
2445                err,
2446                InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err))
2447                if funds_err.got.is_zero() && funds_err.expected == expected_cost
2448            ));
2449        } else {
2450            panic!("Expected Invalid outcome with InsufficientFunds error");
2451        }
2452
2453        // Validate with balance check disabled
2454        let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2455            .disable_balance_check()
2456            .build(InMemoryBlobStore::default());
2457
2458        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2459        assert!(outcome.is_valid()); // Should be valid because balance check is disabled
2460    }
2461
2462    #[test]
2463    fn block_gas_limit_from_latest_header() {
2464        let gas_limit = 60_000_000;
2465        let validator = EthTransactionValidatorBuilder::new(
2466            mock_provider_with_gas_limit(gas_limit),
2467            test_evm_config(),
2468        )
2469        .build(InMemoryBlobStore::default());
2470        let transaction = |gas_limit| {
2471            EthPooledTransaction::try_from_consensus(
2472                TransactionBuilder::default()
2473                    .chain_id(validator.chain_id())
2474                    .gas_limit(gas_limit)
2475                    .to(Address::ZERO)
2476                    .into_eip1559()
2477                    .try_into_recovered()
2478                    .unwrap(),
2479            )
2480            .unwrap()
2481        };
2482
2483        assert_eq!(validator.block_gas_limit(), gas_limit);
2484        assert!(validator
2485            .validate_stateless(TransactionOrigin::External, &transaction(gas_limit))
2486            .is_ok());
2487        assert!(matches!(
2488            validator.validate_stateless(TransactionOrigin::External, &transaction(gas_limit + 1)),
2489            Err(InvalidPoolTransactionError::ExceedsGasLimit(60_000_001, 60_000_000))
2490        ));
2491    }
2492}