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, ValidationTask},
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};
47use tokio::sync::Mutex;
48
49type StatelessValidationFn<T> =
54 Arc<dyn Fn(TransactionOrigin, &T) -> Result<(), InvalidPoolTransactionError> + Send + Sync>;
55
56type StatefulValidationFn<T> = Arc<
61 dyn Fn(TransactionOrigin, &T, &dyn AccountInfoReader) -> Result<(), InvalidPoolTransactionError>
62 + Send
63 + Sync,
64>;
65
66pub struct EthTransactionValidator<Client, T, Evm> {
81 client: Client,
83 blob_store: Box<dyn BlobStore>,
85 fork_tracker: ForkTracker,
87 eip2718: bool,
89 eip1559: bool,
91 eip4844: bool,
93 eip7702: bool,
95 block_gas_limit: AtomicU64,
97 tx_fee_cap: Option<u128>,
99 minimum_priority_fee: Option<u128>,
101 kzg_settings: EnvKzgSettings,
103 local_transactions_config: LocalTransactionConfig,
105 max_tx_input_bytes: usize,
107 max_tx_gas_limit: Option<u64>,
109 disable_balance_check: bool,
111 evm_config: Evm,
113 _marker: PhantomData<T>,
115 validation_metrics: TxPoolValidationMetrics,
117 other_tx_types: U256,
119 eip7594: bool,
123 additional_stateless_validation: Option<StatelessValidationFn<T>>,
126 additional_stateful_validation: Option<StatefulValidationFn<T>>,
129}
130
131impl<Client, Tx, Evm> fmt::Debug for EthTransactionValidator<Client, Tx, Evm> {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 f.debug_struct("EthTransactionValidator")
134 .field("fork_tracker", &self.fork_tracker)
135 .field("eip2718", &self.eip2718)
136 .field("eip1559", &self.eip1559)
137 .field("eip4844", &self.eip4844)
138 .field("eip7702", &self.eip7702)
139 .field("block_gas_limit", &self.block_gas_limit)
140 .field("tx_fee_cap", &self.tx_fee_cap)
141 .field("minimum_priority_fee", &self.minimum_priority_fee)
142 .field("max_tx_input_bytes", &self.max_tx_input_bytes)
143 .field("max_tx_gas_limit", &self.max_tx_gas_limit)
144 .field("disable_balance_check", &self.disable_balance_check)
145 .field("eip7594", &self.eip7594)
146 .field(
147 "additional_stateless_validation",
148 &self.additional_stateless_validation.as_ref().map(|_| "..."),
149 )
150 .field(
151 "additional_stateful_validation",
152 &self.additional_stateful_validation.as_ref().map(|_| "..."),
153 )
154 .finish()
155 }
156}
157
158impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm> {
159 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
161 where
162 Client: ChainSpecProvider,
163 {
164 self.client().chain_spec()
165 }
166
167 pub fn chain_id(&self) -> u64
169 where
170 Client: ChainSpecProvider,
171 {
172 self.client().chain_spec().chain().id()
173 }
174
175 pub const fn client(&self) -> &Client {
177 &self.client
178 }
179
180 pub const fn fork_tracker(&self) -> &ForkTracker {
182 &self.fork_tracker
183 }
184
185 pub const fn eip2718(&self) -> bool {
187 self.eip2718
188 }
189
190 pub const fn eip1559(&self) -> bool {
192 self.eip1559
193 }
194
195 pub const fn eip4844(&self) -> bool {
197 self.eip4844
198 }
199
200 pub const fn eip7702(&self) -> bool {
202 self.eip7702
203 }
204
205 pub const fn tx_fee_cap(&self) -> &Option<u128> {
207 &self.tx_fee_cap
208 }
209
210 pub const fn minimum_priority_fee(&self) -> &Option<u128> {
212 &self.minimum_priority_fee
213 }
214
215 pub const fn kzg_settings(&self) -> &EnvKzgSettings {
217 &self.kzg_settings
218 }
219
220 pub const fn local_transactions_config(&self) -> &LocalTransactionConfig {
222 &self.local_transactions_config
223 }
224
225 pub const fn max_tx_input_bytes(&self) -> usize {
228 self.max_tx_input_bytes
229 }
230
231 pub const fn disable_balance_check(&self) -> bool {
233 self.disable_balance_check
234 }
235
236 pub fn set_additional_stateless_validation<F>(&mut self, f: F)
261 where
262 F: Fn(TransactionOrigin, &Tx) -> Result<(), InvalidPoolTransactionError>
263 + Send
264 + Sync
265 + 'static,
266 {
267 self.additional_stateless_validation = Some(Arc::new(f));
268 }
269
270 pub fn set_additional_stateful_validation<F>(&mut self, f: F)
295 where
296 F: Fn(
297 TransactionOrigin,
298 &Tx,
299 &dyn AccountInfoReader,
300 ) -> Result<(), InvalidPoolTransactionError>
301 + Send
302 + Sync
303 + 'static,
304 {
305 self.additional_stateful_validation = Some(Arc::new(f));
306 }
307}
308
309impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm>
310where
311 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
312 Tx: EthPoolTransaction,
313 Evm: ConfigureEvm,
314{
315 pub fn block_gas_limit(&self) -> u64 {
317 self.max_gas_limit()
318 }
319
320 pub fn validate_one(
324 &self,
325 origin: TransactionOrigin,
326 transaction: Tx,
327 ) -> TransactionValidationOutcome<Tx> {
328 self.validate_one_with_provider(origin, transaction, &mut None)
329 }
330
331 pub fn validate_one_with_state(
338 &self,
339 origin: TransactionOrigin,
340 transaction: Tx,
341 state: &mut Option<Box<dyn AccountInfoReader + Send>>,
342 ) -> TransactionValidationOutcome<Tx> {
343 self.validate_one_with_provider(origin, transaction, state)
344 }
345
346 fn validate_one_with_provider(
350 &self,
351 origin: TransactionOrigin,
352 transaction: Tx,
353 maybe_state: &mut Option<Box<dyn AccountInfoReader + Send>>,
354 ) -> TransactionValidationOutcome<Tx> {
355 match self.validate_stateless(origin, transaction) {
356 Ok(transaction) => {
357 if maybe_state.is_none() {
360 match self.client.latest() {
361 Ok(new_state) => {
362 *maybe_state = Some(Box::new(new_state));
363 }
364 Err(err) => {
365 return TransactionValidationOutcome::Error(
366 *transaction.hash(),
367 Box::new(err),
368 )
369 }
370 }
371 }
372
373 let state = maybe_state.as_deref().expect("provider is set");
374
375 self.validate_stateful(origin, transaction, state)
376 }
377 Err(invalid_outcome) => invalid_outcome,
378 }
379 }
380
381 pub fn validate_one_with_state_provider(
384 &self,
385 origin: TransactionOrigin,
386 transaction: Tx,
387 state: impl AccountInfoReader,
388 ) -> TransactionValidationOutcome<Tx> {
389 let tx = match self.validate_stateless(origin, transaction) {
390 Ok(tx) => tx,
391 Err(invalid_outcome) => return invalid_outcome,
392 };
393 self.validate_stateful(origin, tx, state)
394 }
395
396 pub fn validate_stateless(
402 &self,
403 origin: TransactionOrigin,
404 transaction: Tx,
405 ) -> Result<Tx, TransactionValidationOutcome<Tx>> {
406 match transaction.ty() {
408 EIP2930_TX_TYPE_ID if !self.eip2718 => {
410 return Err(TransactionValidationOutcome::Invalid(
411 transaction,
412 InvalidTransactionError::Eip2930Disabled.into(),
413 ))
414 }
415 EIP1559_TX_TYPE_ID if !self.eip1559 => {
417 return Err(TransactionValidationOutcome::Invalid(
418 transaction,
419 InvalidTransactionError::Eip1559Disabled.into(),
420 ))
421 }
422 EIP4844_TX_TYPE_ID if !self.eip4844 => {
424 return Err(TransactionValidationOutcome::Invalid(
425 transaction,
426 InvalidTransactionError::Eip4844Disabled.into(),
427 ))
428 }
429 EIP7702_TX_TYPE_ID if !self.eip7702 => {
431 return Err(TransactionValidationOutcome::Invalid(
432 transaction,
433 InvalidTransactionError::Eip7702Disabled.into(),
434 ))
435 }
436 LEGACY_TX_TYPE_ID | EIP2930_TX_TYPE_ID | EIP1559_TX_TYPE_ID | EIP4844_TX_TYPE_ID |
438 EIP7702_TX_TYPE_ID => {}
439
440 ty if !self.other_tx_types.bit(ty as usize) => {
441 return Err(TransactionValidationOutcome::Invalid(
442 transaction,
443 InvalidTransactionError::TxTypeNotSupported.into(),
444 ))
445 }
446
447 _ => {}
448 };
449
450 let tx_nonce = transaction.nonce();
452 if tx_nonce == u64::MAX {
453 return Err(TransactionValidationOutcome::Invalid(
454 transaction,
455 InvalidPoolTransactionError::Eip2681,
456 ))
457 }
458
459 if transaction.is_eip4844() {
461 let tx_input_len = transaction.input().len();
466 if tx_input_len > self.max_tx_input_bytes {
467 return Err(TransactionValidationOutcome::Invalid(
468 transaction,
469 InvalidPoolTransactionError::OversizedData {
470 size: tx_input_len,
471 limit: self.max_tx_input_bytes,
472 },
473 ))
474 }
475 } else {
476 let tx_size = transaction.encoded_length();
478 if tx_size > self.max_tx_input_bytes {
479 return Err(TransactionValidationOutcome::Invalid(
480 transaction,
481 InvalidPoolTransactionError::OversizedData {
482 size: tx_size,
483 limit: self.max_tx_input_bytes,
484 },
485 ))
486 }
487 }
488
489 if self.fork_tracker.is_shanghai_activated() {
491 let max_initcode_size =
492 self.fork_tracker.max_initcode_size.load(std::sync::atomic::Ordering::Relaxed);
493 if let Err(err) = transaction.ensure_max_init_code_size(max_initcode_size) {
494 return Err(TransactionValidationOutcome::Invalid(transaction, err))
495 }
496 }
497
498 let transaction_gas_limit = transaction.gas_limit();
500 let block_gas_limit = self.max_gas_limit();
501 if transaction_gas_limit > block_gas_limit {
502 return Err(TransactionValidationOutcome::Invalid(
503 transaction,
504 InvalidPoolTransactionError::ExceedsGasLimit(
505 transaction_gas_limit,
506 block_gas_limit,
507 ),
508 ))
509 }
510
511 if let Some(max_tx_gas_limit) = self.max_tx_gas_limit &&
513 transaction_gas_limit > max_tx_gas_limit
514 {
515 return Err(TransactionValidationOutcome::Invalid(
516 transaction,
517 InvalidPoolTransactionError::MaxTxGasLimitExceeded(
518 transaction_gas_limit,
519 max_tx_gas_limit,
520 ),
521 ))
522 }
523
524 if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
526 return Err(TransactionValidationOutcome::Invalid(
527 transaction,
528 InvalidTransactionError::TipAboveFeeCap.into(),
529 ))
530 }
531
532 let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
534
535 if is_local {
538 match self.tx_fee_cap {
539 Some(0) | None => {} Some(tx_fee_cap_wei) => {
541 let max_tx_fee_wei = transaction.cost().saturating_sub(transaction.value());
542 if max_tx_fee_wei > tx_fee_cap_wei {
543 return Err(TransactionValidationOutcome::Invalid(
544 transaction,
545 InvalidPoolTransactionError::ExceedsFeeCap {
546 max_tx_fee_wei: max_tx_fee_wei.saturating_to(),
547 tx_fee_cap_wei,
548 },
549 ))
550 }
551 }
552 }
553 }
554
555 if !is_local &&
558 transaction.is_dynamic_fee() &&
559 transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
560 {
561 return Err(TransactionValidationOutcome::Invalid(
562 transaction,
563 InvalidPoolTransactionError::PriorityFeeBelowMinimum {
564 minimum_priority_fee: self
565 .minimum_priority_fee
566 .expect("minimum priority fee is expected inside if statement"),
567 },
568 ))
569 }
570
571 if let Some(chain_id) = transaction.chain_id() &&
573 chain_id != self.chain_id()
574 {
575 return Err(TransactionValidationOutcome::Invalid(
576 transaction,
577 InvalidTransactionError::ChainIdMismatch.into(),
578 ))
579 }
580
581 if transaction.is_eip7702() {
582 if !self.fork_tracker.is_prague_activated() {
584 return Err(TransactionValidationOutcome::Invalid(
585 transaction,
586 InvalidTransactionError::TxTypeNotSupported.into(),
587 ))
588 }
589
590 if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
591 return Err(TransactionValidationOutcome::Invalid(
592 transaction,
593 Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into(),
594 ))
595 }
596 }
597
598 if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
599 return Err(TransactionValidationOutcome::Invalid(transaction, err))
600 }
601
602 if transaction.is_eip4844() {
604 if !self.fork_tracker.is_cancun_activated() {
606 return Err(TransactionValidationOutcome::Invalid(
607 transaction,
608 InvalidTransactionError::TxTypeNotSupported.into(),
609 ))
610 }
611
612 let blob_count = transaction.blob_count().unwrap_or(0);
613 if blob_count == 0 {
614 return Err(TransactionValidationOutcome::Invalid(
616 transaction,
617 InvalidPoolTransactionError::Eip4844(
618 Eip4844PoolTransactionError::NoEip4844Blobs,
619 ),
620 ))
621 }
622
623 let max_blob_count = self.fork_tracker.max_blob_count();
624 if blob_count > max_blob_count {
625 return Err(TransactionValidationOutcome::Invalid(
626 transaction,
627 InvalidPoolTransactionError::Eip4844(
628 Eip4844PoolTransactionError::TooManyEip4844Blobs {
629 have: blob_count,
630 permitted: max_blob_count,
631 },
632 ),
633 ))
634 }
635 }
636
637 let tx_gas_limit_cap =
639 self.fork_tracker.tx_gas_limit_cap.load(std::sync::atomic::Ordering::Relaxed);
640 if tx_gas_limit_cap > 0 && transaction.gas_limit() > tx_gas_limit_cap {
641 return Err(TransactionValidationOutcome::Invalid(
642 transaction,
643 InvalidTransactionError::GasLimitTooHigh.into(),
644 ))
645 }
646
647 if let Some(check) = &self.additional_stateless_validation &&
649 let Err(err) = check(origin, &transaction)
650 {
651 return Err(TransactionValidationOutcome::Invalid(transaction, err))
652 }
653
654 Ok(transaction)
655 }
656
657 pub fn validate_stateful<P>(
662 &self,
663 origin: TransactionOrigin,
664 mut transaction: Tx,
665 state: P,
666 ) -> TransactionValidationOutcome<Tx>
667 where
668 P: AccountInfoReader,
669 {
670 let account = match state.basic_account(transaction.sender_ref()) {
672 Ok(account) => account.unwrap_or_default(),
673 Err(err) => {
674 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
675 }
676 };
677
678 match self.validate_sender_bytecode(&transaction, &account, &state) {
680 Err(outcome) => return outcome,
681 Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err),
682 _ => {}
683 };
684
685 if transaction.requires_nonce_check() &&
687 let Err(err) = self.validate_sender_nonce(&transaction, &account)
688 {
689 return TransactionValidationOutcome::Invalid(transaction, err)
690 }
691
692 if let Err(err) = self.validate_sender_balance(&transaction, &account) {
694 return TransactionValidationOutcome::Invalid(transaction, err)
695 }
696
697 let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) {
699 Err(err) => return TransactionValidationOutcome::Invalid(transaction, err),
700 Ok(sidecar) => sidecar,
701 };
702
703 if let Some(check) = &self.additional_stateful_validation &&
705 let Err(err) = check(origin, &transaction, &state)
706 {
707 return TransactionValidationOutcome::Invalid(transaction, err)
708 }
709
710 let authorities = self.recover_authorities(&transaction);
711 TransactionValidationOutcome::Valid {
713 balance: account.balance,
714 state_nonce: account.nonce,
715 bytecode_hash: account.bytecode_hash,
716 transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
717 propagate: match origin {
719 TransactionOrigin::External => true,
720 TransactionOrigin::Local => {
721 self.local_transactions_config.propagate_local_transactions
722 }
723 TransactionOrigin::Private => false,
724 },
725 authorities,
726 }
727 }
728
729 pub fn validate_sender_bytecode(
731 &self,
732 transaction: &Tx,
733 sender: &Account,
734 state: impl BytecodeReader,
735 ) -> Result<Result<(), InvalidPoolTransactionError>, TransactionValidationOutcome<Tx>> {
736 if let Some(code_hash) = &sender.bytecode_hash {
743 let is_eip7702 = if self.fork_tracker.is_prague_activated() {
744 match state.bytecode_by_hash(code_hash) {
745 Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
746 Err(err) => {
747 return Err(TransactionValidationOutcome::Error(
748 *transaction.hash(),
749 Box::new(err),
750 ))
751 }
752 }
753 } else {
754 false
755 };
756
757 if !is_eip7702 {
758 return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into()))
759 }
760 }
761 Ok(Ok(()))
762 }
763
764 pub fn validate_sender_nonce(
766 &self,
767 transaction: &Tx,
768 sender: &Account,
769 ) -> Result<(), InvalidPoolTransactionError> {
770 let tx_nonce = transaction.nonce();
771
772 if tx_nonce < sender.nonce {
773 return Err(InvalidTransactionError::NonceNotConsistent {
774 tx: tx_nonce,
775 state: sender.nonce,
776 }
777 .into())
778 }
779 Ok(())
780 }
781
782 pub fn validate_sender_balance(
784 &self,
785 transaction: &Tx,
786 sender: &Account,
787 ) -> Result<(), InvalidPoolTransactionError> {
788 let cost = transaction.cost();
789
790 if !self.disable_balance_check && cost > &sender.balance {
791 let expected = *cost;
792 return Err(InvalidTransactionError::InsufficientFunds(
793 GotExpected { got: sender.balance, expected }.into(),
794 )
795 .into())
796 }
797 Ok(())
798 }
799
800 pub fn validate_eip4844(
802 &self,
803 transaction: &mut Tx,
804 ) -> Result<Option<BlobTransactionSidecarVariant>, InvalidPoolTransactionError> {
805 let mut maybe_blob_sidecar = None;
806
807 if transaction.is_eip4844() {
809 match transaction.take_blob() {
811 EthBlobTransactionSidecar::None => {
812 return Err(InvalidTransactionError::TxTypeNotSupported.into())
814 }
815 EthBlobTransactionSidecar::Missing => {
816 if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) {
821 } else {
823 return Err(InvalidPoolTransactionError::Eip4844(
824 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
825 ))
826 }
827 }
828 EthBlobTransactionSidecar::Present(sidecar) => {
829 let now = Instant::now();
830
831 if self.eip7594 {
833 if self.fork_tracker.is_osaka_activated() {
835 if sidecar.is_eip4844() {
836 return Err(InvalidPoolTransactionError::Eip4844(
837 Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
838 ))
839 }
840 } else if sidecar.is_eip7594() && !self.allow_7594_sidecars() {
841 return Err(InvalidPoolTransactionError::Eip4844(
842 Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
843 ))
844 }
845 } else {
846 if sidecar.is_eip7594() {
848 return Err(InvalidPoolTransactionError::Eip4844(
849 Eip4844PoolTransactionError::Eip7594SidecarDisallowed,
850 ))
851 }
852 }
853
854 if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
856 return Err(InvalidPoolTransactionError::Eip4844(
857 Eip4844PoolTransactionError::InvalidEip4844Blob(err),
858 ))
859 }
860 self.validation_metrics.blob_validation_duration.record(now.elapsed());
862 maybe_blob_sidecar = Some(sidecar);
864 }
865 }
866 }
867 Ok(maybe_blob_sidecar)
868 }
869
870 fn recover_authorities(&self, transaction: &Tx) -> std::option::Option<Vec<Address>> {
872 transaction
873 .authorization_list()
874 .map(|auths| auths.iter().flat_map(|auth| auth.recover_authority()).collect::<Vec<_>>())
875 }
876
877 fn validate_batch(
879 &self,
880 transactions: impl IntoIterator<Item = (TransactionOrigin, Tx)>,
881 ) -> Vec<TransactionValidationOutcome<Tx>> {
882 let mut provider = None;
883 transactions
884 .into_iter()
885 .map(|(origin, tx)| self.validate_one_with_provider(origin, tx, &mut provider))
886 .collect()
887 }
888
889 fn validate_batch_with_origin(
891 &self,
892 origin: TransactionOrigin,
893 transactions: impl IntoIterator<Item = Tx> + Send,
894 ) -> Vec<TransactionValidationOutcome<Tx>> {
895 let mut provider = None;
896 transactions
897 .into_iter()
898 .map(|tx| self.validate_one_with_provider(origin, tx, &mut provider))
899 .collect()
900 }
901
902 fn on_new_head_block(&self, new_tip_block: &HeaderTy<Evm::Primitives>) {
903 if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
905 self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
906 }
907
908 if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
909 self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
910 }
911
912 if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
913 self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
914 }
915
916 if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
917 self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
918 }
919
920 self.fork_tracker
921 .tip_timestamp
922 .store(new_tip_block.timestamp(), std::sync::atomic::Ordering::Relaxed);
923
924 if let Some(blob_params) =
925 self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
926 {
927 self.fork_tracker
928 .max_blob_count
929 .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed);
930 }
931
932 self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
933
934 let evm_env = self
936 .evm_config
937 .evm_env(new_tip_block)
938 .expect("evm_env should not fail for executed block");
939
940 self.fork_tracker
941 .max_initcode_size
942 .store(evm_env.cfg_env.max_initcode_size(), std::sync::atomic::Ordering::Relaxed);
943 self.fork_tracker
944 .tx_gas_limit_cap
945 .store(evm_env.cfg_env.tx_gas_limit_cap(), std::sync::atomic::Ordering::Relaxed);
946 }
947
948 fn max_gas_limit(&self) -> u64 {
949 self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
950 }
951
952 fn allow_7594_sidecars(&self) -> bool {
954 let tip_timestamp = self.fork_tracker.tip_timestamp();
955
956 if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(12)) {
958 true
959 } else if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(24)) {
960 let current_timestamp =
961 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
962
963 current_timestamp >= tip_timestamp.saturating_add(4)
965 } else {
966 false
967 }
968 }
969}
970
971impl<Client, Tx, Evm> TransactionValidator for EthTransactionValidator<Client, Tx, Evm>
972where
973 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
974 Tx: EthPoolTransaction,
975 Evm: ConfigureEvm,
976{
977 type Transaction = Tx;
978 type Block = BlockTy<Evm::Primitives>;
979
980 async fn validate_transaction(
981 &self,
982 origin: TransactionOrigin,
983 transaction: Self::Transaction,
984 ) -> TransactionValidationOutcome<Self::Transaction> {
985 self.validate_one(origin, transaction)
986 }
987
988 async fn validate_transactions(
989 &self,
990 transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
991 + Send,
992 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
993 self.validate_batch(transactions)
994 }
995
996 async fn validate_transactions_with_origin(
997 &self,
998 origin: TransactionOrigin,
999 transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
1000 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
1001 self.validate_batch_with_origin(origin, transactions)
1002 }
1003
1004 fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
1005 Self::on_new_head_block(self, new_tip_block.header())
1006 }
1007}
1008
1009#[derive(Debug)]
1011pub struct EthTransactionValidatorBuilder<Client, Evm> {
1012 client: Client,
1013 evm_config: Evm,
1015 shanghai: bool,
1017 cancun: bool,
1019 prague: bool,
1021 osaka: bool,
1023 tip_timestamp: u64,
1025 max_blob_count: u64,
1027 eip2718: bool,
1029 eip1559: bool,
1031 eip4844: bool,
1033 eip7702: bool,
1035 block_gas_limit: AtomicU64,
1037 tx_fee_cap: Option<u128>,
1039 minimum_priority_fee: Option<u128>,
1041 additional_tasks: usize,
1045
1046 kzg_settings: EnvKzgSettings,
1048 local_transactions_config: LocalTransactionConfig,
1050 max_tx_input_bytes: usize,
1052 max_tx_gas_limit: Option<u64>,
1054 disable_balance_check: bool,
1056 other_tx_types: U256,
1058 max_initcode_size: usize,
1060 tx_gas_limit_cap: u64,
1062 eip7594: bool,
1066}
1067
1068impl<Client, Evm> EthTransactionValidatorBuilder<Client, Evm> {
1069 pub fn new(client: Client, evm_config: Evm) -> Self
1079 where
1080 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
1081 + BlockReaderIdExt<Header = HeaderTy<Evm::Primitives>>,
1082 Evm: ConfigureEvm,
1083 {
1084 let chain_spec = client.chain_spec();
1085 let tip = client
1086 .header_by_id(BlockId::latest())
1087 .expect("failed to fetch latest header")
1088 .expect("latest header is not found");
1089 let evm_env =
1090 evm_config.evm_env(&tip).expect("evm_env should not fail for existing blocks");
1091
1092 Self {
1093 block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(),
1094 client,
1095 evm_config,
1096 minimum_priority_fee: None,
1097 additional_tasks: 1,
1098 kzg_settings: EnvKzgSettings::Default,
1099 local_transactions_config: Default::default(),
1100 max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
1101 tx_fee_cap: Some(1e18 as u128),
1102 max_tx_gas_limit: None,
1103 eip2718: true,
1105 eip1559: true,
1106 eip4844: true,
1107 eip7702: true,
1108
1109 shanghai: chain_spec.is_shanghai_active_at_timestamp(tip.timestamp()),
1110 cancun: chain_spec.is_cancun_active_at_timestamp(tip.timestamp()),
1111 prague: chain_spec.is_prague_active_at_timestamp(tip.timestamp()),
1112 osaka: chain_spec.is_osaka_active_at_timestamp(tip.timestamp()),
1113
1114 tip_timestamp: tip.timestamp(),
1115
1116 max_blob_count: chain_spec
1117 .blob_params_at_timestamp(tip.timestamp())
1118 .unwrap_or_else(BlobParams::prague)
1119 .max_blobs_per_tx,
1120
1121 disable_balance_check: false,
1123
1124 other_tx_types: U256::ZERO,
1126
1127 tx_gas_limit_cap: evm_env.cfg_env.tx_gas_limit_cap(),
1128 max_initcode_size: evm_env.cfg_env.max_initcode_size(),
1129
1130 eip7594: true,
1132 }
1133 }
1134
1135 pub const fn no_cancun(self) -> Self {
1137 self.set_cancun(false)
1138 }
1139
1140 pub fn with_local_transactions_config(
1142 mut self,
1143 local_transactions_config: LocalTransactionConfig,
1144 ) -> Self {
1145 self.local_transactions_config = local_transactions_config;
1146 self
1147 }
1148
1149 pub const fn set_cancun(mut self, cancun: bool) -> Self {
1151 self.cancun = cancun;
1152 self
1153 }
1154
1155 pub const fn no_shanghai(self) -> Self {
1157 self.set_shanghai(false)
1158 }
1159
1160 pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
1162 self.shanghai = shanghai;
1163 self
1164 }
1165
1166 pub const fn no_prague(self) -> Self {
1168 self.set_prague(false)
1169 }
1170
1171 pub const fn set_prague(mut self, prague: bool) -> Self {
1173 self.prague = prague;
1174 self
1175 }
1176
1177 pub const fn no_osaka(self) -> Self {
1179 self.set_osaka(false)
1180 }
1181
1182 pub const fn set_osaka(mut self, osaka: bool) -> Self {
1184 self.osaka = osaka;
1185 self
1186 }
1187
1188 pub const fn no_eip2718(self) -> Self {
1190 self.set_eip2718(false)
1191 }
1192
1193 pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
1195 self.eip2718 = eip2718;
1196 self
1197 }
1198
1199 pub const fn no_eip1559(self) -> Self {
1201 self.set_eip1559(false)
1202 }
1203
1204 pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
1206 self.eip1559 = eip1559;
1207 self
1208 }
1209
1210 pub const fn no_eip4844(self) -> Self {
1212 self.set_eip4844(false)
1213 }
1214
1215 pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
1217 self.eip4844 = eip4844;
1218 self
1219 }
1220
1221 pub const fn no_eip7702(self) -> Self {
1223 self.set_eip7702(false)
1224 }
1225
1226 pub const fn set_eip7702(mut self, eip7702: bool) -> Self {
1228 self.eip7702 = eip7702;
1229 self
1230 }
1231
1232 pub const fn no_eip7594(self) -> Self {
1239 self.set_eip7594(false)
1240 }
1241
1242 pub const fn set_eip7594(mut self, eip7594: bool) -> Self {
1247 self.eip7594 = eip7594;
1248 self
1249 }
1250
1251 pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
1253 self.kzg_settings = kzg_settings;
1254 self
1255 }
1256
1257 pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: Option<u128>) -> Self {
1259 self.minimum_priority_fee = minimum_priority_fee;
1260 self
1261 }
1262
1263 pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
1265 self.additional_tasks = additional_tasks;
1266 self
1267 }
1268
1269 pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
1271 self.max_tx_input_bytes = max_tx_input_bytes;
1272 self
1273 }
1274
1275 pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
1279 self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
1280 self
1281 }
1282
1283 pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
1287 self.tx_fee_cap = Some(tx_fee_cap);
1288 self
1289 }
1290
1291 pub const fn with_max_tx_gas_limit(mut self, max_tx_gas_limit: Option<u64>) -> Self {
1293 self.max_tx_gas_limit = max_tx_gas_limit;
1294 self
1295 }
1296
1297 pub const fn disable_balance_check(mut self) -> Self {
1299 self.disable_balance_check = true;
1300 self
1301 }
1302
1303 pub const fn with_custom_tx_type(mut self, tx_type: u8) -> Self {
1305 self.other_tx_types.set_bit(tx_type as usize, true);
1306 self
1307 }
1308
1309 pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx, Evm>
1311 where
1312 S: BlobStore,
1313 {
1314 let Self {
1315 client,
1316 evm_config,
1317 shanghai,
1318 cancun,
1319 prague,
1320 osaka,
1321 tip_timestamp,
1322 eip2718,
1323 eip1559,
1324 eip4844,
1325 eip7702,
1326 block_gas_limit,
1327 tx_fee_cap,
1328 minimum_priority_fee,
1329 kzg_settings,
1330 local_transactions_config,
1331 max_tx_input_bytes,
1332 max_tx_gas_limit,
1333 disable_balance_check,
1334 max_blob_count,
1335 additional_tasks: _,
1336 other_tx_types,
1337 max_initcode_size,
1338 tx_gas_limit_cap,
1339 eip7594,
1340 } = self;
1341
1342 let fork_tracker = ForkTracker {
1343 shanghai: AtomicBool::new(shanghai),
1344 cancun: AtomicBool::new(cancun),
1345 prague: AtomicBool::new(prague),
1346 osaka: AtomicBool::new(osaka),
1347 tip_timestamp: AtomicU64::new(tip_timestamp),
1348 max_blob_count: AtomicU64::new(max_blob_count),
1349 max_initcode_size: AtomicUsize::new(max_initcode_size),
1350 tx_gas_limit_cap: AtomicU64::new(tx_gas_limit_cap),
1351 };
1352
1353 EthTransactionValidator {
1354 client,
1355 eip2718,
1356 eip1559,
1357 fork_tracker,
1358 eip4844,
1359 eip7702,
1360 block_gas_limit,
1361 tx_fee_cap,
1362 minimum_priority_fee,
1363 blob_store: Box::new(blob_store),
1364 kzg_settings,
1365 local_transactions_config,
1366 max_tx_input_bytes,
1367 max_tx_gas_limit,
1368 disable_balance_check,
1369 evm_config,
1370 _marker: Default::default(),
1371 validation_metrics: TxPoolValidationMetrics::default(),
1372 other_tx_types,
1373 eip7594,
1374 additional_stateless_validation: None,
1375 additional_stateful_validation: None,
1376 }
1377 }
1378
1379 pub fn build_with_tasks<Tx, S>(
1386 self,
1387 tasks: Runtime,
1388 blob_store: S,
1389 ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx, Evm>>
1390 where
1391 S: BlobStore,
1392 {
1393 let additional_tasks = self.additional_tasks;
1394 let validator = self.build::<Tx, S>(blob_store);
1395
1396 let (tx, task) = ValidationTask::new();
1397
1398 for _ in 0..additional_tasks {
1400 let task = task.clone();
1401 tasks.spawn_blocking_task(async move {
1402 task.run().await;
1403 });
1404 }
1405
1406 tasks.spawn_critical_blocking_task("transaction-validation-service", async move {
1409 task.run().await;
1410 });
1411
1412 let to_validation_task = Arc::new(Mutex::new(tx));
1413
1414 TransactionValidationTaskExecutor { validator: Arc::new(validator), to_validation_task }
1415 }
1416}
1417
1418#[derive(Debug)]
1420pub struct ForkTracker {
1421 pub shanghai: AtomicBool,
1423 pub cancun: AtomicBool,
1425 pub prague: AtomicBool,
1427 pub osaka: AtomicBool,
1429 pub max_blob_count: AtomicU64,
1431 pub tip_timestamp: AtomicU64,
1433 pub max_initcode_size: AtomicUsize,
1435 pub tx_gas_limit_cap: AtomicU64,
1437}
1438
1439impl ForkTracker {
1440 pub fn is_shanghai_activated(&self) -> bool {
1442 self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1443 }
1444
1445 pub fn is_cancun_activated(&self) -> bool {
1447 self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1448 }
1449
1450 pub fn is_prague_activated(&self) -> bool {
1452 self.prague.load(std::sync::atomic::Ordering::Relaxed)
1453 }
1454
1455 pub fn is_osaka_activated(&self) -> bool {
1457 self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1458 }
1459
1460 pub fn tip_timestamp(&self) -> u64 {
1462 self.tip_timestamp.load(std::sync::atomic::Ordering::Relaxed)
1463 }
1464
1465 pub fn max_blob_count(&self) -> u64 {
1467 self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1468 }
1469}
1470
1471pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1475 transaction: &T,
1476 fork_tracker: &ForkTracker,
1477) -> Result<(), InvalidPoolTransactionError> {
1478 use revm_primitives::hardfork::SpecId;
1479 let spec_id = if fork_tracker.is_prague_activated() {
1480 SpecId::PRAGUE
1481 } else if fork_tracker.is_shanghai_activated() {
1482 SpecId::SHANGHAI
1483 } else {
1484 SpecId::MERGE
1485 };
1486
1487 let gas = revm_interpreter::gas::calculate_initial_tx_gas(
1488 spec_id,
1489 transaction.input(),
1490 transaction.is_create(),
1491 transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1492 transaction
1493 .access_list()
1494 .map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
1495 .unwrap_or_default() as u64,
1496 transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
1497 );
1498
1499 let gas_limit = transaction.gas_limit();
1500 if gas_limit < gas.initial_gas || gas_limit < gas.floor_gas {
1501 Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1502 } else {
1503 Ok(())
1504 }
1505}
1506
1507#[cfg(test)]
1508mod tests {
1509 use super::*;
1510 use crate::{
1511 blobstore::InMemoryBlobStore, error::PoolErrorKind, traits::PoolTransaction,
1512 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1513 };
1514 use alloy_consensus::Transaction;
1515 use alloy_eips::eip2718::Decodable2718;
1516 use alloy_primitives::{hex, U256};
1517 use reth_ethereum_primitives::PooledTransactionVariant;
1518 use reth_evm_ethereum::EthEvmConfig;
1519 use reth_primitives_traits::SignedTransaction;
1520 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1521 use revm_primitives::eip3860::MAX_INITCODE_SIZE;
1522
1523 fn test_evm_config() -> EthEvmConfig {
1524 EthEvmConfig::mainnet()
1525 }
1526
1527 fn get_transaction() -> EthPooledTransaction {
1528 let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1529
1530 let data = hex::decode(raw).unwrap();
1531 let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1532
1533 EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1534 }
1535
1536 #[tokio::test]
1538 async fn validate_transaction() {
1539 let transaction = get_transaction();
1540 let mut fork_tracker = ForkTracker {
1541 shanghai: false.into(),
1542 cancun: false.into(),
1543 prague: false.into(),
1544 osaka: false.into(),
1545 tip_timestamp: 0.into(),
1546 max_blob_count: 0.into(),
1547 max_initcode_size: AtomicUsize::new(MAX_INITCODE_SIZE),
1548 tx_gas_limit_cap: AtomicU64::new(0),
1549 };
1550
1551 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1552 assert!(res.is_ok());
1553
1554 fork_tracker.shanghai = true.into();
1555 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1556 assert!(res.is_ok());
1557
1558 let provider = MockEthProvider::default().with_genesis_block();
1559 provider.add_account(
1560 transaction.sender(),
1561 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1562 );
1563 let blob_store = InMemoryBlobStore::default();
1564 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1565 .build(blob_store.clone());
1566
1567 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1568
1569 assert!(outcome.is_valid());
1570
1571 let pool =
1572 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1573
1574 let res = pool.add_external_transaction(transaction.clone()).await;
1575 assert!(res.is_ok());
1576 let tx = pool.get(transaction.hash());
1577 assert!(tx.is_some());
1578 }
1579
1580 #[tokio::test]
1582 async fn invalid_on_gas_limit_too_high() {
1583 let transaction = get_transaction();
1584
1585 let provider = MockEthProvider::default().with_genesis_block();
1586 provider.add_account(
1587 transaction.sender(),
1588 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1589 );
1590
1591 let blob_store = InMemoryBlobStore::default();
1592 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1593 .set_block_gas_limit(1_000_000) .build(blob_store.clone());
1595
1596 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1597
1598 assert!(outcome.is_invalid());
1599
1600 let pool =
1601 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1602
1603 let res = pool.add_external_transaction(transaction.clone()).await;
1604 assert!(res.is_err());
1605 assert!(matches!(
1606 res.unwrap_err().kind,
1607 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1608 1_015_288, 1_000_000
1609 ))
1610 ));
1611 let tx = pool.get(transaction.hash());
1612 assert!(tx.is_none());
1613 }
1614
1615 #[tokio::test]
1616 async fn invalid_on_fee_cap_exceeded() {
1617 let transaction = get_transaction();
1618 let provider = MockEthProvider::default().with_genesis_block();
1619 provider.add_account(
1620 transaction.sender(),
1621 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1622 );
1623
1624 let blob_store = InMemoryBlobStore::default();
1625 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1626 .set_tx_fee_cap(100) .build(blob_store.clone());
1628
1629 let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1630 assert!(outcome.is_invalid());
1631
1632 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1633 assert!(matches!(
1634 err,
1635 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1636 if (max_tx_fee_wei > tx_fee_cap_wei)
1637 ));
1638 }
1639
1640 let pool =
1641 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1642 let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
1643 assert!(res.is_err());
1644 assert!(matches!(
1645 res.unwrap_err().kind,
1646 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
1647 ));
1648 let tx = pool.get(transaction.hash());
1649 assert!(tx.is_none());
1650 }
1651
1652 #[tokio::test]
1653 async fn valid_on_zero_fee_cap() {
1654 let transaction = get_transaction();
1655 let provider = MockEthProvider::default().with_genesis_block();
1656 provider.add_account(
1657 transaction.sender(),
1658 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1659 );
1660
1661 let blob_store = InMemoryBlobStore::default();
1662 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1663 .set_tx_fee_cap(0) .build(blob_store);
1665
1666 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1667 assert!(outcome.is_valid());
1668 }
1669
1670 #[tokio::test]
1671 async fn valid_on_normal_fee_cap() {
1672 let transaction = get_transaction();
1673 let provider = MockEthProvider::default().with_genesis_block();
1674 provider.add_account(
1675 transaction.sender(),
1676 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1677 );
1678
1679 let blob_store = InMemoryBlobStore::default();
1680 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1681 .set_tx_fee_cap(2e18 as u128) .build(blob_store);
1683
1684 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1685 assert!(outcome.is_valid());
1686 }
1687
1688 #[tokio::test]
1689 async fn invalid_on_max_tx_gas_limit_exceeded() {
1690 let transaction = get_transaction();
1691 let provider = MockEthProvider::default().with_genesis_block();
1692 provider.add_account(
1693 transaction.sender(),
1694 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1695 );
1696
1697 let blob_store = InMemoryBlobStore::default();
1698 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1699 .with_max_tx_gas_limit(Some(500_000)) .build(blob_store.clone());
1701
1702 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1703 assert!(outcome.is_invalid());
1704
1705 let pool =
1706 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1707
1708 let res = pool.add_external_transaction(transaction.clone()).await;
1709 assert!(res.is_err());
1710 assert!(matches!(
1711 res.unwrap_err().kind,
1712 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
1713 1_015_288, 500_000
1714 ))
1715 ));
1716 let tx = pool.get(transaction.hash());
1717 assert!(tx.is_none());
1718 }
1719
1720 #[tokio::test]
1721 async fn valid_on_max_tx_gas_limit_disabled() {
1722 let transaction = get_transaction();
1723 let provider = MockEthProvider::default().with_genesis_block();
1724 provider.add_account(
1725 transaction.sender(),
1726 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1727 );
1728
1729 let blob_store = InMemoryBlobStore::default();
1730 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1731 .with_max_tx_gas_limit(None) .build(blob_store);
1733
1734 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1735 assert!(outcome.is_valid());
1736 }
1737
1738 #[tokio::test]
1739 async fn valid_on_max_tx_gas_limit_within_limit() {
1740 let transaction = get_transaction();
1741 let provider = MockEthProvider::default().with_genesis_block();
1742 provider.add_account(
1743 transaction.sender(),
1744 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1745 );
1746
1747 let blob_store = InMemoryBlobStore::default();
1748 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1749 .with_max_tx_gas_limit(Some(2_000_000)) .build(blob_store);
1751
1752 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1753 assert!(outcome.is_valid());
1754 }
1755
1756 fn setup_priority_fee_test() -> (EthPooledTransaction, MockEthProvider) {
1758 let transaction = get_transaction();
1759 let provider = MockEthProvider::default().with_genesis_block();
1760 provider.add_account(
1761 transaction.sender(),
1762 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1763 );
1764 (transaction, provider)
1765 }
1766
1767 fn create_validator_with_minimum_fee(
1769 provider: MockEthProvider,
1770 minimum_priority_fee: Option<u128>,
1771 local_config: Option<LocalTransactionConfig>,
1772 ) -> EthTransactionValidator<MockEthProvider, EthPooledTransaction, EthEvmConfig> {
1773 let blob_store = InMemoryBlobStore::default();
1774 let mut builder = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1775 .with_minimum_priority_fee(minimum_priority_fee);
1776
1777 if let Some(config) = local_config {
1778 builder = builder.with_local_transactions_config(config);
1779 }
1780
1781 builder.build(blob_store)
1782 }
1783
1784 #[tokio::test]
1785 async fn invalid_on_priority_fee_lower_than_configured_minimum() {
1786 let (transaction, provider) = setup_priority_fee_test();
1787
1788 assert!(transaction.is_dynamic_fee());
1790
1791 let minimum_priority_fee =
1793 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1794
1795 let validator =
1796 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1797
1798 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1800 assert!(outcome.is_invalid());
1801
1802 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1803 assert!(matches!(
1804 err,
1805 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1806 if min_fee == minimum_priority_fee
1807 ));
1808 }
1809
1810 let blob_store = InMemoryBlobStore::default();
1812 let pool =
1813 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1814
1815 let res = pool.add_external_transaction(transaction.clone()).await;
1816 assert!(res.is_err());
1817 assert!(matches!(
1818 res.unwrap_err().kind,
1819 PoolErrorKind::InvalidTransaction(
1820 InvalidPoolTransactionError::PriorityFeeBelowMinimum { .. }
1821 )
1822 ));
1823 let tx = pool.get(transaction.hash());
1824 assert!(tx.is_none());
1825
1826 let (_, local_provider) = setup_priority_fee_test();
1828 let validator_local =
1829 create_validator_with_minimum_fee(local_provider, Some(minimum_priority_fee), None);
1830
1831 let local_outcome = validator_local.validate_one(TransactionOrigin::Local, transaction);
1832 assert!(local_outcome.is_valid());
1833 }
1834
1835 #[tokio::test]
1836 async fn valid_on_priority_fee_equal_to_minimum() {
1837 let (transaction, provider) = setup_priority_fee_test();
1838
1839 let tx_priority_fee =
1841 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1842 let validator = create_validator_with_minimum_fee(provider, Some(tx_priority_fee), None);
1843
1844 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1845 assert!(outcome.is_valid());
1846 }
1847
1848 #[tokio::test]
1849 async fn valid_on_priority_fee_above_minimum() {
1850 let (transaction, provider) = setup_priority_fee_test();
1851
1852 let tx_priority_fee =
1854 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1855 let minimum_priority_fee = tx_priority_fee / 2; let validator =
1858 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1859
1860 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1861 assert!(outcome.is_valid());
1862 }
1863
1864 #[tokio::test]
1865 async fn valid_on_minimum_priority_fee_disabled() {
1866 let (transaction, provider) = setup_priority_fee_test();
1867
1868 let validator = create_validator_with_minimum_fee(provider, None, None);
1870
1871 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1872 assert!(outcome.is_valid());
1873 }
1874
1875 #[tokio::test]
1876 async fn priority_fee_validation_applies_to_private_transactions() {
1877 let (transaction, provider) = setup_priority_fee_test();
1878
1879 let minimum_priority_fee =
1881 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1882
1883 let validator =
1884 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1885
1886 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1889 assert!(outcome.is_invalid());
1890
1891 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1892 assert!(matches!(
1893 err,
1894 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1895 if min_fee == minimum_priority_fee
1896 ));
1897 }
1898 }
1899
1900 #[tokio::test]
1901 async fn valid_on_local_config_exempts_private_transactions() {
1902 let (transaction, provider) = setup_priority_fee_test();
1903
1904 let minimum_priority_fee =
1906 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1907
1908 let local_config =
1910 LocalTransactionConfig { propagate_local_transactions: true, ..Default::default() };
1911
1912 let validator = create_validator_with_minimum_fee(
1913 provider,
1914 Some(minimum_priority_fee),
1915 Some(local_config),
1916 );
1917
1918 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1922 assert!(outcome.is_invalid()); }
1924
1925 #[test]
1926 fn reject_oversized_tx() {
1927 let mut transaction = get_transaction();
1928 transaction.encoded_length = DEFAULT_MAX_TX_INPUT_BYTES + 1;
1929 let provider = MockEthProvider::default().with_genesis_block();
1930
1931 let validator = create_validator_with_minimum_fee(provider, None, None);
1933
1934 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1935 let invalid = outcome.as_invalid().unwrap();
1936 assert!(invalid.is_oversized());
1937 }
1938
1939 #[tokio::test]
1940 async fn valid_with_disabled_balance_check() {
1941 let transaction = get_transaction();
1942 let provider = MockEthProvider::default().with_genesis_block();
1943
1944 provider.add_account(
1946 transaction.sender(),
1947 ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
1948 );
1949
1950 let validator =
1952 EthTransactionValidatorBuilder::new(provider.clone(), EthEvmConfig::mainnet())
1953 .build(InMemoryBlobStore::default());
1954
1955 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1956 let expected_cost = *transaction.cost();
1957 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1958 assert!(matches!(
1959 err,
1960 InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err))
1961 if funds_err.got == alloy_primitives::U256::ZERO && funds_err.expected == expected_cost
1962 ));
1963 } else {
1964 panic!("Expected Invalid outcome with InsufficientFunds error");
1965 }
1966
1967 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1969 .disable_balance_check()
1970 .build(InMemoryBlobStore::default());
1971
1972 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1973 assert!(outcome.is_valid()); }
1975}