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    EthBlobTransactionSidecar, EthPoolTransaction, LocalTransactionConfig,
13    TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator,
14};
15use alloy_consensus::{
16    constants::{
17        EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
18        LEGACY_TX_TYPE_ID,
19    },
20    BlockHeader,
21};
22use alloy_eips::{
23    eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M, eip4844::env_settings::EnvKzgSettings,
24    eip7840::BlobParams,
25};
26use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
27use reth_primitives_traits::{
28    transaction::error::InvalidTransactionError, Block, GotExpected, SealedBlock,
29};
30use reth_storage_api::{StateProvider, StateProviderFactory};
31use reth_tasks::TaskSpawner;
32use seismic_alloy_consensus::SEISMIC_TX_TYPE_ID;
33use std::{
34    marker::PhantomData,
35    sync::{
36        atomic::{AtomicBool, AtomicU64},
37        Arc,
38    },
39    time::Instant,
40};
41use tokio::sync::Mutex;
42
43/// Validator for Ethereum transactions.
44/// It is a [`TransactionValidator`] implementation that validates ethereum transaction.
45#[derive(Debug, Clone)]
46pub struct EthTransactionValidator<Client, T> {
47    /// The type that performs the actual validation.
48    inner: Arc<EthTransactionValidatorInner<Client, T>>,
49}
50
51impl<Client, Tx> EthTransactionValidator<Client, Tx> {
52    /// Returns the configured chain spec
53    pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
54    where
55        Client: ChainSpecProvider,
56    {
57        self.client().chain_spec()
58    }
59
60    /// Returns the configured client
61    pub fn client(&self) -> &Client {
62        &self.inner.client
63    }
64}
65
66impl<Client, Tx> EthTransactionValidator<Client, Tx>
67where
68    Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
69    Tx: EthPoolTransaction,
70{
71    /// Validates a single transaction.
72    ///
73    /// See also [`TransactionValidator::validate_transaction`]
74    pub fn validate_one(
75        &self,
76        origin: TransactionOrigin,
77        transaction: Tx,
78    ) -> TransactionValidationOutcome<Tx> {
79        self.inner.validate_one(origin, transaction)
80    }
81
82    /// Validates a single transaction with the provided state provider.
83    ///
84    /// This allows reusing the same provider across multiple transaction validations,
85    /// which can improve performance when validating many transactions.
86    ///
87    /// If `state` is `None`, a new state provider will be created.
88    pub fn validate_one_with_state(
89        &self,
90        origin: TransactionOrigin,
91        transaction: Tx,
92        state: &mut Option<Box<dyn StateProvider>>,
93    ) -> TransactionValidationOutcome<Tx> {
94        self.inner.validate_one_with_provider(origin, transaction, state)
95    }
96
97    /// Validates all given transactions.
98    ///
99    /// Returns all outcomes for the given transactions in the same order.
100    ///
101    /// See also [`Self::validate_one`]
102    pub fn validate_all(
103        &self,
104        transactions: Vec<(TransactionOrigin, Tx)>,
105    ) -> Vec<TransactionValidationOutcome<Tx>> {
106        self.inner.validate_batch(transactions)
107    }
108
109    /// Validates all given transactions with origin.
110    ///
111    /// Returns all outcomes for the given transactions in the same order.
112    ///
113    /// See also [`Self::validate_one`]
114    pub fn validate_all_with_origin(
115        &self,
116        origin: TransactionOrigin,
117        transactions: impl IntoIterator<Item = Tx> + Send,
118    ) -> Vec<TransactionValidationOutcome<Tx>> {
119        self.inner.validate_batch_with_origin(origin, transactions)
120    }
121}
122
123impl<Client, Tx> TransactionValidator for EthTransactionValidator<Client, Tx>
124where
125    Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
126    Tx: EthPoolTransaction,
127{
128    type Transaction = Tx;
129
130    async fn validate_transaction(
131        &self,
132        origin: TransactionOrigin,
133        transaction: Self::Transaction,
134    ) -> TransactionValidationOutcome<Self::Transaction> {
135        self.validate_one(origin, transaction)
136    }
137
138    async fn validate_transactions(
139        &self,
140        transactions: Vec<(TransactionOrigin, Self::Transaction)>,
141    ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
142        self.validate_all(transactions)
143    }
144
145    async fn validate_transactions_with_origin(
146        &self,
147        origin: TransactionOrigin,
148        transactions: impl IntoIterator<Item = Self::Transaction> + Send,
149    ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
150        self.validate_all_with_origin(origin, transactions)
151    }
152
153    fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>)
154    where
155        B: Block,
156    {
157        self.inner.on_new_head_block(new_tip_block.header())
158    }
159}
160
161/// A [`TransactionValidator`] implementation that validates ethereum transaction.
162///
163/// It supports all known ethereum transaction types:
164/// - Legacy
165/// - EIP-2718
166/// - EIP-1559
167/// - EIP-4844
168/// - EIP-7702
169///
170/// And enforces additional constraints such as:
171/// - Maximum transaction size
172/// - Maximum gas limit
173///
174/// And adheres to the configured [`LocalTransactionConfig`].
175#[derive(Debug)]
176pub(crate) struct EthTransactionValidatorInner<Client, T> {
177    /// This type fetches account info from the db
178    client: Client,
179    /// Blobstore used for fetching re-injected blob transactions.
180    blob_store: Box<dyn BlobStore>,
181    /// tracks activated forks relevant for transaction validation
182    fork_tracker: ForkTracker,
183    /// Fork indicator whether we are using EIP-2718 type transactions.
184    eip2718: bool,
185    /// Fork indicator whether we are using EIP-1559 type transactions.
186    eip1559: bool,
187    /// Fork indicator whether we are using EIP-4844 blob transactions.
188    eip4844: bool,
189    /// Fork indicator whether we are using EIP-7702 type transactions.
190    eip7702: bool,
191    /// The current max gas limit
192    block_gas_limit: AtomicU64,
193    /// The current tx fee cap limit in wei locally submitted into the pool.
194    tx_fee_cap: Option<u128>,
195    /// Minimum priority fee to enforce for acceptance into the pool.
196    minimum_priority_fee: Option<u128>,
197    /// Stores the setup and parameters needed for validating KZG proofs.
198    kzg_settings: EnvKzgSettings,
199    /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.
200    local_transactions_config: LocalTransactionConfig,
201    /// Maximum size in bytes a single transaction can have in order to be accepted into the pool.
202    max_tx_input_bytes: usize,
203    /// Marker for the transaction type
204    _marker: PhantomData<T>,
205    /// Metrics for tsx pool validation
206    validation_metrics: TxPoolValidationMetrics,
207}
208
209// === impl EthTransactionValidatorInner ===
210
211impl<Client: ChainSpecProvider, Tx> EthTransactionValidatorInner<Client, Tx> {
212    /// Returns the configured chain id
213    pub(crate) fn chain_id(&self) -> u64 {
214        self.client.chain_spec().chain().id()
215    }
216}
217
218impl<Client, Tx> EthTransactionValidatorInner<Client, Tx>
219where
220    Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
221    Tx: EthPoolTransaction,
222{
223    /// Returns the configured chain spec
224    fn chain_spec(&self) -> Arc<Client::ChainSpec> {
225        self.client.chain_spec()
226    }
227
228    /// Validates a single transaction using an optional cached state provider.
229    /// If no provider is passed, a new one will be created. This allows reusing
230    /// the same provider across multiple txs.
231    fn validate_one_with_provider(
232        &self,
233        origin: TransactionOrigin,
234        transaction: Tx,
235        maybe_state: &mut Option<Box<dyn StateProvider>>,
236    ) -> TransactionValidationOutcome<Tx> {
237        match self.validate_one_no_state(origin, transaction) {
238            Ok(transaction) => {
239                // stateless checks passed, pass transaction down stateful validation pipeline
240                // If we don't have a state provider yet, fetch the latest state
241                if maybe_state.is_none() {
242                    match self.client.latest() {
243                        Ok(new_state) => {
244                            *maybe_state = Some(new_state);
245                        }
246                        Err(err) => {
247                            return TransactionValidationOutcome::Error(
248                                *transaction.hash(),
249                                Box::new(err),
250                            )
251                        }
252                    }
253                }
254
255                let state = maybe_state.as_deref().expect("provider is set");
256
257                self.validate_one_against_state(origin, transaction, state)
258            }
259            Err(invalid_outcome) => invalid_outcome,
260        }
261    }
262
263    /// Performs stateless validation on single transaction. Returns unaltered input transaction
264    /// if all checks pass, so transaction can continue through to stateful validation as argument
265    /// to [`validate_one_against_state`](Self::validate_one_against_state).
266    fn validate_one_no_state(
267        &self,
268        origin: TransactionOrigin,
269        transaction: Tx,
270    ) -> Result<Tx, TransactionValidationOutcome<Tx>> {
271        // Checks for tx_type
272        match transaction.ty() {
273            LEGACY_TX_TYPE_ID => {
274                // Accept legacy transactions
275            }
276            EIP2930_TX_TYPE_ID => {
277                // Accept only legacy transactions until EIP-2718/2930 activates
278                if !self.eip2718 {
279                    return Err(TransactionValidationOutcome::Invalid(
280                        transaction,
281                        InvalidTransactionError::Eip2930Disabled.into(),
282                    ))
283                }
284            }
285            EIP1559_TX_TYPE_ID => {
286                // Reject dynamic fee transactions until EIP-1559 activates.
287                if !self.eip1559 {
288                    return Err(TransactionValidationOutcome::Invalid(
289                        transaction,
290                        InvalidTransactionError::Eip1559Disabled.into(),
291                    ))
292                }
293            }
294            EIP4844_TX_TYPE_ID => {
295                // Reject blob transactions.
296                if !self.eip4844 {
297                    return Err(TransactionValidationOutcome::Invalid(
298                        transaction,
299                        InvalidTransactionError::Eip4844Disabled.into(),
300                    ))
301                }
302            }
303            EIP7702_TX_TYPE_ID => {
304                // Reject EIP-7702 transactions.
305                if !self.eip7702 {
306                    return Err(TransactionValidationOutcome::Invalid(
307                        transaction,
308                        InvalidTransactionError::Eip7702Disabled.into(),
309                    ))
310                }
311            }
312
313            SEISMIC_TX_TYPE_ID => {
314                // Accept seismic transactions
315            }
316
317            _ => {
318                return Err(TransactionValidationOutcome::Invalid(
319                    transaction,
320                    InvalidTransactionError::TxTypeNotSupported.into(),
321                ))
322            }
323        };
324
325        // Reject transactions over defined size to prevent DOS attacks
326        let tx_input_len = transaction.input().len();
327        if tx_input_len > self.max_tx_input_bytes {
328            return Err(TransactionValidationOutcome::Invalid(
329                transaction,
330                InvalidPoolTransactionError::OversizedData(tx_input_len, self.max_tx_input_bytes),
331            ))
332        }
333
334        // Check whether the init code size has been exceeded.
335        if self.fork_tracker.is_shanghai_activated() {
336            if let Err(err) = transaction.ensure_max_init_code_size(MAX_INIT_CODE_BYTE_SIZE) {
337                return Err(TransactionValidationOutcome::Invalid(transaction, err))
338            }
339        }
340
341        // Checks for gas limit
342        let transaction_gas_limit = transaction.gas_limit();
343        let block_gas_limit = self.max_gas_limit();
344        if transaction_gas_limit > block_gas_limit {
345            return Err(TransactionValidationOutcome::Invalid(
346                transaction,
347                InvalidPoolTransactionError::ExceedsGasLimit(
348                    transaction_gas_limit,
349                    block_gas_limit,
350                ),
351            ))
352        }
353
354        // Ensure max_priority_fee_per_gas (if EIP1559) is less than max_fee_per_gas if any.
355        if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
356            return Err(TransactionValidationOutcome::Invalid(
357                transaction,
358                InvalidTransactionError::TipAboveFeeCap.into(),
359            ))
360        }
361
362        // determine whether the transaction should be treated as local
363        let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
364
365        // Ensure max possible transaction fee doesn't exceed configured transaction fee cap.
366        // Only for transactions locally submitted for acceptance into the pool.
367        if is_local {
368            match self.tx_fee_cap {
369                Some(0) | None => {} // Skip if cap is 0 or None
370                Some(tx_fee_cap_wei) => {
371                    // max possible tx fee is (gas_price * gas_limit)
372                    // (if EIP1559) max possible tx fee is (max_fee_per_gas * gas_limit)
373                    let gas_price = transaction.max_fee_per_gas();
374                    let max_tx_fee_wei = gas_price.saturating_mul(transaction.gas_limit() as u128);
375                    if max_tx_fee_wei > tx_fee_cap_wei {
376                        return Err(TransactionValidationOutcome::Invalid(
377                            transaction,
378                            InvalidPoolTransactionError::ExceedsFeeCap {
379                                max_tx_fee_wei,
380                                tx_fee_cap_wei,
381                            },
382                        ))
383                    }
384                }
385            }
386        }
387
388        // Drop non-local transactions with a fee lower than the configured fee for acceptance into
389        // the pool.
390        if !is_local &&
391            transaction.is_dynamic_fee() &&
392            transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
393        {
394            return Err(TransactionValidationOutcome::Invalid(
395                transaction,
396                InvalidPoolTransactionError::Underpriced,
397            ))
398        }
399
400        // Checks for chainid
401        if let Some(chain_id) = transaction.chain_id() {
402            if chain_id != self.chain_id() {
403                return Err(TransactionValidationOutcome::Invalid(
404                    transaction,
405                    InvalidTransactionError::ChainIdMismatch.into(),
406                ))
407            }
408        }
409
410        if transaction.is_eip7702() {
411            // Prague fork is required for 7702 txs
412            if !self.fork_tracker.is_prague_activated() {
413                return Err(TransactionValidationOutcome::Invalid(
414                    transaction,
415                    InvalidTransactionError::TxTypeNotSupported.into(),
416                ))
417            }
418
419            if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
420                return Err(TransactionValidationOutcome::Invalid(
421                    transaction,
422                    Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into(),
423                ))
424            }
425        }
426
427        if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
428            return Err(TransactionValidationOutcome::Invalid(transaction, err))
429        }
430
431        // light blob tx pre-checks
432        if transaction.is_eip4844() {
433            // Cancun fork is required for blob txs
434            if !self.fork_tracker.is_cancun_activated() {
435                return Err(TransactionValidationOutcome::Invalid(
436                    transaction,
437                    InvalidTransactionError::TxTypeNotSupported.into(),
438                ))
439            }
440
441            let blob_count =
442                transaction.blob_versioned_hashes().map(|b| b.len() as u64).unwrap_or(0);
443            if blob_count == 0 {
444                // no blobs
445                return Err(TransactionValidationOutcome::Invalid(
446                    transaction,
447                    InvalidPoolTransactionError::Eip4844(
448                        Eip4844PoolTransactionError::NoEip4844Blobs,
449                    ),
450                ))
451            }
452
453            let max_blob_count = self.fork_tracker.max_blob_count();
454            if blob_count > max_blob_count {
455                return Err(TransactionValidationOutcome::Invalid(
456                    transaction,
457                    InvalidPoolTransactionError::Eip4844(
458                        Eip4844PoolTransactionError::TooManyEip4844Blobs {
459                            have: blob_count,
460                            permitted: max_blob_count,
461                        },
462                    ),
463                ))
464            }
465        }
466
467        Ok(transaction)
468    }
469
470    /// Validates a single transaction using given state provider.
471    fn validate_one_against_state<P>(
472        &self,
473        origin: TransactionOrigin,
474        mut transaction: Tx,
475        state: P,
476    ) -> TransactionValidationOutcome<Tx>
477    where
478        P: StateProvider,
479    {
480        // Use provider to get account info
481        let account = match state.basic_account(transaction.sender_ref()) {
482            Ok(account) => account.unwrap_or_default(),
483            Err(err) => {
484                return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
485            }
486        };
487
488        // Unless Prague is active, the signer account shouldn't have bytecode.
489        //
490        // If Prague is active, only EIP-7702 bytecode is allowed for the sender.
491        //
492        // Any other case means that the account is not an EOA, and should not be able to send
493        // transactions.
494        if let Some(code_hash) = &account.bytecode_hash {
495            let is_eip7702 = if self.fork_tracker.is_prague_activated() {
496                match state.bytecode_by_hash(code_hash) {
497                    Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
498                    Err(err) => {
499                        return TransactionValidationOutcome::Error(
500                            *transaction.hash(),
501                            Box::new(err),
502                        )
503                    }
504                }
505            } else {
506                false
507            };
508
509            if !is_eip7702 {
510                return TransactionValidationOutcome::Invalid(
511                    transaction,
512                    InvalidTransactionError::SignerAccountHasBytecode.into(),
513                )
514            }
515        }
516
517        let tx_nonce = transaction.nonce();
518
519        // Checks for nonce
520        if tx_nonce < account.nonce {
521            return TransactionValidationOutcome::Invalid(
522                transaction,
523                InvalidTransactionError::NonceNotConsistent { tx: tx_nonce, state: account.nonce }
524                    .into(),
525            )
526        }
527
528        let cost = transaction.cost();
529
530        // Checks for max cost
531        if cost > &account.balance {
532            let expected = *cost;
533            return TransactionValidationOutcome::Invalid(
534                transaction,
535                InvalidTransactionError::InsufficientFunds(
536                    GotExpected { got: account.balance, expected }.into(),
537                )
538                .into(),
539            )
540        }
541
542        let mut maybe_blob_sidecar = None;
543
544        // heavy blob tx validation
545        if transaction.is_eip4844() {
546            // extract the blob from the transaction
547            match transaction.take_blob() {
548                EthBlobTransactionSidecar::None => {
549                    // this should not happen
550                    return TransactionValidationOutcome::Invalid(
551                        transaction,
552                        InvalidTransactionError::TxTypeNotSupported.into(),
553                    )
554                }
555                EthBlobTransactionSidecar::Missing => {
556                    // This can happen for re-injected blob transactions (on re-org), since the blob
557                    // is stripped from the transaction and not included in a block.
558                    // check if the blob is in the store, if it's included we previously validated
559                    // it and inserted it
560                    if matches!(self.blob_store.contains(*transaction.hash()), Ok(true)) {
561                        // validated transaction is already in the store
562                    } else {
563                        return TransactionValidationOutcome::Invalid(
564                            transaction,
565                            InvalidPoolTransactionError::Eip4844(
566                                Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
567                            ),
568                        )
569                    }
570                }
571                EthBlobTransactionSidecar::Present(sidecar) => {
572                    let now = Instant::now();
573
574                    if self.fork_tracker.is_osaka_activated() {
575                        if sidecar.is_eip4844() {
576                            return TransactionValidationOutcome::Invalid(
577                                transaction,
578                                InvalidPoolTransactionError::Eip4844(
579                                    Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
580                                ),
581                            )
582                        }
583                    } else if sidecar.is_eip7594() {
584                        return TransactionValidationOutcome::Invalid(
585                            transaction,
586                            InvalidPoolTransactionError::Eip4844(
587                                Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
588                            ),
589                        )
590                    }
591
592                    // validate the blob
593                    if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
594                        return TransactionValidationOutcome::Invalid(
595                            transaction,
596                            InvalidPoolTransactionError::Eip4844(
597                                Eip4844PoolTransactionError::InvalidEip4844Blob(err),
598                            ),
599                        )
600                    }
601                    // Record the duration of successful blob validation as histogram
602                    self.validation_metrics.blob_validation_duration.record(now.elapsed());
603                    // store the extracted blob
604                    maybe_blob_sidecar = Some(sidecar);
605                }
606            }
607        }
608
609        let authorities = transaction.authorization_list().map(|auths| {
610            auths.iter().flat_map(|auth| auth.recover_authority()).collect::<Vec<_>>()
611        });
612        // Return the valid transaction
613        TransactionValidationOutcome::Valid {
614            balance: account.balance,
615            state_nonce: account.nonce,
616            bytecode_hash: account.bytecode_hash,
617            transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
618            // by this point assume all external transactions should be propagated
619            propagate: match origin {
620                TransactionOrigin::External => true,
621                TransactionOrigin::Local => {
622                    self.local_transactions_config.propagate_local_transactions
623                }
624                TransactionOrigin::Private => false,
625            },
626            authorities,
627        }
628    }
629
630    /// Validates a single transaction.
631    fn validate_one(
632        &self,
633        origin: TransactionOrigin,
634        transaction: Tx,
635    ) -> TransactionValidationOutcome<Tx> {
636        let mut provider = None;
637        self.validate_one_with_provider(origin, transaction, &mut provider)
638    }
639
640    /// Validates all given transactions.
641    fn validate_batch(
642        &self,
643        transactions: Vec<(TransactionOrigin, Tx)>,
644    ) -> Vec<TransactionValidationOutcome<Tx>> {
645        let mut provider = None;
646        transactions
647            .into_iter()
648            .map(|(origin, tx)| self.validate_one_with_provider(origin, tx, &mut provider))
649            .collect()
650    }
651
652    /// Validates all given transactions with origin.
653    fn validate_batch_with_origin(
654        &self,
655        origin: TransactionOrigin,
656        transactions: impl IntoIterator<Item = Tx> + Send,
657    ) -> Vec<TransactionValidationOutcome<Tx>> {
658        let mut provider = None;
659        transactions
660            .into_iter()
661            .map(|tx| self.validate_one_with_provider(origin, tx, &mut provider))
662            .collect()
663    }
664
665    fn on_new_head_block<T: BlockHeader>(&self, new_tip_block: &T) {
666        // update all forks
667        if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
668            self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
669        }
670
671        if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
672            self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
673        }
674
675        if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
676            self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
677        }
678
679        if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
680            self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
681        }
682
683        if let Some(blob_params) =
684            self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
685        {
686            self.fork_tracker
687                .max_blob_count
688                .store(blob_params.max_blob_count, std::sync::atomic::Ordering::Relaxed);
689        }
690
691        self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
692    }
693
694    fn max_gas_limit(&self) -> u64 {
695        self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
696    }
697}
698
699/// A builder for [`EthTransactionValidator`] and [`TransactionValidationTaskExecutor`]
700#[derive(Debug)]
701pub struct EthTransactionValidatorBuilder<Client> {
702    client: Client,
703    /// Fork indicator whether we are in the Shanghai stage.
704    shanghai: bool,
705    /// Fork indicator whether we are in the Cancun hardfork.
706    cancun: bool,
707    /// Fork indicator whether we are in the Prague hardfork.
708    prague: bool,
709    /// Fork indicator whether we are in the Osaka hardfork.
710    osaka: bool,
711    /// Max blob count at the block's timestamp.
712    max_blob_count: u64,
713    /// Whether using EIP-2718 type transactions is allowed
714    eip2718: bool,
715    /// Whether using EIP-1559 type transactions is allowed
716    eip1559: bool,
717    /// Whether using EIP-4844 type transactions is allowed
718    eip4844: bool,
719    /// Whether using EIP-7702 type transactions is allowed
720    eip7702: bool,
721    /// The current max gas limit
722    block_gas_limit: AtomicU64,
723    /// The current tx fee cap limit in wei locally submitted into the pool.
724    tx_fee_cap: Option<u128>,
725    /// Minimum priority fee to enforce for acceptance into the pool.
726    minimum_priority_fee: Option<u128>,
727    /// Determines how many additional tasks to spawn
728    ///
729    /// Default is 1
730    additional_tasks: usize,
731
732    /// Stores the setup and parameters needed for validating KZG proofs.
733    kzg_settings: EnvKzgSettings,
734    /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.
735    local_transactions_config: LocalTransactionConfig,
736    /// Max size in bytes of a single transaction allowed
737    max_tx_input_bytes: usize,
738}
739
740impl<Client> EthTransactionValidatorBuilder<Client> {
741    /// Creates a new builder for the given client
742    ///
743    /// By default this assumes the network is on the `Cancun` hardfork and the following
744    /// transactions are allowed:
745    ///  - Legacy
746    ///  - EIP-2718
747    ///  - EIP-1559
748    ///  - EIP-4844
749    pub fn new(client: Client) -> Self {
750        Self {
751            block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(),
752            client,
753            minimum_priority_fee: None,
754            additional_tasks: 1,
755            kzg_settings: EnvKzgSettings::Default,
756            local_transactions_config: Default::default(),
757            max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
758            tx_fee_cap: Some(1e18 as u128),
759            // by default all transaction types are allowed
760            eip2718: true,
761            eip1559: true,
762            eip4844: true,
763            eip7702: true,
764
765            // shanghai is activated by default
766            shanghai: true,
767
768            // cancun is activated by default
769            cancun: true,
770
771            // prague is activated by default
772            prague: true,
773
774            // osaka not yet activated
775            osaka: false,
776
777            // max blob count is prague by default
778            max_blob_count: BlobParams::prague().max_blob_count,
779        }
780    }
781
782    /// Disables the Cancun fork.
783    pub const fn no_cancun(self) -> Self {
784        self.set_cancun(false)
785    }
786
787    /// Whether to allow exemptions for local transaction exemptions.
788    pub fn with_local_transactions_config(
789        mut self,
790        local_transactions_config: LocalTransactionConfig,
791    ) -> Self {
792        self.local_transactions_config = local_transactions_config;
793        self
794    }
795
796    /// Set the Cancun fork.
797    pub const fn set_cancun(mut self, cancun: bool) -> Self {
798        self.cancun = cancun;
799        self
800    }
801
802    /// Disables the Shanghai fork.
803    pub const fn no_shanghai(self) -> Self {
804        self.set_shanghai(false)
805    }
806
807    /// Set the Shanghai fork.
808    pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
809        self.shanghai = shanghai;
810        self
811    }
812
813    /// Disables the Prague fork.
814    pub const fn no_prague(self) -> Self {
815        self.set_prague(false)
816    }
817
818    /// Set the Prague fork.
819    pub const fn set_prague(mut self, prague: bool) -> Self {
820        self.prague = prague;
821        self
822    }
823
824    /// Disables the Osaka fork.
825    pub const fn no_osaka(self) -> Self {
826        self.set_osaka(false)
827    }
828
829    /// Set the Osaka fork.
830    pub const fn set_osaka(mut self, osaka: bool) -> Self {
831        self.osaka = osaka;
832        self
833    }
834
835    /// Disables the support for EIP-2718 transactions.
836    pub const fn no_eip2718(self) -> Self {
837        self.set_eip2718(false)
838    }
839
840    /// Set the support for EIP-2718 transactions.
841    pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
842        self.eip2718 = eip2718;
843        self
844    }
845
846    /// Disables the support for EIP-1559 transactions.
847    pub const fn no_eip1559(self) -> Self {
848        self.set_eip1559(false)
849    }
850
851    /// Set the support for EIP-1559 transactions.
852    pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
853        self.eip1559 = eip1559;
854        self
855    }
856
857    /// Disables the support for EIP-4844 transactions.
858    pub const fn no_eip4844(self) -> Self {
859        self.set_eip4844(false)
860    }
861
862    /// Set the support for EIP-4844 transactions.
863    pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
864        self.eip4844 = eip4844;
865        self
866    }
867
868    /// Sets the [`EnvKzgSettings`] to use for validating KZG proofs.
869    pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
870        self.kzg_settings = kzg_settings;
871        self
872    }
873
874    /// Sets a minimum priority fee that's enforced for acceptance into the pool.
875    pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: u128) -> Self {
876        self.minimum_priority_fee = Some(minimum_priority_fee);
877        self
878    }
879
880    /// Sets the number of additional tasks to spawn.
881    pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
882        self.additional_tasks = additional_tasks;
883        self
884    }
885
886    /// Configures validation rules based on the head block's timestamp.
887    ///
888    /// For example, whether the Shanghai and Cancun hardfork is activated at launch.
889    pub fn with_head_timestamp(mut self, timestamp: u64) -> Self
890    where
891        Client: ChainSpecProvider<ChainSpec: EthereumHardforks>,
892    {
893        self.shanghai = self.client.chain_spec().is_shanghai_active_at_timestamp(timestamp);
894        self.cancun = self.client.chain_spec().is_cancun_active_at_timestamp(timestamp);
895        self.prague = self.client.chain_spec().is_prague_active_at_timestamp(timestamp);
896        self.osaka = self.client.chain_spec().is_osaka_active_at_timestamp(timestamp);
897        self.max_blob_count = self
898            .client
899            .chain_spec()
900            .blob_params_at_timestamp(timestamp)
901            .unwrap_or_else(BlobParams::cancun)
902            .max_blob_count;
903        self
904    }
905
906    /// Sets a max size in bytes of a single transaction allowed into the pool
907    pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
908        self.max_tx_input_bytes = max_tx_input_bytes;
909        self
910    }
911
912    /// Sets the block gas limit
913    ///
914    /// Transactions with a gas limit greater than this will be rejected.
915    pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
916        self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
917        self
918    }
919
920    /// Sets the block gas limit
921    ///
922    /// Transactions with a gas limit greater than this will be rejected.
923    pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
924        self.tx_fee_cap = Some(tx_fee_cap);
925        self
926    }
927
928    /// Builds a the [`EthTransactionValidator`] without spawning validator tasks.
929    pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx>
930    where
931        S: BlobStore,
932    {
933        let Self {
934            client,
935            shanghai,
936            cancun,
937            prague,
938            osaka,
939            eip2718,
940            eip1559,
941            eip4844,
942            eip7702,
943            block_gas_limit,
944            tx_fee_cap,
945            minimum_priority_fee,
946            kzg_settings,
947            local_transactions_config,
948            max_tx_input_bytes,
949            ..
950        } = self;
951
952        // TODO: use osaka max blob count once <https://github.com/alloy-rs/alloy/pull/2427> is released
953        let max_blob_count = if prague {
954            BlobParams::prague().max_blob_count
955        } else {
956            BlobParams::cancun().max_blob_count
957        };
958
959        let fork_tracker = ForkTracker {
960            shanghai: AtomicBool::new(shanghai),
961            cancun: AtomicBool::new(cancun),
962            prague: AtomicBool::new(prague),
963            osaka: AtomicBool::new(osaka),
964            max_blob_count: AtomicU64::new(max_blob_count),
965        };
966
967        let inner = EthTransactionValidatorInner {
968            client,
969            eip2718,
970            eip1559,
971            fork_tracker,
972            eip4844,
973            eip7702,
974            block_gas_limit,
975            tx_fee_cap,
976            minimum_priority_fee,
977            blob_store: Box::new(blob_store),
978            kzg_settings,
979            local_transactions_config,
980            max_tx_input_bytes,
981            _marker: Default::default(),
982            validation_metrics: TxPoolValidationMetrics::default(),
983        };
984
985        EthTransactionValidator { inner: Arc::new(inner) }
986    }
987
988    /// Builds a [`EthTransactionValidator`] and spawns validation tasks via the
989    /// [`TransactionValidationTaskExecutor`]
990    ///
991    /// The validator will spawn `additional_tasks` additional tasks for validation.
992    ///
993    /// By default this will spawn 1 additional task.
994    pub fn build_with_tasks<Tx, T, S>(
995        self,
996        tasks: T,
997        blob_store: S,
998    ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx>>
999    where
1000        T: TaskSpawner,
1001        S: BlobStore,
1002    {
1003        let additional_tasks = self.additional_tasks;
1004        let validator = self.build(blob_store);
1005
1006        let (tx, task) = ValidationTask::new();
1007
1008        // Spawn validation tasks, they are blocking because they perform db lookups
1009        for _ in 0..additional_tasks {
1010            let task = task.clone();
1011            tasks.spawn_blocking(Box::pin(async move {
1012                task.run().await;
1013            }));
1014        }
1015
1016        // we spawn them on critical tasks because validation, especially for EIP-4844 can be quite
1017        // heavy
1018        tasks.spawn_critical_blocking(
1019            "transaction-validation-service",
1020            Box::pin(async move {
1021                task.run().await;
1022            }),
1023        );
1024
1025        let to_validation_task = Arc::new(Mutex::new(tx));
1026
1027        TransactionValidationTaskExecutor { validator, to_validation_task }
1028    }
1029}
1030
1031/// Keeps track of whether certain forks are activated
1032#[derive(Debug)]
1033pub struct ForkTracker {
1034    /// Tracks if shanghai is activated at the block's timestamp.
1035    pub shanghai: AtomicBool,
1036    /// Tracks if cancun is activated at the block's timestamp.
1037    pub cancun: AtomicBool,
1038    /// Tracks if prague is activated at the block's timestamp.
1039    pub prague: AtomicBool,
1040    /// Tracks if osaka is activated at the block's timestamp.
1041    pub osaka: AtomicBool,
1042    /// Tracks max blob count at the block's timestamp.
1043    pub max_blob_count: AtomicU64,
1044}
1045
1046impl ForkTracker {
1047    /// Returns `true` if Shanghai fork is activated.
1048    pub fn is_shanghai_activated(&self) -> bool {
1049        self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1050    }
1051
1052    /// Returns `true` if Cancun fork is activated.
1053    pub fn is_cancun_activated(&self) -> bool {
1054        self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1055    }
1056
1057    /// Returns `true` if Prague fork is activated.
1058    pub fn is_prague_activated(&self) -> bool {
1059        self.prague.load(std::sync::atomic::Ordering::Relaxed)
1060    }
1061
1062    /// Returns `true` if Osaka fork is activated.
1063    pub fn is_osaka_activated(&self) -> bool {
1064        self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1065    }
1066
1067    /// Returns the max blob count.
1068    pub fn max_blob_count(&self) -> u64 {
1069        self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1070    }
1071}
1072
1073/// Ensures that gas limit of the transaction exceeds the intrinsic gas of the transaction.
1074///
1075/// Caution: This only checks past the Merge hardfork.
1076pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1077    transaction: &T,
1078    fork_tracker: &ForkTracker,
1079) -> Result<(), InvalidPoolTransactionError> {
1080    use revm_primitives::hardfork::SpecId;
1081    let spec_id = if fork_tracker.is_prague_activated() {
1082        SpecId::PRAGUE
1083    } else if fork_tracker.is_shanghai_activated() {
1084        SpecId::SHANGHAI
1085    } else {
1086        SpecId::MERGE
1087    };
1088
1089    let gas = revm_interpreter::gas::calculate_initial_tx_gas(
1090        spec_id,
1091        transaction.input(),
1092        transaction.is_create(),
1093        transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1094        transaction
1095            .access_list()
1096            .map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
1097            .unwrap_or_default() as u64,
1098        transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
1099    );
1100
1101    let gas_limit = transaction.gas_limit();
1102    if gas_limit < gas.initial_gas || gas_limit < gas.floor_gas {
1103        Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1104    } else {
1105        Ok(())
1106    }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111    use super::*;
1112    use crate::{
1113        blobstore::InMemoryBlobStore, error::PoolErrorKind, traits::PoolTransaction,
1114        CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1115    };
1116    use alloy_consensus::Transaction;
1117    use alloy_eips::eip2718::Decodable2718;
1118    use alloy_primitives::{hex, U256};
1119    use reth_ethereum_primitives::PooledTransactionVariant;
1120    use reth_primitives_traits::SignedTransaction;
1121    use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1122
1123    fn get_transaction() -> EthPooledTransaction {
1124        let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1125
1126        let data = hex::decode(raw).unwrap();
1127        let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1128
1129        EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1130    }
1131
1132    // <https://github.com/paradigmxyz/reth/issues/5178>
1133    #[tokio::test]
1134    async fn validate_transaction() {
1135        let transaction = get_transaction();
1136        let mut fork_tracker = ForkTracker {
1137            shanghai: false.into(),
1138            cancun: false.into(),
1139            prague: false.into(),
1140            osaka: false.into(),
1141            max_blob_count: 0.into(),
1142        };
1143
1144        let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1145        assert!(res.is_ok());
1146
1147        fork_tracker.shanghai = true.into();
1148        let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1149        assert!(res.is_ok());
1150
1151        let provider = MockEthProvider::default();
1152        provider.add_account(
1153            transaction.sender(),
1154            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1155        );
1156        let blob_store = InMemoryBlobStore::default();
1157        let validator = EthTransactionValidatorBuilder::new(provider).build(blob_store.clone());
1158
1159        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1160
1161        assert!(outcome.is_valid());
1162
1163        let pool =
1164            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1165
1166        let res = pool.add_external_transaction(transaction.clone()).await;
1167        assert!(res.is_ok());
1168        let tx = pool.get(transaction.hash());
1169        assert!(tx.is_some());
1170    }
1171
1172    // <https://github.com/paradigmxyz/reth/issues/8550>
1173    #[tokio::test]
1174    async fn invalid_on_gas_limit_too_high() {
1175        let transaction = get_transaction();
1176
1177        let provider = MockEthProvider::default();
1178        provider.add_account(
1179            transaction.sender(),
1180            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1181        );
1182
1183        let blob_store = InMemoryBlobStore::default();
1184        let validator = EthTransactionValidatorBuilder::new(provider)
1185            .set_block_gas_limit(1_000_000) // tx gas limit is 1_015_288
1186            .build(blob_store.clone());
1187
1188        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1189
1190        assert!(outcome.is_invalid());
1191
1192        let pool =
1193            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1194
1195        let res = pool.add_external_transaction(transaction.clone()).await;
1196        assert!(res.is_err());
1197        assert!(matches!(
1198            res.unwrap_err().kind,
1199            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1200                1_015_288, 1_000_000
1201            ))
1202        ));
1203        let tx = pool.get(transaction.hash());
1204        assert!(tx.is_none());
1205    }
1206
1207    #[tokio::test]
1208    async fn invalid_on_fee_cap_exceeded() {
1209        let transaction = get_transaction();
1210        let provider = MockEthProvider::default();
1211        provider.add_account(
1212            transaction.sender(),
1213            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1214        );
1215
1216        let blob_store = InMemoryBlobStore::default();
1217        let validator = EthTransactionValidatorBuilder::new(provider)
1218            .set_tx_fee_cap(100) // 100 wei cap
1219            .build(blob_store.clone());
1220
1221        let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1222        assert!(outcome.is_invalid());
1223
1224        if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1225            assert!(matches!(
1226                err,
1227                InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1228                if (max_tx_fee_wei > tx_fee_cap_wei)
1229            ));
1230        }
1231
1232        let pool =
1233            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1234        let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
1235        assert!(res.is_err());
1236        assert!(matches!(
1237            res.unwrap_err().kind,
1238            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
1239        ));
1240        let tx = pool.get(transaction.hash());
1241        assert!(tx.is_none());
1242    }
1243
1244    #[tokio::test]
1245    async fn valid_on_zero_fee_cap() {
1246        let transaction = get_transaction();
1247        let provider = MockEthProvider::default();
1248        provider.add_account(
1249            transaction.sender(),
1250            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1251        );
1252
1253        let blob_store = InMemoryBlobStore::default();
1254        let validator = EthTransactionValidatorBuilder::new(provider)
1255            .set_tx_fee_cap(0) // no cap
1256            .build(blob_store);
1257
1258        let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1259        assert!(outcome.is_valid());
1260    }
1261
1262    #[tokio::test]
1263    async fn valid_on_normal_fee_cap() {
1264        let transaction = get_transaction();
1265        let provider = MockEthProvider::default();
1266        provider.add_account(
1267            transaction.sender(),
1268            ExtendedAccount::new(transaction.nonce(), U256::MAX),
1269        );
1270
1271        let blob_store = InMemoryBlobStore::default();
1272        let validator = EthTransactionValidatorBuilder::new(provider)
1273            .set_tx_fee_cap(2e18 as u128) // 2 ETH cap
1274            .build(blob_store);
1275
1276        let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1277        assert!(outcome.is_valid());
1278    }
1279}