reth_transaction_pool/validate/
eth.rs

1//! Ethereum transaction validator.
2
3use super::constants::DEFAULT_MAX_TX_INPUT_BYTES;
4use crate::{
5    blobstore::BlobStore,
6    error::{
7        Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
8    },
9    metrics::TxPoolValidationMetrics,
10    traits::TransactionOrigin,
11    validate::{ValidTransaction, ValidationTask, MAX_INIT_CODE_BYTE_SIZE},
12    Address, BlobTransactionSidecarVariant, EthBlobTransactionSidecar, EthPoolTransaction,
13    LocalTransactionConfig, TransactionValidationOutcome, TransactionValidationTaskExecutor,
14    TransactionValidator,
15};
16
17use alloy_consensus::{
18    constants::{
19        EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
20        LEGACY_TX_TYPE_ID,
21    },
22    BlockHeader,
23};
24use alloy_eips::{
25    eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M, eip4844::env_settings::EnvKzgSettings,
26    eip7840::BlobParams,
27};
28use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
29use reth_primitives_traits::{
30    constants::MAX_TX_GAS_LIMIT_OSAKA, transaction::error::InvalidTransactionError, Account, Block,
31    GotExpected, SealedBlock,
32};
33use reth_storage_api::{AccountInfoReader, BytecodeReader, StateProviderFactory};
34use reth_tasks::TaskSpawner;
35use revm_primitives::U256;
36use std::{
37    marker::PhantomData,
38    sync::{
39        atomic::{AtomicBool, AtomicU64},
40        Arc,
41    },
42    time::{Instant, SystemTime},
43};
44use tokio::sync::Mutex;
45
46/// A [`TransactionValidator`] implementation that validates ethereum transaction.
47///
48/// It supports all known ethereum transaction types:
49/// - Legacy
50/// - EIP-2718
51/// - EIP-1559
52/// - EIP-4844
53/// - EIP-7702
54///
55/// And enforces additional constraints such as:
56/// - Maximum transaction size
57/// - Maximum gas limit
58///
59/// And adheres to the configured [`LocalTransactionConfig`].
60#[derive(Debug)]
61pub struct EthTransactionValidator<Client, T> {
62    /// This type fetches account info from the db
63    client: Client,
64    /// Blobstore used for fetching re-injected blob transactions.
65    blob_store: Box<dyn BlobStore>,
66    /// tracks activated forks relevant for transaction validation
67    fork_tracker: ForkTracker,
68    /// Fork indicator whether we are using EIP-2718 type transactions.
69    eip2718: bool,
70    /// Fork indicator whether we are using EIP-1559 type transactions.
71    eip1559: bool,
72    /// Fork indicator whether we are using EIP-4844 blob transactions.
73    eip4844: bool,
74    /// Fork indicator whether we are using EIP-7702 type transactions.
75    eip7702: bool,
76    /// The current max gas limit
77    block_gas_limit: AtomicU64,
78    /// The current tx fee cap limit in wei locally submitted into the pool.
79    tx_fee_cap: Option<u128>,
80    /// Minimum priority fee to enforce for acceptance into the pool.
81    minimum_priority_fee: Option<u128>,
82    /// Stores the setup and parameters needed for validating KZG proofs.
83    kzg_settings: EnvKzgSettings,
84    /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.
85    local_transactions_config: LocalTransactionConfig,
86    /// Maximum size in bytes a single transaction can have in order to be accepted into the pool.
87    max_tx_input_bytes: usize,
88    /// Maximum gas limit for individual transactions
89    max_tx_gas_limit: Option<u64>,
90    /// Disable balance checks during transaction validation
91    disable_balance_check: bool,
92    /// Marker for the transaction type
93    _marker: PhantomData<T>,
94    /// Metrics for tsx pool validation
95    validation_metrics: TxPoolValidationMetrics,
96    /// Bitmap of custom transaction types that are allowed.
97    other_tx_types: U256,
98}
99
100impl<Client, Tx> EthTransactionValidator<Client, Tx> {
101    /// Returns the configured chain spec
102    pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
103    where
104        Client: ChainSpecProvider,
105    {
106        self.client().chain_spec()
107    }
108
109    /// Returns the configured chain id
110    pub fn chain_id(&self) -> u64
111    where
112        Client: ChainSpecProvider,
113    {
114        self.client().chain_spec().chain().id()
115    }
116
117    /// Returns the configured client
118    pub const fn client(&self) -> &Client {
119        &self.client
120    }
121
122    /// Returns the tracks activated forks relevant for transaction validation
123    pub const fn fork_tracker(&self) -> &ForkTracker {
124        &self.fork_tracker
125    }
126
127    /// Returns if there are EIP-2718 type transactions
128    pub const fn eip2718(&self) -> bool {
129        self.eip2718
130    }
131
132    /// Returns if there are EIP-1559 type transactions
133    pub const fn eip1559(&self) -> bool {
134        self.eip1559
135    }
136
137    /// Returns if there are EIP-4844 blob transactions
138    pub const fn eip4844(&self) -> bool {
139        self.eip4844
140    }
141
142    /// Returns if there are EIP-7702 type transactions
143    pub const fn eip7702(&self) -> bool {
144        self.eip7702
145    }
146
147    /// Returns the current tx fee cap limit in wei locally submitted into the pool
148    pub const fn tx_fee_cap(&self) -> &Option<u128> {
149        &self.tx_fee_cap
150    }
151
152    /// Returns the minimum priority fee to enforce for acceptance into the pool
153    pub const fn minimum_priority_fee(&self) -> &Option<u128> {
154        &self.minimum_priority_fee
155    }
156
157    /// Returns the setup and parameters needed for validating KZG proofs.
158    pub const fn kzg_settings(&self) -> &EnvKzgSettings {
159        &self.kzg_settings
160    }
161
162    /// Returns the config to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions..
163    pub const fn local_transactions_config(&self) -> &LocalTransactionConfig {
164        &self.local_transactions_config
165    }
166
167    /// Returns the maximum size in bytes a single transaction can have in order to be accepted into
168    /// the pool.
169    pub const fn max_tx_input_bytes(&self) -> usize {
170        self.max_tx_input_bytes
171    }
172
173    /// Returns whether balance checks are disabled for this validator.
174    pub const fn disable_balance_check(&self) -> bool {
175        self.disable_balance_check
176    }
177}
178
179impl<Client, Tx> EthTransactionValidator<Client, Tx>
180where
181    Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
182    Tx: EthPoolTransaction,
183{
184    /// Returns the current max gas limit
185    pub fn block_gas_limit(&self) -> u64 {
186        self.max_gas_limit()
187    }
188
189    /// Validates a single transaction.
190    ///
191    /// See also [`TransactionValidator::validate_transaction`]
192    pub fn validate_one(
193        &self,
194        origin: TransactionOrigin,
195        transaction: Tx,
196    ) -> TransactionValidationOutcome<Tx> {
197        self.validate_one_with_provider(origin, transaction, &mut None)
198    }
199
200    /// Validates a single transaction with the provided state provider.
201    ///
202    /// This allows reusing the same provider across multiple transaction validations,
203    /// which can improve performance when validating many transactions.
204    ///
205    /// If `state` is `None`, a new state provider will be created.
206    pub fn validate_one_with_state(
207        &self,
208        origin: TransactionOrigin,
209        transaction: Tx,
210        state: &mut Option<Box<dyn AccountInfoReader>>,
211    ) -> TransactionValidationOutcome<Tx> {
212        self.validate_one_with_provider(origin, transaction, state)
213    }
214
215    /// Validates a single transaction using an optional cached state provider.
216    /// If no provider is passed, a new one will be created. This allows reusing
217    /// the same provider across multiple txs.
218    fn validate_one_with_provider(
219        &self,
220        origin: TransactionOrigin,
221        transaction: Tx,
222        maybe_state: &mut Option<Box<dyn AccountInfoReader>>,
223    ) -> TransactionValidationOutcome<Tx> {
224        match self.validate_one_no_state(origin, transaction) {
225            Ok(transaction) => {
226                // stateless checks passed, pass transaction down stateful validation pipeline
227                // If we don't have a state provider yet, fetch the latest state
228                if maybe_state.is_none() {
229                    match self.client.latest() {
230                        Ok(new_state) => {
231                            *maybe_state = Some(Box::new(new_state));
232                        }
233                        Err(err) => {
234                            return TransactionValidationOutcome::Error(
235                                *transaction.hash(),
236                                Box::new(err),
237                            )
238                        }
239                    }
240                }
241
242                let state = maybe_state.as_deref().expect("provider is set");
243
244                self.validate_one_against_state(origin, transaction, state)
245            }
246            Err(invalid_outcome) => invalid_outcome,
247        }
248    }
249
250    /// Validates a single transaction with the provided state provider.
251    pub fn validate_one_with_state_provider(
252        &self,
253        origin: TransactionOrigin,
254        transaction: Tx,
255        state: impl AccountInfoReader,
256    ) -> TransactionValidationOutcome<Tx> {
257        let tx = match self.validate_one_no_state(origin, transaction) {
258            Ok(tx) => tx,
259            Err(invalid_outcome) => return invalid_outcome,
260        };
261        self.validate_one_against_state(origin, tx, state)
262    }
263
264    /// Performs stateless validation on single transaction. Returns unaltered input transaction
265    /// if all checks pass, so transaction can continue through to stateful validation as argument
266    /// to [`validate_one_against_state`](Self::validate_one_against_state).
267    fn validate_one_no_state(
268        &self,
269        origin: TransactionOrigin,
270        transaction: Tx,
271    ) -> Result<Tx, TransactionValidationOutcome<Tx>> {
272        // Checks for tx_type
273        match transaction.ty() {
274            LEGACY_TX_TYPE_ID => {
275                // Accept legacy transactions
276            }
277            EIP2930_TX_TYPE_ID => {
278                // Accept only legacy transactions until EIP-2718/2930 activates
279                if !self.eip2718 {
280                    return Err(TransactionValidationOutcome::Invalid(
281                        transaction,
282                        InvalidTransactionError::Eip2930Disabled.into(),
283                    ))
284                }
285            }
286            EIP1559_TX_TYPE_ID => {
287                // Reject dynamic fee transactions until EIP-1559 activates.
288                if !self.eip1559 {
289                    return Err(TransactionValidationOutcome::Invalid(
290                        transaction,
291                        InvalidTransactionError::Eip1559Disabled.into(),
292                    ))
293                }
294            }
295            EIP4844_TX_TYPE_ID => {
296                // Reject blob transactions.
297                if !self.eip4844 {
298                    return Err(TransactionValidationOutcome::Invalid(
299                        transaction,
300                        InvalidTransactionError::Eip4844Disabled.into(),
301                    ))
302                }
303            }
304            EIP7702_TX_TYPE_ID => {
305                // Reject EIP-7702 transactions.
306                if !self.eip7702 {
307                    return Err(TransactionValidationOutcome::Invalid(
308                        transaction,
309                        InvalidTransactionError::Eip7702Disabled.into(),
310                    ))
311                }
312            }
313
314            ty if !self.other_tx_types.bit(ty as usize) => {
315                return Err(TransactionValidationOutcome::Invalid(
316                    transaction,
317                    InvalidTransactionError::TxTypeNotSupported.into(),
318                ))
319            }
320
321            _ => {}
322        };
323
324        // Reject transactions with a nonce equal to U64::max according to EIP-2681
325        let tx_nonce = transaction.nonce();
326        if tx_nonce == u64::MAX {
327            return Err(TransactionValidationOutcome::Invalid(
328                transaction,
329                InvalidPoolTransactionError::Eip2681,
330            ))
331        }
332
333        // Reject transactions over defined size to prevent DOS attacks
334        if transaction.is_eip4844() {
335            // Since blob transactions are pulled instead of pushed, and only the consensus data is
336            // kept in memory while the sidecar is cached on disk, there is no critical limit that
337            // should be enforced. Still, enforcing some cap on the input bytes. blob txs also must
338            // be executable right away when they enter the pool.
339            let tx_input_len = transaction.input().len();
340            if tx_input_len > self.max_tx_input_bytes {
341                return Err(TransactionValidationOutcome::Invalid(
342                    transaction,
343                    InvalidPoolTransactionError::OversizedData {
344                        size: tx_input_len,
345                        limit: self.max_tx_input_bytes,
346                    },
347                ))
348            }
349        } else {
350            // ensure the size of the non-blob transaction
351            let tx_size = transaction.encoded_length();
352            if tx_size > self.max_tx_input_bytes {
353                return Err(TransactionValidationOutcome::Invalid(
354                    transaction,
355                    InvalidPoolTransactionError::OversizedData {
356                        size: tx_size,
357                        limit: self.max_tx_input_bytes,
358                    },
359                ))
360            }
361        }
362
363        // Check whether the init code size has been exceeded.
364        if self.fork_tracker.is_shanghai_activated() &&
365            let Err(err) = transaction.ensure_max_init_code_size(MAX_INIT_CODE_BYTE_SIZE)
366        {
367            return Err(TransactionValidationOutcome::Invalid(transaction, err))
368        }
369
370        // Checks for gas limit
371        let transaction_gas_limit = transaction.gas_limit();
372        let block_gas_limit = self.max_gas_limit();
373        if transaction_gas_limit > block_gas_limit {
374            return Err(TransactionValidationOutcome::Invalid(
375                transaction,
376                InvalidPoolTransactionError::ExceedsGasLimit(
377                    transaction_gas_limit,
378                    block_gas_limit,
379                ),
380            ))
381        }
382
383        // Check individual transaction gas limit if configured
384        if let Some(max_tx_gas_limit) = self.max_tx_gas_limit &&
385            transaction_gas_limit > max_tx_gas_limit
386        {
387            return Err(TransactionValidationOutcome::Invalid(
388                transaction,
389                InvalidPoolTransactionError::MaxTxGasLimitExceeded(
390                    transaction_gas_limit,
391                    max_tx_gas_limit,
392                ),
393            ))
394        }
395
396        // Ensure max_priority_fee_per_gas (if EIP1559) is less than max_fee_per_gas if any.
397        if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
398            return Err(TransactionValidationOutcome::Invalid(
399                transaction,
400                InvalidTransactionError::TipAboveFeeCap.into(),
401            ))
402        }
403
404        // determine whether the transaction should be treated as local
405        let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
406
407        // Ensure max possible transaction fee doesn't exceed configured transaction fee cap.
408        // Only for transactions locally submitted for acceptance into the pool.
409        if is_local {
410            match self.tx_fee_cap {
411                Some(0) | None => {} // Skip if cap is 0 or None
412                Some(tx_fee_cap_wei) => {
413                    let max_tx_fee_wei = transaction.cost().saturating_sub(transaction.value());
414                    if max_tx_fee_wei > tx_fee_cap_wei {
415                        return Err(TransactionValidationOutcome::Invalid(
416                            transaction,
417                            InvalidPoolTransactionError::ExceedsFeeCap {
418                                max_tx_fee_wei: max_tx_fee_wei.saturating_to(),
419                                tx_fee_cap_wei,
420                            },
421                        ))
422                    }
423                }
424            }
425        }
426
427        // Drop non-local transactions with a fee lower than the configured fee for acceptance into
428        // the pool.
429        if !is_local &&
430            transaction.is_dynamic_fee() &&
431            transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
432        {
433            return Err(TransactionValidationOutcome::Invalid(
434                transaction,
435                InvalidPoolTransactionError::PriorityFeeBelowMinimum {
436                    minimum_priority_fee: self
437                        .minimum_priority_fee
438                        .expect("minimum priority fee is expected inside if statement"),
439                },
440            ))
441        }
442
443        // Checks for chainid
444        if let Some(chain_id) = transaction.chain_id() &&
445            chain_id != self.chain_id()
446        {
447            return Err(TransactionValidationOutcome::Invalid(
448                transaction,
449                InvalidTransactionError::ChainIdMismatch.into(),
450            ))
451        }
452
453        if transaction.is_eip7702() {
454            // Prague fork is required for 7702 txs
455            if !self.fork_tracker.is_prague_activated() {
456                return Err(TransactionValidationOutcome::Invalid(
457                    transaction,
458                    InvalidTransactionError::TxTypeNotSupported.into(),
459                ))
460            }
461
462            if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
463                return Err(TransactionValidationOutcome::Invalid(
464                    transaction,
465                    Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into(),
466                ))
467            }
468        }
469
470        if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
471            return Err(TransactionValidationOutcome::Invalid(transaction, err))
472        }
473
474        // light blob tx pre-checks
475        if transaction.is_eip4844() {
476            // Cancun fork is required for blob txs
477            if !self.fork_tracker.is_cancun_activated() {
478                return Err(TransactionValidationOutcome::Invalid(
479                    transaction,
480                    InvalidTransactionError::TxTypeNotSupported.into(),
481                ))
482            }
483
484            let blob_count = transaction.blob_count().unwrap_or(0);
485            if blob_count == 0 {
486                // no blobs
487                return Err(TransactionValidationOutcome::Invalid(
488                    transaction,
489                    InvalidPoolTransactionError::Eip4844(
490                        Eip4844PoolTransactionError::NoEip4844Blobs,
491                    ),
492                ))
493            }
494
495            let max_blob_count = self.fork_tracker.max_blob_count();
496            if blob_count > max_blob_count {
497                return Err(TransactionValidationOutcome::Invalid(
498                    transaction,
499                    InvalidPoolTransactionError::Eip4844(
500                        Eip4844PoolTransactionError::TooManyEip4844Blobs {
501                            have: blob_count,
502                            permitted: max_blob_count,
503                        },
504                    ),
505                ))
506            }
507        }
508
509        // Osaka validation of max tx gas.
510        if self.fork_tracker.is_osaka_activated() &&
511            transaction.gas_limit() > MAX_TX_GAS_LIMIT_OSAKA
512        {
513            return Err(TransactionValidationOutcome::Invalid(
514                transaction,
515                InvalidTransactionError::GasLimitTooHigh.into(),
516            ))
517        }
518
519        Ok(transaction)
520    }
521
522    /// Validates a single transaction using given state provider.
523    fn validate_one_against_state<P>(
524        &self,
525        origin: TransactionOrigin,
526        mut transaction: Tx,
527        state: P,
528    ) -> TransactionValidationOutcome<Tx>
529    where
530        P: AccountInfoReader,
531    {
532        // Use provider to get account info
533        let account = match state.basic_account(transaction.sender_ref()) {
534            Ok(account) => account.unwrap_or_default(),
535            Err(err) => {
536                return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
537            }
538        };
539
540        // check for bytecode
541        match self.validate_sender_bytecode(&transaction, &account, &state) {
542            Err(outcome) => return outcome,
543            Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err),
544            _ => {}
545        };
546
547        // Checks for nonce
548        if transaction.requires_nonce_check() &&
549            let Err(err) = self.validate_sender_nonce(&transaction, &account)
550        {
551            return TransactionValidationOutcome::Invalid(transaction, err)
552        }
553
554        // checks for max cost not exceedng account_balance
555        if let Err(err) = self.validate_sender_balance(&transaction, &account) {
556            return TransactionValidationOutcome::Invalid(transaction, err)
557        }
558
559        // heavy blob tx validation
560        let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) {
561            Err(err) => return TransactionValidationOutcome::Invalid(transaction, err),
562            Ok(sidecar) => sidecar,
563        };
564
565        let authorities = self.recover_authorities(&transaction);
566        // Return the valid transaction
567        TransactionValidationOutcome::Valid {
568            balance: account.balance,
569            state_nonce: account.nonce,
570            bytecode_hash: account.bytecode_hash,
571            transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
572            // by this point assume all external transactions should be propagated
573            propagate: match origin {
574                TransactionOrigin::External => true,
575                TransactionOrigin::Local => {
576                    self.local_transactions_config.propagate_local_transactions
577                }
578                TransactionOrigin::Private => false,
579            },
580            authorities,
581        }
582    }
583
584    /// Validates that the sender’s account has valid or no bytecode.
585    pub fn validate_sender_bytecode(
586        &self,
587        transaction: &Tx,
588        sender: &Account,
589        state: impl BytecodeReader,
590    ) -> Result<Result<(), InvalidPoolTransactionError>, TransactionValidationOutcome<Tx>> {
591        // Unless Prague is active, the signer account shouldn't have bytecode.
592        //
593        // If Prague is active, only EIP-7702 bytecode is allowed for the sender.
594        //
595        // Any other case means that the account is not an EOA, and should not be able to send
596        // transactions.
597        if let Some(code_hash) = &sender.bytecode_hash {
598            let is_eip7702 = if self.fork_tracker.is_prague_activated() {
599                match state.bytecode_by_hash(code_hash) {
600                    Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
601                    Err(err) => {
602                        return Err(TransactionValidationOutcome::Error(
603                            *transaction.hash(),
604                            Box::new(err),
605                        ))
606                    }
607                }
608            } else {
609                false
610            };
611
612            if !is_eip7702 {
613                return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into()))
614            }
615        }
616        Ok(Ok(()))
617    }
618
619    /// Checks if the transaction nonce is valid.
620    pub fn validate_sender_nonce(
621        &self,
622        transaction: &Tx,
623        sender: &Account,
624    ) -> Result<(), InvalidPoolTransactionError> {
625        let tx_nonce = transaction.nonce();
626
627        if tx_nonce < sender.nonce {
628            return Err(InvalidTransactionError::NonceNotConsistent {
629                tx: tx_nonce,
630                state: sender.nonce,
631            }
632            .into())
633        }
634        Ok(())
635    }
636
637    /// Ensures the sender has sufficient account balance.
638    pub fn validate_sender_balance(
639        &self,
640        transaction: &Tx,
641        sender: &Account,
642    ) -> Result<(), InvalidPoolTransactionError> {
643        let cost = transaction.cost();
644
645        if !self.disable_balance_check && cost > &sender.balance {
646            let expected = *cost;
647            return Err(InvalidTransactionError::InsufficientFunds(
648                GotExpected { got: sender.balance, expected }.into(),
649            )
650            .into())
651        }
652        Ok(())
653    }
654
655    /// Validates EIP-4844 blob sidecar data and returns the extracted sidecar, if any.
656    pub fn validate_eip4844(
657        &self,
658        transaction: &mut Tx,
659    ) -> Result<Option<BlobTransactionSidecarVariant>, InvalidPoolTransactionError> {
660        let mut maybe_blob_sidecar = None;
661
662        // heavy blob tx validation
663        if transaction.is_eip4844() {
664            // extract the blob from the transaction
665            match transaction.take_blob() {
666                EthBlobTransactionSidecar::None => {
667                    // this should not happen
668                    return Err(InvalidTransactionError::TxTypeNotSupported.into())
669                }
670                EthBlobTransactionSidecar::Missing => {
671                    // This can happen for re-injected blob transactions (on re-org), since the blob
672                    // is stripped from the transaction and not included in a block.
673                    // check if the blob is in the store, if it's included we previously validated
674                    // it and inserted it
675                    if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) {
676                        // validated transaction is already in the store
677                    } else {
678                        return Err(InvalidPoolTransactionError::Eip4844(
679                            Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
680                        ))
681                    }
682                }
683                EthBlobTransactionSidecar::Present(sidecar) => {
684                    let now = Instant::now();
685
686                    if self.fork_tracker.is_osaka_activated() {
687                        if sidecar.is_eip4844() {
688                            return Err(InvalidPoolTransactionError::Eip4844(
689                                Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
690                            ))
691                        }
692                    } else if sidecar.is_eip7594() && !self.allow_7594_sidecars() {
693                        return Err(InvalidPoolTransactionError::Eip4844(
694                            Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
695                        ))
696                    }
697
698                    // validate the blob
699                    if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
700                        return Err(InvalidPoolTransactionError::Eip4844(
701                            Eip4844PoolTransactionError::InvalidEip4844Blob(err),
702                        ))
703                    }
704                    // Record the duration of successful blob validation as histogram
705                    self.validation_metrics.blob_validation_duration.record(now.elapsed());
706                    // store the extracted blob
707                    maybe_blob_sidecar = Some(sidecar);
708                }
709            }
710        }
711        Ok(maybe_blob_sidecar)
712    }
713
714    /// Returns the recovered authorities for the given transaction
715    fn recover_authorities(&self, transaction: &Tx) -> std::option::Option<Vec<Address>> {
716        transaction
717            .authorization_list()
718            .map(|auths| auths.iter().flat_map(|auth| auth.recover_authority()).collect::<Vec<_>>())
719    }
720
721    /// Validates all given transactions.
722    fn validate_batch(
723        &self,
724        transactions: Vec<(TransactionOrigin, Tx)>,
725    ) -> Vec<TransactionValidationOutcome<Tx>> {
726        let mut provider = None;
727        transactions
728            .into_iter()
729            .map(|(origin, tx)| self.validate_one_with_provider(origin, tx, &mut provider))
730            .collect()
731    }
732
733    /// Validates all given transactions with origin.
734    fn validate_batch_with_origin(
735        &self,
736        origin: TransactionOrigin,
737        transactions: impl IntoIterator<Item = Tx> + Send,
738    ) -> Vec<TransactionValidationOutcome<Tx>> {
739        let mut provider = None;
740        transactions
741            .into_iter()
742            .map(|tx| self.validate_one_with_provider(origin, tx, &mut provider))
743            .collect()
744    }
745
746    fn on_new_head_block<T: BlockHeader>(&self, new_tip_block: &T) {
747        // update all forks
748        if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
749            self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
750        }
751
752        if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
753            self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
754        }
755
756        if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
757            self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
758        }
759
760        if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
761            self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
762        }
763
764        self.fork_tracker
765            .tip_timestamp
766            .store(new_tip_block.timestamp(), std::sync::atomic::Ordering::Relaxed);
767
768        if let Some(blob_params) =
769            self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
770        {
771            self.fork_tracker
772                .max_blob_count
773                .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed);
774        }
775
776        self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
777    }
778
779    fn max_gas_limit(&self) -> u64 {
780        self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
781    }
782
783    /// Returns whether EIP-7594 sidecars are allowed
784    fn allow_7594_sidecars(&self) -> bool {
785        let tip_timestamp = self.fork_tracker.tip_timestamp();
786
787        // If next block is Osaka, allow 7594 sidecars
788        if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(12)) {
789            true
790        } else if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(24)) {
791            let current_timestamp =
792                SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
793
794            // Allow after 4 seconds into last non-Osaka slot
795            current_timestamp >= tip_timestamp.saturating_add(4)
796        } else {
797            false
798        }
799    }
800}
801
802impl<Client, Tx> TransactionValidator for EthTransactionValidator<Client, Tx>
803where
804    Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
805    Tx: EthPoolTransaction,
806{
807    type Transaction = Tx;
808
809    async fn validate_transaction(
810        &self,
811        origin: TransactionOrigin,
812        transaction: Self::Transaction,
813    ) -> TransactionValidationOutcome<Self::Transaction> {
814        self.validate_one(origin, transaction)
815    }
816
817    async fn validate_transactions(
818        &self,
819        transactions: Vec<(TransactionOrigin, Self::Transaction)>,
820    ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
821        self.validate_batch(transactions)
822    }
823
824    async fn validate_transactions_with_origin(
825        &self,
826        origin: TransactionOrigin,
827        transactions: impl IntoIterator<Item = Self::Transaction> + Send,
828    ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
829        self.validate_batch_with_origin(origin, transactions)
830    }
831
832    fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>)
833    where
834        B: Block,
835    {
836        self.on_new_head_block(new_tip_block.header())
837    }
838}
839
840/// A builder for [`EthTransactionValidator`] and [`TransactionValidationTaskExecutor`]
841#[derive(Debug)]
842pub struct EthTransactionValidatorBuilder<Client> {
843    client: Client,
844    /// Fork indicator whether we are in the Shanghai stage.
845    shanghai: bool,
846    /// Fork indicator whether we are in the Cancun hardfork.
847    cancun: bool,
848    /// Fork indicator whether we are in the Prague hardfork.
849    prague: bool,
850    /// Fork indicator whether we are in the Osaka hardfork.
851    osaka: bool,
852    /// Timestamp of the tip block.
853    tip_timestamp: u64,
854    /// Max blob count at the block's timestamp.
855    max_blob_count: u64,
856    /// Whether using EIP-2718 type transactions is allowed
857    eip2718: bool,
858    /// Whether using EIP-1559 type transactions is allowed
859    eip1559: bool,
860    /// Whether using EIP-4844 type transactions is allowed
861    eip4844: bool,
862    /// Whether using EIP-7702 type transactions is allowed
863    eip7702: bool,
864    /// The current max gas limit
865    block_gas_limit: AtomicU64,
866    /// The current tx fee cap limit in wei locally submitted into the pool.
867    tx_fee_cap: Option<u128>,
868    /// Minimum priority fee to enforce for acceptance into the pool.
869    minimum_priority_fee: Option<u128>,
870    /// Determines how many additional tasks to spawn
871    ///
872    /// Default is 1
873    additional_tasks: usize,
874
875    /// Stores the setup and parameters needed for validating KZG proofs.
876    kzg_settings: EnvKzgSettings,
877    /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.
878    local_transactions_config: LocalTransactionConfig,
879    /// Max size in bytes of a single transaction allowed
880    max_tx_input_bytes: usize,
881    /// Maximum gas limit for individual transactions
882    max_tx_gas_limit: Option<u64>,
883    /// Disable balance checks during transaction validation
884    disable_balance_check: bool,
885    /// Bitmap of custom transaction types that are allowed.
886    other_tx_types: U256,
887}
888
889impl<Client> EthTransactionValidatorBuilder<Client> {
890    /// Creates a new builder for the given client
891    ///
892    /// By default this assumes the network is on the `Prague` hardfork and the following
893    /// transactions are allowed:
894    ///  - Legacy
895    ///  - EIP-2718
896    ///  - EIP-1559
897    ///  - EIP-4844
898    ///  - EIP-7702
899    pub fn new(client: Client) -> Self {
900        Self {
901            block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(),
902            client,
903            minimum_priority_fee: None,
904            additional_tasks: 1,
905            kzg_settings: EnvKzgSettings::Default,
906            local_transactions_config: Default::default(),
907            max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
908            tx_fee_cap: Some(1e18 as u128),
909            max_tx_gas_limit: None,
910            // by default all transaction types are allowed
911            eip2718: true,
912            eip1559: true,
913            eip4844: true,
914            eip7702: true,
915
916            // shanghai is activated by default
917            shanghai: true,
918
919            // cancun is activated by default
920            cancun: true,
921
922            // prague is activated by default
923            prague: true,
924
925            // osaka not yet activated
926            osaka: false,
927
928            tip_timestamp: 0,
929
930            // max blob count is prague by default
931            max_blob_count: BlobParams::prague().max_blobs_per_tx,
932
933            // balance checks are enabled by default
934            disable_balance_check: false,
935
936            // no custom transaction types by default
937            other_tx_types: U256::ZERO,
938        }
939    }
940
941    /// Disables the Cancun fork.
942    pub const fn no_cancun(self) -> Self {
943        self.set_cancun(false)
944    }
945
946    /// Whether to allow exemptions for local transaction exemptions.
947    pub fn with_local_transactions_config(
948        mut self,
949        local_transactions_config: LocalTransactionConfig,
950    ) -> Self {
951        self.local_transactions_config = local_transactions_config;
952        self
953    }
954
955    /// Set the Cancun fork.
956    pub const fn set_cancun(mut self, cancun: bool) -> Self {
957        self.cancun = cancun;
958        self
959    }
960
961    /// Disables the Shanghai fork.
962    pub const fn no_shanghai(self) -> Self {
963        self.set_shanghai(false)
964    }
965
966    /// Set the Shanghai fork.
967    pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
968        self.shanghai = shanghai;
969        self
970    }
971
972    /// Disables the Prague fork.
973    pub const fn no_prague(self) -> Self {
974        self.set_prague(false)
975    }
976
977    /// Set the Prague fork.
978    pub const fn set_prague(mut self, prague: bool) -> Self {
979        self.prague = prague;
980        self
981    }
982
983    /// Disables the Osaka fork.
984    pub const fn no_osaka(self) -> Self {
985        self.set_osaka(false)
986    }
987
988    /// Set the Osaka fork.
989    pub const fn set_osaka(mut self, osaka: bool) -> Self {
990        self.osaka = osaka;
991        self
992    }
993
994    /// Disables the support for EIP-2718 transactions.
995    pub const fn no_eip2718(self) -> Self {
996        self.set_eip2718(false)
997    }
998
999    /// Set the support for EIP-2718 transactions.
1000    pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
1001        self.eip2718 = eip2718;
1002        self
1003    }
1004
1005    /// Disables the support for EIP-1559 transactions.
1006    pub const fn no_eip1559(self) -> Self {
1007        self.set_eip1559(false)
1008    }
1009
1010    /// Set the support for EIP-1559 transactions.
1011    pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
1012        self.eip1559 = eip1559;
1013        self
1014    }
1015
1016    /// Disables the support for EIP-4844 transactions.
1017    pub const fn no_eip4844(self) -> Self {
1018        self.set_eip4844(false)
1019    }
1020
1021    /// Set the support for EIP-4844 transactions.
1022    pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
1023        self.eip4844 = eip4844;
1024        self
1025    }
1026
1027    /// Sets the [`EnvKzgSettings`] to use for validating KZG proofs.
1028    pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
1029        self.kzg_settings = kzg_settings;
1030        self
1031    }
1032
1033    /// Sets a minimum priority fee that's enforced for acceptance into the pool.
1034    pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: Option<u128>) -> Self {
1035        self.minimum_priority_fee = minimum_priority_fee;
1036        self
1037    }
1038
1039    /// Sets the number of additional tasks to spawn.
1040    pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
1041        self.additional_tasks = additional_tasks;
1042        self
1043    }
1044
1045    /// Configures validation rules based on the head block's timestamp.
1046    ///
1047    /// For example, whether the Shanghai and Cancun hardfork is activated at launch, or max blob
1048    /// counts.
1049    pub fn with_head_timestamp(mut self, timestamp: u64) -> Self
1050    where
1051        Client: ChainSpecProvider<ChainSpec: EthereumHardforks>,
1052    {
1053        self.shanghai = self.client.chain_spec().is_shanghai_active_at_timestamp(timestamp);
1054        self.cancun = self.client.chain_spec().is_cancun_active_at_timestamp(timestamp);
1055        self.prague = self.client.chain_spec().is_prague_active_at_timestamp(timestamp);
1056        self.osaka = self.client.chain_spec().is_osaka_active_at_timestamp(timestamp);
1057        self.tip_timestamp = timestamp;
1058        self.max_blob_count = self
1059            .client
1060            .chain_spec()
1061            .blob_params_at_timestamp(timestamp)
1062            .unwrap_or_else(BlobParams::cancun)
1063            .max_blobs_per_tx;
1064        self
1065    }
1066
1067    /// Sets a max size in bytes of a single transaction allowed into the pool
1068    pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
1069        self.max_tx_input_bytes = max_tx_input_bytes;
1070        self
1071    }
1072
1073    /// Sets the block gas limit
1074    ///
1075    /// Transactions with a gas limit greater than this will be rejected.
1076    pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
1077        self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
1078        self
1079    }
1080
1081    /// Sets the block gas limit
1082    ///
1083    /// Transactions with a gas limit greater than this will be rejected.
1084    pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
1085        self.tx_fee_cap = Some(tx_fee_cap);
1086        self
1087    }
1088
1089    /// Sets the maximum gas limit for individual transactions
1090    pub const fn with_max_tx_gas_limit(mut self, max_tx_gas_limit: Option<u64>) -> Self {
1091        self.max_tx_gas_limit = max_tx_gas_limit;
1092        self
1093    }
1094
1095    /// Disables balance checks during transaction validation
1096    pub const fn disable_balance_check(mut self) -> Self {
1097        self.disable_balance_check = true;
1098        self
1099    }
1100
1101    /// Adds a custom transaction type to the validator.
1102    pub const fn with_custom_tx_type(mut self, tx_type: u8) -> Self {
1103        self.other_tx_types.set_bit(tx_type as usize, true);
1104        self
1105    }
1106
1107    /// Builds a the [`EthTransactionValidator`] without spawning validator tasks.
1108    pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx>
1109    where
1110        S: BlobStore,
1111    {
1112        let Self {
1113            client,
1114            shanghai,
1115            cancun,
1116            prague,
1117            osaka,
1118            tip_timestamp,
1119            eip2718,
1120            eip1559,
1121            eip4844,
1122            eip7702,
1123            block_gas_limit,
1124            tx_fee_cap,
1125            minimum_priority_fee,
1126            kzg_settings,
1127            local_transactions_config,
1128            max_tx_input_bytes,
1129            max_tx_gas_limit,
1130            disable_balance_check,
1131            max_blob_count,
1132            additional_tasks: _,
1133            other_tx_types,
1134        } = self;
1135
1136        let fork_tracker = ForkTracker {
1137            shanghai: AtomicBool::new(shanghai),
1138            cancun: AtomicBool::new(cancun),
1139            prague: AtomicBool::new(prague),
1140            osaka: AtomicBool::new(osaka),
1141            tip_timestamp: AtomicU64::new(tip_timestamp),
1142            max_blob_count: AtomicU64::new(max_blob_count),
1143        };
1144
1145        EthTransactionValidator {
1146            client,
1147            eip2718,
1148            eip1559,
1149            fork_tracker,
1150            eip4844,
1151            eip7702,
1152            block_gas_limit,
1153            tx_fee_cap,
1154            minimum_priority_fee,
1155            blob_store: Box::new(blob_store),
1156            kzg_settings,
1157            local_transactions_config,
1158            max_tx_input_bytes,
1159            max_tx_gas_limit,
1160            disable_balance_check,
1161            _marker: Default::default(),
1162            validation_metrics: TxPoolValidationMetrics::default(),
1163            other_tx_types,
1164        }
1165    }
1166
1167    /// Builds a [`EthTransactionValidator`] and spawns validation tasks via the
1168    /// [`TransactionValidationTaskExecutor`]
1169    ///
1170    /// The validator will spawn `additional_tasks` additional tasks for validation.
1171    ///
1172    /// By default this will spawn 1 additional task.
1173    pub fn build_with_tasks<Tx, T, S>(
1174        self,
1175        tasks: T,
1176        blob_store: S,
1177    ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx>>
1178    where
1179        T: TaskSpawner,
1180        S: BlobStore,
1181    {
1182        let additional_tasks = self.additional_tasks;
1183        let validator = self.build(blob_store);
1184
1185        let (tx, task) = ValidationTask::new();
1186
1187        // Spawn validation tasks, they are blocking because they perform db lookups
1188        for _ in 0..additional_tasks {
1189            let task = task.clone();
1190            tasks.spawn_blocking(Box::pin(async move {
1191                task.run().await;
1192            }));
1193        }
1194
1195        // we spawn them on critical tasks because validation, especially for EIP-4844 can be quite
1196        // heavy
1197        tasks.spawn_critical_blocking(
1198            "transaction-validation-service",
1199            Box::pin(async move {
1200                task.run().await;
1201            }),
1202        );
1203
1204        let to_validation_task = Arc::new(Mutex::new(tx));
1205
1206        TransactionValidationTaskExecutor { validator: Arc::new(validator), to_validation_task }
1207    }
1208}
1209
1210/// Keeps track of whether certain forks are activated
1211#[derive(Debug)]
1212pub struct ForkTracker {
1213    /// Tracks if shanghai is activated at the block's timestamp.
1214    pub shanghai: AtomicBool,
1215    /// Tracks if cancun is activated at the block's timestamp.
1216    pub cancun: AtomicBool,
1217    /// Tracks if prague is activated at the block's timestamp.
1218    pub prague: AtomicBool,
1219    /// Tracks if osaka is activated at the block's timestamp.
1220    pub osaka: AtomicBool,
1221    /// Tracks max blob count per transaction at the block's timestamp.
1222    pub max_blob_count: AtomicU64,
1223    /// Tracks the timestamp of the tip block.
1224    pub tip_timestamp: AtomicU64,
1225}
1226
1227impl ForkTracker {
1228    /// Returns `true` if Shanghai fork is activated.
1229    pub fn is_shanghai_activated(&self) -> bool {
1230        self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1231    }
1232
1233    /// Returns `true` if Cancun fork is activated.
1234    pub fn is_cancun_activated(&self) -> bool {
1235        self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1236    }
1237
1238    /// Returns `true` if Prague fork is activated.
1239    pub fn is_prague_activated(&self) -> bool {
1240        self.prague.load(std::sync::atomic::Ordering::Relaxed)
1241    }
1242
1243    /// Returns `true` if Osaka fork is activated.
1244    pub fn is_osaka_activated(&self) -> bool {
1245        self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1246    }
1247
1248    /// Returns the timestamp of the tip block.
1249    pub fn tip_timestamp(&self) -> u64 {
1250        self.tip_timestamp.load(std::sync::atomic::Ordering::Relaxed)
1251    }
1252
1253    /// Returns the max allowed blob count per transaction.
1254    pub fn max_blob_count(&self) -> u64 {
1255        self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1256    }
1257}
1258
1259/// Ensures that gas limit of the transaction exceeds the intrinsic gas of the transaction.
1260///
1261/// Caution: This only checks past the Merge hardfork.
1262pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1263    transaction: &T,
1264    fork_tracker: &ForkTracker,
1265) -> Result<(), InvalidPoolTransactionError> {
1266    use revm_primitives::hardfork::SpecId;
1267    let spec_id = if fork_tracker.is_prague_activated() {
1268        SpecId::PRAGUE
1269    } else if fork_tracker.is_shanghai_activated() {
1270        SpecId::SHANGHAI
1271    } else {
1272        SpecId::MERGE
1273    };
1274
1275    let gas = revm_interpreter::gas::calculate_initial_tx_gas(
1276        spec_id,
1277        transaction.input(),
1278        transaction.is_create(),
1279        transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1280        transaction
1281            .access_list()
1282            .map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
1283            .unwrap_or_default() as u64,
1284        transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
1285    );
1286
1287    let gas_limit = transaction.gas_limit();
1288    if gas_limit < gas.initial_gas || gas_limit < gas.floor_gas {
1289        Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1290    } else {
1291        Ok(())
1292    }
1293}
1294
1295#[cfg(test)]
1296mod tests {
1297    use super::*;
1298    use crate::{
1299        blobstore::InMemoryBlobStore, error::PoolErrorKind, traits::PoolTransaction,
1300        CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1301    };
1302    use alloy_consensus::Transaction;
1303    use alloy_eips::eip2718::Decodable2718;
1304    use alloy_primitives::{hex, U256};
1305    use reth_ethereum_primitives::PooledTransactionVariant;
1306    use reth_primitives_traits::SignedTransaction;
1307    use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1308
1309    fn get_transaction() -> EthPooledTransaction {
1310        let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1311
1312        let data = hex::decode(raw).unwrap();
1313        let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1314
1315        EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1316    }
1317
1318    // <https://github.com/paradigmxyz/reth/issues/5178>
1319    #[tokio::test]
1320    async fn validate_transaction() {
1321        let transaction = get_transaction();
1322        let mut fork_tracker = ForkTracker {
1323            shanghai: false.into(),
1324            cancun: false.into(),
1325            prague: false.into(),
1326            osaka: false.into(),
1327            tip_timestamp: 0.into(),
1328            max_blob_count: 0.into(),
1329        };
1330
1331        let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1332        assert!(res.is_ok());
1333
1334        fork_tracker.shanghai = true.into();
1335        let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1336        assert!(res.is_ok());
1337
1338        let provider = MockEthProvider::default();
1339        provider.add_account(
1340            transaction.sender(),
1341            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1342        );
1343        let blob_store = InMemoryBlobStore::default();
1344        let validator = EthTransactionValidatorBuilder::new(provider).build(blob_store.clone());
1345
1346        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1347
1348        assert!(outcome.is_valid());
1349
1350        let pool =
1351            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1352
1353        let res = pool.add_external_transaction(transaction.clone()).await;
1354        assert!(res.is_ok());
1355        let tx = pool.get(transaction.hash());
1356        assert!(tx.is_some());
1357    }
1358
1359    // <https://github.com/paradigmxyz/reth/issues/8550>
1360    #[tokio::test]
1361    async fn invalid_on_gas_limit_too_high() {
1362        let transaction = get_transaction();
1363
1364        let provider = MockEthProvider::default();
1365        provider.add_account(
1366            transaction.sender(),
1367            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1368        );
1369
1370        let blob_store = InMemoryBlobStore::default();
1371        let validator = EthTransactionValidatorBuilder::new(provider)
1372            .set_block_gas_limit(1_000_000) // tx gas limit is 1_015_288
1373            .build(blob_store.clone());
1374
1375        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1376
1377        assert!(outcome.is_invalid());
1378
1379        let pool =
1380            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1381
1382        let res = pool.add_external_transaction(transaction.clone()).await;
1383        assert!(res.is_err());
1384        assert!(matches!(
1385            res.unwrap_err().kind,
1386            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1387                1_015_288, 1_000_000
1388            ))
1389        ));
1390        let tx = pool.get(transaction.hash());
1391        assert!(tx.is_none());
1392    }
1393
1394    #[tokio::test]
1395    async fn invalid_on_fee_cap_exceeded() {
1396        let transaction = get_transaction();
1397        let provider = MockEthProvider::default();
1398        provider.add_account(
1399            transaction.sender(),
1400            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1401        );
1402
1403        let blob_store = InMemoryBlobStore::default();
1404        let validator = EthTransactionValidatorBuilder::new(provider)
1405            .set_tx_fee_cap(100) // 100 wei cap
1406            .build(blob_store.clone());
1407
1408        let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1409        assert!(outcome.is_invalid());
1410
1411        if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1412            assert!(matches!(
1413                err,
1414                InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1415                if (max_tx_fee_wei > tx_fee_cap_wei)
1416            ));
1417        }
1418
1419        let pool =
1420            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1421        let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
1422        assert!(res.is_err());
1423        assert!(matches!(
1424            res.unwrap_err().kind,
1425            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
1426        ));
1427        let tx = pool.get(transaction.hash());
1428        assert!(tx.is_none());
1429    }
1430
1431    #[tokio::test]
1432    async fn valid_on_zero_fee_cap() {
1433        let transaction = get_transaction();
1434        let provider = MockEthProvider::default();
1435        provider.add_account(
1436            transaction.sender(),
1437            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1438        );
1439
1440        let blob_store = InMemoryBlobStore::default();
1441        let validator = EthTransactionValidatorBuilder::new(provider)
1442            .set_tx_fee_cap(0) // no cap
1443            .build(blob_store);
1444
1445        let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1446        assert!(outcome.is_valid());
1447    }
1448
1449    #[tokio::test]
1450    async fn valid_on_normal_fee_cap() {
1451        let transaction = get_transaction();
1452        let provider = MockEthProvider::default();
1453        provider.add_account(
1454            transaction.sender(),
1455            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1456        );
1457
1458        let blob_store = InMemoryBlobStore::default();
1459        let validator = EthTransactionValidatorBuilder::new(provider)
1460            .set_tx_fee_cap(2e18 as u128) // 2 ETH cap
1461            .build(blob_store);
1462
1463        let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1464        assert!(outcome.is_valid());
1465    }
1466
1467    #[tokio::test]
1468    async fn invalid_on_max_tx_gas_limit_exceeded() {
1469        let transaction = get_transaction();
1470        let provider = MockEthProvider::default();
1471        provider.add_account(
1472            transaction.sender(),
1473            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1474        );
1475
1476        let blob_store = InMemoryBlobStore::default();
1477        let validator = EthTransactionValidatorBuilder::new(provider)
1478            .with_max_tx_gas_limit(Some(500_000)) // Set limit lower than transaction gas limit (1_015_288)
1479            .build(blob_store.clone());
1480
1481        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1482        assert!(outcome.is_invalid());
1483
1484        let pool =
1485            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1486
1487        let res = pool.add_external_transaction(transaction.clone()).await;
1488        assert!(res.is_err());
1489        assert!(matches!(
1490            res.unwrap_err().kind,
1491            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
1492                1_015_288, 500_000
1493            ))
1494        ));
1495        let tx = pool.get(transaction.hash());
1496        assert!(tx.is_none());
1497    }
1498
1499    #[tokio::test]
1500    async fn valid_on_max_tx_gas_limit_disabled() {
1501        let transaction = get_transaction();
1502        let provider = MockEthProvider::default();
1503        provider.add_account(
1504            transaction.sender(),
1505            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1506        );
1507
1508        let blob_store = InMemoryBlobStore::default();
1509        let validator = EthTransactionValidatorBuilder::new(provider)
1510            .with_max_tx_gas_limit(None) // disabled
1511            .build(blob_store);
1512
1513        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1514        assert!(outcome.is_valid());
1515    }
1516
1517    #[tokio::test]
1518    async fn valid_on_max_tx_gas_limit_within_limit() {
1519        let transaction = get_transaction();
1520        let provider = MockEthProvider::default();
1521        provider.add_account(
1522            transaction.sender(),
1523            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1524        );
1525
1526        let blob_store = InMemoryBlobStore::default();
1527        let validator = EthTransactionValidatorBuilder::new(provider)
1528            .with_max_tx_gas_limit(Some(2_000_000)) // Set limit higher than transaction gas limit (1_015_288)
1529            .build(blob_store);
1530
1531        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1532        assert!(outcome.is_valid());
1533    }
1534
1535    // Helper function to set up common test infrastructure for priority fee tests
1536    fn setup_priority_fee_test() -> (EthPooledTransaction, MockEthProvider) {
1537        let transaction = get_transaction();
1538        let provider = MockEthProvider::default();
1539        provider.add_account(
1540            transaction.sender(),
1541            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1542        );
1543        (transaction, provider)
1544    }
1545
1546    // Helper function to create a validator with minimum priority fee
1547    fn create_validator_with_minimum_fee(
1548        provider: MockEthProvider,
1549        minimum_priority_fee: Option<u128>,
1550        local_config: Option<LocalTransactionConfig>,
1551    ) -> EthTransactionValidator<MockEthProvider, EthPooledTransaction> {
1552        let blob_store = InMemoryBlobStore::default();
1553        let mut builder = EthTransactionValidatorBuilder::new(provider)
1554            .with_minimum_priority_fee(minimum_priority_fee);
1555
1556        if let Some(config) = local_config {
1557            builder = builder.with_local_transactions_config(config);
1558        }
1559
1560        builder.build(blob_store)
1561    }
1562
1563    #[tokio::test]
1564    async fn invalid_on_priority_fee_lower_than_configured_minimum() {
1565        let (transaction, provider) = setup_priority_fee_test();
1566
1567        // Verify the test transaction is a dynamic fee transaction
1568        assert!(transaction.is_dynamic_fee());
1569
1570        // Set minimum priority fee to be double the transaction's priority fee
1571        let minimum_priority_fee =
1572            transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1573
1574        let validator =
1575            create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1576
1577        // External transaction should be rejected due to low priority fee
1578        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1579        assert!(outcome.is_invalid());
1580
1581        if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1582            assert!(matches!(
1583                err,
1584                InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1585                if min_fee == minimum_priority_fee
1586            ));
1587        }
1588
1589        // Test pool integration
1590        let blob_store = InMemoryBlobStore::default();
1591        let pool =
1592            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1593
1594        let res = pool.add_external_transaction(transaction.clone()).await;
1595        assert!(res.is_err());
1596        assert!(matches!(
1597            res.unwrap_err().kind,
1598            PoolErrorKind::InvalidTransaction(
1599                InvalidPoolTransactionError::PriorityFeeBelowMinimum { .. }
1600            )
1601        ));
1602        let tx = pool.get(transaction.hash());
1603        assert!(tx.is_none());
1604
1605        // Local transactions should still be accepted regardless of minimum priority fee
1606        let (_, local_provider) = setup_priority_fee_test();
1607        let validator_local =
1608            create_validator_with_minimum_fee(local_provider, Some(minimum_priority_fee), None);
1609
1610        let local_outcome = validator_local.validate_one(TransactionOrigin::Local, transaction);
1611        assert!(local_outcome.is_valid());
1612    }
1613
1614    #[tokio::test]
1615    async fn valid_on_priority_fee_equal_to_minimum() {
1616        let (transaction, provider) = setup_priority_fee_test();
1617
1618        // Set minimum priority fee equal to transaction's priority fee
1619        let tx_priority_fee =
1620            transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1621        let validator = create_validator_with_minimum_fee(provider, Some(tx_priority_fee), None);
1622
1623        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1624        assert!(outcome.is_valid());
1625    }
1626
1627    #[tokio::test]
1628    async fn valid_on_priority_fee_above_minimum() {
1629        let (transaction, provider) = setup_priority_fee_test();
1630
1631        // Set minimum priority fee below transaction's priority fee
1632        let tx_priority_fee =
1633            transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1634        let minimum_priority_fee = tx_priority_fee / 2; // Half of transaction's priority fee
1635
1636        let validator =
1637            create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1638
1639        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1640        assert!(outcome.is_valid());
1641    }
1642
1643    #[tokio::test]
1644    async fn valid_on_minimum_priority_fee_disabled() {
1645        let (transaction, provider) = setup_priority_fee_test();
1646
1647        // No minimum priority fee set (default is None)
1648        let validator = create_validator_with_minimum_fee(provider, None, None);
1649
1650        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1651        assert!(outcome.is_valid());
1652    }
1653
1654    #[tokio::test]
1655    async fn priority_fee_validation_applies_to_private_transactions() {
1656        let (transaction, provider) = setup_priority_fee_test();
1657
1658        // Set minimum priority fee to be double the transaction's priority fee
1659        let minimum_priority_fee =
1660            transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1661
1662        let validator =
1663            create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1664
1665        // Private transactions are also subject to minimum priority fee validation
1666        // because they are not considered "local" by default unless specifically configured
1667        let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1668        assert!(outcome.is_invalid());
1669
1670        if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1671            assert!(matches!(
1672                err,
1673                InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1674                if min_fee == minimum_priority_fee
1675            ));
1676        }
1677    }
1678
1679    #[tokio::test]
1680    async fn valid_on_local_config_exempts_private_transactions() {
1681        let (transaction, provider) = setup_priority_fee_test();
1682
1683        // Set minimum priority fee to be double the transaction's priority fee
1684        let minimum_priority_fee =
1685            transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1686
1687        // Configure local transactions to include all private transactions
1688        let local_config =
1689            LocalTransactionConfig { propagate_local_transactions: true, ..Default::default() };
1690
1691        let validator = create_validator_with_minimum_fee(
1692            provider,
1693            Some(minimum_priority_fee),
1694            Some(local_config),
1695        );
1696
1697        // With appropriate local config, the behavior depends on the local transaction logic
1698        // This test documents the current behavior - private transactions are still validated
1699        // unless the sender is specifically whitelisted in local_transactions_config
1700        let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1701        assert!(outcome.is_invalid()); // Still invalid because sender not in whitelist
1702    }
1703
1704    #[test]
1705    fn reject_oversized_tx() {
1706        let mut transaction = get_transaction();
1707        transaction.encoded_length = DEFAULT_MAX_TX_INPUT_BYTES + 1;
1708        let provider = MockEthProvider::default();
1709
1710        // No minimum priority fee set (default is None)
1711        let validator = create_validator_with_minimum_fee(provider, None, None);
1712
1713        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1714        let invalid = outcome.as_invalid().unwrap();
1715        assert!(invalid.is_oversized());
1716    }
1717
1718    #[tokio::test]
1719    async fn valid_with_disabled_balance_check() {
1720        let transaction = get_transaction();
1721        let provider = MockEthProvider::default();
1722
1723        // Set account with 0 balance
1724        provider.add_account(
1725            transaction.sender(),
1726            ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
1727        );
1728
1729        // Validate with balance check enabled
1730        let validator = EthTransactionValidatorBuilder::new(provider.clone())
1731            .build(InMemoryBlobStore::default());
1732
1733        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1734        let expected_cost = *transaction.cost();
1735        if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1736            assert!(matches!(
1737                err,
1738                InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err))
1739                if funds_err.got == alloy_primitives::U256::ZERO && funds_err.expected == expected_cost
1740            ));
1741        } else {
1742            panic!("Expected Invalid outcome with InsufficientFunds error");
1743        }
1744
1745        // Validate with balance check disabled
1746        let validator = EthTransactionValidatorBuilder::new(provider)
1747            .disable_balance_check() // This should allow the transaction through despite zero balance
1748            .build(InMemoryBlobStore::default());
1749
1750        let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1751        assert!(outcome.is_valid()); // Should be valid because balance check is disabled
1752    }
1753}