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