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    traits::TransactionOrigin,
10    validate::{ValidTransaction, ValidationTask, MAX_INIT_CODE_BYTE_SIZE},
11    EthBlobTransactionSidecar, EthPoolTransaction, LocalTransactionConfig,
12    TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator,
13};
14use alloy_consensus::{
15    constants::{
16        EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
17        LEGACY_TX_TYPE_ID, SEISMIC_TX_TYPE_ID,
18    },
19    BlockHeader,
20};
21use alloy_eips::{
22    eip1559::ETHEREUM_BLOCK_GAS_LIMIT,
23    eip4844::{env_settings::EnvKzgSettings, MAX_BLOBS_PER_BLOCK},
24};
25use reth_chainspec::{ChainSpec, EthereumHardforks};
26use reth_primitives::{InvalidTransactionError, SealedBlock};
27use reth_primitives_traits::GotExpected;
28use reth_storage_api::{AccountReader, StateProviderFactory};
29use reth_tasks::TaskSpawner;
30use std::{
31    marker::PhantomData,
32    sync::{
33        atomic::{AtomicBool, AtomicU64},
34        Arc,
35    },
36};
37use tokio::sync::Mutex;
38
39/// Validator for Ethereum transactions.
40#[derive(Debug, Clone)]
41pub struct EthTransactionValidator<Client, T> {
42    /// The type that performs the actual validation.
43    inner: Arc<EthTransactionValidatorInner<Client, T>>,
44}
45
46impl<Client, Tx> EthTransactionValidator<Client, Tx> {
47    /// Returns the configured chain spec
48    pub fn chain_spec(&self) -> &Arc<ChainSpec> {
49        &self.inner.chain_spec
50    }
51
52    /// Returns the configured client
53    pub fn client(&self) -> &Client {
54        &self.inner.client
55    }
56}
57
58impl<Client, Tx> EthTransactionValidator<Client, Tx>
59where
60    Client: StateProviderFactory,
61    Tx: EthPoolTransaction,
62{
63    /// Validates a single transaction.
64    ///
65    /// See also [`TransactionValidator::validate_transaction`]
66    pub fn validate_one(
67        &self,
68        origin: TransactionOrigin,
69        transaction: Tx,
70    ) -> TransactionValidationOutcome<Tx> {
71        self.inner.validate_one(origin, transaction)
72    }
73
74    /// Validates all given transactions.
75    ///
76    /// Returns all outcomes for the given transactions in the same order.
77    ///
78    /// See also [`Self::validate_one`]
79    pub fn validate_all(
80        &self,
81        transactions: Vec<(TransactionOrigin, Tx)>,
82    ) -> Vec<TransactionValidationOutcome<Tx>> {
83        transactions.into_iter().map(|(origin, tx)| self.validate_one(origin, tx)).collect()
84    }
85}
86
87impl<Client, Tx> TransactionValidator for EthTransactionValidator<Client, Tx>
88where
89    Client: StateProviderFactory,
90    Tx: EthPoolTransaction,
91{
92    type Transaction = Tx;
93
94    async fn validate_transaction(
95        &self,
96        origin: TransactionOrigin,
97        transaction: Self::Transaction,
98    ) -> TransactionValidationOutcome<Self::Transaction> {
99        self.validate_one(origin, transaction)
100    }
101
102    async fn validate_transactions(
103        &self,
104        transactions: Vec<(TransactionOrigin, Self::Transaction)>,
105    ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
106        self.validate_all(transactions)
107    }
108
109    fn on_new_head_block(&self, new_tip_block: &SealedBlock) {
110        self.inner.on_new_head_block(new_tip_block.header())
111    }
112}
113
114/// A [`TransactionValidator`] implementation that validates ethereum transaction.
115///
116/// It supports all known ethereum transaction types:
117/// - Legacy
118/// - EIP-2718
119/// - EIP-1559
120/// - EIP-4844
121/// - EIP-7702
122///
123/// And enforces additional constraints such as:
124/// - Maximum transaction size
125/// - Maximum gas limit
126///
127/// And adheres to the configured [`LocalTransactionConfig`].
128#[derive(Debug)]
129pub(crate) struct EthTransactionValidatorInner<Client, T> {
130    /// Spec of the chain
131    chain_spec: Arc<ChainSpec>,
132    /// This type fetches account info from the db
133    client: Client,
134    /// Blobstore used for fetching re-injected blob transactions.
135    blob_store: Box<dyn BlobStore>,
136    /// tracks activated forks relevant for transaction validation
137    fork_tracker: ForkTracker,
138    /// Fork indicator whether we are using EIP-2718 type transactions.
139    eip2718: bool,
140    /// Fork indicator whether we are using EIP-1559 type transactions.
141    eip1559: bool,
142    /// Fork indicator whether we are using EIP-4844 blob transactions.
143    eip4844: bool,
144    /// Fork indicator whether we are using EIP-7702 type transactions.
145    eip7702: bool,
146    /// The current max gas limit
147    block_gas_limit: AtomicU64,
148    /// Minimum priority fee to enforce for acceptance into the pool.
149    minimum_priority_fee: Option<u128>,
150    /// Stores the setup and parameters needed for validating KZG proofs.
151    kzg_settings: EnvKzgSettings,
152    /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.
153    local_transactions_config: LocalTransactionConfig,
154    /// Maximum size in bytes a single transaction can have in order to be accepted into the pool.
155    max_tx_input_bytes: usize,
156    /// Marker for the transaction type
157    _marker: PhantomData<T>,
158}
159
160// === impl EthTransactionValidatorInner ===
161
162impl<Client, Tx> EthTransactionValidatorInner<Client, Tx> {
163    /// Returns the configured chain id
164    pub(crate) fn chain_id(&self) -> u64 {
165        self.chain_spec.chain().id()
166    }
167}
168
169impl<Client, Tx> EthTransactionValidatorInner<Client, Tx>
170where
171    Client: StateProviderFactory,
172    Tx: EthPoolTransaction,
173{
174    /// Validates a single transaction.
175    fn validate_one(
176        &self,
177        origin: TransactionOrigin,
178        mut transaction: Tx,
179    ) -> TransactionValidationOutcome<Tx> {
180        // Checks for tx_type
181        match transaction.tx_type() {
182            LEGACY_TX_TYPE_ID => {
183                // Accept legacy transactions
184            }
185            EIP2930_TX_TYPE_ID => {
186                // Accept only legacy transactions until EIP-2718/2930 activates
187                if !self.eip2718 {
188                    return TransactionValidationOutcome::Invalid(
189                        transaction,
190                        InvalidTransactionError::Eip2930Disabled.into(),
191                    )
192                }
193            }
194            EIP1559_TX_TYPE_ID => {
195                // Reject dynamic fee transactions until EIP-1559 activates.
196                if !self.eip1559 {
197                    return TransactionValidationOutcome::Invalid(
198                        transaction,
199                        InvalidTransactionError::Eip1559Disabled.into(),
200                    )
201                }
202            }
203            EIP4844_TX_TYPE_ID => {
204                // Reject blob transactions.
205                if !self.eip4844 {
206                    return TransactionValidationOutcome::Invalid(
207                        transaction,
208                        InvalidTransactionError::Eip4844Disabled.into(),
209                    )
210                }
211            }
212            EIP7702_TX_TYPE_ID => {
213                // Reject EIP-7702 transactions.
214                if !self.eip7702 {
215                    return TransactionValidationOutcome::Invalid(
216                        transaction,
217                        InvalidTransactionError::Eip7702Disabled.into(),
218                    )
219                }
220            }
221
222            SEISMIC_TX_TYPE_ID => {
223                // Accept Seismic transactions.
224            }
225
226            _ => {
227                return TransactionValidationOutcome::Invalid(
228                    transaction,
229                    InvalidTransactionError::TxTypeNotSupported.into(),
230                )
231            }
232        };
233
234        // Reject transactions over defined size to prevent DOS attacks
235        let transaction_size = transaction.size();
236        if transaction_size > self.max_tx_input_bytes {
237            return TransactionValidationOutcome::Invalid(
238                transaction,
239                InvalidPoolTransactionError::OversizedData(
240                    transaction_size,
241                    self.max_tx_input_bytes,
242                ),
243            )
244        }
245
246        // Check whether the init code size has been exceeded.
247        if self.fork_tracker.is_shanghai_activated() {
248            if let Err(err) = transaction.ensure_max_init_code_size(MAX_INIT_CODE_BYTE_SIZE) {
249                return TransactionValidationOutcome::Invalid(transaction, err)
250            }
251        }
252
253        // Checks for gas limit
254        let transaction_gas_limit = transaction.gas_limit();
255        let block_gas_limit = self.max_gas_limit();
256        if transaction_gas_limit > block_gas_limit {
257            return TransactionValidationOutcome::Invalid(
258                transaction,
259                InvalidPoolTransactionError::ExceedsGasLimit(
260                    transaction_gas_limit,
261                    block_gas_limit,
262                ),
263            )
264        }
265
266        // Ensure max_priority_fee_per_gas (if EIP1559) is less than max_fee_per_gas if any.
267        if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
268            return TransactionValidationOutcome::Invalid(
269                transaction,
270                InvalidTransactionError::TipAboveFeeCap.into(),
271            )
272        }
273
274        // Drop non-local transactions with a fee lower than the configured fee for acceptance into
275        // the pool.
276        if !self.local_transactions_config.is_local(origin, transaction.sender_ref()) &&
277            transaction.is_eip1559() &&
278            transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
279        {
280            return TransactionValidationOutcome::Invalid(
281                transaction,
282                InvalidPoolTransactionError::Underpriced,
283            )
284        }
285
286        // Checks for chainid
287        if let Some(chain_id) = transaction.chain_id() {
288            if chain_id != self.chain_id() {
289                return TransactionValidationOutcome::Invalid(
290                    transaction,
291                    InvalidTransactionError::ChainIdMismatch.into(),
292                )
293            }
294        }
295
296        if transaction.is_eip7702() {
297            // Cancun fork is required for 7702 txs
298            if !self.fork_tracker.is_prague_activated() {
299                return TransactionValidationOutcome::Invalid(
300                    transaction,
301                    InvalidTransactionError::TxTypeNotSupported.into(),
302                )
303            }
304
305            if transaction.authorization_count() == 0 {
306                return TransactionValidationOutcome::Invalid(
307                    transaction,
308                    Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into(),
309                )
310            }
311        }
312
313        if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
314            return TransactionValidationOutcome::Invalid(transaction, err)
315        }
316
317        // light blob tx pre-checks
318        if transaction.is_eip4844() {
319            // Cancun fork is required for blob txs
320            if !self.fork_tracker.is_cancun_activated() {
321                return TransactionValidationOutcome::Invalid(
322                    transaction,
323                    InvalidTransactionError::TxTypeNotSupported.into(),
324                )
325            }
326
327            let blob_count = transaction.blob_count();
328            if blob_count == 0 {
329                // no blobs
330                return TransactionValidationOutcome::Invalid(
331                    transaction,
332                    InvalidPoolTransactionError::Eip4844(
333                        Eip4844PoolTransactionError::NoEip4844Blobs,
334                    ),
335                )
336            }
337
338            if blob_count > MAX_BLOBS_PER_BLOCK {
339                // too many blobs
340                return TransactionValidationOutcome::Invalid(
341                    transaction,
342                    InvalidPoolTransactionError::Eip4844(
343                        Eip4844PoolTransactionError::TooManyEip4844Blobs {
344                            have: blob_count,
345                            permitted: MAX_BLOBS_PER_BLOCK,
346                        },
347                    ),
348                )
349            }
350        }
351
352        let account = match self
353            .client
354            .latest()
355            .and_then(|state| state.basic_account(transaction.sender()))
356        {
357            Ok(account) => account.unwrap_or_default(),
358            Err(err) => {
359                return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
360            }
361        };
362
363        // Unless Prague is active, the signer account shouldn't have bytecode.
364        //
365        // If Prague is active, only EIP-7702 bytecode is allowed for the sender.
366        //
367        // Any other case means that the account is not an EOA, and should not be able to send
368        // transactions.
369        if account.has_bytecode() {
370            let is_eip7702 = if self.fork_tracker.is_prague_activated() {
371                match self
372                    .client
373                    .latest()
374                    .and_then(|state| state.bytecode_by_hash(account.get_bytecode_hash()))
375                {
376                    Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
377                    Err(err) => {
378                        return TransactionValidationOutcome::Error(
379                            *transaction.hash(),
380                            Box::new(err),
381                        )
382                    }
383                }
384            } else {
385                false
386            };
387
388            if !is_eip7702 {
389                return TransactionValidationOutcome::Invalid(
390                    transaction,
391                    InvalidTransactionError::SignerAccountHasBytecode.into(),
392                )
393            }
394        }
395
396        let tx_nonce = transaction.nonce();
397
398        // Checks for nonce
399        if tx_nonce < account.nonce {
400            return TransactionValidationOutcome::Invalid(
401                transaction,
402                InvalidTransactionError::NonceNotConsistent { tx: tx_nonce, state: account.nonce }
403                    .into(),
404            )
405        }
406
407        let cost = transaction.cost();
408
409        // Checks for max cost
410        if cost > &account.balance {
411            let expected = *cost;
412            return TransactionValidationOutcome::Invalid(
413                transaction,
414                InvalidTransactionError::InsufficientFunds(
415                    GotExpected { got: account.balance, expected }.into(),
416                )
417                .into(),
418            )
419        }
420
421        let mut maybe_blob_sidecar = None;
422
423        // heavy blob tx validation
424        if transaction.is_eip4844() {
425            // extract the blob from the transaction
426            match transaction.take_blob() {
427                EthBlobTransactionSidecar::None => {
428                    // this should not happen
429                    return TransactionValidationOutcome::Invalid(
430                        transaction,
431                        InvalidTransactionError::TxTypeNotSupported.into(),
432                    )
433                }
434                EthBlobTransactionSidecar::Missing => {
435                    // This can happen for re-injected blob transactions (on re-org), since the blob
436                    // is stripped from the transaction and not included in a block.
437                    // check if the blob is in the store, if it's included we previously validated
438                    // it and inserted it
439                    if matches!(self.blob_store.contains(*transaction.hash()), Ok(true)) {
440                        // validated transaction is already in the store
441                    } else {
442                        return TransactionValidationOutcome::Invalid(
443                            transaction,
444                            InvalidPoolTransactionError::Eip4844(
445                                Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
446                            ),
447                        )
448                    }
449                }
450                EthBlobTransactionSidecar::Present(blob) => {
451                    // validate the blob
452                    if let Err(err) = transaction.validate_blob(&blob, self.kzg_settings.get()) {
453                        return TransactionValidationOutcome::Invalid(
454                            transaction,
455                            InvalidPoolTransactionError::Eip4844(
456                                Eip4844PoolTransactionError::InvalidEip4844Blob(err),
457                            ),
458                        )
459                    }
460                    // store the extracted blob
461                    maybe_blob_sidecar = Some(blob);
462                }
463            }
464        }
465
466        // Return the valid transaction
467        TransactionValidationOutcome::Valid {
468            balance: account.balance,
469            state_nonce: account.nonce,
470            transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
471            // by this point assume all external transactions should be propagated
472            propagate: match origin {
473                TransactionOrigin::External => true,
474                TransactionOrigin::Local => {
475                    self.local_transactions_config.propagate_local_transactions
476                }
477                TransactionOrigin::Private => false,
478            },
479        }
480    }
481
482    fn on_new_head_block<T: BlockHeader>(&self, new_tip_block: &T) {
483        // update all forks
484        if self.chain_spec.is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
485            self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
486        }
487
488        if self.chain_spec.is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
489            self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
490        }
491
492        if self.chain_spec.is_prague_active_at_timestamp(new_tip_block.timestamp()) {
493            self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
494        }
495
496        self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
497    }
498
499    fn max_gas_limit(&self) -> u64 {
500        self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
501    }
502}
503
504/// A builder for [`TransactionValidationTaskExecutor`]
505#[derive(Debug)]
506pub struct EthTransactionValidatorBuilder {
507    chain_spec: Arc<ChainSpec>,
508    /// Fork indicator whether we are in the Shanghai stage.
509    shanghai: bool,
510    /// Fork indicator whether we are in the Cancun hardfork.
511    cancun: bool,
512    /// Fork indicator whether we are in the Cancun hardfork.
513    prague: bool,
514    /// Whether using EIP-2718 type transactions is allowed
515    eip2718: bool,
516    /// Whether using EIP-1559 type transactions is allowed
517    eip1559: bool,
518    /// Whether using EIP-4844 type transactions is allowed
519    eip4844: bool,
520    /// Whether using EIP-7702 type transactions is allowed
521    eip7702: bool,
522    /// The current max gas limit
523    block_gas_limit: AtomicU64,
524    /// Minimum priority fee to enforce for acceptance into the pool.
525    minimum_priority_fee: Option<u128>,
526    /// Determines how many additional tasks to spawn
527    ///
528    /// Default is 1
529    additional_tasks: usize,
530
531    /// Stores the setup and parameters needed for validating KZG proofs.
532    kzg_settings: EnvKzgSettings,
533    /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.
534    local_transactions_config: LocalTransactionConfig,
535    /// Max size in bytes of a single transaction allowed
536    max_tx_input_bytes: usize,
537}
538
539impl EthTransactionValidatorBuilder {
540    /// Creates a new builder for the given [`ChainSpec`]
541    ///
542    /// By default this assumes the network is on the `Cancun` hardfork and the following
543    /// transactions are allowed:
544    ///  - Legacy
545    ///  - EIP-2718
546    ///  - EIP-1559
547    ///  - EIP-4844
548    pub fn new(chain_spec: Arc<ChainSpec>) -> Self {
549        Self {
550            block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT.into(),
551            chain_spec,
552            minimum_priority_fee: None,
553            additional_tasks: 1,
554            kzg_settings: EnvKzgSettings::Default,
555            local_transactions_config: Default::default(),
556            max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
557
558            // by default all transaction types are allowed
559            eip2718: true,
560            eip1559: true,
561            eip4844: true,
562            eip7702: true,
563
564            // shanghai is activated by default
565            shanghai: true,
566
567            // cancun is activated by default
568            cancun: true,
569
570            // prague not yet activated
571            prague: false,
572        }
573    }
574
575    /// Disables the Cancun fork.
576    pub const fn no_cancun(self) -> Self {
577        self.set_cancun(false)
578    }
579
580    /// Whether to allow exemptions for local transaction exemptions.
581    pub fn with_local_transactions_config(
582        mut self,
583        local_transactions_config: LocalTransactionConfig,
584    ) -> Self {
585        self.local_transactions_config = local_transactions_config;
586        self
587    }
588
589    /// Set the Cancun fork.
590    pub const fn set_cancun(mut self, cancun: bool) -> Self {
591        self.cancun = cancun;
592        self
593    }
594
595    /// Disables the Shanghai fork.
596    pub const fn no_shanghai(self) -> Self {
597        self.set_shanghai(false)
598    }
599
600    /// Set the Shanghai fork.
601    pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
602        self.shanghai = shanghai;
603        self
604    }
605
606    /// Disables the Prague fork.
607    pub const fn no_prague(self) -> Self {
608        self.set_prague(false)
609    }
610
611    /// Set the Prague fork.
612    pub const fn set_prague(mut self, prague: bool) -> Self {
613        self.prague = prague;
614        self
615    }
616
617    /// Disables the support for EIP-2718 transactions.
618    pub const fn no_eip2718(self) -> Self {
619        self.set_eip2718(false)
620    }
621
622    /// Set the support for EIP-2718 transactions.
623    pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
624        self.eip2718 = eip2718;
625        self
626    }
627
628    /// Disables the support for EIP-1559 transactions.
629    pub const fn no_eip1559(self) -> Self {
630        self.set_eip1559(false)
631    }
632
633    /// Set the support for EIP-1559 transactions.
634    pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
635        self.eip1559 = eip1559;
636        self
637    }
638
639    /// Disables the support for EIP-4844 transactions.
640    pub const fn no_eip4844(self) -> Self {
641        self.set_eip4844(false)
642    }
643
644    /// Set the support for EIP-4844 transactions.
645    pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
646        self.eip4844 = eip4844;
647        self
648    }
649
650    /// Sets the [`EnvKzgSettings`] to use for validating KZG proofs.
651    pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
652        self.kzg_settings = kzg_settings;
653        self
654    }
655
656    /// Sets a minimum priority fee that's enforced for acceptance into the pool.
657    pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: u128) -> Self {
658        self.minimum_priority_fee = Some(minimum_priority_fee);
659        self
660    }
661
662    /// Sets the number of additional tasks to spawn.
663    pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
664        self.additional_tasks = additional_tasks;
665        self
666    }
667
668    /// Configures validation rules based on the head block's timestamp.
669    ///
670    /// For example, whether the Shanghai and Cancun hardfork is activated at launch.
671    pub fn with_head_timestamp(mut self, timestamp: u64) -> Self {
672        self.cancun = self.chain_spec.is_cancun_active_at_timestamp(timestamp);
673        self.shanghai = self.chain_spec.is_shanghai_active_at_timestamp(timestamp);
674        self.prague = self.chain_spec.is_prague_active_at_timestamp(timestamp);
675        self
676    }
677
678    /// Sets a max size in bytes of a single transaction allowed into the pool
679    pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
680        self.max_tx_input_bytes = max_tx_input_bytes;
681        self
682    }
683
684    /// Sets the block gas limit
685    ///
686    /// Transactions with a gas limit greater than this will be rejected.
687    pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
688        self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
689        self
690    }
691
692    /// Builds a the [`EthTransactionValidator`] without spawning validator tasks.
693    pub fn build<Client, Tx, S>(
694        self,
695        client: Client,
696        blob_store: S,
697    ) -> EthTransactionValidator<Client, Tx>
698    where
699        S: BlobStore,
700    {
701        let Self {
702            chain_spec,
703            shanghai,
704            cancun,
705            prague,
706            eip2718,
707            eip1559,
708            eip4844,
709            eip7702,
710            block_gas_limit,
711            minimum_priority_fee,
712            kzg_settings,
713            local_transactions_config,
714            max_tx_input_bytes,
715            ..
716        } = self;
717
718        let fork_tracker = ForkTracker {
719            shanghai: AtomicBool::new(shanghai),
720            cancun: AtomicBool::new(cancun),
721            prague: AtomicBool::new(prague),
722        };
723
724        let inner = EthTransactionValidatorInner {
725            chain_spec,
726            client,
727            eip2718,
728            eip1559,
729            fork_tracker,
730            eip4844,
731            eip7702,
732            block_gas_limit,
733            minimum_priority_fee,
734            blob_store: Box::new(blob_store),
735            kzg_settings,
736            local_transactions_config,
737            max_tx_input_bytes,
738            _marker: Default::default(),
739        };
740
741        EthTransactionValidator { inner: Arc::new(inner) }
742    }
743
744    /// Builds a [`EthTransactionValidator`] and spawns validation tasks via the
745    /// [`TransactionValidationTaskExecutor`]
746    ///
747    /// The validator will spawn `additional_tasks` additional tasks for validation.
748    ///
749    /// By default this will spawn 1 additional task.
750    pub fn build_with_tasks<Client, Tx, T, S>(
751        self,
752        client: Client,
753        tasks: T,
754        blob_store: S,
755    ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx>>
756    where
757        T: TaskSpawner,
758        S: BlobStore,
759    {
760        let additional_tasks = self.additional_tasks;
761        let validator = self.build(client, blob_store);
762
763        let (tx, task) = ValidationTask::new();
764
765        // Spawn validation tasks, they are blocking because they perform db lookups
766        for _ in 0..additional_tasks {
767            let task = task.clone();
768            tasks.spawn_blocking(Box::pin(async move {
769                task.run().await;
770            }));
771        }
772
773        // we spawn them on critical tasks because validation, especially for EIP-4844 can be quite
774        // heavy
775        tasks.spawn_critical_blocking(
776            "transaction-validation-service",
777            Box::pin(async move {
778                task.run().await;
779            }),
780        );
781
782        let to_validation_task = Arc::new(Mutex::new(tx));
783
784        TransactionValidationTaskExecutor { validator, to_validation_task }
785    }
786}
787
788/// Keeps track of whether certain forks are activated
789#[derive(Debug)]
790pub struct ForkTracker {
791    /// Tracks if shanghai is activated at the block's timestamp.
792    pub shanghai: AtomicBool,
793    /// Tracks if cancun is activated at the block's timestamp.
794    pub cancun: AtomicBool,
795    /// Tracks if prague is activated at the block's timestamp.
796    pub prague: AtomicBool,
797}
798
799impl ForkTracker {
800    /// Returns `true` if Shanghai fork is activated.
801    pub fn is_shanghai_activated(&self) -> bool {
802        self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
803    }
804
805    /// Returns `true` if Cancun fork is activated.
806    pub fn is_cancun_activated(&self) -> bool {
807        self.cancun.load(std::sync::atomic::Ordering::Relaxed)
808    }
809
810    /// Returns `true` if Prague fork is activated.
811    pub fn is_prague_activated(&self) -> bool {
812        self.prague.load(std::sync::atomic::Ordering::Relaxed)
813    }
814}
815
816/// Ensures that gas limit of the transaction exceeds the intrinsic gas of the transaction.
817///
818/// Caution: This only checks past the Merge hardfork.
819pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
820    transaction: &T,
821    fork_tracker: &ForkTracker,
822) -> Result<(), InvalidPoolTransactionError> {
823    use revm_primitives::SpecId;
824    let spec_id = if fork_tracker.is_prague_activated() {
825        SpecId::PRAGUE
826    } else if fork_tracker.is_shanghai_activated() {
827        SpecId::SHANGHAI
828    } else {
829        SpecId::MERGE
830    };
831
832    let gas_after_merge = revm_interpreter::gas::validate_initial_tx_gas(
833        spec_id,
834        transaction.input(),
835        transaction.is_create(),
836        transaction.access_list().map(|list| list.0.as_slice()).unwrap_or(&[]),
837        transaction.authorization_count() as u64,
838    );
839
840    if transaction.gas_limit() < gas_after_merge {
841        Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
842    } else {
843        Ok(())
844    }
845}
846
847#[cfg(test)]
848mod tests {
849    use super::*;
850    use crate::{
851        blobstore::InMemoryBlobStore, error::PoolErrorKind, traits::PoolTransaction,
852        CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
853    };
854    use alloy_eips::eip2718::Decodable2718;
855    use alloy_primitives::{hex, U256};
856    use reth_chainspec::MAINNET;
857    use reth_primitives::PooledTransactionsElement;
858    use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
859
860    fn get_transaction() -> EthPooledTransaction {
861        let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
862
863        let data = hex::decode(raw).unwrap();
864        let tx = PooledTransactionsElement::decode_2718(&mut data.as_ref()).unwrap();
865
866        tx.try_into_ecrecovered().unwrap().into()
867    }
868
869    // <https://github.com/paradigmxyz/reth/issues/5178>
870    #[tokio::test]
871    async fn validate_transaction() {
872        let transaction = get_transaction();
873        let mut fork_tracker =
874            ForkTracker { shanghai: false.into(), cancun: false.into(), prague: false.into() };
875
876        let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
877        assert!(res.is_ok());
878
879        fork_tracker.shanghai = true.into();
880        let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
881        assert!(res.is_ok());
882
883        let provider = MockEthProvider::default();
884        provider.add_account(
885            transaction.sender(),
886            ExtendedAccount::new(transaction.nonce(), U256::MAX),
887        );
888        let blob_store = InMemoryBlobStore::default();
889        let validator = EthTransactionValidatorBuilder::new(MAINNET.clone())
890            .build(provider, blob_store.clone());
891
892        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
893
894        assert!(outcome.is_valid());
895
896        let pool =
897            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
898
899        let res = pool.add_external_transaction(transaction.clone()).await;
900        assert!(res.is_ok());
901        let tx = pool.get(transaction.hash());
902        assert!(tx.is_some());
903    }
904
905    // <https://github.com/paradigmxyz/reth/issues/8550>
906    #[tokio::test]
907    async fn invalid_on_gas_limit_too_high() {
908        let transaction = get_transaction();
909
910        let provider = MockEthProvider::default();
911        provider.add_account(
912            transaction.sender(),
913            ExtendedAccount::new(transaction.nonce(), U256::MAX),
914        );
915
916        let blob_store = InMemoryBlobStore::default();
917        let validator = EthTransactionValidatorBuilder::new(MAINNET.clone())
918            .set_block_gas_limit(1_000_000) // tx gas limit is 1_015_288
919            .build(provider, blob_store.clone());
920
921        let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
922
923        assert!(outcome.is_invalid());
924
925        let pool =
926            Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
927
928        let res = pool.add_external_transaction(transaction.clone()).await;
929        assert!(res.is_err());
930        assert!(matches!(
931            res.unwrap_err().kind,
932            PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
933                1_015_288, 1_000_000
934            ))
935        ));
936        let tx = pool.get(transaction.hash());
937        assert!(tx.is_none());
938    }
939}