1use super::constants::DEFAULT_MAX_TX_INPUT_BYTES;
4use crate::{
5 blobstore::{BlobStore, PooledBlobSidecar},
6 error::{
7 Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
8 },
9 metrics::TxPoolValidationMetrics,
10 traits::TransactionOrigin,
11 validate::ValidTransaction,
12 Address, EthBlobTransactionSidecar, EthPoolTransaction, LocalTransactionConfig,
13 TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator,
14};
15
16use alloy_consensus::{
17 constants::{
18 EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
19 KECCAK_EMPTY, LEGACY_TX_TYPE_ID,
20 },
21 BlockHeader,
22};
23use alloy_eips::{
24 eip4844::env_settings::EnvKzgSettings, eip7840::BlobParams, merge::SLOT_DURATION_SECS, BlockId,
25};
26use alloy_primitives::U256;
27use alloy_rlp::Encodable;
28use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
29use reth_evm::ConfigureEvm;
30use reth_primitives_traits::{
31 transaction::error::InvalidTransactionError, Account, BlockTy, GotExpected, HeaderTy,
32 SealedBlock,
33};
34use reth_storage_api::{
35 errors::ProviderError, AccountInfoReader, BlockReaderIdExt, BytecodeReader, StateProviderBox,
36 StateProviderFactory,
37};
38use reth_tasks::Runtime;
39use revm::context_interface::Cfg;
40use std::{
41 fmt,
42 marker::PhantomData,
43 sync::{
44 atomic::{AtomicBool, AtomicU64, AtomicUsize},
45 Arc,
46 },
47 time::{Instant, SystemTime},
48};
49
50pub type StatelessValidationFn<T> =
55 Arc<dyn Fn(TransactionOrigin, &T) -> Result<(), InvalidPoolTransactionError> + Send + Sync>;
56
57pub type StatefulValidationFn<T> = Arc<
62 dyn Fn(TransactionOrigin, &T, &dyn AccountInfoReader) -> Result<(), InvalidPoolTransactionError>
63 + Send
64 + Sync,
65>;
66
67pub struct EthTransactionValidator<Client, T, Evm> {
82 client: Client,
84 chain_id: u64,
86 blob_store: Box<dyn BlobStore>,
88 fork_tracker: ForkTracker,
90 eip2718: bool,
92 eip1559: bool,
94 eip4844: bool,
96 eip7702: bool,
98 block_gas_limit: AtomicU64,
100 tx_fee_cap: Option<u128>,
102 minimum_priority_fee: Option<u128>,
106 kzg_settings: EnvKzgSettings,
108 local_transactions_config: LocalTransactionConfig,
110 max_tx_input_bytes: usize,
112 max_tx_gas_limit: Option<u64>,
114 disable_balance_check: bool,
116 evm_config: Evm,
118 _marker: PhantomData<T>,
120 validation_metrics: TxPoolValidationMetrics,
122 other_tx_types: U256,
124 eip7594: bool,
128 additional_stateless_validation: Option<StatelessValidationFn<T>>,
131 additional_stateful_validation: Option<StatefulValidationFn<T>>,
134}
135
136impl<Client, Tx, Evm> fmt::Debug for EthTransactionValidator<Client, Tx, Evm> {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 f.debug_struct("EthTransactionValidator")
139 .field("fork_tracker", &self.fork_tracker)
140 .field("eip2718", &self.eip2718)
141 .field("eip1559", &self.eip1559)
142 .field("eip4844", &self.eip4844)
143 .field("eip7702", &self.eip7702)
144 .field("block_gas_limit", &self.block_gas_limit)
145 .field("tx_fee_cap", &self.tx_fee_cap)
146 .field("minimum_priority_fee", &self.minimum_priority_fee)
147 .field("max_tx_input_bytes", &self.max_tx_input_bytes)
148 .field("max_tx_gas_limit", &self.max_tx_gas_limit)
149 .field("disable_balance_check", &self.disable_balance_check)
150 .field("eip7594", &self.eip7594)
151 .field(
152 "additional_stateless_validation",
153 &self.additional_stateless_validation.as_ref().map(|_| "..."),
154 )
155 .field(
156 "additional_stateful_validation",
157 &self.additional_stateful_validation.as_ref().map(|_| "..."),
158 )
159 .finish()
160 }
161}
162
163impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm> {
164 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
166 where
167 Client: ChainSpecProvider,
168 {
169 self.client().chain_spec()
170 }
171
172 pub const fn chain_id(&self) -> u64 {
174 self.chain_id
175 }
176
177 pub const fn client(&self) -> &Client {
179 &self.client
180 }
181
182 pub const fn fork_tracker(&self) -> &ForkTracker {
184 &self.fork_tracker
185 }
186
187 pub const fn evm_config(&self) -> &Evm {
189 &self.evm_config
190 }
191
192 pub const fn eip2718(&self) -> bool {
194 self.eip2718
195 }
196
197 pub const fn eip1559(&self) -> bool {
199 self.eip1559
200 }
201
202 pub const fn eip4844(&self) -> bool {
204 self.eip4844
205 }
206
207 pub const fn eip7702(&self) -> bool {
209 self.eip7702
210 }
211
212 pub const fn tx_fee_cap(&self) -> &Option<u128> {
214 &self.tx_fee_cap
215 }
216
217 pub const fn minimum_priority_fee(&self) -> &Option<u128> {
219 &self.minimum_priority_fee
220 }
221
222 pub const fn kzg_settings(&self) -> &EnvKzgSettings {
224 &self.kzg_settings
225 }
226
227 pub const fn local_transactions_config(&self) -> &LocalTransactionConfig {
229 &self.local_transactions_config
230 }
231
232 pub const fn max_tx_input_bytes(&self) -> usize {
235 self.max_tx_input_bytes
236 }
237
238 pub const fn disable_balance_check(&self) -> bool {
240 self.disable_balance_check
241 }
242
243 pub fn set_additional_stateless_validation<F>(&mut self, f: F)
268 where
269 F: Fn(TransactionOrigin, &Tx) -> Result<(), InvalidPoolTransactionError>
270 + Send
271 + Sync
272 + 'static,
273 {
274 self.additional_stateless_validation = Some(Arc::new(f));
275 }
276
277 pub fn set_additional_stateless_validation_fn(&mut self, f: StatelessValidationFn<Tx>) {
284 self.additional_stateless_validation = Some(f);
285 }
286
287 pub fn set_additional_stateless_validation_fn_opt(
292 &mut self,
293 f: Option<StatelessValidationFn<Tx>>,
294 ) {
295 self.additional_stateless_validation = f;
296 }
297
298 pub fn set_additional_stateful_validation<F>(&mut self, f: F)
323 where
324 F: Fn(
325 TransactionOrigin,
326 &Tx,
327 &dyn AccountInfoReader,
328 ) -> Result<(), InvalidPoolTransactionError>
329 + Send
330 + Sync
331 + 'static,
332 {
333 self.additional_stateful_validation = Some(Arc::new(f));
334 }
335
336 pub fn set_additional_stateful_validation_fn(&mut self, f: StatefulValidationFn<Tx>) {
343 self.additional_stateful_validation = Some(f);
344 }
345
346 pub fn set_additional_stateful_validation_fn_opt(
351 &mut self,
352 f: Option<StatefulValidationFn<Tx>>,
353 ) {
354 self.additional_stateful_validation = f;
355 }
356}
357
358impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm>
359where
360 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
361 Tx: EthPoolTransaction,
362 Evm: ConfigureEvm,
363{
364 pub fn block_gas_limit(&self) -> u64 {
366 self.max_gas_limit()
367 }
368
369 pub fn validate_one(
373 &self,
374 origin: TransactionOrigin,
375 transaction: Tx,
376 ) -> TransactionValidationOutcome<Tx> {
377 let mut state: Option<StateProviderBox> = None;
378 self.validate_one_with_provider(origin, transaction, &mut state, || self.client.latest())
379 }
380
381 pub fn validate_one_with_state(
388 &self,
389 origin: TransactionOrigin,
390 transaction: Tx,
391 state: &mut Option<Box<dyn AccountInfoReader + Send>>,
392 ) -> TransactionValidationOutcome<Tx> {
393 self.validate_one_with_provider(origin, transaction, state, || {
394 self.client.latest().map(|state| Box::new(state) as Box<dyn AccountInfoReader + Send>)
395 })
396 }
397
398 fn validate_one_with_provider<P, F>(
402 &self,
403 origin: TransactionOrigin,
404 transaction: Tx,
405 maybe_state: &mut Option<P>,
406 state_provider: F,
407 ) -> TransactionValidationOutcome<Tx>
408 where
409 P: AccountInfoReader,
410 F: FnOnce() -> Result<P, ProviderError>,
411 {
412 match self.validate_stateless(origin, &transaction) {
413 Ok(()) => {
414 if maybe_state.is_none() {
417 match state_provider() {
418 Ok(new_state) => {
419 *maybe_state = Some(new_state);
420 }
421 Err(err) => {
422 return TransactionValidationOutcome::Error(
423 *transaction.hash(),
424 Box::new(err),
425 )
426 }
427 }
428 }
429
430 let state = maybe_state.as_ref().expect("provider is set");
431
432 self.validate_stateful(origin, transaction, state)
433 }
434 Err(err) => TransactionValidationOutcome::Invalid(transaction, err),
435 }
436 }
437
438 pub fn validate_one_with_state_provider(
441 &self,
442 origin: TransactionOrigin,
443 transaction: Tx,
444 state: impl AccountInfoReader,
445 ) -> TransactionValidationOutcome<Tx> {
446 if let Err(err) = self.validate_stateless(origin, &transaction) {
447 return TransactionValidationOutcome::Invalid(transaction, err);
448 }
449 self.validate_stateful(origin, transaction, state)
450 }
451
452 pub fn validate_stateless(
457 &self,
458 origin: TransactionOrigin,
459 transaction: &Tx,
460 ) -> Result<(), InvalidPoolTransactionError> {
461 match transaction.ty() {
463 EIP2930_TX_TYPE_ID if !self.eip2718 => {
465 return Err(InvalidTransactionError::Eip2930Disabled.into())
466 }
467 EIP1559_TX_TYPE_ID if !self.eip1559 => {
469 return Err(InvalidTransactionError::Eip1559Disabled.into())
470 }
471 EIP4844_TX_TYPE_ID if !self.eip4844 => {
473 return Err(InvalidTransactionError::Eip4844Disabled.into())
474 }
475 EIP7702_TX_TYPE_ID if !self.eip7702 => {
477 return Err(InvalidTransactionError::Eip7702Disabled.into())
478 }
479 LEGACY_TX_TYPE_ID | EIP2930_TX_TYPE_ID | EIP1559_TX_TYPE_ID | EIP4844_TX_TYPE_ID |
481 EIP7702_TX_TYPE_ID => {}
482
483 ty if !self.other_tx_types.bit(ty as usize) => {
484 return Err(InvalidTransactionError::TxTypeNotSupported.into())
485 }
486
487 _ => {}
488 };
489
490 if transaction.requires_nonce_bound_check() && transaction.nonce() == u64::MAX {
492 return Err(InvalidPoolTransactionError::Eip2681)
493 }
494
495 if transaction.is_eip4844() {
497 let tx_size = transaction.input().len().saturating_add(
502 transaction
503 .access_list()
504 .map(|access_list| access_list.length())
505 .unwrap_or_default(),
506 );
507 if tx_size > self.max_tx_input_bytes {
508 self.validation_metrics.rejected_oversized_data.increment(1);
509 return Err(InvalidPoolTransactionError::OversizedData {
510 size: tx_size,
511 limit: self.max_tx_input_bytes,
512 })
513 }
514 } else {
515 let tx_size = transaction.encoded_length();
517 if tx_size > self.max_tx_input_bytes {
518 self.validation_metrics.rejected_oversized_data.increment(1);
519 return Err(InvalidPoolTransactionError::OversizedData {
520 size: tx_size,
521 limit: self.max_tx_input_bytes,
522 })
523 }
524 }
525
526 if self.fork_tracker.is_shanghai_activated() {
528 let max_initcode_size =
529 self.fork_tracker.max_initcode_size.load(std::sync::atomic::Ordering::Relaxed);
530 transaction.ensure_max_init_code_size(max_initcode_size)?;
531 }
532
533 let transaction_gas_limit = transaction.gas_limit();
535 let block_gas_limit = self.max_gas_limit();
536 if transaction_gas_limit > block_gas_limit {
537 return Err(InvalidPoolTransactionError::ExceedsGasLimit(
538 transaction_gas_limit,
539 block_gas_limit,
540 ))
541 }
542
543 if let Some(max_tx_gas_limit) = self.max_tx_gas_limit &&
545 transaction_gas_limit > max_tx_gas_limit
546 {
547 return Err(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
548 transaction_gas_limit,
549 max_tx_gas_limit,
550 ))
551 }
552
553 if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
555 self.validation_metrics.rejected_tip_above_fee_cap.increment(1);
556 return Err(InvalidTransactionError::TipAboveFeeCap.into())
557 }
558
559 let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
561
562 if is_local {
565 match self.tx_fee_cap {
566 Some(0) | None => {} Some(tx_fee_cap_wei) => {
568 let max_tx_fee_wei = transaction.cost().saturating_sub(transaction.value());
569 if max_tx_fee_wei > tx_fee_cap_wei {
570 self.validation_metrics.rejected_exceeds_fee_cap.increment(1);
571 return Err(InvalidPoolTransactionError::ExceedsFeeCap {
572 max_tx_fee_wei: max_tx_fee_wei.saturating_to(),
573 tx_fee_cap_wei,
574 })
575 }
576 }
577 }
578 }
579
580 if let Some(minimum_priority_fee) = self.minimum_priority_fee &&
584 transaction.priority_fee_or_price() < minimum_priority_fee
585 {
586 self.validation_metrics.rejected_priority_fee_below_minimum.increment(1);
587 return Err(InvalidPoolTransactionError::PriorityFeeBelowMinimum {
588 minimum_priority_fee,
589 })
590 }
591
592 if let Some(chain_id) = transaction.chain_id() &&
594 chain_id != self.chain_id()
595 {
596 return Err(InvalidTransactionError::ChainIdMismatch.into())
597 }
598
599 if transaction.is_eip7702() {
600 if !self.fork_tracker.is_prague_activated() {
602 return Err(InvalidTransactionError::TxTypeNotSupported.into())
603 }
604
605 if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
606 return Err(Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into())
607 }
608 }
609
610 ensure_intrinsic_gas(transaction, &self.fork_tracker).inspect_err(|err| {
611 if matches!(err, InvalidPoolTransactionError::IntrinsicGasTooLow) {
612 self.validation_metrics.rejected_intrinsic_gas_too_low.increment(1);
613 } else {
614 self.validation_metrics.rejected_intrinsic_gas_too_high.increment(1);
615 }
616 })?;
617
618 if transaction.is_eip4844() {
620 if !self.fork_tracker.is_cancun_activated() {
622 return Err(InvalidTransactionError::TxTypeNotSupported.into())
623 }
624
625 let blob_count = transaction.blob_count().unwrap_or(0);
626 if blob_count == 0 {
627 self.validation_metrics.invalid_4844.increment(1);
629 return Err(InvalidPoolTransactionError::Eip4844(
630 Eip4844PoolTransactionError::NoEip4844Blobs,
631 ))
632 }
633
634 let max_blob_count = self.fork_tracker.max_blob_count();
635 if blob_count > max_blob_count {
636 self.validation_metrics.invalid_4844.increment(1);
637 return Err(InvalidPoolTransactionError::Eip4844(
638 Eip4844PoolTransactionError::TooManyEip4844Blobs {
639 have: blob_count,
640 permitted: max_blob_count,
641 },
642 ))
643 }
644 }
645
646 let tx_gas_limit_cap =
648 self.fork_tracker.tx_gas_limit_cap.load(std::sync::atomic::Ordering::Relaxed);
649 if tx_gas_limit_cap > 0 && transaction.gas_limit() > tx_gas_limit_cap {
650 return Err(InvalidTransactionError::GasLimitTooHigh.into())
651 }
652
653 if let Some(check) = &self.additional_stateless_validation {
655 check(origin, transaction)?;
656 }
657
658 Ok(())
659 }
660
661 pub fn validate_stateful<P>(
666 &self,
667 origin: TransactionOrigin,
668 mut transaction: Tx,
669 state: P,
670 ) -> TransactionValidationOutcome<Tx>
671 where
672 P: AccountInfoReader,
673 {
674 let account = match state.basic_account(transaction.sender_ref()) {
676 Ok(account) => account.unwrap_or_default(),
677 Err(err) => {
678 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
679 }
680 };
681
682 match self.validate_sender_bytecode(&transaction, &account, &state) {
684 Err(outcome) => return outcome,
685 Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err),
686 _ => {}
687 };
688
689 if transaction.requires_nonce_check() &&
691 let Err(err) = self.validate_sender_nonce(&transaction, &account)
692 {
693 return TransactionValidationOutcome::Invalid(transaction, err)
694 }
695
696 if let Err(err) = self.validate_sender_balance(&transaction, &account) {
698 return TransactionValidationOutcome::Invalid(transaction, err)
699 }
700
701 let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) {
703 Err(err) => return TransactionValidationOutcome::Invalid(transaction, err),
704 Ok(sidecar) => sidecar,
705 };
706
707 if let Some(check) = &self.additional_stateful_validation &&
709 let Err(err) = check(origin, &transaction, &state)
710 {
711 return TransactionValidationOutcome::Invalid(transaction, err)
712 }
713
714 let authorities = self.recover_authorities(&transaction);
715 TransactionValidationOutcome::Valid {
717 balance: account.balance,
718 state_nonce: account.nonce,
719 bytecode_hash: account.bytecode_hash,
720 transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
721 propagate: match origin {
723 TransactionOrigin::External => true,
724 TransactionOrigin::Local => {
725 self.local_transactions_config.propagate_local_transactions
726 }
727 TransactionOrigin::Private => false,
728 },
729 authorities,
730 }
731 }
732
733 pub fn validate_sender_bytecode(
735 &self,
736 transaction: &Tx,
737 sender: &Account,
738 state: impl BytecodeReader,
739 ) -> Result<Result<(), InvalidPoolTransactionError>, TransactionValidationOutcome<Tx>> {
740 if let Some(code_hash) = &sender.bytecode_hash &&
747 *code_hash != KECCAK_EMPTY
748 {
749 let is_eip7702 = if self.fork_tracker.is_prague_activated() {
750 match state.bytecode_by_hash(code_hash) {
751 Ok(bytecode) => bytecode.is_some_and(|b| b.is_eip7702()),
752 Err(err) => {
753 return Err(TransactionValidationOutcome::Error(
754 *transaction.hash(),
755 Box::new(err),
756 ))
757 }
758 }
759 } else {
760 false
761 };
762
763 if !is_eip7702 {
764 return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into()))
765 }
766 }
767 Ok(Ok(()))
768 }
769
770 pub fn validate_sender_nonce(
772 &self,
773 transaction: &Tx,
774 sender: &Account,
775 ) -> Result<(), InvalidPoolTransactionError> {
776 let tx_nonce = transaction.nonce();
777
778 if tx_nonce < sender.nonce {
779 return Err(InvalidTransactionError::NonceNotConsistent {
780 tx: tx_nonce,
781 state: sender.nonce,
782 }
783 .into())
784 }
785 Ok(())
786 }
787
788 pub fn validate_sender_balance(
790 &self,
791 transaction: &Tx,
792 sender: &Account,
793 ) -> Result<(), InvalidPoolTransactionError> {
794 let cost = transaction.cost();
795
796 if !self.disable_balance_check && cost > &sender.balance {
797 let expected = *cost;
798 return Err(InvalidTransactionError::InsufficientFunds(
799 GotExpected { got: sender.balance, expected }.into(),
800 )
801 .into())
802 }
803 Ok(())
804 }
805
806 pub fn validate_eip4844(
808 &self,
809 transaction: &mut Tx,
810 ) -> Result<Option<PooledBlobSidecar>, InvalidPoolTransactionError> {
811 let mut maybe_blob_sidecar = None;
812
813 if transaction.is_eip4844() {
815 match transaction.take_blob() {
817 EthBlobTransactionSidecar::None => {
818 self.validation_metrics.invalid_4844.increment(1);
820 return Err(InvalidTransactionError::TxTypeNotSupported.into())
821 }
822 EthBlobTransactionSidecar::Missing => {
823 if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) {
828 } else {
830 self.validation_metrics.invalid_4844.increment(1);
831 return Err(InvalidPoolTransactionError::Eip4844(
832 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
833 ))
834 }
835 }
836 EthBlobTransactionSidecar::Present(sidecar) => {
837 let now = Instant::now();
838
839 if self.eip7594 {
841 if self.fork_tracker.is_osaka_activated() {
843 if sidecar.is_eip4844() {
844 self.validation_metrics.invalid_4844.increment(1);
845 return Err(InvalidPoolTransactionError::Eip4844(
846 Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
847 ))
848 }
849 } else if sidecar.is_eip7594() && !self.allow_7594_sidecars() {
850 self.validation_metrics.invalid_4844.increment(1);
851 return Err(InvalidPoolTransactionError::Eip4844(
852 Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
853 ))
854 }
855 } else {
856 if sidecar.is_eip7594() {
858 self.validation_metrics.invalid_4844.increment(1);
859 return Err(InvalidPoolTransactionError::Eip4844(
860 Eip4844PoolTransactionError::Eip7594SidecarDisallowed,
861 ))
862 }
863 }
864
865 if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
867 self.validation_metrics.invalid_4844.increment(1);
868 return Err(InvalidPoolTransactionError::Eip4844(
869 Eip4844PoolTransactionError::InvalidEip4844Blob(err),
870 ))
871 }
872 self.validation_metrics.blob_validation_duration.record(now.elapsed());
874 maybe_blob_sidecar = Some(sidecar);
876 }
877 }
878 }
879 Ok(maybe_blob_sidecar)
880 }
881
882 fn recover_authorities(&self, transaction: &Tx) -> std::option::Option<Vec<Address>> {
884 transaction.authorization_list().map(|auths| {
885 let chain_id = U256::from(self.chain_id());
886 auths
887 .iter()
888 .filter(|auth| auth.chain_id().is_zero() || auth.chain_id() == &chain_id)
889 .flat_map(|auth| auth.recover_authority())
890 .collect()
891 })
892 }
893
894 fn validate_batch(
896 &self,
897 transactions: impl IntoIterator<Item = (TransactionOrigin, Tx)>,
898 ) -> Vec<TransactionValidationOutcome<Tx>> {
899 let mut provider: Option<StateProviderBox> = None;
900 transactions
901 .into_iter()
902 .map(|(origin, tx)| {
903 self.validate_one_with_provider(origin, tx, &mut provider, || self.client.latest())
904 })
905 .collect()
906 }
907
908 fn validate_batch_with_origin(
910 &self,
911 origin: TransactionOrigin,
912 transactions: impl IntoIterator<Item = Tx> + Send,
913 ) -> Vec<TransactionValidationOutcome<Tx>> {
914 let mut provider: Option<StateProviderBox> = None;
915 transactions
916 .into_iter()
917 .map(|tx| {
918 self.validate_one_with_provider(origin, tx, &mut provider, || self.client.latest())
919 })
920 .collect()
921 }
922
923 fn on_new_head_block(&self, new_tip_block: &HeaderTy<Evm::Primitives>) {
924 if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
926 self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
927 }
928
929 if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
930 self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
931 }
932
933 if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
934 self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
935 }
936
937 if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
938 self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
939 }
940
941 if self.chain_spec().is_amsterdam_active_at_timestamp(new_tip_block.timestamp()) {
942 self.fork_tracker.amsterdam.store(true, std::sync::atomic::Ordering::Relaxed);
943 }
944
945 self.fork_tracker
946 .tip_timestamp
947 .store(new_tip_block.timestamp(), std::sync::atomic::Ordering::Relaxed);
948
949 if let Some(blob_params) =
950 self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
951 {
952 self.fork_tracker
953 .max_blob_count
954 .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed);
955 }
956
957 self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
958
959 let evm_env = self
961 .evm_config
962 .evm_env(new_tip_block)
963 .expect("evm_env should not fail for executed block");
964
965 self.fork_tracker
966 .max_initcode_size
967 .store(evm_env.cfg_env.max_initcode_size(), std::sync::atomic::Ordering::Relaxed);
968 let (tx_gas_limit_cap, tx_regular_gas_cap) = tx_gas_caps(&evm_env.cfg_env);
969 self.fork_tracker
970 .tx_gas_limit_cap
971 .store(tx_gas_limit_cap, std::sync::atomic::Ordering::Relaxed);
972 self.fork_tracker
973 .tx_regular_gas_cap
974 .store(tx_regular_gas_cap, std::sync::atomic::Ordering::Relaxed);
975 }
976
977 fn max_gas_limit(&self) -> u64 {
978 self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
979 }
980
981 fn allow_7594_sidecars(&self) -> bool {
983 let tip_timestamp = self.fork_tracker.tip_timestamp();
984
985 if self
987 .chain_spec()
988 .is_osaka_active_at_timestamp(tip_timestamp.saturating_add(SLOT_DURATION_SECS))
989 {
990 true
991 } else if self
992 .chain_spec()
993 .is_osaka_active_at_timestamp(tip_timestamp.saturating_add(2 * SLOT_DURATION_SECS))
994 {
995 let current_timestamp =
996 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
997
998 current_timestamp >= tip_timestamp.saturating_add(4)
1000 } else {
1001 false
1002 }
1003 }
1004}
1005
1006impl<Client, Tx, Evm> TransactionValidator for EthTransactionValidator<Client, Tx, Evm>
1007where
1008 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
1009 Tx: EthPoolTransaction,
1010 Evm: ConfigureEvm,
1011{
1012 type Transaction = Tx;
1013 type Block = BlockTy<Evm::Primitives>;
1014
1015 async fn validate_transaction(
1016 &self,
1017 origin: TransactionOrigin,
1018 transaction: Self::Transaction,
1019 ) -> TransactionValidationOutcome<Self::Transaction> {
1020 self.validate_one(origin, transaction)
1021 }
1022
1023 async fn validate_transactions(
1024 &self,
1025 transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
1026 + Send,
1027 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
1028 self.validate_batch(transactions)
1029 }
1030
1031 async fn validate_transactions_with_origin(
1032 &self,
1033 origin: TransactionOrigin,
1034 transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
1035 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
1036 self.validate_batch_with_origin(origin, transactions)
1037 }
1038
1039 fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
1040 Self::on_new_head_block(self, new_tip_block.header())
1041 }
1042}
1043
1044#[derive(Debug)]
1046pub struct EthTransactionValidatorBuilder<Client, Evm> {
1047 client: Client,
1048 chain_id: u64,
1050 evm_config: Evm,
1052 shanghai: bool,
1054 cancun: bool,
1056 prague: bool,
1058 osaka: bool,
1060 amsterdam: bool,
1062 tip_timestamp: u64,
1064 max_blob_count: u64,
1066 eip2718: bool,
1068 eip1559: bool,
1070 eip4844: bool,
1072 eip7702: bool,
1074 block_gas_limit: AtomicU64,
1076 tx_fee_cap: Option<u128>,
1078 minimum_priority_fee: Option<u128>,
1082 additional_tasks: usize,
1086
1087 kzg_settings: EnvKzgSettings,
1089 local_transactions_config: LocalTransactionConfig,
1091 max_tx_input_bytes: usize,
1093 max_tx_gas_limit: Option<u64>,
1095 disable_balance_check: bool,
1097 other_tx_types: U256,
1099 max_initcode_size: usize,
1101 tx_gas_limit_cap: u64,
1103 tx_regular_gas_cap: u64,
1105 eip7594: bool,
1109}
1110
1111impl<Client, Evm> EthTransactionValidatorBuilder<Client, Evm> {
1112 pub fn new(client: Client, evm_config: Evm) -> Self
1122 where
1123 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
1124 + BlockReaderIdExt<Header = HeaderTy<Evm::Primitives>>,
1125 Evm: ConfigureEvm,
1126 {
1127 let chain_spec = client.chain_spec();
1128 let tip = client
1129 .header_by_id(BlockId::latest())
1130 .expect("failed to fetch latest header")
1131 .expect("latest header is not found");
1132 let evm_env =
1133 evm_config.evm_env(&tip).expect("evm_env should not fail for existing blocks");
1134 let (tx_gas_limit_cap, tx_regular_gas_cap) = tx_gas_caps(&evm_env.cfg_env);
1135
1136 Self {
1137 block_gas_limit: tip.gas_limit().into(),
1138 client,
1139 chain_id: chain_spec.chain().id(),
1140 evm_config,
1141 minimum_priority_fee: None,
1142 additional_tasks: 1,
1143 kzg_settings: EnvKzgSettings::Default,
1144 local_transactions_config: Default::default(),
1145 max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
1146 tx_fee_cap: Some(1e18 as u128),
1147 max_tx_gas_limit: None,
1148 eip2718: true,
1150 eip1559: true,
1151 eip4844: true,
1152 eip7702: true,
1153
1154 shanghai: chain_spec.is_shanghai_active_at_timestamp(tip.timestamp()),
1155 cancun: chain_spec.is_cancun_active_at_timestamp(tip.timestamp()),
1156 prague: chain_spec.is_prague_active_at_timestamp(tip.timestamp()),
1157 osaka: chain_spec.is_osaka_active_at_timestamp(tip.timestamp()),
1158 amsterdam: chain_spec.is_amsterdam_active_at_timestamp(tip.timestamp()),
1159
1160 tip_timestamp: tip.timestamp(),
1161
1162 max_blob_count: chain_spec
1163 .blob_params_at_timestamp(tip.timestamp())
1164 .unwrap_or_else(BlobParams::prague)
1165 .max_blobs_per_tx,
1166
1167 disable_balance_check: false,
1169
1170 other_tx_types: U256::ZERO,
1172
1173 tx_gas_limit_cap,
1174 tx_regular_gas_cap,
1175 max_initcode_size: evm_env.cfg_env.max_initcode_size(),
1176
1177 eip7594: true,
1179 }
1180 }
1181
1182 pub const fn no_cancun(self) -> Self {
1184 self.set_cancun(false)
1185 }
1186
1187 pub fn with_local_transactions_config(
1189 mut self,
1190 local_transactions_config: LocalTransactionConfig,
1191 ) -> Self {
1192 self.local_transactions_config = local_transactions_config;
1193 self
1194 }
1195
1196 pub const fn set_cancun(mut self, cancun: bool) -> Self {
1198 self.cancun = cancun;
1199 self
1200 }
1201
1202 pub const fn no_shanghai(self) -> Self {
1204 self.set_shanghai(false)
1205 }
1206
1207 pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
1209 self.shanghai = shanghai;
1210 self
1211 }
1212
1213 pub const fn no_prague(self) -> Self {
1215 self.set_prague(false)
1216 }
1217
1218 pub const fn set_prague(mut self, prague: bool) -> Self {
1220 self.prague = prague;
1221 self
1222 }
1223
1224 pub const fn no_osaka(self) -> Self {
1226 self.set_osaka(false)
1227 }
1228
1229 pub const fn set_osaka(mut self, osaka: bool) -> Self {
1231 self.osaka = osaka;
1232 self
1233 }
1234
1235 pub const fn no_amsterdam(self) -> Self {
1237 self.set_amsterdam(false)
1238 }
1239
1240 pub const fn set_amsterdam(mut self, amsterdam: bool) -> Self {
1242 self.amsterdam = amsterdam;
1243 self
1244 }
1245
1246 pub const fn no_eip2718(self) -> Self {
1248 self.set_eip2718(false)
1249 }
1250
1251 pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
1253 self.eip2718 = eip2718;
1254 self
1255 }
1256
1257 pub const fn no_eip1559(self) -> Self {
1259 self.set_eip1559(false)
1260 }
1261
1262 pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
1264 self.eip1559 = eip1559;
1265 self
1266 }
1267
1268 pub const fn no_eip4844(self) -> Self {
1270 self.set_eip4844(false)
1271 }
1272
1273 pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
1275 self.eip4844 = eip4844;
1276 self
1277 }
1278
1279 pub const fn no_eip7702(self) -> Self {
1281 self.set_eip7702(false)
1282 }
1283
1284 pub const fn set_eip7702(mut self, eip7702: bool) -> Self {
1286 self.eip7702 = eip7702;
1287 self
1288 }
1289
1290 pub const fn no_eip7594(self) -> Self {
1297 self.set_eip7594(false)
1298 }
1299
1300 pub const fn set_eip7594(mut self, eip7594: bool) -> Self {
1305 self.eip7594 = eip7594;
1306 self
1307 }
1308
1309 pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
1311 self.kzg_settings = kzg_settings;
1312 self
1313 }
1314
1315 pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: Option<u128>) -> Self {
1320 self.minimum_priority_fee = minimum_priority_fee;
1321 self
1322 }
1323
1324 pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
1326 self.additional_tasks = additional_tasks;
1327 self
1328 }
1329
1330 pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
1332 self.max_tx_input_bytes = max_tx_input_bytes;
1333 self
1334 }
1335
1336 pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
1340 self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
1341 self
1342 }
1343
1344 pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
1348 self.tx_fee_cap = Some(tx_fee_cap);
1349 self
1350 }
1351
1352 pub const fn with_max_tx_gas_limit(mut self, max_tx_gas_limit: Option<u64>) -> Self {
1354 self.max_tx_gas_limit = max_tx_gas_limit;
1355 self
1356 }
1357
1358 pub const fn disable_balance_check(mut self) -> Self {
1360 self.disable_balance_check = true;
1361 self
1362 }
1363
1364 pub const fn with_custom_tx_type(mut self, tx_type: u8) -> Self {
1366 self.other_tx_types.set_bit(tx_type as usize, true);
1367 self
1368 }
1369
1370 pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx, Evm>
1372 where
1373 S: BlobStore,
1374 {
1375 let Self {
1376 client,
1377 chain_id,
1378 evm_config,
1379 shanghai,
1380 cancun,
1381 prague,
1382 osaka,
1383 amsterdam,
1384 tip_timestamp,
1385 eip2718,
1386 eip1559,
1387 eip4844,
1388 eip7702,
1389 block_gas_limit,
1390 tx_fee_cap,
1391 minimum_priority_fee,
1392 kzg_settings,
1393 local_transactions_config,
1394 max_tx_input_bytes,
1395 max_tx_gas_limit,
1396 disable_balance_check,
1397 max_blob_count,
1398 additional_tasks: _,
1399 other_tx_types,
1400 max_initcode_size,
1401 tx_gas_limit_cap,
1402 tx_regular_gas_cap,
1403 eip7594,
1404 } = self;
1405
1406 let fork_tracker = ForkTracker {
1407 shanghai: AtomicBool::new(shanghai),
1408 cancun: AtomicBool::new(cancun),
1409 prague: AtomicBool::new(prague),
1410 osaka: AtomicBool::new(osaka),
1411 amsterdam: AtomicBool::new(amsterdam),
1412 tip_timestamp: AtomicU64::new(tip_timestamp),
1413 max_blob_count: AtomicU64::new(max_blob_count),
1414 max_initcode_size: AtomicUsize::new(max_initcode_size),
1415 tx_gas_limit_cap: AtomicU64::new(tx_gas_limit_cap),
1416 tx_regular_gas_cap: AtomicU64::new(tx_regular_gas_cap),
1417 };
1418
1419 EthTransactionValidator {
1420 client,
1421 chain_id,
1422 eip2718,
1423 eip1559,
1424 fork_tracker,
1425 eip4844,
1426 eip7702,
1427 block_gas_limit,
1428 tx_fee_cap,
1429 minimum_priority_fee,
1430 blob_store: Box::new(blob_store),
1431 kzg_settings,
1432 local_transactions_config,
1433 max_tx_input_bytes,
1434 max_tx_gas_limit,
1435 disable_balance_check,
1436 evm_config,
1437 _marker: Default::default(),
1438 validation_metrics: TxPoolValidationMetrics::default(),
1439 other_tx_types,
1440 eip7594,
1441 additional_stateless_validation: None,
1442 additional_stateful_validation: None,
1443 }
1444 }
1445
1446 pub fn build_with_tasks<Tx, S>(
1453 self,
1454 tasks: Runtime,
1455 blob_store: S,
1456 ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx, Evm>>
1457 where
1458 S: BlobStore,
1459 {
1460 let additional_tasks = self.additional_tasks;
1461 let validator = self.build::<Tx, S>(blob_store);
1462 TransactionValidationTaskExecutor::spawn(validator, &tasks, additional_tasks)
1463 }
1464}
1465
1466#[derive(Debug)]
1468pub struct ForkTracker {
1469 pub shanghai: AtomicBool,
1471 pub cancun: AtomicBool,
1473 pub prague: AtomicBool,
1475 pub osaka: AtomicBool,
1477 pub amsterdam: AtomicBool,
1479 pub max_blob_count: AtomicU64,
1481 pub tip_timestamp: AtomicU64,
1483 pub max_initcode_size: AtomicUsize,
1485 pub tx_gas_limit_cap: AtomicU64,
1487 pub tx_regular_gas_cap: AtomicU64,
1489}
1490
1491impl ForkTracker {
1492 pub fn is_shanghai_activated(&self) -> bool {
1494 self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1495 }
1496
1497 pub fn is_cancun_activated(&self) -> bool {
1499 self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1500 }
1501
1502 pub fn is_prague_activated(&self) -> bool {
1504 self.prague.load(std::sync::atomic::Ordering::Relaxed)
1505 }
1506
1507 pub fn is_osaka_activated(&self) -> bool {
1509 self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1510 }
1511
1512 pub fn is_amsterdam_activated(&self) -> bool {
1514 self.amsterdam.load(std::sync::atomic::Ordering::Relaxed)
1515 }
1516
1517 pub fn tip_timestamp(&self) -> u64 {
1519 self.tip_timestamp.load(std::sync::atomic::Ordering::Relaxed)
1520 }
1521
1522 pub fn max_blob_count(&self) -> u64 {
1524 self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1525 }
1526}
1527
1528pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1533 transaction: &T,
1534 fork_tracker: &ForkTracker,
1535) -> Result<(), InvalidPoolTransactionError> {
1536 use revm::primitives::hardfork::SpecId;
1537 let spec_id = if fork_tracker.is_amsterdam_activated() {
1538 SpecId::AMSTERDAM
1539 } else if fork_tracker.is_prague_activated() {
1540 SpecId::PRAGUE
1541 } else if fork_tracker.is_shanghai_activated() {
1542 SpecId::SHANGHAI
1543 } else {
1544 SpecId::MERGE
1545 };
1546
1547 let eip2780 = fork_tracker.is_amsterdam_activated().then(|| {
1550 revm::context_interface::cfg::gas_params::Eip2780TxInfo {
1551 value: transaction.value(),
1552 is_self_transfer: transaction.kind().to() == Some(&transaction.sender()),
1554 }
1555 });
1556
1557 let gas = revm::interpreter::gas::calculate_initial_tx_gas(
1558 spec_id,
1559 transaction.input(),
1560 transaction.is_create(),
1561 transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1562 transaction.access_list().map(|l| l.storage_keys_count()).unwrap_or_default() as u64,
1563 transaction.authorization_count().unwrap_or_default(),
1564 eip2780,
1565 );
1566
1567 let gas_limit = transaction.gas_limit();
1568 let regular_gas_cap =
1571 fork_tracker.tx_regular_gas_cap.load(std::sync::atomic::Ordering::Relaxed);
1572 if gas_limit < gas.initial_total_gas() || gas_limit < gas.floor_gas() {
1573 Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1574 } else if regular_gas_cap > 0 &&
1575 gas.initial_regular_gas().max(gas.floor_gas()) > regular_gas_cap
1576 {
1577 Err(InvalidTransactionError::GasTooHigh.into())
1580 } else {
1581 Ok(())
1582 }
1583}
1584
1585fn tx_gas_caps(cfg: &impl Cfg) -> (u64, u64) {
1591 let cap = cfg.tx_gas_limit_cap();
1592 if cfg.is_amsterdam_eip8037_enabled() {
1593 (0, cap)
1594 } else {
1595 (cap, 0)
1596 }
1597}
1598
1599#[cfg(test)]
1600mod tests {
1601 use super::*;
1602 use crate::{
1603 blobstore::InMemoryBlobStore, error::PoolErrorKind, test_utils::TransactionBuilder,
1604 traits::PoolTransaction, CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1605 };
1606 use alloy_consensus::Transaction;
1607 use alloy_eips::{
1608 eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M,
1609 eip2718::{Decodable2718, Encodable2718},
1610 eip2930::{AccessList, AccessListItem},
1611 eip7702::Authorization,
1612 };
1613 use alloy_primitives::{hex, Address, Bytes, Signature, B256, U256};
1614 use reth_chainspec::{ChainSpecBuilder, MAINNET};
1615 use reth_ethereum_primitives::PooledTransactionVariant;
1616 use reth_evm_ethereum::EthEvmConfig;
1617 use reth_primitives_traits::SignedTransaction;
1618 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1619 use revm::primitives::eip3860::MAX_INITCODE_SIZE;
1620
1621 fn test_evm_config() -> EthEvmConfig {
1622 EthEvmConfig::mainnet()
1623 }
1624
1625 fn mock_provider() -> MockEthProvider {
1628 mock_provider_with_gas_limit(ETHEREUM_BLOCK_GAS_LIMIT_30M)
1629 }
1630
1631 fn mock_provider_with_gas_limit(gas_limit: u64) -> MockEthProvider {
1632 let mut genesis = MAINNET.genesis.clone();
1633 genesis.gas_limit = gas_limit;
1634 MockEthProvider::default()
1635 .with_chain_spec(ChainSpecBuilder::mainnet().genesis(genesis).build())
1636 .with_genesis_block()
1637 }
1638
1639 fn get_transaction() -> EthPooledTransaction {
1640 let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1641
1642 let data = hex::decode(raw).unwrap();
1643 let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1644
1645 EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1646 }
1647
1648 fn eip1559_tx(
1649 to: Address,
1650 sender: Address,
1651 value: u64,
1652 gas_limit: u64,
1653 ) -> EthPooledTransaction {
1654 let tx = alloy_consensus::TxEip1559 {
1655 chain_id: 1,
1656 nonce: 0,
1657 gas_limit,
1658 max_fee_per_gas: 1,
1659 max_priority_fee_per_gas: 0,
1660 to: to.into(),
1661 value: U256::from(value),
1662 ..Default::default()
1663 };
1664 pooled_eip1559_tx(tx, sender)
1665 }
1666
1667 fn pooled_eip1559_tx(tx: alloy_consensus::TxEip1559, sender: Address) -> EthPooledTransaction {
1668 let signed = reth_ethereum_primitives::TransactionSigned::new_unhashed(
1669 tx.into(),
1670 alloy_primitives::Signature::test_signature(),
1671 );
1672 EthPooledTransaction::new(
1673 alloy_consensus::transaction::Recovered::new_unchecked(signed, sender),
1674 200,
1675 )
1676 }
1677
1678 fn amsterdam_fork_tracker() -> ForkTracker {
1679 ForkTracker {
1680 shanghai: true.into(),
1681 cancun: true.into(),
1682 prague: true.into(),
1683 osaka: true.into(),
1684 amsterdam: true.into(),
1685 tip_timestamp: 0.into(),
1686 max_blob_count: 0.into(),
1687 max_initcode_size: AtomicUsize::new(MAX_INITCODE_SIZE),
1688 tx_gas_limit_cap: AtomicU64::new(0),
1689 tx_regular_gas_cap: AtomicU64::new(0),
1690 }
1691 }
1692
1693 #[test]
1697 fn intrinsic_gas_eip2780() {
1698 let sender = Address::repeat_byte(1);
1699 let recipient = Address::repeat_byte(2);
1700
1701 let amsterdam = amsterdam_fork_tracker;
1702 let pre_amsterdam = || ForkTracker { amsterdam: false.into(), ..amsterdam() };
1703
1704 let self_transfer = eip1559_tx(sender, sender, 1, 15_000);
1706 assert!(ensure_intrinsic_gas(&self_transfer, &amsterdam()).is_ok());
1707 assert!(ensure_intrinsic_gas(&self_transfer, &pre_amsterdam()).is_err());
1708
1709 let zero_value = eip1559_tx(recipient, sender, 0, 15_000);
1711 assert!(ensure_intrinsic_gas(&zero_value, &amsterdam()).is_ok());
1712 assert!(
1713 ensure_intrinsic_gas(&eip1559_tx(recipient, sender, 0, 14_999), &amsterdam()).is_err()
1714 );
1715
1716 assert!(
1718 ensure_intrinsic_gas(&eip1559_tx(recipient, sender, 1, 15_000), &amsterdam()).is_err()
1719 );
1720 assert!(
1721 ensure_intrinsic_gas(&eip1559_tx(recipient, sender, 1, 21_000), &amsterdam()).is_ok()
1722 );
1723 }
1724
1725 #[test]
1728 fn intrinsic_regular_gas_cap_eip8037() {
1729 let cap = revm::primitives::eip7825::TX_GAS_LIMIT_CAP;
1730 let fork_tracker =
1731 ForkTracker { tx_regular_gas_cap: AtomicU64::new(cap), ..amsterdam_fork_tracker() };
1732 let is_too_high = |res| {
1733 matches!(
1734 res,
1735 Err(InvalidPoolTransactionError::Consensus(InvalidTransactionError::GasTooHigh))
1736 )
1737 };
1738 let tx = |tx| {
1739 pooled_eip1559_tx(
1740 alloy_consensus::TxEip1559 {
1741 chain_id: 1,
1742 gas_limit: 2 * cap,
1743 max_fee_per_gas: 1,
1744 to: Address::repeat_byte(2).into(),
1745 ..tx
1746 },
1747 Address::repeat_byte(1),
1748 )
1749 };
1750
1751 let access_list_tx = |addresses: u64| {
1754 tx(alloy_consensus::TxEip1559 {
1755 access_list: AccessList(
1756 (0..addresses)
1757 .map(|i| AccessListItem {
1758 address: Address::left_padding_from(&i.to_be_bytes()),
1759 storage_keys: vec![],
1760 })
1761 .collect(),
1762 ),
1763 ..Default::default()
1764 })
1765 };
1766 assert!(ensure_intrinsic_gas(&access_list_tx(4_010), &fork_tracker).is_ok());
1768 assert!(is_too_high(ensure_intrinsic_gas(&access_list_tx(4_011), &fork_tracker)));
1770
1771 let calldata_tx = |len: usize| {
1774 tx(alloy_consensus::TxEip1559 { input: vec![1; len].into(), ..Default::default() })
1775 };
1776 assert!(ensure_intrinsic_gas(&calldata_tx(261_909), &fork_tracker).is_ok());
1778 assert!(is_too_high(ensure_intrinsic_gas(&calldata_tx(261_910), &fork_tracker)));
1780
1781 let no_cap = amsterdam_fork_tracker();
1783 assert!(ensure_intrinsic_gas(&access_list_tx(4_011), &no_cap).is_ok());
1784 assert!(ensure_intrinsic_gas(&calldata_tx(261_910), &no_cap).is_ok());
1785 }
1786
1787 #[tokio::test]
1789 async fn validate_transaction() {
1790 let transaction = get_transaction();
1791 let mut fork_tracker = ForkTracker {
1792 shanghai: false.into(),
1793 cancun: false.into(),
1794 prague: false.into(),
1795 osaka: false.into(),
1796 amsterdam: false.into(),
1797 tip_timestamp: 0.into(),
1798 max_blob_count: 0.into(),
1799 max_initcode_size: AtomicUsize::new(MAX_INITCODE_SIZE),
1800 tx_gas_limit_cap: AtomicU64::new(0),
1801 tx_regular_gas_cap: AtomicU64::new(0),
1802 };
1803
1804 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1805 assert!(res.is_ok());
1806
1807 fork_tracker.shanghai = true.into();
1808 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1809 assert!(res.is_ok());
1810
1811 let provider = mock_provider();
1812 provider.add_account(
1813 transaction.sender(),
1814 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1815 );
1816 let blob_store = InMemoryBlobStore::default();
1817 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1818 .build(blob_store.clone());
1819
1820 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1821
1822 assert!(outcome.is_valid());
1823
1824 let pool =
1825 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1826
1827 let res = pool.add_external_transaction(transaction.clone()).await;
1828 assert!(res.is_ok());
1829 let tx = pool.get(transaction.hash());
1830 assert!(tx.is_some());
1831 }
1832
1833 #[test]
1834 fn accepts_sender_with_empty_bytecode() {
1835 let transaction = get_transaction();
1836 let provider = mock_provider();
1837 provider.add_account(
1838 transaction.sender(),
1839 ExtendedAccount::new(transaction.nonce(), U256::MAX).with_bytecode(Bytes::new()),
1840 );
1841 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1842 .build(InMemoryBlobStore::default());
1843
1844 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1845
1846 assert!(outcome.is_valid());
1847 }
1848
1849 #[test]
1850 fn validates_nonce_bound() {
1851 let provider = mock_provider();
1852 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1853 .build(InMemoryBlobStore::default());
1854 let transaction = |nonce| {
1855 EthPooledTransaction::try_from_consensus(
1856 TransactionBuilder::default()
1857 .chain_id(validator.chain_id())
1858 .nonce(nonce)
1859 .gas_limit(21_000)
1860 .to(Address::ZERO)
1861 .into_eip1559()
1862 .try_into_recovered()
1863 .unwrap(),
1864 )
1865 .unwrap()
1866 };
1867
1868 assert!(validator
1869 .validate_stateless(TransactionOrigin::External, &transaction(u64::MAX - 1))
1870 .is_ok());
1871 assert!(matches!(
1872 validator.validate_stateless(TransactionOrigin::External, &transaction(u64::MAX)),
1873 Err(InvalidPoolTransactionError::Eip2681)
1874 ));
1875 }
1876
1877 #[test]
1878 fn validates_configured_chain_id() {
1879 let provider = mock_provider();
1880 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1881 .build(InMemoryBlobStore::default());
1882 let transaction = |chain_id| {
1883 EthPooledTransaction::try_from_consensus(
1884 TransactionBuilder::default()
1885 .chain_id(chain_id)
1886 .gas_limit(21_000)
1887 .to(Address::ZERO)
1888 .into_eip1559()
1889 .try_into_recovered()
1890 .unwrap(),
1891 )
1892 .unwrap()
1893 };
1894
1895 assert!(validator
1896 .validate_stateless(TransactionOrigin::External, &transaction(validator.chain_id()))
1897 .is_ok());
1898 assert!(matches!(
1899 validator.validate_stateless(
1900 TransactionOrigin::External,
1901 &transaction(validator.chain_id() + 1)
1902 ),
1903 Err(InvalidPoolTransactionError::Consensus(InvalidTransactionError::ChainIdMismatch))
1904 ));
1905 }
1906
1907 #[test]
1908 fn only_recovers_authorities_for_matching_chain_id() {
1909 let validator = EthTransactionValidatorBuilder::new(mock_provider(), test_evm_config())
1910 .build(InMemoryBlobStore::default());
1911 let auth = |chain_id, address| {
1912 Authorization { chain_id, address, nonce: 0 }.into_signed(Signature::test_signature())
1913 };
1914 let universal = auth(U256::ZERO, Address::repeat_byte(1));
1915 let local = auth(U256::from(validator.chain_id()), Address::repeat_byte(2));
1916 let foreign = auth(U256::from(validator.chain_id() + 1), Address::repeat_byte(3));
1917 let oversized = auth(U256::from(u64::MAX) + U256::from(1), Address::repeat_byte(4));
1918 let expected =
1919 vec![universal.recover_authority().unwrap(), local.recover_authority().unwrap()];
1920 let tx = alloy_consensus::TxEip7702 {
1921 chain_id: validator.chain_id(),
1922 authorization_list: vec![universal, foreign, local, oversized],
1923 ..Default::default()
1924 };
1925 let signed = reth_ethereum_primitives::TransactionSigned::new_unhashed(
1926 tx.into(),
1927 Signature::test_signature(),
1928 );
1929 let transaction = EthPooledTransaction::new(
1930 alloy_consensus::transaction::Recovered::new_unchecked(signed, Address::ZERO),
1931 200,
1932 );
1933
1934 assert_eq!(validator.recover_authorities(&transaction), Some(expected));
1935 }
1936
1937 #[tokio::test]
1939 async fn invalid_on_gas_limit_too_high() {
1940 let transaction = get_transaction();
1941
1942 let provider = MockEthProvider::default().with_genesis_block();
1943 provider.add_account(
1944 transaction.sender(),
1945 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1946 );
1947
1948 let blob_store = InMemoryBlobStore::default();
1949 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1950 .set_block_gas_limit(1_000_000) .build(blob_store.clone());
1952
1953 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1954
1955 assert!(outcome.is_invalid());
1956
1957 let pool =
1958 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1959
1960 let res = pool.add_external_transaction(transaction.clone()).await;
1961 assert!(res.is_err());
1962 assert!(matches!(
1963 res.unwrap_err().kind,
1964 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1965 1_015_288, 1_000_000
1966 ))
1967 ));
1968 let tx = pool.get(transaction.hash());
1969 assert!(tx.is_none());
1970 }
1971
1972 #[tokio::test]
1973 async fn invalid_on_fee_cap_exceeded() {
1974 let transaction = get_transaction();
1975 let provider = mock_provider();
1976 provider.add_account(
1977 transaction.sender(),
1978 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1979 );
1980
1981 let blob_store = InMemoryBlobStore::default();
1982 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1983 .set_tx_fee_cap(100) .build(blob_store.clone());
1985
1986 let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1987 assert!(outcome.is_invalid());
1988
1989 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1990 assert!(matches!(
1991 err,
1992 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1993 if (max_tx_fee_wei > tx_fee_cap_wei)
1994 ));
1995 }
1996
1997 let pool =
1998 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1999 let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
2000 assert!(res.is_err());
2001 assert!(matches!(
2002 res.unwrap_err().kind,
2003 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
2004 ));
2005 let tx = pool.get(transaction.hash());
2006 assert!(tx.is_none());
2007 }
2008
2009 #[tokio::test]
2010 async fn valid_on_zero_fee_cap() {
2011 let transaction = get_transaction();
2012 let provider = mock_provider();
2013 provider.add_account(
2014 transaction.sender(),
2015 ExtendedAccount::new(transaction.nonce(), U256::MAX),
2016 );
2017
2018 let blob_store = InMemoryBlobStore::default();
2019 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2020 .set_tx_fee_cap(0) .build(blob_store);
2022
2023 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
2024 assert!(outcome.is_valid());
2025 }
2026
2027 #[tokio::test]
2028 async fn valid_on_normal_fee_cap() {
2029 let transaction = get_transaction();
2030 let provider = mock_provider();
2031 provider.add_account(
2032 transaction.sender(),
2033 ExtendedAccount::new(transaction.nonce(), U256::MAX),
2034 );
2035
2036 let blob_store = InMemoryBlobStore::default();
2037 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2038 .set_tx_fee_cap(2e18 as u128) .build(blob_store);
2040
2041 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
2042 assert!(outcome.is_valid());
2043 }
2044
2045 #[tokio::test]
2046 async fn invalid_on_max_tx_gas_limit_exceeded() {
2047 let transaction = get_transaction();
2048 let provider = mock_provider();
2049 provider.add_account(
2050 transaction.sender(),
2051 ExtendedAccount::new(transaction.nonce(), U256::MAX),
2052 );
2053
2054 let blob_store = InMemoryBlobStore::default();
2055 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2056 .with_max_tx_gas_limit(Some(500_000)) .build(blob_store.clone());
2058
2059 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
2060 assert!(outcome.is_invalid());
2061
2062 let pool =
2063 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
2064
2065 let res = pool.add_external_transaction(transaction.clone()).await;
2066 assert!(res.is_err());
2067 assert!(matches!(
2068 res.unwrap_err().kind,
2069 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
2070 1_015_288, 500_000
2071 ))
2072 ));
2073 let tx = pool.get(transaction.hash());
2074 assert!(tx.is_none());
2075 }
2076
2077 #[tokio::test]
2078 async fn valid_on_max_tx_gas_limit_disabled() {
2079 let transaction = get_transaction();
2080 let provider = mock_provider();
2081 provider.add_account(
2082 transaction.sender(),
2083 ExtendedAccount::new(transaction.nonce(), U256::MAX),
2084 );
2085
2086 let blob_store = InMemoryBlobStore::default();
2087 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2088 .with_max_tx_gas_limit(None) .build(blob_store);
2090
2091 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2092 assert!(outcome.is_valid());
2093 }
2094
2095 #[tokio::test]
2096 async fn valid_on_max_tx_gas_limit_within_limit() {
2097 let transaction = get_transaction();
2098 let provider = mock_provider();
2099 provider.add_account(
2100 transaction.sender(),
2101 ExtendedAccount::new(transaction.nonce(), U256::MAX),
2102 );
2103
2104 let blob_store = InMemoryBlobStore::default();
2105 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2106 .with_max_tx_gas_limit(Some(2_000_000)) .build(blob_store);
2108
2109 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2110 assert!(outcome.is_valid());
2111 }
2112
2113 fn setup_priority_fee_test() -> (EthPooledTransaction, MockEthProvider) {
2115 let transaction = get_transaction();
2116 let provider = mock_provider();
2117 provider.add_account(
2118 transaction.sender(),
2119 ExtendedAccount::new(transaction.nonce(), U256::MAX),
2120 );
2121 (transaction, provider)
2122 }
2123
2124 fn create_validator_with_minimum_fee(
2126 provider: MockEthProvider,
2127 minimum_priority_fee: Option<u128>,
2128 local_config: Option<LocalTransactionConfig>,
2129 ) -> EthTransactionValidator<MockEthProvider, EthPooledTransaction, EthEvmConfig> {
2130 let blob_store = InMemoryBlobStore::default();
2131 let mut builder = EthTransactionValidatorBuilder::new(provider, test_evm_config())
2132 .with_minimum_priority_fee(minimum_priority_fee);
2133
2134 if let Some(config) = local_config {
2135 builder = builder.with_local_transactions_config(config);
2136 }
2137
2138 builder.build(blob_store)
2139 }
2140
2141 #[tokio::test]
2142 async fn invalid_on_priority_fee_lower_than_configured_minimum() {
2143 let (transaction, provider) = setup_priority_fee_test();
2144
2145 assert!(transaction.is_dynamic_fee());
2147
2148 let minimum_priority_fee =
2150 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
2151
2152 let validator =
2153 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2154
2155 for origin in
2156 [TransactionOrigin::External, TransactionOrigin::Local, TransactionOrigin::Private]
2157 {
2158 let outcome = validator.validate_one(origin, transaction.clone());
2159 assert!(matches!(
2160 outcome,
2161 TransactionValidationOutcome::Invalid(
2162 _,
2163 InvalidPoolTransactionError::PriorityFeeBelowMinimum {
2164 minimum_priority_fee: min_fee
2165 }
2166 ) if min_fee == minimum_priority_fee
2167 ));
2168 }
2169
2170 let blob_store = InMemoryBlobStore::default();
2172 let pool =
2173 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
2174
2175 let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
2176 assert!(res.is_err());
2177 assert!(matches!(
2178 res.unwrap_err().kind,
2179 PoolErrorKind::InvalidTransaction(
2180 InvalidPoolTransactionError::PriorityFeeBelowMinimum { .. }
2181 )
2182 ));
2183 let tx = pool.get(transaction.hash());
2184 assert!(tx.is_none());
2185 }
2186
2187 #[tokio::test]
2188 async fn valid_on_priority_fee_equal_to_minimum() {
2189 let (transaction, provider) = setup_priority_fee_test();
2190
2191 let tx_priority_fee =
2193 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
2194 let validator = create_validator_with_minimum_fee(provider, Some(tx_priority_fee), None);
2195
2196 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2197 assert!(outcome.is_valid());
2198 }
2199
2200 #[tokio::test]
2201 async fn valid_on_priority_fee_above_minimum() {
2202 let (transaction, provider) = setup_priority_fee_test();
2203
2204 let tx_priority_fee =
2206 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
2207 let minimum_priority_fee = tx_priority_fee / 2; let validator =
2210 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2211
2212 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2213 assert!(outcome.is_valid());
2214 }
2215
2216 #[tokio::test]
2217 async fn valid_on_minimum_priority_fee_disabled() {
2218 let (transaction, provider) = setup_priority_fee_test();
2219
2220 let validator = create_validator_with_minimum_fee(provider, None, None);
2222
2223 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2224 assert!(outcome.is_valid());
2225 }
2226
2227 #[tokio::test]
2228 async fn priority_fee_validation_applies_to_private_transactions() {
2229 let (transaction, provider) = setup_priority_fee_test();
2230
2231 let minimum_priority_fee =
2233 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
2234
2235 let validator =
2236 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2237
2238 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
2241 assert!(outcome.is_invalid());
2242
2243 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
2244 assert!(matches!(
2245 err,
2246 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
2247 if min_fee == minimum_priority_fee
2248 ));
2249 }
2250 }
2251
2252 #[tokio::test]
2253 async fn valid_on_local_config_exempts_private_transactions() {
2254 let (transaction, provider) = setup_priority_fee_test();
2255
2256 let minimum_priority_fee =
2258 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
2259
2260 let local_config =
2262 LocalTransactionConfig { propagate_local_transactions: true, ..Default::default() };
2263
2264 let validator = create_validator_with_minimum_fee(
2265 provider,
2266 Some(minimum_priority_fee),
2267 Some(local_config),
2268 );
2269
2270 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
2274 assert!(outcome.is_invalid()); }
2276
2277 fn setup_gas_price_txs(gas_price: u128) -> ([EthPooledTransaction; 2], MockEthProvider) {
2280 let sender = Address::repeat_byte(1);
2281 let to = Address::repeat_byte(2);
2282
2283 let legacy = alloy_consensus::TxLegacy {
2284 chain_id: Some(1),
2285 gas_price,
2286 gas_limit: 21_000,
2287 to: to.into(),
2288 ..Default::default()
2289 };
2290 let eip2930 = alloy_consensus::TxEip2930 {
2291 chain_id: 1,
2292 gas_price,
2293 gas_limit: 21_000,
2294 to: to.into(),
2295 ..Default::default()
2296 };
2297
2298 let txs = [legacy.into(), eip2930.into()].map(|tx: alloy_consensus::TypedTransaction| {
2299 let signed = reth_ethereum_primitives::TransactionSigned::new_unhashed(
2300 tx.into(),
2301 alloy_primitives::Signature::test_signature(),
2302 );
2303 EthPooledTransaction::new(
2304 alloy_consensus::transaction::Recovered::new_unchecked(signed, sender),
2305 200,
2306 )
2307 });
2308
2309 let provider = mock_provider();
2310 provider.add_account(sender, ExtendedAccount::new(0, U256::MAX));
2311
2312 (txs, provider)
2313 }
2314
2315 #[tokio::test]
2316 async fn invalid_on_gas_price_lower_than_configured_minimum() {
2317 let gas_price = 1_000_000_000;
2318 let minimum_priority_fee = gas_price + 1;
2319 let (txs, provider) = setup_gas_price_txs(gas_price);
2320 let validator =
2321 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2322
2323 for transaction in txs {
2325 assert!(!transaction.is_dynamic_fee());
2326
2327 for origin in
2328 [TransactionOrigin::External, TransactionOrigin::Local, TransactionOrigin::Private]
2329 {
2330 let outcome = validator.validate_one(origin, transaction.clone());
2331 assert!(matches!(
2332 outcome,
2333 TransactionValidationOutcome::Invalid(
2334 _,
2335 InvalidPoolTransactionError::PriorityFeeBelowMinimum {
2336 minimum_priority_fee: min_fee
2337 }
2338 ) if min_fee == minimum_priority_fee
2339 ));
2340 }
2341 }
2342 }
2343
2344 #[tokio::test]
2345 async fn valid_on_gas_price_at_or_above_minimum() {
2346 let gas_price = 1_000_000_000;
2347 let (txs, provider) = setup_gas_price_txs(gas_price);
2348
2349 for minimum_priority_fee in [gas_price, gas_price / 2] {
2350 let validator = create_validator_with_minimum_fee(
2351 provider.clone(),
2352 Some(minimum_priority_fee),
2353 None,
2354 );
2355
2356 for transaction in txs.clone() {
2357 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2358 assert!(outcome.is_valid());
2359 }
2360 }
2361 }
2362
2363 #[tokio::test]
2364 async fn valid_on_gas_price_with_minimum_priority_fee_disabled() {
2365 let (txs, provider) = setup_gas_price_txs(1);
2366 let validator = create_validator_with_minimum_fee(provider, None, None);
2367
2368 for transaction in txs {
2369 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2370 assert!(outcome.is_valid());
2371 }
2372 }
2373
2374 #[test]
2375 fn reject_oversized_tx() {
2376 let mut transaction = get_transaction();
2377 transaction.encoded_length = DEFAULT_MAX_TX_INPUT_BYTES + 1;
2378 let provider = MockEthProvider::default().with_genesis_block();
2379
2380 let validator = create_validator_with_minimum_fee(provider, None, None);
2382
2383 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2384 let invalid = outcome.as_invalid().unwrap();
2385 assert!(invalid.is_oversized());
2386 }
2387
2388 #[test]
2389 fn reject_blob_tx_with_oversized_access_list() {
2390 let max_tx_input_bytes = 512;
2391 let provider = MockEthProvider::default().with_genesis_block();
2392 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
2393 .with_max_tx_input_bytes(max_tx_input_bytes)
2394 .build(InMemoryBlobStore::default());
2395
2396 let blob_tx_with_access_list = |storage_keys: usize| {
2397 let access_list = AccessList(vec![AccessListItem {
2398 address: Address::random(),
2399 storage_keys: (0..storage_keys).map(|_| B256::random()).collect(),
2400 }]);
2401 let tx = TransactionBuilder::default()
2402 .access_list(access_list)
2403 .into_eip4844()
2404 .try_into_recovered()
2405 .unwrap();
2406 let encoded_length = tx.encode_2718_len();
2407 EthPooledTransaction::new(tx, encoded_length)
2408 };
2409
2410 let is_oversized = |tx: &EthPooledTransaction| {
2411 matches!(
2412 validator.validate_stateless(TransactionOrigin::External, tx),
2413 Err(InvalidPoolTransactionError::OversizedData { .. })
2414 )
2415 };
2416
2417 let small = blob_tx_with_access_list(1);
2418 assert!(!is_oversized(&small));
2419
2420 let large = blob_tx_with_access_list(64);
2421 assert!(large.input().is_empty());
2422 assert!(is_oversized(&large));
2423 }
2424
2425 #[tokio::test]
2426 async fn valid_with_disabled_balance_check() {
2427 let transaction = get_transaction();
2428 let provider = mock_provider();
2429
2430 provider.add_account(
2432 transaction.sender(),
2433 ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
2434 );
2435
2436 let validator =
2438 EthTransactionValidatorBuilder::new(provider.clone(), EthEvmConfig::mainnet())
2439 .build(InMemoryBlobStore::default());
2440
2441 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
2442 let expected_cost = *transaction.cost();
2443 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
2444 assert!(matches!(
2445 err,
2446 InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err))
2447 if funds_err.got.is_zero() && funds_err.expected == expected_cost
2448 ));
2449 } else {
2450 panic!("Expected Invalid outcome with InsufficientFunds error");
2451 }
2452
2453 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2455 .disable_balance_check()
2456 .build(InMemoryBlobStore::default());
2457
2458 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2459 assert!(outcome.is_valid()); }
2461
2462 #[test]
2463 fn block_gas_limit_from_latest_header() {
2464 let gas_limit = 60_000_000;
2465 let validator = EthTransactionValidatorBuilder::new(
2466 mock_provider_with_gas_limit(gas_limit),
2467 test_evm_config(),
2468 )
2469 .build(InMemoryBlobStore::default());
2470 let transaction = |gas_limit| {
2471 EthPooledTransaction::try_from_consensus(
2472 TransactionBuilder::default()
2473 .chain_id(validator.chain_id())
2474 .gas_limit(gas_limit)
2475 .to(Address::ZERO)
2476 .into_eip1559()
2477 .try_into_recovered()
2478 .unwrap(),
2479 )
2480 .unwrap()
2481 };
2482
2483 assert_eq!(validator.block_gas_limit(), gas_limit);
2484 assert!(validator
2485 .validate_stateless(TransactionOrigin::External, &transaction(gas_limit))
2486 .is_ok());
2487 assert!(matches!(
2488 validator.validate_stateless(TransactionOrigin::External, &transaction(gas_limit + 1)),
2489 Err(InvalidPoolTransactionError::ExceedsGasLimit(60_000_001, 60_000_000))
2490 ));
2491 }
2492}