reth_transaction_pool/validate/
eth.rs

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