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 eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M, eip4844::env_settings::EnvKzgSettings,
25 eip7840::BlobParams, BlockId,
26};
27use alloy_primitives::U256;
28use alloy_rlp::Encodable;
29use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
30use reth_evm::ConfigureEvm;
31use reth_primitives_traits::{
32 transaction::error::InvalidTransactionError, Account, BlockTy, GotExpected, HeaderTy,
33 SealedBlock,
34};
35use reth_storage_api::{
36 errors::ProviderError, AccountInfoReader, BlockReaderIdExt, BytecodeReader, StateProviderBox,
37 StateProviderFactory,
38};
39use reth_tasks::Runtime;
40use revm::context_interface::Cfg;
41use std::{
42 fmt,
43 marker::PhantomData,
44 sync::{
45 atomic::{AtomicBool, AtomicU64, AtomicUsize},
46 Arc,
47 },
48 time::{Instant, SystemTime},
49};
50
51pub type StatelessValidationFn<T> =
56 Arc<dyn Fn(TransactionOrigin, &T) -> Result<(), InvalidPoolTransactionError> + Send + Sync>;
57
58pub type StatefulValidationFn<T> = Arc<
63 dyn Fn(TransactionOrigin, &T, &dyn AccountInfoReader) -> Result<(), InvalidPoolTransactionError>
64 + Send
65 + Sync,
66>;
67
68pub struct EthTransactionValidator<Client, T, Evm> {
83 client: Client,
85 chain_id: u64,
87 blob_store: Box<dyn BlobStore>,
89 fork_tracker: ForkTracker,
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>,
105 kzg_settings: EnvKzgSettings,
107 local_transactions_config: LocalTransactionConfig,
109 max_tx_input_bytes: usize,
111 max_tx_gas_limit: Option<u64>,
113 disable_balance_check: bool,
115 evm_config: Evm,
117 _marker: PhantomData<T>,
119 validation_metrics: TxPoolValidationMetrics,
121 other_tx_types: U256,
123 eip7594: bool,
127 additional_stateless_validation: Option<StatelessValidationFn<T>>,
130 additional_stateful_validation: Option<StatefulValidationFn<T>>,
133}
134
135impl<Client, Tx, Evm> fmt::Debug for EthTransactionValidator<Client, Tx, Evm> {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 f.debug_struct("EthTransactionValidator")
138 .field("fork_tracker", &self.fork_tracker)
139 .field("eip2718", &self.eip2718)
140 .field("eip1559", &self.eip1559)
141 .field("eip4844", &self.eip4844)
142 .field("eip7702", &self.eip7702)
143 .field("block_gas_limit", &self.block_gas_limit)
144 .field("tx_fee_cap", &self.tx_fee_cap)
145 .field("minimum_priority_fee", &self.minimum_priority_fee)
146 .field("max_tx_input_bytes", &self.max_tx_input_bytes)
147 .field("max_tx_gas_limit", &self.max_tx_gas_limit)
148 .field("disable_balance_check", &self.disable_balance_check)
149 .field("eip7594", &self.eip7594)
150 .field(
151 "additional_stateless_validation",
152 &self.additional_stateless_validation.as_ref().map(|_| "..."),
153 )
154 .field(
155 "additional_stateful_validation",
156 &self.additional_stateful_validation.as_ref().map(|_| "..."),
157 )
158 .finish()
159 }
160}
161
162impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm> {
163 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
165 where
166 Client: ChainSpecProvider,
167 {
168 self.client().chain_spec()
169 }
170
171 pub const fn chain_id(&self) -> u64 {
173 self.chain_id
174 }
175
176 pub const fn client(&self) -> &Client {
178 &self.client
179 }
180
181 pub const fn fork_tracker(&self) -> &ForkTracker {
183 &self.fork_tracker
184 }
185
186 pub const fn evm_config(&self) -> &Evm {
188 &self.evm_config
189 }
190
191 pub const fn eip2718(&self) -> bool {
193 self.eip2718
194 }
195
196 pub const fn eip1559(&self) -> bool {
198 self.eip1559
199 }
200
201 pub const fn eip4844(&self) -> bool {
203 self.eip4844
204 }
205
206 pub const fn eip7702(&self) -> bool {
208 self.eip7702
209 }
210
211 pub const fn tx_fee_cap(&self) -> &Option<u128> {
213 &self.tx_fee_cap
214 }
215
216 pub const fn minimum_priority_fee(&self) -> &Option<u128> {
218 &self.minimum_priority_fee
219 }
220
221 pub const fn kzg_settings(&self) -> &EnvKzgSettings {
223 &self.kzg_settings
224 }
225
226 pub const fn local_transactions_config(&self) -> &LocalTransactionConfig {
228 &self.local_transactions_config
229 }
230
231 pub const fn max_tx_input_bytes(&self) -> usize {
234 self.max_tx_input_bytes
235 }
236
237 pub const fn disable_balance_check(&self) -> bool {
239 self.disable_balance_check
240 }
241
242 pub fn set_additional_stateless_validation<F>(&mut self, f: F)
267 where
268 F: Fn(TransactionOrigin, &Tx) -> Result<(), InvalidPoolTransactionError>
269 + Send
270 + Sync
271 + 'static,
272 {
273 self.additional_stateless_validation = Some(Arc::new(f));
274 }
275
276 pub fn set_additional_stateless_validation_fn(&mut self, f: StatelessValidationFn<Tx>) {
283 self.additional_stateless_validation = Some(f);
284 }
285
286 pub fn set_additional_stateless_validation_fn_opt(
291 &mut self,
292 f: Option<StatelessValidationFn<Tx>>,
293 ) {
294 self.additional_stateless_validation = f;
295 }
296
297 pub fn set_additional_stateful_validation<F>(&mut self, f: F)
322 where
323 F: Fn(
324 TransactionOrigin,
325 &Tx,
326 &dyn AccountInfoReader,
327 ) -> Result<(), InvalidPoolTransactionError>
328 + Send
329 + Sync
330 + 'static,
331 {
332 self.additional_stateful_validation = Some(Arc::new(f));
333 }
334
335 pub fn set_additional_stateful_validation_fn(&mut self, f: StatefulValidationFn<Tx>) {
342 self.additional_stateful_validation = Some(f);
343 }
344
345 pub fn set_additional_stateful_validation_fn_opt(
350 &mut self,
351 f: Option<StatefulValidationFn<Tx>>,
352 ) {
353 self.additional_stateful_validation = f;
354 }
355}
356
357impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm>
358where
359 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
360 Tx: EthPoolTransaction,
361 Evm: ConfigureEvm,
362{
363 pub fn block_gas_limit(&self) -> u64 {
365 self.max_gas_limit()
366 }
367
368 pub fn validate_one(
372 &self,
373 origin: TransactionOrigin,
374 transaction: Tx,
375 ) -> TransactionValidationOutcome<Tx> {
376 let mut state: Option<StateProviderBox> = None;
377 self.validate_one_with_provider(origin, transaction, &mut state, || self.client.latest())
378 }
379
380 pub fn validate_one_with_state(
387 &self,
388 origin: TransactionOrigin,
389 transaction: Tx,
390 state: &mut Option<Box<dyn AccountInfoReader + Send>>,
391 ) -> TransactionValidationOutcome<Tx> {
392 self.validate_one_with_provider(origin, transaction, state, || {
393 self.client.latest().map(|state| Box::new(state) as Box<dyn AccountInfoReader + Send>)
394 })
395 }
396
397 fn validate_one_with_provider<P, F>(
401 &self,
402 origin: TransactionOrigin,
403 transaction: Tx,
404 maybe_state: &mut Option<P>,
405 state_provider: F,
406 ) -> TransactionValidationOutcome<Tx>
407 where
408 P: AccountInfoReader,
409 F: FnOnce() -> Result<P, ProviderError>,
410 {
411 match self.validate_stateless(origin, &transaction) {
412 Ok(()) => {
413 if maybe_state.is_none() {
416 match state_provider() {
417 Ok(new_state) => {
418 *maybe_state = Some(new_state);
419 }
420 Err(err) => {
421 return TransactionValidationOutcome::Error(
422 *transaction.hash(),
423 Box::new(err),
424 )
425 }
426 }
427 }
428
429 let state = maybe_state.as_ref().expect("provider is set");
430
431 self.validate_stateful(origin, transaction, state)
432 }
433 Err(err) => TransactionValidationOutcome::Invalid(transaction, err),
434 }
435 }
436
437 pub fn validate_one_with_state_provider(
440 &self,
441 origin: TransactionOrigin,
442 transaction: Tx,
443 state: impl AccountInfoReader,
444 ) -> TransactionValidationOutcome<Tx> {
445 if let Err(err) = self.validate_stateless(origin, &transaction) {
446 return TransactionValidationOutcome::Invalid(transaction, err);
447 }
448 self.validate_stateful(origin, transaction, state)
449 }
450
451 pub fn validate_stateless(
456 &self,
457 origin: TransactionOrigin,
458 transaction: &Tx,
459 ) -> Result<(), InvalidPoolTransactionError> {
460 match transaction.ty() {
462 EIP2930_TX_TYPE_ID if !self.eip2718 => {
464 return Err(InvalidTransactionError::Eip2930Disabled.into())
465 }
466 EIP1559_TX_TYPE_ID if !self.eip1559 => {
468 return Err(InvalidTransactionError::Eip1559Disabled.into())
469 }
470 EIP4844_TX_TYPE_ID if !self.eip4844 => {
472 return Err(InvalidTransactionError::Eip4844Disabled.into())
473 }
474 EIP7702_TX_TYPE_ID if !self.eip7702 => {
476 return Err(InvalidTransactionError::Eip7702Disabled.into())
477 }
478 LEGACY_TX_TYPE_ID | EIP2930_TX_TYPE_ID | EIP1559_TX_TYPE_ID | EIP4844_TX_TYPE_ID |
480 EIP7702_TX_TYPE_ID => {}
481
482 ty if !self.other_tx_types.bit(ty as usize) => {
483 return Err(InvalidTransactionError::TxTypeNotSupported.into())
484 }
485
486 _ => {}
487 };
488
489 let tx_nonce = transaction.nonce();
491 if tx_nonce == u64::MAX {
492 return Err(InvalidPoolTransactionError::Eip2681)
493 }
494
495 if transaction.is_eip4844() {
497 let tx_size = transaction.input().len().saturating_add(
502 transaction
503 .access_list()
504 .map(|access_list| access_list.length())
505 .unwrap_or_default(),
506 );
507 if tx_size > self.max_tx_input_bytes {
508 return Err(InvalidPoolTransactionError::OversizedData {
509 size: tx_size,
510 limit: self.max_tx_input_bytes,
511 })
512 }
513 } else {
514 let tx_size = transaction.encoded_length();
516 if tx_size > self.max_tx_input_bytes {
517 return Err(InvalidPoolTransactionError::OversizedData {
518 size: tx_size,
519 limit: self.max_tx_input_bytes,
520 })
521 }
522 }
523
524 if self.fork_tracker.is_shanghai_activated() {
526 let max_initcode_size =
527 self.fork_tracker.max_initcode_size.load(std::sync::atomic::Ordering::Relaxed);
528 transaction.ensure_max_init_code_size(max_initcode_size)?;
529 }
530
531 let transaction_gas_limit = transaction.gas_limit();
533 let block_gas_limit = self.max_gas_limit();
534 if transaction_gas_limit > block_gas_limit {
535 return Err(InvalidPoolTransactionError::ExceedsGasLimit(
536 transaction_gas_limit,
537 block_gas_limit,
538 ))
539 }
540
541 if let Some(max_tx_gas_limit) = self.max_tx_gas_limit &&
543 transaction_gas_limit > max_tx_gas_limit
544 {
545 return Err(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
546 transaction_gas_limit,
547 max_tx_gas_limit,
548 ))
549 }
550
551 if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
553 return Err(InvalidTransactionError::TipAboveFeeCap.into())
554 }
555
556 let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
558
559 if is_local {
562 match self.tx_fee_cap {
563 Some(0) | None => {} Some(tx_fee_cap_wei) => {
565 let max_tx_fee_wei = transaction.cost().saturating_sub(transaction.value());
566 if max_tx_fee_wei > tx_fee_cap_wei {
567 return Err(InvalidPoolTransactionError::ExceedsFeeCap {
568 max_tx_fee_wei: max_tx_fee_wei.saturating_to(),
569 tx_fee_cap_wei,
570 })
571 }
572 }
573 }
574 }
575
576 if !is_local &&
579 transaction.is_dynamic_fee() &&
580 transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
581 {
582 return Err(InvalidPoolTransactionError::PriorityFeeBelowMinimum {
583 minimum_priority_fee: self
584 .minimum_priority_fee
585 .expect("minimum priority fee is expected inside if statement"),
586 })
587 }
588
589 if let Some(chain_id) = transaction.chain_id() &&
591 chain_id != self.chain_id()
592 {
593 return Err(InvalidTransactionError::ChainIdMismatch.into())
594 }
595
596 if transaction.is_eip7702() {
597 if !self.fork_tracker.is_prague_activated() {
599 return Err(InvalidTransactionError::TxTypeNotSupported.into())
600 }
601
602 if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
603 return Err(Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into())
604 }
605 }
606
607 ensure_intrinsic_gas(transaction, &self.fork_tracker)?;
608
609 if transaction.is_eip4844() {
611 if !self.fork_tracker.is_cancun_activated() {
613 return Err(InvalidTransactionError::TxTypeNotSupported.into())
614 }
615
616 let blob_count = transaction.blob_count().unwrap_or(0);
617 if blob_count == 0 {
618 return Err(InvalidPoolTransactionError::Eip4844(
620 Eip4844PoolTransactionError::NoEip4844Blobs,
621 ))
622 }
623
624 let max_blob_count = self.fork_tracker.max_blob_count();
625 if blob_count > max_blob_count {
626 return Err(InvalidPoolTransactionError::Eip4844(
627 Eip4844PoolTransactionError::TooManyEip4844Blobs {
628 have: blob_count,
629 permitted: max_blob_count,
630 },
631 ))
632 }
633 }
634
635 let tx_gas_limit_cap =
637 self.fork_tracker.tx_gas_limit_cap.load(std::sync::atomic::Ordering::Relaxed);
638 if tx_gas_limit_cap > 0 && transaction.gas_limit() > tx_gas_limit_cap {
639 return Err(InvalidTransactionError::GasLimitTooHigh.into())
640 }
641
642 if let Some(check) = &self.additional_stateless_validation {
644 check(origin, transaction)?;
645 }
646
647 Ok(())
648 }
649
650 pub fn validate_stateful<P>(
655 &self,
656 origin: TransactionOrigin,
657 mut transaction: Tx,
658 state: P,
659 ) -> TransactionValidationOutcome<Tx>
660 where
661 P: AccountInfoReader,
662 {
663 let account = match state.basic_account(transaction.sender_ref()) {
665 Ok(account) => account.unwrap_or_default(),
666 Err(err) => {
667 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
668 }
669 };
670
671 match self.validate_sender_bytecode(&transaction, &account, &state) {
673 Err(outcome) => return outcome,
674 Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err),
675 _ => {}
676 };
677
678 if transaction.requires_nonce_check() &&
680 let Err(err) = self.validate_sender_nonce(&transaction, &account)
681 {
682 return TransactionValidationOutcome::Invalid(transaction, err)
683 }
684
685 if let Err(err) = self.validate_sender_balance(&transaction, &account) {
687 return TransactionValidationOutcome::Invalid(transaction, err)
688 }
689
690 let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) {
692 Err(err) => return TransactionValidationOutcome::Invalid(transaction, err),
693 Ok(sidecar) => sidecar,
694 };
695
696 if let Some(check) = &self.additional_stateful_validation &&
698 let Err(err) = check(origin, &transaction, &state)
699 {
700 return TransactionValidationOutcome::Invalid(transaction, err)
701 }
702
703 let authorities = self.recover_authorities(&transaction);
704 TransactionValidationOutcome::Valid {
706 balance: account.balance,
707 state_nonce: account.nonce,
708 bytecode_hash: account.bytecode_hash,
709 transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
710 propagate: match origin {
712 TransactionOrigin::External => true,
713 TransactionOrigin::Local => {
714 self.local_transactions_config.propagate_local_transactions
715 }
716 TransactionOrigin::Private => false,
717 },
718 authorities,
719 }
720 }
721
722 pub fn validate_sender_bytecode(
724 &self,
725 transaction: &Tx,
726 sender: &Account,
727 state: impl BytecodeReader,
728 ) -> Result<Result<(), InvalidPoolTransactionError>, TransactionValidationOutcome<Tx>> {
729 if let Some(code_hash) = &sender.bytecode_hash &&
736 *code_hash != KECCAK_EMPTY
737 {
738 let is_eip7702 = if self.fork_tracker.is_prague_activated() {
739 match state.bytecode_by_hash(code_hash) {
740 Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
741 Err(err) => {
742 return Err(TransactionValidationOutcome::Error(
743 *transaction.hash(),
744 Box::new(err),
745 ))
746 }
747 }
748 } else {
749 false
750 };
751
752 if !is_eip7702 {
753 return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into()))
754 }
755 }
756 Ok(Ok(()))
757 }
758
759 pub fn validate_sender_nonce(
761 &self,
762 transaction: &Tx,
763 sender: &Account,
764 ) -> Result<(), InvalidPoolTransactionError> {
765 let tx_nonce = transaction.nonce();
766
767 if tx_nonce < sender.nonce {
768 return Err(InvalidTransactionError::NonceNotConsistent {
769 tx: tx_nonce,
770 state: sender.nonce,
771 }
772 .into())
773 }
774 Ok(())
775 }
776
777 pub fn validate_sender_balance(
779 &self,
780 transaction: &Tx,
781 sender: &Account,
782 ) -> Result<(), InvalidPoolTransactionError> {
783 let cost = transaction.cost();
784
785 if !self.disable_balance_check && cost > &sender.balance {
786 let expected = *cost;
787 return Err(InvalidTransactionError::InsufficientFunds(
788 GotExpected { got: sender.balance, expected }.into(),
789 )
790 .into())
791 }
792 Ok(())
793 }
794
795 pub fn validate_eip4844(
797 &self,
798 transaction: &mut Tx,
799 ) -> Result<Option<PooledBlobSidecar>, InvalidPoolTransactionError> {
800 let mut maybe_blob_sidecar = None;
801
802 if transaction.is_eip4844() {
804 match transaction.take_blob() {
806 EthBlobTransactionSidecar::None => {
807 return Err(InvalidTransactionError::TxTypeNotSupported.into())
809 }
810 EthBlobTransactionSidecar::Missing => {
811 if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) {
816 } else {
818 return Err(InvalidPoolTransactionError::Eip4844(
819 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
820 ))
821 }
822 }
823 EthBlobTransactionSidecar::Present(sidecar) => {
824 let now = Instant::now();
825
826 if self.eip7594 {
828 if self.fork_tracker.is_osaka_activated() {
830 if sidecar.is_eip4844() {
831 return Err(InvalidPoolTransactionError::Eip4844(
832 Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
833 ))
834 }
835 } else if sidecar.is_eip7594() && !self.allow_7594_sidecars() {
836 return Err(InvalidPoolTransactionError::Eip4844(
837 Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
838 ))
839 }
840 } else {
841 if sidecar.is_eip7594() {
843 return Err(InvalidPoolTransactionError::Eip4844(
844 Eip4844PoolTransactionError::Eip7594SidecarDisallowed,
845 ))
846 }
847 }
848
849 if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
851 return Err(InvalidPoolTransactionError::Eip4844(
852 Eip4844PoolTransactionError::InvalidEip4844Blob(err),
853 ))
854 }
855 self.validation_metrics.blob_validation_duration.record(now.elapsed());
857 maybe_blob_sidecar = Some(sidecar);
859 }
860 }
861 }
862 Ok(maybe_blob_sidecar)
863 }
864
865 fn recover_authorities(&self, transaction: &Tx) -> std::option::Option<Vec<Address>> {
867 transaction
868 .authorization_list()
869 .map(|auths| auths.iter().flat_map(|auth| auth.recover_authority()).collect::<Vec<_>>())
870 }
871
872 fn validate_batch(
874 &self,
875 transactions: impl IntoIterator<Item = (TransactionOrigin, Tx)>,
876 ) -> Vec<TransactionValidationOutcome<Tx>> {
877 let mut provider: Option<StateProviderBox> = None;
878 transactions
879 .into_iter()
880 .map(|(origin, tx)| {
881 self.validate_one_with_provider(origin, tx, &mut provider, || self.client.latest())
882 })
883 .collect()
884 }
885
886 fn validate_batch_with_origin(
888 &self,
889 origin: TransactionOrigin,
890 transactions: impl IntoIterator<Item = Tx> + Send,
891 ) -> Vec<TransactionValidationOutcome<Tx>> {
892 let mut provider: Option<StateProviderBox> = None;
893 transactions
894 .into_iter()
895 .map(|tx| {
896 self.validate_one_with_provider(origin, tx, &mut provider, || self.client.latest())
897 })
898 .collect()
899 }
900
901 fn on_new_head_block(&self, new_tip_block: &HeaderTy<Evm::Primitives>) {
902 if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
904 self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
905 }
906
907 if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
908 self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
909 }
910
911 if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
912 self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
913 }
914
915 if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
916 self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
917 }
918
919 if self.chain_spec().is_amsterdam_active_at_timestamp(new_tip_block.timestamp()) {
920 self.fork_tracker.amsterdam.store(true, std::sync::atomic::Ordering::Relaxed);
921 }
922
923 self.fork_tracker
924 .tip_timestamp
925 .store(new_tip_block.timestamp(), std::sync::atomic::Ordering::Relaxed);
926
927 if let Some(blob_params) =
928 self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
929 {
930 self.fork_tracker
931 .max_blob_count
932 .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed);
933 }
934
935 self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
936
937 let evm_env = self
939 .evm_config
940 .evm_env(new_tip_block)
941 .expect("evm_env should not fail for executed block");
942
943 self.fork_tracker
944 .max_initcode_size
945 .store(evm_env.cfg_env.max_initcode_size(), std::sync::atomic::Ordering::Relaxed);
946 let tx_gas_limit_cap = if evm_env.cfg_env.is_amsterdam_eip8037_enabled() {
950 0
951 } else {
952 evm_env.cfg_env.tx_gas_limit_cap()
953 };
954 self.fork_tracker
955 .tx_gas_limit_cap
956 .store(tx_gas_limit_cap, std::sync::atomic::Ordering::Relaxed);
957 }
958
959 fn max_gas_limit(&self) -> u64 {
960 self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
961 }
962
963 fn allow_7594_sidecars(&self) -> bool {
965 let tip_timestamp = self.fork_tracker.tip_timestamp();
966
967 if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(12)) {
969 true
970 } else if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(24)) {
971 let current_timestamp =
972 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
973
974 current_timestamp >= tip_timestamp.saturating_add(4)
976 } else {
977 false
978 }
979 }
980}
981
982impl<Client, Tx, Evm> TransactionValidator for EthTransactionValidator<Client, Tx, Evm>
983where
984 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
985 Tx: EthPoolTransaction,
986 Evm: ConfigureEvm,
987{
988 type Transaction = Tx;
989 type Block = BlockTy<Evm::Primitives>;
990
991 async fn validate_transaction(
992 &self,
993 origin: TransactionOrigin,
994 transaction: Self::Transaction,
995 ) -> TransactionValidationOutcome<Self::Transaction> {
996 self.validate_one(origin, transaction)
997 }
998
999 async fn validate_transactions(
1000 &self,
1001 transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
1002 + Send,
1003 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
1004 self.validate_batch(transactions)
1005 }
1006
1007 async fn validate_transactions_with_origin(
1008 &self,
1009 origin: TransactionOrigin,
1010 transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
1011 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
1012 self.validate_batch_with_origin(origin, transactions)
1013 }
1014
1015 fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
1016 Self::on_new_head_block(self, new_tip_block.header())
1017 }
1018}
1019
1020#[derive(Debug)]
1022pub struct EthTransactionValidatorBuilder<Client, Evm> {
1023 client: Client,
1024 chain_id: u64,
1026 evm_config: Evm,
1028 shanghai: bool,
1030 cancun: bool,
1032 prague: bool,
1034 osaka: bool,
1036 amsterdam: bool,
1038 tip_timestamp: u64,
1040 max_blob_count: u64,
1042 eip2718: bool,
1044 eip1559: bool,
1046 eip4844: bool,
1048 eip7702: bool,
1050 block_gas_limit: AtomicU64,
1052 tx_fee_cap: Option<u128>,
1054 minimum_priority_fee: Option<u128>,
1056 additional_tasks: usize,
1060
1061 kzg_settings: EnvKzgSettings,
1063 local_transactions_config: LocalTransactionConfig,
1065 max_tx_input_bytes: usize,
1067 max_tx_gas_limit: Option<u64>,
1069 disable_balance_check: bool,
1071 other_tx_types: U256,
1073 max_initcode_size: usize,
1075 tx_gas_limit_cap: u64,
1077 eip7594: bool,
1081}
1082
1083impl<Client, Evm> EthTransactionValidatorBuilder<Client, Evm> {
1084 pub fn new(client: Client, evm_config: Evm) -> Self
1094 where
1095 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
1096 + BlockReaderIdExt<Header = HeaderTy<Evm::Primitives>>,
1097 Evm: ConfigureEvm,
1098 {
1099 let chain_spec = client.chain_spec();
1100 let tip = client
1101 .header_by_id(BlockId::latest())
1102 .expect("failed to fetch latest header")
1103 .expect("latest header is not found");
1104 let evm_env =
1105 evm_config.evm_env(&tip).expect("evm_env should not fail for existing blocks");
1106
1107 Self {
1108 block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(),
1109 client,
1110 chain_id: chain_spec.chain().id(),
1111 evm_config,
1112 minimum_priority_fee: None,
1113 additional_tasks: 1,
1114 kzg_settings: EnvKzgSettings::Default,
1115 local_transactions_config: Default::default(),
1116 max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
1117 tx_fee_cap: Some(1e18 as u128),
1118 max_tx_gas_limit: None,
1119 eip2718: true,
1121 eip1559: true,
1122 eip4844: true,
1123 eip7702: true,
1124
1125 shanghai: chain_spec.is_shanghai_active_at_timestamp(tip.timestamp()),
1126 cancun: chain_spec.is_cancun_active_at_timestamp(tip.timestamp()),
1127 prague: chain_spec.is_prague_active_at_timestamp(tip.timestamp()),
1128 osaka: chain_spec.is_osaka_active_at_timestamp(tip.timestamp()),
1129 amsterdam: chain_spec.is_amsterdam_active_at_timestamp(tip.timestamp()),
1130
1131 tip_timestamp: tip.timestamp(),
1132
1133 max_blob_count: chain_spec
1134 .blob_params_at_timestamp(tip.timestamp())
1135 .unwrap_or_else(BlobParams::prague)
1136 .max_blobs_per_tx,
1137
1138 disable_balance_check: false,
1140
1141 other_tx_types: U256::ZERO,
1143
1144 tx_gas_limit_cap: if evm_env.cfg_env.is_amsterdam_eip8037_enabled() {
1146 0
1147 } else {
1148 evm_env.cfg_env.tx_gas_limit_cap()
1149 },
1150 max_initcode_size: evm_env.cfg_env.max_initcode_size(),
1151
1152 eip7594: true,
1154 }
1155 }
1156
1157 pub const fn no_cancun(self) -> Self {
1159 self.set_cancun(false)
1160 }
1161
1162 pub fn with_local_transactions_config(
1164 mut self,
1165 local_transactions_config: LocalTransactionConfig,
1166 ) -> Self {
1167 self.local_transactions_config = local_transactions_config;
1168 self
1169 }
1170
1171 pub const fn set_cancun(mut self, cancun: bool) -> Self {
1173 self.cancun = cancun;
1174 self
1175 }
1176
1177 pub const fn no_shanghai(self) -> Self {
1179 self.set_shanghai(false)
1180 }
1181
1182 pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
1184 self.shanghai = shanghai;
1185 self
1186 }
1187
1188 pub const fn no_prague(self) -> Self {
1190 self.set_prague(false)
1191 }
1192
1193 pub const fn set_prague(mut self, prague: bool) -> Self {
1195 self.prague = prague;
1196 self
1197 }
1198
1199 pub const fn no_osaka(self) -> Self {
1201 self.set_osaka(false)
1202 }
1203
1204 pub const fn set_osaka(mut self, osaka: bool) -> Self {
1206 self.osaka = osaka;
1207 self
1208 }
1209
1210 pub const fn no_amsterdam(self) -> Self {
1212 self.set_amsterdam(false)
1213 }
1214
1215 pub const fn set_amsterdam(mut self, amsterdam: bool) -> Self {
1217 self.amsterdam = amsterdam;
1218 self
1219 }
1220
1221 pub const fn no_eip2718(self) -> Self {
1223 self.set_eip2718(false)
1224 }
1225
1226 pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
1228 self.eip2718 = eip2718;
1229 self
1230 }
1231
1232 pub const fn no_eip1559(self) -> Self {
1234 self.set_eip1559(false)
1235 }
1236
1237 pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
1239 self.eip1559 = eip1559;
1240 self
1241 }
1242
1243 pub const fn no_eip4844(self) -> Self {
1245 self.set_eip4844(false)
1246 }
1247
1248 pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
1250 self.eip4844 = eip4844;
1251 self
1252 }
1253
1254 pub const fn no_eip7702(self) -> Self {
1256 self.set_eip7702(false)
1257 }
1258
1259 pub const fn set_eip7702(mut self, eip7702: bool) -> Self {
1261 self.eip7702 = eip7702;
1262 self
1263 }
1264
1265 pub const fn no_eip7594(self) -> Self {
1272 self.set_eip7594(false)
1273 }
1274
1275 pub const fn set_eip7594(mut self, eip7594: bool) -> Self {
1280 self.eip7594 = eip7594;
1281 self
1282 }
1283
1284 pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
1286 self.kzg_settings = kzg_settings;
1287 self
1288 }
1289
1290 pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: Option<u128>) -> Self {
1292 self.minimum_priority_fee = minimum_priority_fee;
1293 self
1294 }
1295
1296 pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
1298 self.additional_tasks = additional_tasks;
1299 self
1300 }
1301
1302 pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
1304 self.max_tx_input_bytes = max_tx_input_bytes;
1305 self
1306 }
1307
1308 pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
1312 self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
1313 self
1314 }
1315
1316 pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
1320 self.tx_fee_cap = Some(tx_fee_cap);
1321 self
1322 }
1323
1324 pub const fn with_max_tx_gas_limit(mut self, max_tx_gas_limit: Option<u64>) -> Self {
1326 self.max_tx_gas_limit = max_tx_gas_limit;
1327 self
1328 }
1329
1330 pub const fn disable_balance_check(mut self) -> Self {
1332 self.disable_balance_check = true;
1333 self
1334 }
1335
1336 pub const fn with_custom_tx_type(mut self, tx_type: u8) -> Self {
1338 self.other_tx_types.set_bit(tx_type as usize, true);
1339 self
1340 }
1341
1342 pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx, Evm>
1344 where
1345 S: BlobStore,
1346 {
1347 let Self {
1348 client,
1349 chain_id,
1350 evm_config,
1351 shanghai,
1352 cancun,
1353 prague,
1354 osaka,
1355 amsterdam,
1356 tip_timestamp,
1357 eip2718,
1358 eip1559,
1359 eip4844,
1360 eip7702,
1361 block_gas_limit,
1362 tx_fee_cap,
1363 minimum_priority_fee,
1364 kzg_settings,
1365 local_transactions_config,
1366 max_tx_input_bytes,
1367 max_tx_gas_limit,
1368 disable_balance_check,
1369 max_blob_count,
1370 additional_tasks: _,
1371 other_tx_types,
1372 max_initcode_size,
1373 tx_gas_limit_cap,
1374 eip7594,
1375 } = self;
1376
1377 let fork_tracker = ForkTracker {
1378 shanghai: AtomicBool::new(shanghai),
1379 cancun: AtomicBool::new(cancun),
1380 prague: AtomicBool::new(prague),
1381 osaka: AtomicBool::new(osaka),
1382 amsterdam: AtomicBool::new(amsterdam),
1383 tip_timestamp: AtomicU64::new(tip_timestamp),
1384 max_blob_count: AtomicU64::new(max_blob_count),
1385 max_initcode_size: AtomicUsize::new(max_initcode_size),
1386 tx_gas_limit_cap: AtomicU64::new(tx_gas_limit_cap),
1387 };
1388
1389 EthTransactionValidator {
1390 client,
1391 chain_id,
1392 eip2718,
1393 eip1559,
1394 fork_tracker,
1395 eip4844,
1396 eip7702,
1397 block_gas_limit,
1398 tx_fee_cap,
1399 minimum_priority_fee,
1400 blob_store: Box::new(blob_store),
1401 kzg_settings,
1402 local_transactions_config,
1403 max_tx_input_bytes,
1404 max_tx_gas_limit,
1405 disable_balance_check,
1406 evm_config,
1407 _marker: Default::default(),
1408 validation_metrics: TxPoolValidationMetrics::default(),
1409 other_tx_types,
1410 eip7594,
1411 additional_stateless_validation: None,
1412 additional_stateful_validation: None,
1413 }
1414 }
1415
1416 pub fn build_with_tasks<Tx, S>(
1423 self,
1424 tasks: Runtime,
1425 blob_store: S,
1426 ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx, Evm>>
1427 where
1428 S: BlobStore,
1429 {
1430 let additional_tasks = self.additional_tasks;
1431 let validator = self.build::<Tx, S>(blob_store);
1432 TransactionValidationTaskExecutor::spawn(validator, &tasks, additional_tasks)
1433 }
1434}
1435
1436#[derive(Debug)]
1438pub struct ForkTracker {
1439 pub shanghai: AtomicBool,
1441 pub cancun: AtomicBool,
1443 pub prague: AtomicBool,
1445 pub osaka: AtomicBool,
1447 pub amsterdam: AtomicBool,
1449 pub max_blob_count: AtomicU64,
1451 pub tip_timestamp: AtomicU64,
1453 pub max_initcode_size: AtomicUsize,
1455 pub tx_gas_limit_cap: AtomicU64,
1457}
1458
1459impl ForkTracker {
1460 pub fn is_shanghai_activated(&self) -> bool {
1462 self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1463 }
1464
1465 pub fn is_cancun_activated(&self) -> bool {
1467 self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1468 }
1469
1470 pub fn is_prague_activated(&self) -> bool {
1472 self.prague.load(std::sync::atomic::Ordering::Relaxed)
1473 }
1474
1475 pub fn is_osaka_activated(&self) -> bool {
1477 self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1478 }
1479
1480 pub fn is_amsterdam_activated(&self) -> bool {
1482 self.amsterdam.load(std::sync::atomic::Ordering::Relaxed)
1483 }
1484
1485 pub fn tip_timestamp(&self) -> u64 {
1487 self.tip_timestamp.load(std::sync::atomic::Ordering::Relaxed)
1488 }
1489
1490 pub fn max_blob_count(&self) -> u64 {
1492 self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1493 }
1494}
1495
1496pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1500 transaction: &T,
1501 fork_tracker: &ForkTracker,
1502) -> Result<(), InvalidPoolTransactionError> {
1503 use revm::primitives::hardfork::SpecId;
1504 let spec_id = if fork_tracker.is_amsterdam_activated() {
1505 SpecId::AMSTERDAM
1506 } else if fork_tracker.is_prague_activated() {
1507 SpecId::PRAGUE
1508 } else if fork_tracker.is_shanghai_activated() {
1509 SpecId::SHANGHAI
1510 } else {
1511 SpecId::MERGE
1512 };
1513
1514 let eip2780 = fork_tracker.is_amsterdam_activated().then(|| {
1517 revm::context_interface::cfg::gas_params::Eip2780TxInfo {
1518 value: transaction.value(),
1519 is_self_transfer: transaction.kind().to() == Some(&transaction.sender()),
1521 }
1522 });
1523
1524 let gas = revm::interpreter::gas::calculate_initial_tx_gas(
1525 spec_id,
1526 transaction.input(),
1527 transaction.is_create(),
1528 transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1529 transaction
1530 .access_list()
1531 .map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
1532 .unwrap_or_default() as u64,
1533 transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
1534 eip2780,
1535 );
1536
1537 let gas_limit = transaction.gas_limit();
1538 if gas_limit < gas.initial_total_gas() || gas_limit < gas.floor_gas {
1539 Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1540 } else {
1541 Ok(())
1542 }
1543}
1544
1545#[cfg(test)]
1546mod tests {
1547 use super::*;
1548 use crate::{
1549 blobstore::InMemoryBlobStore, error::PoolErrorKind, test_utils::TransactionBuilder,
1550 traits::PoolTransaction, CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1551 };
1552 use alloy_consensus::Transaction;
1553 use alloy_eips::{
1554 eip2718::{Decodable2718, Encodable2718},
1555 eip2930::{AccessList, AccessListItem},
1556 };
1557 use alloy_primitives::{hex, Address, Bytes, B256, U256};
1558 use reth_ethereum_primitives::PooledTransactionVariant;
1559 use reth_evm_ethereum::EthEvmConfig;
1560 use reth_primitives_traits::SignedTransaction;
1561 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1562 use revm::primitives::eip3860::MAX_INITCODE_SIZE;
1563
1564 fn test_evm_config() -> EthEvmConfig {
1565 EthEvmConfig::mainnet()
1566 }
1567
1568 fn get_transaction() -> EthPooledTransaction {
1569 let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1570
1571 let data = hex::decode(raw).unwrap();
1572 let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1573
1574 EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1575 }
1576
1577 fn eip1559_tx(
1578 to: Address,
1579 sender: Address,
1580 value: u64,
1581 gas_limit: u64,
1582 ) -> EthPooledTransaction {
1583 let tx = alloy_consensus::TxEip1559 {
1584 chain_id: 1,
1585 nonce: 0,
1586 gas_limit,
1587 max_fee_per_gas: 1,
1588 max_priority_fee_per_gas: 0,
1589 to: to.into(),
1590 value: U256::from(value),
1591 ..Default::default()
1592 };
1593 let signed = reth_ethereum_primitives::TransactionSigned::new_unhashed(
1594 tx.into(),
1595 alloy_primitives::Signature::test_signature(),
1596 );
1597 EthPooledTransaction::new(
1598 alloy_consensus::transaction::Recovered::new_unchecked(signed, sender),
1599 200,
1600 )
1601 }
1602
1603 #[test]
1607 fn intrinsic_gas_eip2780() {
1608 let sender = Address::repeat_byte(1);
1609 let recipient = Address::repeat_byte(2);
1610
1611 let amsterdam = || ForkTracker {
1612 shanghai: true.into(),
1613 cancun: true.into(),
1614 prague: true.into(),
1615 osaka: true.into(),
1616 amsterdam: true.into(),
1617 tip_timestamp: 0.into(),
1618 max_blob_count: 0.into(),
1619 max_initcode_size: AtomicUsize::new(MAX_INITCODE_SIZE),
1620 tx_gas_limit_cap: AtomicU64::new(0),
1621 };
1622 let pre_amsterdam = || ForkTracker { amsterdam: false.into(), ..amsterdam() };
1623
1624 let self_transfer = eip1559_tx(sender, sender, 1, 15_000);
1626 assert!(ensure_intrinsic_gas(&self_transfer, &amsterdam()).is_ok());
1627 assert!(ensure_intrinsic_gas(&self_transfer, &pre_amsterdam()).is_err());
1628
1629 let zero_value = eip1559_tx(recipient, sender, 0, 15_000);
1631 assert!(ensure_intrinsic_gas(&zero_value, &amsterdam()).is_ok());
1632 assert!(
1633 ensure_intrinsic_gas(&eip1559_tx(recipient, sender, 0, 14_999), &amsterdam()).is_err()
1634 );
1635
1636 assert!(
1638 ensure_intrinsic_gas(&eip1559_tx(recipient, sender, 1, 15_000), &amsterdam()).is_err()
1639 );
1640 assert!(
1641 ensure_intrinsic_gas(&eip1559_tx(recipient, sender, 1, 21_000), &amsterdam()).is_ok()
1642 );
1643 }
1644
1645 #[tokio::test]
1647 async fn validate_transaction() {
1648 let transaction = get_transaction();
1649 let mut fork_tracker = ForkTracker {
1650 shanghai: false.into(),
1651 cancun: false.into(),
1652 prague: false.into(),
1653 osaka: false.into(),
1654 amsterdam: false.into(),
1655 tip_timestamp: 0.into(),
1656 max_blob_count: 0.into(),
1657 max_initcode_size: AtomicUsize::new(MAX_INITCODE_SIZE),
1658 tx_gas_limit_cap: AtomicU64::new(0),
1659 };
1660
1661 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1662 assert!(res.is_ok());
1663
1664 fork_tracker.shanghai = true.into();
1665 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1666 assert!(res.is_ok());
1667
1668 let provider = MockEthProvider::default().with_genesis_block();
1669 provider.add_account(
1670 transaction.sender(),
1671 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1672 );
1673 let blob_store = InMemoryBlobStore::default();
1674 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1675 .build(blob_store.clone());
1676
1677 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1678
1679 assert!(outcome.is_valid());
1680
1681 let pool =
1682 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1683
1684 let res = pool.add_external_transaction(transaction.clone()).await;
1685 assert!(res.is_ok());
1686 let tx = pool.get(transaction.hash());
1687 assert!(tx.is_some());
1688 }
1689
1690 #[test]
1691 fn accepts_sender_with_empty_bytecode() {
1692 let transaction = get_transaction();
1693 let provider = MockEthProvider::default().with_genesis_block();
1694 provider.add_account(
1695 transaction.sender(),
1696 ExtendedAccount::new(transaction.nonce(), U256::MAX).with_bytecode(Bytes::new()),
1697 );
1698 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1699 .build(InMemoryBlobStore::default());
1700
1701 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1702
1703 assert!(outcome.is_valid());
1704 }
1705
1706 #[test]
1707 fn validates_configured_chain_id() {
1708 let provider = MockEthProvider::default().with_genesis_block();
1709 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1710 .build(InMemoryBlobStore::default());
1711 let transaction = |chain_id| {
1712 EthPooledTransaction::try_from_consensus(
1713 TransactionBuilder::default()
1714 .chain_id(chain_id)
1715 .gas_limit(21_000)
1716 .to(Address::ZERO)
1717 .into_eip1559()
1718 .try_into_recovered()
1719 .unwrap(),
1720 )
1721 .unwrap()
1722 };
1723
1724 assert!(validator
1725 .validate_stateless(TransactionOrigin::External, &transaction(validator.chain_id()))
1726 .is_ok());
1727 assert!(matches!(
1728 validator.validate_stateless(
1729 TransactionOrigin::External,
1730 &transaction(validator.chain_id() + 1)
1731 ),
1732 Err(InvalidPoolTransactionError::Consensus(InvalidTransactionError::ChainIdMismatch))
1733 ));
1734 }
1735
1736 #[tokio::test]
1738 async fn invalid_on_gas_limit_too_high() {
1739 let transaction = get_transaction();
1740
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, test_evm_config())
1749 .set_block_gas_limit(1_000_000) .build(blob_store.clone());
1751
1752 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1753
1754 assert!(outcome.is_invalid());
1755
1756 let pool =
1757 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1758
1759 let res = pool.add_external_transaction(transaction.clone()).await;
1760 assert!(res.is_err());
1761 assert!(matches!(
1762 res.unwrap_err().kind,
1763 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1764 1_015_288, 1_000_000
1765 ))
1766 ));
1767 let tx = pool.get(transaction.hash());
1768 assert!(tx.is_none());
1769 }
1770
1771 #[tokio::test]
1772 async fn invalid_on_fee_cap_exceeded() {
1773 let transaction = get_transaction();
1774 let provider = MockEthProvider::default().with_genesis_block();
1775 provider.add_account(
1776 transaction.sender(),
1777 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1778 );
1779
1780 let blob_store = InMemoryBlobStore::default();
1781 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1782 .set_tx_fee_cap(100) .build(blob_store.clone());
1784
1785 let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1786 assert!(outcome.is_invalid());
1787
1788 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1789 assert!(matches!(
1790 err,
1791 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1792 if (max_tx_fee_wei > tx_fee_cap_wei)
1793 ));
1794 }
1795
1796 let pool =
1797 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1798 let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
1799 assert!(res.is_err());
1800 assert!(matches!(
1801 res.unwrap_err().kind,
1802 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
1803 ));
1804 let tx = pool.get(transaction.hash());
1805 assert!(tx.is_none());
1806 }
1807
1808 #[tokio::test]
1809 async fn valid_on_zero_fee_cap() {
1810 let transaction = get_transaction();
1811 let provider = MockEthProvider::default().with_genesis_block();
1812 provider.add_account(
1813 transaction.sender(),
1814 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1815 );
1816
1817 let blob_store = InMemoryBlobStore::default();
1818 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1819 .set_tx_fee_cap(0) .build(blob_store);
1821
1822 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1823 assert!(outcome.is_valid());
1824 }
1825
1826 #[tokio::test]
1827 async fn valid_on_normal_fee_cap() {
1828 let transaction = get_transaction();
1829 let provider = MockEthProvider::default().with_genesis_block();
1830 provider.add_account(
1831 transaction.sender(),
1832 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1833 );
1834
1835 let blob_store = InMemoryBlobStore::default();
1836 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1837 .set_tx_fee_cap(2e18 as u128) .build(blob_store);
1839
1840 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1841 assert!(outcome.is_valid());
1842 }
1843
1844 #[tokio::test]
1845 async fn invalid_on_max_tx_gas_limit_exceeded() {
1846 let transaction = get_transaction();
1847 let provider = MockEthProvider::default().with_genesis_block();
1848 provider.add_account(
1849 transaction.sender(),
1850 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1851 );
1852
1853 let blob_store = InMemoryBlobStore::default();
1854 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1855 .with_max_tx_gas_limit(Some(500_000)) .build(blob_store.clone());
1857
1858 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1859 assert!(outcome.is_invalid());
1860
1861 let pool =
1862 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1863
1864 let res = pool.add_external_transaction(transaction.clone()).await;
1865 assert!(res.is_err());
1866 assert!(matches!(
1867 res.unwrap_err().kind,
1868 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
1869 1_015_288, 500_000
1870 ))
1871 ));
1872 let tx = pool.get(transaction.hash());
1873 assert!(tx.is_none());
1874 }
1875
1876 #[tokio::test]
1877 async fn valid_on_max_tx_gas_limit_disabled() {
1878 let transaction = get_transaction();
1879 let provider = MockEthProvider::default().with_genesis_block();
1880 provider.add_account(
1881 transaction.sender(),
1882 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1883 );
1884
1885 let blob_store = InMemoryBlobStore::default();
1886 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1887 .with_max_tx_gas_limit(None) .build(blob_store);
1889
1890 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1891 assert!(outcome.is_valid());
1892 }
1893
1894 #[tokio::test]
1895 async fn valid_on_max_tx_gas_limit_within_limit() {
1896 let transaction = get_transaction();
1897 let provider = MockEthProvider::default().with_genesis_block();
1898 provider.add_account(
1899 transaction.sender(),
1900 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1901 );
1902
1903 let blob_store = InMemoryBlobStore::default();
1904 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1905 .with_max_tx_gas_limit(Some(2_000_000)) .build(blob_store);
1907
1908 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1909 assert!(outcome.is_valid());
1910 }
1911
1912 fn setup_priority_fee_test() -> (EthPooledTransaction, MockEthProvider) {
1914 let transaction = get_transaction();
1915 let provider = MockEthProvider::default().with_genesis_block();
1916 provider.add_account(
1917 transaction.sender(),
1918 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1919 );
1920 (transaction, provider)
1921 }
1922
1923 fn create_validator_with_minimum_fee(
1925 provider: MockEthProvider,
1926 minimum_priority_fee: Option<u128>,
1927 local_config: Option<LocalTransactionConfig>,
1928 ) -> EthTransactionValidator<MockEthProvider, EthPooledTransaction, EthEvmConfig> {
1929 let blob_store = InMemoryBlobStore::default();
1930 let mut builder = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1931 .with_minimum_priority_fee(minimum_priority_fee);
1932
1933 if let Some(config) = local_config {
1934 builder = builder.with_local_transactions_config(config);
1935 }
1936
1937 builder.build(blob_store)
1938 }
1939
1940 #[tokio::test]
1941 async fn invalid_on_priority_fee_lower_than_configured_minimum() {
1942 let (transaction, provider) = setup_priority_fee_test();
1943
1944 assert!(transaction.is_dynamic_fee());
1946
1947 let minimum_priority_fee =
1949 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1950
1951 let validator =
1952 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1953
1954 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1956 assert!(outcome.is_invalid());
1957
1958 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1959 assert!(matches!(
1960 err,
1961 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1962 if min_fee == minimum_priority_fee
1963 ));
1964 }
1965
1966 let blob_store = InMemoryBlobStore::default();
1968 let pool =
1969 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1970
1971 let res = pool.add_external_transaction(transaction.clone()).await;
1972 assert!(res.is_err());
1973 assert!(matches!(
1974 res.unwrap_err().kind,
1975 PoolErrorKind::InvalidTransaction(
1976 InvalidPoolTransactionError::PriorityFeeBelowMinimum { .. }
1977 )
1978 ));
1979 let tx = pool.get(transaction.hash());
1980 assert!(tx.is_none());
1981
1982 let (_, local_provider) = setup_priority_fee_test();
1984 let validator_local =
1985 create_validator_with_minimum_fee(local_provider, Some(minimum_priority_fee), None);
1986
1987 let local_outcome = validator_local.validate_one(TransactionOrigin::Local, transaction);
1988 assert!(local_outcome.is_valid());
1989 }
1990
1991 #[tokio::test]
1992 async fn valid_on_priority_fee_equal_to_minimum() {
1993 let (transaction, provider) = setup_priority_fee_test();
1994
1995 let tx_priority_fee =
1997 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1998 let validator = create_validator_with_minimum_fee(provider, Some(tx_priority_fee), None);
1999
2000 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2001 assert!(outcome.is_valid());
2002 }
2003
2004 #[tokio::test]
2005 async fn valid_on_priority_fee_above_minimum() {
2006 let (transaction, provider) = setup_priority_fee_test();
2007
2008 let tx_priority_fee =
2010 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
2011 let minimum_priority_fee = tx_priority_fee / 2; let validator =
2014 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2015
2016 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2017 assert!(outcome.is_valid());
2018 }
2019
2020 #[tokio::test]
2021 async fn valid_on_minimum_priority_fee_disabled() {
2022 let (transaction, provider) = setup_priority_fee_test();
2023
2024 let validator = create_validator_with_minimum_fee(provider, None, None);
2026
2027 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2028 assert!(outcome.is_valid());
2029 }
2030
2031 #[tokio::test]
2032 async fn priority_fee_validation_applies_to_private_transactions() {
2033 let (transaction, provider) = setup_priority_fee_test();
2034
2035 let minimum_priority_fee =
2037 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
2038
2039 let validator =
2040 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
2041
2042 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
2045 assert!(outcome.is_invalid());
2046
2047 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
2048 assert!(matches!(
2049 err,
2050 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
2051 if min_fee == minimum_priority_fee
2052 ));
2053 }
2054 }
2055
2056 #[tokio::test]
2057 async fn valid_on_local_config_exempts_private_transactions() {
2058 let (transaction, provider) = setup_priority_fee_test();
2059
2060 let minimum_priority_fee =
2062 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
2063
2064 let local_config =
2066 LocalTransactionConfig { propagate_local_transactions: true, ..Default::default() };
2067
2068 let validator = create_validator_with_minimum_fee(
2069 provider,
2070 Some(minimum_priority_fee),
2071 Some(local_config),
2072 );
2073
2074 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
2078 assert!(outcome.is_invalid()); }
2080
2081 #[test]
2082 fn reject_oversized_tx() {
2083 let mut transaction = get_transaction();
2084 transaction.encoded_length = DEFAULT_MAX_TX_INPUT_BYTES + 1;
2085 let provider = MockEthProvider::default().with_genesis_block();
2086
2087 let validator = create_validator_with_minimum_fee(provider, None, None);
2089
2090 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2091 let invalid = outcome.as_invalid().unwrap();
2092 assert!(invalid.is_oversized());
2093 }
2094
2095 #[test]
2096 fn reject_blob_tx_with_oversized_access_list() {
2097 let max_tx_input_bytes = 512;
2098 let provider = MockEthProvider::default().with_genesis_block();
2099 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
2100 .with_max_tx_input_bytes(max_tx_input_bytes)
2101 .build(InMemoryBlobStore::default());
2102
2103 let blob_tx_with_access_list = |storage_keys: usize| {
2104 let access_list = AccessList(vec![AccessListItem {
2105 address: Address::random(),
2106 storage_keys: (0..storage_keys).map(|_| B256::random()).collect(),
2107 }]);
2108 let tx = TransactionBuilder::default()
2109 .access_list(access_list)
2110 .into_eip4844()
2111 .try_into_recovered()
2112 .unwrap();
2113 let encoded_length = tx.encode_2718_len();
2114 EthPooledTransaction::new(tx, encoded_length)
2115 };
2116
2117 let is_oversized = |tx: &EthPooledTransaction| {
2118 matches!(
2119 validator.validate_stateless(TransactionOrigin::External, tx),
2120 Err(InvalidPoolTransactionError::OversizedData { .. })
2121 )
2122 };
2123
2124 let small = blob_tx_with_access_list(1);
2125 assert!(!is_oversized(&small));
2126
2127 let large = blob_tx_with_access_list(64);
2128 assert!(large.input().is_empty());
2129 assert!(is_oversized(&large));
2130 }
2131
2132 #[tokio::test]
2133 async fn valid_with_disabled_balance_check() {
2134 let transaction = get_transaction();
2135 let provider = MockEthProvider::default().with_genesis_block();
2136
2137 provider.add_account(
2139 transaction.sender(),
2140 ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
2141 );
2142
2143 let validator =
2145 EthTransactionValidatorBuilder::new(provider.clone(), EthEvmConfig::mainnet())
2146 .build(InMemoryBlobStore::default());
2147
2148 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
2149 let expected_cost = *transaction.cost();
2150 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
2151 assert!(matches!(
2152 err,
2153 InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err))
2154 if funds_err.got == alloy_primitives::U256::ZERO && funds_err.expected == expected_cost
2155 ));
2156 } else {
2157 panic!("Expected Invalid outcome with InsufficientFunds error");
2158 }
2159
2160 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
2162 .disable_balance_check()
2163 .build(InMemoryBlobStore::default());
2164
2165 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
2166 assert!(outcome.is_valid()); }
2168}