1use 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,
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, BlockId,
27};
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::{AccountInfoReader, BlockReaderIdExt, BytecodeReader, StateProviderFactory};
35use reth_tasks::Runtime;
36use revm::context_interface::Cfg;
37use revm_primitives::U256;
38use std::{
39 fmt,
40 marker::PhantomData,
41 sync::{
42 atomic::{AtomicBool, AtomicU64, AtomicUsize},
43 Arc,
44 },
45 time::{Instant, SystemTime},
46};
47
48pub type StatelessValidationFn<T> =
53 Arc<dyn Fn(TransactionOrigin, &T) -> Result<(), InvalidPoolTransactionError> + Send + Sync>;
54
55pub type StatefulValidationFn<T> = Arc<
60 dyn Fn(TransactionOrigin, &T, &dyn AccountInfoReader) -> Result<(), InvalidPoolTransactionError>
61 + Send
62 + Sync,
63>;
64
65pub struct EthTransactionValidator<Client, T, Evm> {
80 client: Client,
82 blob_store: Box<dyn BlobStore>,
84 fork_tracker: ForkTracker,
86 eip2718: bool,
88 eip1559: bool,
90 eip4844: bool,
92 eip7702: bool,
94 block_gas_limit: AtomicU64,
96 tx_fee_cap: Option<u128>,
98 minimum_priority_fee: Option<u128>,
100 kzg_settings: EnvKzgSettings,
102 local_transactions_config: LocalTransactionConfig,
104 max_tx_input_bytes: usize,
106 max_tx_gas_limit: Option<u64>,
108 disable_balance_check: bool,
110 evm_config: Evm,
112 _marker: PhantomData<T>,
114 validation_metrics: TxPoolValidationMetrics,
116 other_tx_types: U256,
118 eip7594: bool,
122 additional_stateless_validation: Option<StatelessValidationFn<T>>,
125 additional_stateful_validation: Option<StatefulValidationFn<T>>,
128}
129
130impl<Client, Tx, Evm> fmt::Debug for EthTransactionValidator<Client, Tx, Evm> {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 f.debug_struct("EthTransactionValidator")
133 .field("fork_tracker", &self.fork_tracker)
134 .field("eip2718", &self.eip2718)
135 .field("eip1559", &self.eip1559)
136 .field("eip4844", &self.eip4844)
137 .field("eip7702", &self.eip7702)
138 .field("block_gas_limit", &self.block_gas_limit)
139 .field("tx_fee_cap", &self.tx_fee_cap)
140 .field("minimum_priority_fee", &self.minimum_priority_fee)
141 .field("max_tx_input_bytes", &self.max_tx_input_bytes)
142 .field("max_tx_gas_limit", &self.max_tx_gas_limit)
143 .field("disable_balance_check", &self.disable_balance_check)
144 .field("eip7594", &self.eip7594)
145 .field(
146 "additional_stateless_validation",
147 &self.additional_stateless_validation.as_ref().map(|_| "..."),
148 )
149 .field(
150 "additional_stateful_validation",
151 &self.additional_stateful_validation.as_ref().map(|_| "..."),
152 )
153 .finish()
154 }
155}
156
157impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm> {
158 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
160 where
161 Client: ChainSpecProvider,
162 {
163 self.client().chain_spec()
164 }
165
166 pub fn chain_id(&self) -> u64
168 where
169 Client: ChainSpecProvider,
170 {
171 self.client().chain_spec().chain().id()
172 }
173
174 pub const fn client(&self) -> &Client {
176 &self.client
177 }
178
179 pub const fn fork_tracker(&self) -> &ForkTracker {
181 &self.fork_tracker
182 }
183
184 pub const fn evm_config(&self) -> &Evm {
186 &self.evm_config
187 }
188
189 pub const fn eip2718(&self) -> bool {
191 self.eip2718
192 }
193
194 pub const fn eip1559(&self) -> bool {
196 self.eip1559
197 }
198
199 pub const fn eip4844(&self) -> bool {
201 self.eip4844
202 }
203
204 pub const fn eip7702(&self) -> bool {
206 self.eip7702
207 }
208
209 pub const fn tx_fee_cap(&self) -> &Option<u128> {
211 &self.tx_fee_cap
212 }
213
214 pub const fn minimum_priority_fee(&self) -> &Option<u128> {
216 &self.minimum_priority_fee
217 }
218
219 pub const fn kzg_settings(&self) -> &EnvKzgSettings {
221 &self.kzg_settings
222 }
223
224 pub const fn local_transactions_config(&self) -> &LocalTransactionConfig {
226 &self.local_transactions_config
227 }
228
229 pub const fn max_tx_input_bytes(&self) -> usize {
232 self.max_tx_input_bytes
233 }
234
235 pub const fn disable_balance_check(&self) -> bool {
237 self.disable_balance_check
238 }
239
240 pub fn set_additional_stateless_validation<F>(&mut self, f: F)
265 where
266 F: Fn(TransactionOrigin, &Tx) -> Result<(), InvalidPoolTransactionError>
267 + Send
268 + Sync
269 + 'static,
270 {
271 self.additional_stateless_validation = Some(Arc::new(f));
272 }
273
274 pub fn set_additional_stateless_validation_fn(&mut self, f: StatelessValidationFn<Tx>) {
281 self.additional_stateless_validation = Some(f);
282 }
283
284 pub fn set_additional_stateless_validation_fn_opt(
289 &mut self,
290 f: Option<StatelessValidationFn<Tx>>,
291 ) {
292 self.additional_stateless_validation = f;
293 }
294
295 pub fn set_additional_stateful_validation<F>(&mut self, f: F)
320 where
321 F: Fn(
322 TransactionOrigin,
323 &Tx,
324 &dyn AccountInfoReader,
325 ) -> Result<(), InvalidPoolTransactionError>
326 + Send
327 + Sync
328 + 'static,
329 {
330 self.additional_stateful_validation = Some(Arc::new(f));
331 }
332
333 pub fn set_additional_stateful_validation_fn(&mut self, f: StatefulValidationFn<Tx>) {
340 self.additional_stateful_validation = Some(f);
341 }
342
343 pub fn set_additional_stateful_validation_fn_opt(
348 &mut self,
349 f: Option<StatefulValidationFn<Tx>>,
350 ) {
351 self.additional_stateful_validation = f;
352 }
353}
354
355impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm>
356where
357 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
358 Tx: EthPoolTransaction,
359 Evm: ConfigureEvm,
360{
361 pub fn block_gas_limit(&self) -> u64 {
363 self.max_gas_limit()
364 }
365
366 pub fn validate_one(
370 &self,
371 origin: TransactionOrigin,
372 transaction: Tx,
373 ) -> TransactionValidationOutcome<Tx> {
374 self.validate_one_with_provider(origin, transaction, &mut None)
375 }
376
377 pub fn validate_one_with_state(
384 &self,
385 origin: TransactionOrigin,
386 transaction: Tx,
387 state: &mut Option<Box<dyn AccountInfoReader + Send>>,
388 ) -> TransactionValidationOutcome<Tx> {
389 self.validate_one_with_provider(origin, transaction, state)
390 }
391
392 fn validate_one_with_provider(
396 &self,
397 origin: TransactionOrigin,
398 transaction: Tx,
399 maybe_state: &mut Option<Box<dyn AccountInfoReader + Send>>,
400 ) -> TransactionValidationOutcome<Tx> {
401 match self.validate_stateless(origin, &transaction) {
402 Ok(()) => {
403 if maybe_state.is_none() {
406 match self.client.latest() {
407 Ok(new_state) => {
408 *maybe_state = Some(Box::new(new_state));
409 }
410 Err(err) => {
411 return TransactionValidationOutcome::Error(
412 *transaction.hash(),
413 Box::new(err),
414 )
415 }
416 }
417 }
418
419 let state = maybe_state.as_deref().expect("provider is set");
420
421 self.validate_stateful(origin, transaction, state)
422 }
423 Err(err) => TransactionValidationOutcome::Invalid(transaction, err),
424 }
425 }
426
427 pub fn validate_one_with_state_provider(
430 &self,
431 origin: TransactionOrigin,
432 transaction: Tx,
433 state: impl AccountInfoReader,
434 ) -> TransactionValidationOutcome<Tx> {
435 if let Err(err) = self.validate_stateless(origin, &transaction) {
436 return TransactionValidationOutcome::Invalid(transaction, err);
437 }
438 self.validate_stateful(origin, transaction, state)
439 }
440
441 pub fn validate_stateless(
446 &self,
447 origin: TransactionOrigin,
448 transaction: &Tx,
449 ) -> Result<(), InvalidPoolTransactionError> {
450 match transaction.ty() {
452 EIP2930_TX_TYPE_ID if !self.eip2718 => {
454 return Err(InvalidTransactionError::Eip2930Disabled.into())
455 }
456 EIP1559_TX_TYPE_ID if !self.eip1559 => {
458 return Err(InvalidTransactionError::Eip1559Disabled.into())
459 }
460 EIP4844_TX_TYPE_ID if !self.eip4844 => {
462 return Err(InvalidTransactionError::Eip4844Disabled.into())
463 }
464 EIP7702_TX_TYPE_ID if !self.eip7702 => {
466 return Err(InvalidTransactionError::Eip7702Disabled.into())
467 }
468 LEGACY_TX_TYPE_ID | EIP2930_TX_TYPE_ID | EIP1559_TX_TYPE_ID | EIP4844_TX_TYPE_ID |
470 EIP7702_TX_TYPE_ID => {}
471
472 ty if !self.other_tx_types.bit(ty as usize) => {
473 return Err(InvalidTransactionError::TxTypeNotSupported.into())
474 }
475
476 _ => {}
477 };
478
479 let tx_nonce = transaction.nonce();
481 if tx_nonce == u64::MAX {
482 return Err(InvalidPoolTransactionError::Eip2681)
483 }
484
485 if transaction.is_eip4844() {
487 let tx_input_len = transaction.input().len();
492 if tx_input_len > self.max_tx_input_bytes {
493 return Err(InvalidPoolTransactionError::OversizedData {
494 size: tx_input_len,
495 limit: self.max_tx_input_bytes,
496 })
497 }
498 } else {
499 let tx_size = transaction.encoded_length();
501 if tx_size > self.max_tx_input_bytes {
502 return Err(InvalidPoolTransactionError::OversizedData {
503 size: tx_size,
504 limit: self.max_tx_input_bytes,
505 })
506 }
507 }
508
509 if self.fork_tracker.is_shanghai_activated() {
511 let max_initcode_size =
512 self.fork_tracker.max_initcode_size.load(std::sync::atomic::Ordering::Relaxed);
513 transaction.ensure_max_init_code_size(max_initcode_size)?;
514 }
515
516 let transaction_gas_limit = transaction.gas_limit();
518 let block_gas_limit = self.max_gas_limit();
519 if transaction_gas_limit > block_gas_limit {
520 return Err(InvalidPoolTransactionError::ExceedsGasLimit(
521 transaction_gas_limit,
522 block_gas_limit,
523 ))
524 }
525
526 if let Some(max_tx_gas_limit) = self.max_tx_gas_limit &&
528 transaction_gas_limit > max_tx_gas_limit
529 {
530 return Err(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
531 transaction_gas_limit,
532 max_tx_gas_limit,
533 ))
534 }
535
536 if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
538 return Err(InvalidTransactionError::TipAboveFeeCap.into())
539 }
540
541 let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
543
544 if is_local {
547 match self.tx_fee_cap {
548 Some(0) | None => {} Some(tx_fee_cap_wei) => {
550 let max_tx_fee_wei = transaction.cost().saturating_sub(transaction.value());
551 if max_tx_fee_wei > tx_fee_cap_wei {
552 return Err(InvalidPoolTransactionError::ExceedsFeeCap {
553 max_tx_fee_wei: max_tx_fee_wei.saturating_to(),
554 tx_fee_cap_wei,
555 })
556 }
557 }
558 }
559 }
560
561 if !is_local &&
564 transaction.is_dynamic_fee() &&
565 transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
566 {
567 return Err(InvalidPoolTransactionError::PriorityFeeBelowMinimum {
568 minimum_priority_fee: self
569 .minimum_priority_fee
570 .expect("minimum priority fee is expected inside if statement"),
571 })
572 }
573
574 if let Some(chain_id) = transaction.chain_id() &&
576 chain_id != self.chain_id()
577 {
578 return Err(InvalidTransactionError::ChainIdMismatch.into())
579 }
580
581 if transaction.is_eip7702() {
582 if !self.fork_tracker.is_prague_activated() {
584 return Err(InvalidTransactionError::TxTypeNotSupported.into())
585 }
586
587 if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
588 return Err(Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into())
589 }
590 }
591
592 ensure_intrinsic_gas(transaction, &self.fork_tracker)?;
593
594 if transaction.is_eip4844() {
596 if !self.fork_tracker.is_cancun_activated() {
598 return Err(InvalidTransactionError::TxTypeNotSupported.into())
599 }
600
601 let blob_count = transaction.blob_count().unwrap_or(0);
602 if blob_count == 0 {
603 return Err(InvalidPoolTransactionError::Eip4844(
605 Eip4844PoolTransactionError::NoEip4844Blobs,
606 ))
607 }
608
609 let max_blob_count = self.fork_tracker.max_blob_count();
610 if blob_count > max_blob_count {
611 return Err(InvalidPoolTransactionError::Eip4844(
612 Eip4844PoolTransactionError::TooManyEip4844Blobs {
613 have: blob_count,
614 permitted: max_blob_count,
615 },
616 ))
617 }
618 }
619
620 let tx_gas_limit_cap =
622 self.fork_tracker.tx_gas_limit_cap.load(std::sync::atomic::Ordering::Relaxed);
623 if tx_gas_limit_cap > 0 && transaction.gas_limit() > tx_gas_limit_cap {
624 return Err(InvalidTransactionError::GasLimitTooHigh.into())
625 }
626
627 if let Some(check) = &self.additional_stateless_validation {
629 check(origin, transaction)?;
630 }
631
632 Ok(())
633 }
634
635 pub fn validate_stateful<P>(
640 &self,
641 origin: TransactionOrigin,
642 mut transaction: Tx,
643 state: P,
644 ) -> TransactionValidationOutcome<Tx>
645 where
646 P: AccountInfoReader,
647 {
648 let account = match state.basic_account(transaction.sender_ref()) {
650 Ok(account) => account.unwrap_or_default(),
651 Err(err) => {
652 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
653 }
654 };
655
656 match self.validate_sender_bytecode(&transaction, &account, &state) {
658 Err(outcome) => return outcome,
659 Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err),
660 _ => {}
661 };
662
663 if transaction.requires_nonce_check() &&
665 let Err(err) = self.validate_sender_nonce(&transaction, &account)
666 {
667 return TransactionValidationOutcome::Invalid(transaction, err)
668 }
669
670 if let Err(err) = self.validate_sender_balance(&transaction, &account) {
672 return TransactionValidationOutcome::Invalid(transaction, err)
673 }
674
675 let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) {
677 Err(err) => return TransactionValidationOutcome::Invalid(transaction, err),
678 Ok(sidecar) => sidecar,
679 };
680
681 if let Some(check) = &self.additional_stateful_validation &&
683 let Err(err) = check(origin, &transaction, &state)
684 {
685 return TransactionValidationOutcome::Invalid(transaction, err)
686 }
687
688 let authorities = self.recover_authorities(&transaction);
689 TransactionValidationOutcome::Valid {
691 balance: account.balance,
692 state_nonce: account.nonce,
693 bytecode_hash: account.bytecode_hash,
694 transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
695 propagate: match origin {
697 TransactionOrigin::External => true,
698 TransactionOrigin::Local => {
699 self.local_transactions_config.propagate_local_transactions
700 }
701 TransactionOrigin::Private => false,
702 },
703 authorities,
704 }
705 }
706
707 pub fn validate_sender_bytecode(
709 &self,
710 transaction: &Tx,
711 sender: &Account,
712 state: impl BytecodeReader,
713 ) -> Result<Result<(), InvalidPoolTransactionError>, TransactionValidationOutcome<Tx>> {
714 if let Some(code_hash) = &sender.bytecode_hash {
721 let is_eip7702 = if self.fork_tracker.is_prague_activated() {
722 match state.bytecode_by_hash(code_hash) {
723 Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
724 Err(err) => {
725 return Err(TransactionValidationOutcome::Error(
726 *transaction.hash(),
727 Box::new(err),
728 ))
729 }
730 }
731 } else {
732 false
733 };
734
735 if !is_eip7702 {
736 return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into()))
737 }
738 }
739 Ok(Ok(()))
740 }
741
742 pub fn validate_sender_nonce(
744 &self,
745 transaction: &Tx,
746 sender: &Account,
747 ) -> Result<(), InvalidPoolTransactionError> {
748 let tx_nonce = transaction.nonce();
749
750 if tx_nonce < sender.nonce {
751 return Err(InvalidTransactionError::NonceNotConsistent {
752 tx: tx_nonce,
753 state: sender.nonce,
754 }
755 .into())
756 }
757 Ok(())
758 }
759
760 pub fn validate_sender_balance(
762 &self,
763 transaction: &Tx,
764 sender: &Account,
765 ) -> Result<(), InvalidPoolTransactionError> {
766 let cost = transaction.cost();
767
768 if !self.disable_balance_check && cost > &sender.balance {
769 let expected = *cost;
770 return Err(InvalidTransactionError::InsufficientFunds(
771 GotExpected { got: sender.balance, expected }.into(),
772 )
773 .into())
774 }
775 Ok(())
776 }
777
778 pub fn validate_eip4844(
780 &self,
781 transaction: &mut Tx,
782 ) -> Result<Option<BlobTransactionSidecarVariant>, InvalidPoolTransactionError> {
783 let mut maybe_blob_sidecar = None;
784
785 if transaction.is_eip4844() {
787 match transaction.take_blob() {
789 EthBlobTransactionSidecar::None => {
790 return Err(InvalidTransactionError::TxTypeNotSupported.into())
792 }
793 EthBlobTransactionSidecar::Missing => {
794 if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) {
799 } else {
801 return Err(InvalidPoolTransactionError::Eip4844(
802 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
803 ))
804 }
805 }
806 EthBlobTransactionSidecar::Present(sidecar) => {
807 let now = Instant::now();
808
809 if self.eip7594 {
811 if self.fork_tracker.is_osaka_activated() {
813 if sidecar.is_eip4844() {
814 return Err(InvalidPoolTransactionError::Eip4844(
815 Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
816 ))
817 }
818 } else if sidecar.is_eip7594() && !self.allow_7594_sidecars() {
819 return Err(InvalidPoolTransactionError::Eip4844(
820 Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
821 ))
822 }
823 } else {
824 if sidecar.is_eip7594() {
826 return Err(InvalidPoolTransactionError::Eip4844(
827 Eip4844PoolTransactionError::Eip7594SidecarDisallowed,
828 ))
829 }
830 }
831
832 if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
834 return Err(InvalidPoolTransactionError::Eip4844(
835 Eip4844PoolTransactionError::InvalidEip4844Blob(err),
836 ))
837 }
838 self.validation_metrics.blob_validation_duration.record(now.elapsed());
840 maybe_blob_sidecar = Some(sidecar);
842 }
843 }
844 }
845 Ok(maybe_blob_sidecar)
846 }
847
848 fn recover_authorities(&self, transaction: &Tx) -> std::option::Option<Vec<Address>> {
850 transaction
851 .authorization_list()
852 .map(|auths| auths.iter().flat_map(|auth| auth.recover_authority()).collect::<Vec<_>>())
853 }
854
855 fn validate_batch(
857 &self,
858 transactions: impl IntoIterator<Item = (TransactionOrigin, Tx)>,
859 ) -> Vec<TransactionValidationOutcome<Tx>> {
860 let mut provider = None;
861 transactions
862 .into_iter()
863 .map(|(origin, tx)| self.validate_one_with_provider(origin, tx, &mut provider))
864 .collect()
865 }
866
867 fn validate_batch_with_origin(
869 &self,
870 origin: TransactionOrigin,
871 transactions: impl IntoIterator<Item = Tx> + Send,
872 ) -> Vec<TransactionValidationOutcome<Tx>> {
873 let mut provider = None;
874 transactions
875 .into_iter()
876 .map(|tx| self.validate_one_with_provider(origin, tx, &mut provider))
877 .collect()
878 }
879
880 fn on_new_head_block(&self, new_tip_block: &HeaderTy<Evm::Primitives>) {
881 if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
883 self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
884 }
885
886 if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
887 self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
888 }
889
890 if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
891 self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
892 }
893
894 if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
895 self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
896 }
897
898 self.fork_tracker
899 .tip_timestamp
900 .store(new_tip_block.timestamp(), std::sync::atomic::Ordering::Relaxed);
901
902 if let Some(blob_params) =
903 self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
904 {
905 self.fork_tracker
906 .max_blob_count
907 .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed);
908 }
909
910 self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
911
912 let evm_env = self
914 .evm_config
915 .evm_env(new_tip_block)
916 .expect("evm_env should not fail for executed block");
917
918 self.fork_tracker
919 .max_initcode_size
920 .store(evm_env.cfg_env.max_initcode_size(), std::sync::atomic::Ordering::Relaxed);
921 let tx_gas_limit_cap = if evm_env.cfg_env.is_amsterdam_eip8037_enabled() {
925 0
926 } else {
927 evm_env.cfg_env.tx_gas_limit_cap()
928 };
929 self.fork_tracker
930 .tx_gas_limit_cap
931 .store(tx_gas_limit_cap, std::sync::atomic::Ordering::Relaxed);
932 }
933
934 fn max_gas_limit(&self) -> u64 {
935 self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
936 }
937
938 fn allow_7594_sidecars(&self) -> bool {
940 let tip_timestamp = self.fork_tracker.tip_timestamp();
941
942 if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(12)) {
944 true
945 } else if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(24)) {
946 let current_timestamp =
947 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
948
949 current_timestamp >= tip_timestamp.saturating_add(4)
951 } else {
952 false
953 }
954 }
955}
956
957impl<Client, Tx, Evm> TransactionValidator for EthTransactionValidator<Client, Tx, Evm>
958where
959 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
960 Tx: EthPoolTransaction,
961 Evm: ConfigureEvm,
962{
963 type Transaction = Tx;
964 type Block = BlockTy<Evm::Primitives>;
965
966 async fn validate_transaction(
967 &self,
968 origin: TransactionOrigin,
969 transaction: Self::Transaction,
970 ) -> TransactionValidationOutcome<Self::Transaction> {
971 self.validate_one(origin, transaction)
972 }
973
974 async fn validate_transactions(
975 &self,
976 transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
977 + Send,
978 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
979 self.validate_batch(transactions)
980 }
981
982 async fn validate_transactions_with_origin(
983 &self,
984 origin: TransactionOrigin,
985 transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
986 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
987 self.validate_batch_with_origin(origin, transactions)
988 }
989
990 fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
991 Self::on_new_head_block(self, new_tip_block.header())
992 }
993}
994
995#[derive(Debug)]
997pub struct EthTransactionValidatorBuilder<Client, Evm> {
998 client: Client,
999 evm_config: Evm,
1001 shanghai: bool,
1003 cancun: bool,
1005 prague: bool,
1007 osaka: bool,
1009 tip_timestamp: u64,
1011 max_blob_count: u64,
1013 eip2718: bool,
1015 eip1559: bool,
1017 eip4844: bool,
1019 eip7702: bool,
1021 block_gas_limit: AtomicU64,
1023 tx_fee_cap: Option<u128>,
1025 minimum_priority_fee: Option<u128>,
1027 additional_tasks: usize,
1031
1032 kzg_settings: EnvKzgSettings,
1034 local_transactions_config: LocalTransactionConfig,
1036 max_tx_input_bytes: usize,
1038 max_tx_gas_limit: Option<u64>,
1040 disable_balance_check: bool,
1042 other_tx_types: U256,
1044 max_initcode_size: usize,
1046 tx_gas_limit_cap: u64,
1048 eip7594: bool,
1052}
1053
1054impl<Client, Evm> EthTransactionValidatorBuilder<Client, Evm> {
1055 pub fn new(client: Client, evm_config: Evm) -> Self
1065 where
1066 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
1067 + BlockReaderIdExt<Header = HeaderTy<Evm::Primitives>>,
1068 Evm: ConfigureEvm,
1069 {
1070 let chain_spec = client.chain_spec();
1071 let tip = client
1072 .header_by_id(BlockId::latest())
1073 .expect("failed to fetch latest header")
1074 .expect("latest header is not found");
1075 let evm_env =
1076 evm_config.evm_env(&tip).expect("evm_env should not fail for existing blocks");
1077
1078 Self {
1079 block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(),
1080 client,
1081 evm_config,
1082 minimum_priority_fee: None,
1083 additional_tasks: 1,
1084 kzg_settings: EnvKzgSettings::Default,
1085 local_transactions_config: Default::default(),
1086 max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
1087 tx_fee_cap: Some(1e18 as u128),
1088 max_tx_gas_limit: None,
1089 eip2718: true,
1091 eip1559: true,
1092 eip4844: true,
1093 eip7702: true,
1094
1095 shanghai: chain_spec.is_shanghai_active_at_timestamp(tip.timestamp()),
1096 cancun: chain_spec.is_cancun_active_at_timestamp(tip.timestamp()),
1097 prague: chain_spec.is_prague_active_at_timestamp(tip.timestamp()),
1098 osaka: chain_spec.is_osaka_active_at_timestamp(tip.timestamp()),
1099
1100 tip_timestamp: tip.timestamp(),
1101
1102 max_blob_count: chain_spec
1103 .blob_params_at_timestamp(tip.timestamp())
1104 .unwrap_or_else(BlobParams::prague)
1105 .max_blobs_per_tx,
1106
1107 disable_balance_check: false,
1109
1110 other_tx_types: U256::ZERO,
1112
1113 tx_gas_limit_cap: if evm_env.cfg_env.is_amsterdam_eip8037_enabled() {
1115 0
1116 } else {
1117 evm_env.cfg_env.tx_gas_limit_cap()
1118 },
1119 max_initcode_size: evm_env.cfg_env.max_initcode_size(),
1120
1121 eip7594: true,
1123 }
1124 }
1125
1126 pub const fn no_cancun(self) -> Self {
1128 self.set_cancun(false)
1129 }
1130
1131 pub fn with_local_transactions_config(
1133 mut self,
1134 local_transactions_config: LocalTransactionConfig,
1135 ) -> Self {
1136 self.local_transactions_config = local_transactions_config;
1137 self
1138 }
1139
1140 pub const fn set_cancun(mut self, cancun: bool) -> Self {
1142 self.cancun = cancun;
1143 self
1144 }
1145
1146 pub const fn no_shanghai(self) -> Self {
1148 self.set_shanghai(false)
1149 }
1150
1151 pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
1153 self.shanghai = shanghai;
1154 self
1155 }
1156
1157 pub const fn no_prague(self) -> Self {
1159 self.set_prague(false)
1160 }
1161
1162 pub const fn set_prague(mut self, prague: bool) -> Self {
1164 self.prague = prague;
1165 self
1166 }
1167
1168 pub const fn no_osaka(self) -> Self {
1170 self.set_osaka(false)
1171 }
1172
1173 pub const fn set_osaka(mut self, osaka: bool) -> Self {
1175 self.osaka = osaka;
1176 self
1177 }
1178
1179 pub const fn no_eip2718(self) -> Self {
1181 self.set_eip2718(false)
1182 }
1183
1184 pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
1186 self.eip2718 = eip2718;
1187 self
1188 }
1189
1190 pub const fn no_eip1559(self) -> Self {
1192 self.set_eip1559(false)
1193 }
1194
1195 pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
1197 self.eip1559 = eip1559;
1198 self
1199 }
1200
1201 pub const fn no_eip4844(self) -> Self {
1203 self.set_eip4844(false)
1204 }
1205
1206 pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
1208 self.eip4844 = eip4844;
1209 self
1210 }
1211
1212 pub const fn no_eip7702(self) -> Self {
1214 self.set_eip7702(false)
1215 }
1216
1217 pub const fn set_eip7702(mut self, eip7702: bool) -> Self {
1219 self.eip7702 = eip7702;
1220 self
1221 }
1222
1223 pub const fn no_eip7594(self) -> Self {
1230 self.set_eip7594(false)
1231 }
1232
1233 pub const fn set_eip7594(mut self, eip7594: bool) -> Self {
1238 self.eip7594 = eip7594;
1239 self
1240 }
1241
1242 pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
1244 self.kzg_settings = kzg_settings;
1245 self
1246 }
1247
1248 pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: Option<u128>) -> Self {
1250 self.minimum_priority_fee = minimum_priority_fee;
1251 self
1252 }
1253
1254 pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
1256 self.additional_tasks = additional_tasks;
1257 self
1258 }
1259
1260 pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
1262 self.max_tx_input_bytes = max_tx_input_bytes;
1263 self
1264 }
1265
1266 pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
1270 self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
1271 self
1272 }
1273
1274 pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
1278 self.tx_fee_cap = Some(tx_fee_cap);
1279 self
1280 }
1281
1282 pub const fn with_max_tx_gas_limit(mut self, max_tx_gas_limit: Option<u64>) -> Self {
1284 self.max_tx_gas_limit = max_tx_gas_limit;
1285 self
1286 }
1287
1288 pub const fn disable_balance_check(mut self) -> Self {
1290 self.disable_balance_check = true;
1291 self
1292 }
1293
1294 pub const fn with_custom_tx_type(mut self, tx_type: u8) -> Self {
1296 self.other_tx_types.set_bit(tx_type as usize, true);
1297 self
1298 }
1299
1300 pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx, Evm>
1302 where
1303 S: BlobStore,
1304 {
1305 let Self {
1306 client,
1307 evm_config,
1308 shanghai,
1309 cancun,
1310 prague,
1311 osaka,
1312 tip_timestamp,
1313 eip2718,
1314 eip1559,
1315 eip4844,
1316 eip7702,
1317 block_gas_limit,
1318 tx_fee_cap,
1319 minimum_priority_fee,
1320 kzg_settings,
1321 local_transactions_config,
1322 max_tx_input_bytes,
1323 max_tx_gas_limit,
1324 disable_balance_check,
1325 max_blob_count,
1326 additional_tasks: _,
1327 other_tx_types,
1328 max_initcode_size,
1329 tx_gas_limit_cap,
1330 eip7594,
1331 } = self;
1332
1333 let fork_tracker = ForkTracker {
1334 shanghai: AtomicBool::new(shanghai),
1335 cancun: AtomicBool::new(cancun),
1336 prague: AtomicBool::new(prague),
1337 osaka: AtomicBool::new(osaka),
1338 tip_timestamp: AtomicU64::new(tip_timestamp),
1339 max_blob_count: AtomicU64::new(max_blob_count),
1340 max_initcode_size: AtomicUsize::new(max_initcode_size),
1341 tx_gas_limit_cap: AtomicU64::new(tx_gas_limit_cap),
1342 };
1343
1344 EthTransactionValidator {
1345 client,
1346 eip2718,
1347 eip1559,
1348 fork_tracker,
1349 eip4844,
1350 eip7702,
1351 block_gas_limit,
1352 tx_fee_cap,
1353 minimum_priority_fee,
1354 blob_store: Box::new(blob_store),
1355 kzg_settings,
1356 local_transactions_config,
1357 max_tx_input_bytes,
1358 max_tx_gas_limit,
1359 disable_balance_check,
1360 evm_config,
1361 _marker: Default::default(),
1362 validation_metrics: TxPoolValidationMetrics::default(),
1363 other_tx_types,
1364 eip7594,
1365 additional_stateless_validation: None,
1366 additional_stateful_validation: None,
1367 }
1368 }
1369
1370 pub fn build_with_tasks<Tx, S>(
1377 self,
1378 tasks: Runtime,
1379 blob_store: S,
1380 ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx, Evm>>
1381 where
1382 S: BlobStore,
1383 {
1384 let additional_tasks = self.additional_tasks;
1385 let validator = self.build::<Tx, S>(blob_store);
1386 TransactionValidationTaskExecutor::spawn(validator, &tasks, additional_tasks)
1387 }
1388}
1389
1390#[derive(Debug)]
1392pub struct ForkTracker {
1393 pub shanghai: AtomicBool,
1395 pub cancun: AtomicBool,
1397 pub prague: AtomicBool,
1399 pub osaka: AtomicBool,
1401 pub max_blob_count: AtomicU64,
1403 pub tip_timestamp: AtomicU64,
1405 pub max_initcode_size: AtomicUsize,
1407 pub tx_gas_limit_cap: AtomicU64,
1409}
1410
1411impl ForkTracker {
1412 pub fn is_shanghai_activated(&self) -> bool {
1414 self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1415 }
1416
1417 pub fn is_cancun_activated(&self) -> bool {
1419 self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1420 }
1421
1422 pub fn is_prague_activated(&self) -> bool {
1424 self.prague.load(std::sync::atomic::Ordering::Relaxed)
1425 }
1426
1427 pub fn is_osaka_activated(&self) -> bool {
1429 self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1430 }
1431
1432 pub fn tip_timestamp(&self) -> u64 {
1434 self.tip_timestamp.load(std::sync::atomic::Ordering::Relaxed)
1435 }
1436
1437 pub fn max_blob_count(&self) -> u64 {
1439 self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1440 }
1441}
1442
1443pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1447 transaction: &T,
1448 fork_tracker: &ForkTracker,
1449) -> Result<(), InvalidPoolTransactionError> {
1450 use revm_primitives::hardfork::SpecId;
1451 let spec_id = if fork_tracker.is_prague_activated() {
1452 SpecId::PRAGUE
1453 } else if fork_tracker.is_shanghai_activated() {
1454 SpecId::SHANGHAI
1455 } else {
1456 SpecId::MERGE
1457 };
1458
1459 let gas = revm_interpreter::gas::calculate_initial_tx_gas(
1460 spec_id,
1461 transaction.input(),
1462 transaction.is_create(),
1463 transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1464 transaction
1465 .access_list()
1466 .map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
1467 .unwrap_or_default() as u64,
1468 transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
1469 );
1470
1471 let gas_limit = transaction.gas_limit();
1472 if gas_limit < gas.initial_total_gas() || gas_limit < gas.floor_gas {
1473 Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1474 } else {
1475 Ok(())
1476 }
1477}
1478
1479#[cfg(test)]
1480mod tests {
1481 use super::*;
1482 use crate::{
1483 blobstore::InMemoryBlobStore, error::PoolErrorKind, traits::PoolTransaction,
1484 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1485 };
1486 use alloy_consensus::Transaction;
1487 use alloy_eips::eip2718::Decodable2718;
1488 use alloy_primitives::{hex, U256};
1489 use reth_ethereum_primitives::PooledTransactionVariant;
1490 use reth_evm_ethereum::EthEvmConfig;
1491 use reth_primitives_traits::SignedTransaction;
1492 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1493 use revm_primitives::eip3860::MAX_INITCODE_SIZE;
1494
1495 fn test_evm_config() -> EthEvmConfig {
1496 EthEvmConfig::mainnet()
1497 }
1498
1499 fn get_transaction() -> EthPooledTransaction {
1500 let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1501
1502 let data = hex::decode(raw).unwrap();
1503 let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1504
1505 EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1506 }
1507
1508 #[tokio::test]
1510 async fn validate_transaction() {
1511 let transaction = get_transaction();
1512 let mut fork_tracker = ForkTracker {
1513 shanghai: false.into(),
1514 cancun: false.into(),
1515 prague: false.into(),
1516 osaka: false.into(),
1517 tip_timestamp: 0.into(),
1518 max_blob_count: 0.into(),
1519 max_initcode_size: AtomicUsize::new(MAX_INITCODE_SIZE),
1520 tx_gas_limit_cap: AtomicU64::new(0),
1521 };
1522
1523 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1524 assert!(res.is_ok());
1525
1526 fork_tracker.shanghai = true.into();
1527 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1528 assert!(res.is_ok());
1529
1530 let provider = MockEthProvider::default().with_genesis_block();
1531 provider.add_account(
1532 transaction.sender(),
1533 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1534 );
1535 let blob_store = InMemoryBlobStore::default();
1536 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1537 .build(blob_store.clone());
1538
1539 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1540
1541 assert!(outcome.is_valid());
1542
1543 let pool =
1544 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1545
1546 let res = pool.add_external_transaction(transaction.clone()).await;
1547 assert!(res.is_ok());
1548 let tx = pool.get(transaction.hash());
1549 assert!(tx.is_some());
1550 }
1551
1552 #[tokio::test]
1554 async fn invalid_on_gas_limit_too_high() {
1555 let transaction = get_transaction();
1556
1557 let provider = MockEthProvider::default().with_genesis_block();
1558 provider.add_account(
1559 transaction.sender(),
1560 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1561 );
1562
1563 let blob_store = InMemoryBlobStore::default();
1564 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1565 .set_block_gas_limit(1_000_000) .build(blob_store.clone());
1567
1568 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1569
1570 assert!(outcome.is_invalid());
1571
1572 let pool =
1573 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1574
1575 let res = pool.add_external_transaction(transaction.clone()).await;
1576 assert!(res.is_err());
1577 assert!(matches!(
1578 res.unwrap_err().kind,
1579 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1580 1_015_288, 1_000_000
1581 ))
1582 ));
1583 let tx = pool.get(transaction.hash());
1584 assert!(tx.is_none());
1585 }
1586
1587 #[tokio::test]
1588 async fn invalid_on_fee_cap_exceeded() {
1589 let transaction = get_transaction();
1590 let provider = MockEthProvider::default().with_genesis_block();
1591 provider.add_account(
1592 transaction.sender(),
1593 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1594 );
1595
1596 let blob_store = InMemoryBlobStore::default();
1597 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1598 .set_tx_fee_cap(100) .build(blob_store.clone());
1600
1601 let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1602 assert!(outcome.is_invalid());
1603
1604 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1605 assert!(matches!(
1606 err,
1607 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1608 if (max_tx_fee_wei > tx_fee_cap_wei)
1609 ));
1610 }
1611
1612 let pool =
1613 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1614 let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
1615 assert!(res.is_err());
1616 assert!(matches!(
1617 res.unwrap_err().kind,
1618 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
1619 ));
1620 let tx = pool.get(transaction.hash());
1621 assert!(tx.is_none());
1622 }
1623
1624 #[tokio::test]
1625 async fn valid_on_zero_fee_cap() {
1626 let transaction = get_transaction();
1627 let provider = MockEthProvider::default().with_genesis_block();
1628 provider.add_account(
1629 transaction.sender(),
1630 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1631 );
1632
1633 let blob_store = InMemoryBlobStore::default();
1634 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1635 .set_tx_fee_cap(0) .build(blob_store);
1637
1638 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1639 assert!(outcome.is_valid());
1640 }
1641
1642 #[tokio::test]
1643 async fn valid_on_normal_fee_cap() {
1644 let transaction = get_transaction();
1645 let provider = MockEthProvider::default().with_genesis_block();
1646 provider.add_account(
1647 transaction.sender(),
1648 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1649 );
1650
1651 let blob_store = InMemoryBlobStore::default();
1652 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1653 .set_tx_fee_cap(2e18 as u128) .build(blob_store);
1655
1656 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1657 assert!(outcome.is_valid());
1658 }
1659
1660 #[tokio::test]
1661 async fn invalid_on_max_tx_gas_limit_exceeded() {
1662 let transaction = get_transaction();
1663 let provider = MockEthProvider::default().with_genesis_block();
1664 provider.add_account(
1665 transaction.sender(),
1666 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1667 );
1668
1669 let blob_store = InMemoryBlobStore::default();
1670 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1671 .with_max_tx_gas_limit(Some(500_000)) .build(blob_store.clone());
1673
1674 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1675 assert!(outcome.is_invalid());
1676
1677 let pool =
1678 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1679
1680 let res = pool.add_external_transaction(transaction.clone()).await;
1681 assert!(res.is_err());
1682 assert!(matches!(
1683 res.unwrap_err().kind,
1684 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
1685 1_015_288, 500_000
1686 ))
1687 ));
1688 let tx = pool.get(transaction.hash());
1689 assert!(tx.is_none());
1690 }
1691
1692 #[tokio::test]
1693 async fn valid_on_max_tx_gas_limit_disabled() {
1694 let transaction = get_transaction();
1695 let provider = MockEthProvider::default().with_genesis_block();
1696 provider.add_account(
1697 transaction.sender(),
1698 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1699 );
1700
1701 let blob_store = InMemoryBlobStore::default();
1702 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1703 .with_max_tx_gas_limit(None) .build(blob_store);
1705
1706 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1707 assert!(outcome.is_valid());
1708 }
1709
1710 #[tokio::test]
1711 async fn valid_on_max_tx_gas_limit_within_limit() {
1712 let transaction = get_transaction();
1713 let provider = MockEthProvider::default().with_genesis_block();
1714 provider.add_account(
1715 transaction.sender(),
1716 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1717 );
1718
1719 let blob_store = InMemoryBlobStore::default();
1720 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1721 .with_max_tx_gas_limit(Some(2_000_000)) .build(blob_store);
1723
1724 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1725 assert!(outcome.is_valid());
1726 }
1727
1728 fn setup_priority_fee_test() -> (EthPooledTransaction, MockEthProvider) {
1730 let transaction = get_transaction();
1731 let provider = MockEthProvider::default().with_genesis_block();
1732 provider.add_account(
1733 transaction.sender(),
1734 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1735 );
1736 (transaction, provider)
1737 }
1738
1739 fn create_validator_with_minimum_fee(
1741 provider: MockEthProvider,
1742 minimum_priority_fee: Option<u128>,
1743 local_config: Option<LocalTransactionConfig>,
1744 ) -> EthTransactionValidator<MockEthProvider, EthPooledTransaction, EthEvmConfig> {
1745 let blob_store = InMemoryBlobStore::default();
1746 let mut builder = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1747 .with_minimum_priority_fee(minimum_priority_fee);
1748
1749 if let Some(config) = local_config {
1750 builder = builder.with_local_transactions_config(config);
1751 }
1752
1753 builder.build(blob_store)
1754 }
1755
1756 #[tokio::test]
1757 async fn invalid_on_priority_fee_lower_than_configured_minimum() {
1758 let (transaction, provider) = setup_priority_fee_test();
1759
1760 assert!(transaction.is_dynamic_fee());
1762
1763 let minimum_priority_fee =
1765 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1766
1767 let validator =
1768 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1769
1770 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1772 assert!(outcome.is_invalid());
1773
1774 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1775 assert!(matches!(
1776 err,
1777 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1778 if min_fee == minimum_priority_fee
1779 ));
1780 }
1781
1782 let blob_store = InMemoryBlobStore::default();
1784 let pool =
1785 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1786
1787 let res = pool.add_external_transaction(transaction.clone()).await;
1788 assert!(res.is_err());
1789 assert!(matches!(
1790 res.unwrap_err().kind,
1791 PoolErrorKind::InvalidTransaction(
1792 InvalidPoolTransactionError::PriorityFeeBelowMinimum { .. }
1793 )
1794 ));
1795 let tx = pool.get(transaction.hash());
1796 assert!(tx.is_none());
1797
1798 let (_, local_provider) = setup_priority_fee_test();
1800 let validator_local =
1801 create_validator_with_minimum_fee(local_provider, Some(minimum_priority_fee), None);
1802
1803 let local_outcome = validator_local.validate_one(TransactionOrigin::Local, transaction);
1804 assert!(local_outcome.is_valid());
1805 }
1806
1807 #[tokio::test]
1808 async fn valid_on_priority_fee_equal_to_minimum() {
1809 let (transaction, provider) = setup_priority_fee_test();
1810
1811 let tx_priority_fee =
1813 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1814 let validator = create_validator_with_minimum_fee(provider, Some(tx_priority_fee), None);
1815
1816 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1817 assert!(outcome.is_valid());
1818 }
1819
1820 #[tokio::test]
1821 async fn valid_on_priority_fee_above_minimum() {
1822 let (transaction, provider) = setup_priority_fee_test();
1823
1824 let tx_priority_fee =
1826 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1827 let minimum_priority_fee = tx_priority_fee / 2; let validator =
1830 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1831
1832 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1833 assert!(outcome.is_valid());
1834 }
1835
1836 #[tokio::test]
1837 async fn valid_on_minimum_priority_fee_disabled() {
1838 let (transaction, provider) = setup_priority_fee_test();
1839
1840 let validator = create_validator_with_minimum_fee(provider, None, None);
1842
1843 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1844 assert!(outcome.is_valid());
1845 }
1846
1847 #[tokio::test]
1848 async fn priority_fee_validation_applies_to_private_transactions() {
1849 let (transaction, provider) = setup_priority_fee_test();
1850
1851 let minimum_priority_fee =
1853 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1854
1855 let validator =
1856 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1857
1858 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1861 assert!(outcome.is_invalid());
1862
1863 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1864 assert!(matches!(
1865 err,
1866 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1867 if min_fee == minimum_priority_fee
1868 ));
1869 }
1870 }
1871
1872 #[tokio::test]
1873 async fn valid_on_local_config_exempts_private_transactions() {
1874 let (transaction, provider) = setup_priority_fee_test();
1875
1876 let minimum_priority_fee =
1878 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1879
1880 let local_config =
1882 LocalTransactionConfig { propagate_local_transactions: true, ..Default::default() };
1883
1884 let validator = create_validator_with_minimum_fee(
1885 provider,
1886 Some(minimum_priority_fee),
1887 Some(local_config),
1888 );
1889
1890 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1894 assert!(outcome.is_invalid()); }
1896
1897 #[test]
1898 fn reject_oversized_tx() {
1899 let mut transaction = get_transaction();
1900 transaction.encoded_length = DEFAULT_MAX_TX_INPUT_BYTES + 1;
1901 let provider = MockEthProvider::default().with_genesis_block();
1902
1903 let validator = create_validator_with_minimum_fee(provider, None, None);
1905
1906 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1907 let invalid = outcome.as_invalid().unwrap();
1908 assert!(invalid.is_oversized());
1909 }
1910
1911 #[tokio::test]
1912 async fn valid_with_disabled_balance_check() {
1913 let transaction = get_transaction();
1914 let provider = MockEthProvider::default().with_genesis_block();
1915
1916 provider.add_account(
1918 transaction.sender(),
1919 ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
1920 );
1921
1922 let validator =
1924 EthTransactionValidatorBuilder::new(provider.clone(), EthEvmConfig::mainnet())
1925 .build(InMemoryBlobStore::default());
1926
1927 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1928 let expected_cost = *transaction.cost();
1929 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1930 assert!(matches!(
1931 err,
1932 InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err))
1933 if funds_err.got == alloy_primitives::U256::ZERO && funds_err.expected == expected_cost
1934 ));
1935 } else {
1936 panic!("Expected Invalid outcome with InsufficientFunds error");
1937 }
1938
1939 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1941 .disable_balance_check()
1942 .build(InMemoryBlobStore::default());
1943
1944 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1945 assert!(outcome.is_valid()); }
1947}