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