1use super::constants::DEFAULT_MAX_TX_INPUT_BYTES;
4use crate::{
5 blobstore::BlobStore,
6 error::{
7 Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
8 },
9 metrics::TxPoolValidationMetrics,
10 traits::TransactionOrigin,
11 validate::{ValidTransaction, ValidationTask},
12 Address, BlobTransactionSidecarVariant, EthBlobTransactionSidecar, EthPoolTransaction,
13 LocalTransactionConfig, TransactionValidationOutcome, TransactionValidationTaskExecutor,
14 TransactionValidator,
15};
16
17use alloy_consensus::{
18 constants::{
19 EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
20 LEGACY_TX_TYPE_ID,
21 },
22 BlockHeader,
23};
24use alloy_eips::{
25 eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M, eip4844::env_settings::EnvKzgSettings,
26 eip7840::BlobParams, BlockId,
27};
28use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
29use reth_evm::ConfigureEvm;
30use reth_primitives_traits::{
31 transaction::error::InvalidTransactionError, Account, BlockTy, GotExpected, HeaderTy,
32 SealedBlock,
33};
34use reth_storage_api::{AccountInfoReader, BlockReaderIdExt, BytecodeReader, StateProviderFactory};
35use reth_tasks::TaskSpawner;
36use revm::context_interface::Cfg;
37use revm_primitives::U256;
38use std::{
39 marker::PhantomData,
40 sync::{
41 atomic::{AtomicBool, AtomicU64, AtomicUsize},
42 Arc,
43 },
44 time::{Instant, SystemTime},
45};
46use tokio::sync::Mutex;
47
48#[derive(Debug)]
63pub struct EthTransactionValidator<Client, T, Evm> {
64 client: Client,
66 blob_store: Box<dyn BlobStore>,
68 fork_tracker: ForkTracker,
70 eip2718: bool,
72 eip1559: bool,
74 eip4844: bool,
76 eip7702: bool,
78 block_gas_limit: AtomicU64,
80 tx_fee_cap: Option<u128>,
82 minimum_priority_fee: Option<u128>,
84 kzg_settings: EnvKzgSettings,
86 local_transactions_config: LocalTransactionConfig,
88 max_tx_input_bytes: usize,
90 max_tx_gas_limit: Option<u64>,
92 disable_balance_check: bool,
94 evm_config: Evm,
96 _marker: PhantomData<T>,
98 validation_metrics: TxPoolValidationMetrics,
100 other_tx_types: U256,
102 eip7594: bool,
106}
107
108impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm> {
109 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
111 where
112 Client: ChainSpecProvider,
113 {
114 self.client().chain_spec()
115 }
116
117 pub fn chain_id(&self) -> u64
119 where
120 Client: ChainSpecProvider,
121 {
122 self.client().chain_spec().chain().id()
123 }
124
125 pub const fn client(&self) -> &Client {
127 &self.client
128 }
129
130 pub const fn fork_tracker(&self) -> &ForkTracker {
132 &self.fork_tracker
133 }
134
135 pub const fn eip2718(&self) -> bool {
137 self.eip2718
138 }
139
140 pub const fn eip1559(&self) -> bool {
142 self.eip1559
143 }
144
145 pub const fn eip4844(&self) -> bool {
147 self.eip4844
148 }
149
150 pub const fn eip7702(&self) -> bool {
152 self.eip7702
153 }
154
155 pub const fn tx_fee_cap(&self) -> &Option<u128> {
157 &self.tx_fee_cap
158 }
159
160 pub const fn minimum_priority_fee(&self) -> &Option<u128> {
162 &self.minimum_priority_fee
163 }
164
165 pub const fn kzg_settings(&self) -> &EnvKzgSettings {
167 &self.kzg_settings
168 }
169
170 pub const fn local_transactions_config(&self) -> &LocalTransactionConfig {
172 &self.local_transactions_config
173 }
174
175 pub const fn max_tx_input_bytes(&self) -> usize {
178 self.max_tx_input_bytes
179 }
180
181 pub const fn disable_balance_check(&self) -> bool {
183 self.disable_balance_check
184 }
185}
186
187impl<Client, Tx, Evm> EthTransactionValidator<Client, Tx, Evm>
188where
189 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
190 Tx: EthPoolTransaction,
191 Evm: ConfigureEvm,
192{
193 pub fn block_gas_limit(&self) -> u64 {
195 self.max_gas_limit()
196 }
197
198 pub fn validate_one(
202 &self,
203 origin: TransactionOrigin,
204 transaction: Tx,
205 ) -> TransactionValidationOutcome<Tx> {
206 self.validate_one_with_provider(origin, transaction, &mut None)
207 }
208
209 pub fn validate_one_with_state(
216 &self,
217 origin: TransactionOrigin,
218 transaction: Tx,
219 state: &mut Option<Box<dyn AccountInfoReader + Send>>,
220 ) -> TransactionValidationOutcome<Tx> {
221 self.validate_one_with_provider(origin, transaction, state)
222 }
223
224 fn validate_one_with_provider(
228 &self,
229 origin: TransactionOrigin,
230 transaction: Tx,
231 maybe_state: &mut Option<Box<dyn AccountInfoReader + Send>>,
232 ) -> TransactionValidationOutcome<Tx> {
233 match self.validate_stateless(origin, transaction) {
234 Ok(transaction) => {
235 if maybe_state.is_none() {
238 match self.client.latest() {
239 Ok(new_state) => {
240 *maybe_state = Some(Box::new(new_state));
241 }
242 Err(err) => {
243 return TransactionValidationOutcome::Error(
244 *transaction.hash(),
245 Box::new(err),
246 )
247 }
248 }
249 }
250
251 let state = maybe_state.as_deref().expect("provider is set");
252
253 self.validate_stateful(origin, transaction, state)
254 }
255 Err(invalid_outcome) => invalid_outcome,
256 }
257 }
258
259 pub fn validate_one_with_state_provider(
262 &self,
263 origin: TransactionOrigin,
264 transaction: Tx,
265 state: impl AccountInfoReader,
266 ) -> TransactionValidationOutcome<Tx> {
267 let tx = match self.validate_stateless(origin, transaction) {
268 Ok(tx) => tx,
269 Err(invalid_outcome) => return invalid_outcome,
270 };
271 self.validate_stateful(origin, tx, state)
272 }
273
274 pub fn validate_stateless(
280 &self,
281 origin: TransactionOrigin,
282 transaction: Tx,
283 ) -> Result<Tx, TransactionValidationOutcome<Tx>> {
284 match transaction.ty() {
286 LEGACY_TX_TYPE_ID => {
287 }
289 EIP2930_TX_TYPE_ID => {
290 if !self.eip2718 {
292 return Err(TransactionValidationOutcome::Invalid(
293 transaction,
294 InvalidTransactionError::Eip2930Disabled.into(),
295 ))
296 }
297 }
298 EIP1559_TX_TYPE_ID => {
299 if !self.eip1559 {
301 return Err(TransactionValidationOutcome::Invalid(
302 transaction,
303 InvalidTransactionError::Eip1559Disabled.into(),
304 ))
305 }
306 }
307 EIP4844_TX_TYPE_ID => {
308 if !self.eip4844 {
310 return Err(TransactionValidationOutcome::Invalid(
311 transaction,
312 InvalidTransactionError::Eip4844Disabled.into(),
313 ))
314 }
315 }
316 EIP7702_TX_TYPE_ID => {
317 if !self.eip7702 {
319 return Err(TransactionValidationOutcome::Invalid(
320 transaction,
321 InvalidTransactionError::Eip7702Disabled.into(),
322 ))
323 }
324 }
325
326 ty if !self.other_tx_types.bit(ty as usize) => {
327 return Err(TransactionValidationOutcome::Invalid(
328 transaction,
329 InvalidTransactionError::TxTypeNotSupported.into(),
330 ))
331 }
332
333 _ => {}
334 };
335
336 let tx_nonce = transaction.nonce();
338 if tx_nonce == u64::MAX {
339 return Err(TransactionValidationOutcome::Invalid(
340 transaction,
341 InvalidPoolTransactionError::Eip2681,
342 ))
343 }
344
345 if transaction.is_eip4844() {
347 let tx_input_len = transaction.input().len();
352 if tx_input_len > self.max_tx_input_bytes {
353 return Err(TransactionValidationOutcome::Invalid(
354 transaction,
355 InvalidPoolTransactionError::OversizedData {
356 size: tx_input_len,
357 limit: self.max_tx_input_bytes,
358 },
359 ))
360 }
361 } else {
362 let tx_size = transaction.encoded_length();
364 if tx_size > self.max_tx_input_bytes {
365 return Err(TransactionValidationOutcome::Invalid(
366 transaction,
367 InvalidPoolTransactionError::OversizedData {
368 size: tx_size,
369 limit: self.max_tx_input_bytes,
370 },
371 ))
372 }
373 }
374
375 if self.fork_tracker.is_shanghai_activated() {
377 let max_initcode_size =
378 self.fork_tracker.max_initcode_size.load(std::sync::atomic::Ordering::Relaxed);
379 if let Err(err) = transaction.ensure_max_init_code_size(max_initcode_size) {
380 return Err(TransactionValidationOutcome::Invalid(transaction, err))
381 }
382 }
383
384 let transaction_gas_limit = transaction.gas_limit();
386 let block_gas_limit = self.max_gas_limit();
387 if transaction_gas_limit > block_gas_limit {
388 return Err(TransactionValidationOutcome::Invalid(
389 transaction,
390 InvalidPoolTransactionError::ExceedsGasLimit(
391 transaction_gas_limit,
392 block_gas_limit,
393 ),
394 ))
395 }
396
397 if let Some(max_tx_gas_limit) = self.max_tx_gas_limit &&
399 transaction_gas_limit > max_tx_gas_limit
400 {
401 return Err(TransactionValidationOutcome::Invalid(
402 transaction,
403 InvalidPoolTransactionError::MaxTxGasLimitExceeded(
404 transaction_gas_limit,
405 max_tx_gas_limit,
406 ),
407 ))
408 }
409
410 if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
412 return Err(TransactionValidationOutcome::Invalid(
413 transaction,
414 InvalidTransactionError::TipAboveFeeCap.into(),
415 ))
416 }
417
418 let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
420
421 if is_local {
424 match self.tx_fee_cap {
425 Some(0) | None => {} Some(tx_fee_cap_wei) => {
427 let max_tx_fee_wei = transaction.cost().saturating_sub(transaction.value());
428 if max_tx_fee_wei > tx_fee_cap_wei {
429 return Err(TransactionValidationOutcome::Invalid(
430 transaction,
431 InvalidPoolTransactionError::ExceedsFeeCap {
432 max_tx_fee_wei: max_tx_fee_wei.saturating_to(),
433 tx_fee_cap_wei,
434 },
435 ))
436 }
437 }
438 }
439 }
440
441 if !is_local &&
444 transaction.is_dynamic_fee() &&
445 transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
446 {
447 return Err(TransactionValidationOutcome::Invalid(
448 transaction,
449 InvalidPoolTransactionError::PriorityFeeBelowMinimum {
450 minimum_priority_fee: self
451 .minimum_priority_fee
452 .expect("minimum priority fee is expected inside if statement"),
453 },
454 ))
455 }
456
457 if let Some(chain_id) = transaction.chain_id() &&
459 chain_id != self.chain_id()
460 {
461 return Err(TransactionValidationOutcome::Invalid(
462 transaction,
463 InvalidTransactionError::ChainIdMismatch.into(),
464 ))
465 }
466
467 if transaction.is_eip7702() {
468 if !self.fork_tracker.is_prague_activated() {
470 return Err(TransactionValidationOutcome::Invalid(
471 transaction,
472 InvalidTransactionError::TxTypeNotSupported.into(),
473 ))
474 }
475
476 if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
477 return Err(TransactionValidationOutcome::Invalid(
478 transaction,
479 Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into(),
480 ))
481 }
482 }
483
484 if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
485 return Err(TransactionValidationOutcome::Invalid(transaction, err))
486 }
487
488 if transaction.is_eip4844() {
490 if !self.fork_tracker.is_cancun_activated() {
492 return Err(TransactionValidationOutcome::Invalid(
493 transaction,
494 InvalidTransactionError::TxTypeNotSupported.into(),
495 ))
496 }
497
498 let blob_count = transaction.blob_count().unwrap_or(0);
499 if blob_count == 0 {
500 return Err(TransactionValidationOutcome::Invalid(
502 transaction,
503 InvalidPoolTransactionError::Eip4844(
504 Eip4844PoolTransactionError::NoEip4844Blobs,
505 ),
506 ))
507 }
508
509 let max_blob_count = self.fork_tracker.max_blob_count();
510 if blob_count > max_blob_count {
511 return Err(TransactionValidationOutcome::Invalid(
512 transaction,
513 InvalidPoolTransactionError::Eip4844(
514 Eip4844PoolTransactionError::TooManyEip4844Blobs {
515 have: blob_count,
516 permitted: max_blob_count,
517 },
518 ),
519 ))
520 }
521 }
522
523 let tx_gas_limit_cap =
525 self.fork_tracker.tx_gas_limit_cap.load(std::sync::atomic::Ordering::Relaxed);
526 if tx_gas_limit_cap > 0 && transaction.gas_limit() > tx_gas_limit_cap {
527 return Err(TransactionValidationOutcome::Invalid(
528 transaction,
529 InvalidTransactionError::GasLimitTooHigh.into(),
530 ))
531 }
532
533 Ok(transaction)
534 }
535
536 pub fn validate_stateful<P>(
541 &self,
542 origin: TransactionOrigin,
543 mut transaction: Tx,
544 state: P,
545 ) -> TransactionValidationOutcome<Tx>
546 where
547 P: AccountInfoReader,
548 {
549 let account = match state.basic_account(transaction.sender_ref()) {
551 Ok(account) => account.unwrap_or_default(),
552 Err(err) => {
553 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
554 }
555 };
556
557 match self.validate_sender_bytecode(&transaction, &account, &state) {
559 Err(outcome) => return outcome,
560 Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err),
561 _ => {}
562 };
563
564 if transaction.requires_nonce_check() &&
566 let Err(err) = self.validate_sender_nonce(&transaction, &account)
567 {
568 return TransactionValidationOutcome::Invalid(transaction, err)
569 }
570
571 if let Err(err) = self.validate_sender_balance(&transaction, &account) {
573 return TransactionValidationOutcome::Invalid(transaction, err)
574 }
575
576 let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) {
578 Err(err) => return TransactionValidationOutcome::Invalid(transaction, err),
579 Ok(sidecar) => sidecar,
580 };
581
582 let authorities = self.recover_authorities(&transaction);
583 TransactionValidationOutcome::Valid {
585 balance: account.balance,
586 state_nonce: account.nonce,
587 bytecode_hash: account.bytecode_hash,
588 transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
589 propagate: match origin {
591 TransactionOrigin::External => true,
592 TransactionOrigin::Local => {
593 self.local_transactions_config.propagate_local_transactions
594 }
595 TransactionOrigin::Private => false,
596 },
597 authorities,
598 }
599 }
600
601 pub fn validate_sender_bytecode(
603 &self,
604 transaction: &Tx,
605 sender: &Account,
606 state: impl BytecodeReader,
607 ) -> Result<Result<(), InvalidPoolTransactionError>, TransactionValidationOutcome<Tx>> {
608 if let Some(code_hash) = &sender.bytecode_hash {
615 let is_eip7702 = if self.fork_tracker.is_prague_activated() {
616 match state.bytecode_by_hash(code_hash) {
617 Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
618 Err(err) => {
619 return Err(TransactionValidationOutcome::Error(
620 *transaction.hash(),
621 Box::new(err),
622 ))
623 }
624 }
625 } else {
626 false
627 };
628
629 if !is_eip7702 {
630 return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into()))
631 }
632 }
633 Ok(Ok(()))
634 }
635
636 pub fn validate_sender_nonce(
638 &self,
639 transaction: &Tx,
640 sender: &Account,
641 ) -> Result<(), InvalidPoolTransactionError> {
642 let tx_nonce = transaction.nonce();
643
644 if tx_nonce < sender.nonce {
645 return Err(InvalidTransactionError::NonceNotConsistent {
646 tx: tx_nonce,
647 state: sender.nonce,
648 }
649 .into())
650 }
651 Ok(())
652 }
653
654 pub fn validate_sender_balance(
656 &self,
657 transaction: &Tx,
658 sender: &Account,
659 ) -> Result<(), InvalidPoolTransactionError> {
660 let cost = transaction.cost();
661
662 if !self.disable_balance_check && cost > &sender.balance {
663 let expected = *cost;
664 return Err(InvalidTransactionError::InsufficientFunds(
665 GotExpected { got: sender.balance, expected }.into(),
666 )
667 .into())
668 }
669 Ok(())
670 }
671
672 pub fn validate_eip4844(
674 &self,
675 transaction: &mut Tx,
676 ) -> Result<Option<BlobTransactionSidecarVariant>, InvalidPoolTransactionError> {
677 let mut maybe_blob_sidecar = None;
678
679 if transaction.is_eip4844() {
681 match transaction.take_blob() {
683 EthBlobTransactionSidecar::None => {
684 return Err(InvalidTransactionError::TxTypeNotSupported.into())
686 }
687 EthBlobTransactionSidecar::Missing => {
688 if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) {
693 } else {
695 return Err(InvalidPoolTransactionError::Eip4844(
696 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
697 ))
698 }
699 }
700 EthBlobTransactionSidecar::Present(sidecar) => {
701 let now = Instant::now();
702
703 if self.eip7594 {
705 if self.fork_tracker.is_osaka_activated() {
707 if sidecar.is_eip4844() {
708 return Err(InvalidPoolTransactionError::Eip4844(
709 Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
710 ))
711 }
712 } else if sidecar.is_eip7594() && !self.allow_7594_sidecars() {
713 return Err(InvalidPoolTransactionError::Eip4844(
714 Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
715 ))
716 }
717 } else {
718 if sidecar.is_eip7594() {
720 return Err(InvalidPoolTransactionError::Eip4844(
721 Eip4844PoolTransactionError::Eip7594SidecarDisallowed,
722 ))
723 }
724 }
725
726 if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
728 return Err(InvalidPoolTransactionError::Eip4844(
729 Eip4844PoolTransactionError::InvalidEip4844Blob(err),
730 ))
731 }
732 self.validation_metrics.blob_validation_duration.record(now.elapsed());
734 maybe_blob_sidecar = Some(sidecar);
736 }
737 }
738 }
739 Ok(maybe_blob_sidecar)
740 }
741
742 fn recover_authorities(&self, transaction: &Tx) -> std::option::Option<Vec<Address>> {
744 transaction
745 .authorization_list()
746 .map(|auths| auths.iter().flat_map(|auth| auth.recover_authority()).collect::<Vec<_>>())
747 }
748
749 fn validate_batch(
751 &self,
752 transactions: impl IntoIterator<Item = (TransactionOrigin, Tx)>,
753 ) -> Vec<TransactionValidationOutcome<Tx>> {
754 let mut provider = None;
755 transactions
756 .into_iter()
757 .map(|(origin, tx)| self.validate_one_with_provider(origin, tx, &mut provider))
758 .collect()
759 }
760
761 fn validate_batch_with_origin(
763 &self,
764 origin: TransactionOrigin,
765 transactions: impl IntoIterator<Item = Tx> + Send,
766 ) -> Vec<TransactionValidationOutcome<Tx>> {
767 let mut provider = None;
768 transactions
769 .into_iter()
770 .map(|tx| self.validate_one_with_provider(origin, tx, &mut provider))
771 .collect()
772 }
773
774 fn on_new_head_block(&self, new_tip_block: &HeaderTy<Evm::Primitives>) {
775 if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
777 self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
778 }
779
780 if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
781 self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
782 }
783
784 if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
785 self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
786 }
787
788 if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
789 self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
790 }
791
792 self.fork_tracker
793 .tip_timestamp
794 .store(new_tip_block.timestamp(), std::sync::atomic::Ordering::Relaxed);
795
796 if let Some(blob_params) =
797 self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
798 {
799 self.fork_tracker
800 .max_blob_count
801 .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed);
802 }
803
804 self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
805
806 let evm_env = self
808 .evm_config
809 .evm_env(new_tip_block)
810 .expect("evm_env should not fail for executed block");
811
812 self.fork_tracker
813 .max_initcode_size
814 .store(evm_env.cfg_env.max_initcode_size(), std::sync::atomic::Ordering::Relaxed);
815 self.fork_tracker
816 .tx_gas_limit_cap
817 .store(evm_env.cfg_env.tx_gas_limit_cap(), std::sync::atomic::Ordering::Relaxed);
818 }
819
820 fn max_gas_limit(&self) -> u64 {
821 self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
822 }
823
824 fn allow_7594_sidecars(&self) -> bool {
826 let tip_timestamp = self.fork_tracker.tip_timestamp();
827
828 if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(12)) {
830 true
831 } else if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(24)) {
832 let current_timestamp =
833 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
834
835 current_timestamp >= tip_timestamp.saturating_add(4)
837 } else {
838 false
839 }
840 }
841}
842
843impl<Client, Tx, Evm> TransactionValidator for EthTransactionValidator<Client, Tx, Evm>
844where
845 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + StateProviderFactory,
846 Tx: EthPoolTransaction,
847 Evm: ConfigureEvm,
848{
849 type Transaction = Tx;
850 type Block = BlockTy<Evm::Primitives>;
851
852 async fn validate_transaction(
853 &self,
854 origin: TransactionOrigin,
855 transaction: Self::Transaction,
856 ) -> TransactionValidationOutcome<Self::Transaction> {
857 self.validate_one(origin, transaction)
858 }
859
860 async fn validate_transactions(
861 &self,
862 transactions: impl IntoIterator<Item = (TransactionOrigin, Self::Transaction), IntoIter: Send>
863 + Send,
864 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
865 self.validate_batch(transactions)
866 }
867
868 async fn validate_transactions_with_origin(
869 &self,
870 origin: TransactionOrigin,
871 transactions: impl IntoIterator<Item = Self::Transaction, IntoIter: Send> + Send,
872 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
873 self.validate_batch_with_origin(origin, transactions)
874 }
875
876 fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
877 Self::on_new_head_block(self, new_tip_block.header())
878 }
879}
880
881#[derive(Debug)]
883pub struct EthTransactionValidatorBuilder<Client, Evm> {
884 client: Client,
885 evm_config: Evm,
887 shanghai: bool,
889 cancun: bool,
891 prague: bool,
893 osaka: bool,
895 tip_timestamp: u64,
897 max_blob_count: u64,
899 eip2718: bool,
901 eip1559: bool,
903 eip4844: bool,
905 eip7702: bool,
907 block_gas_limit: AtomicU64,
909 tx_fee_cap: Option<u128>,
911 minimum_priority_fee: Option<u128>,
913 additional_tasks: usize,
917
918 kzg_settings: EnvKzgSettings,
920 local_transactions_config: LocalTransactionConfig,
922 max_tx_input_bytes: usize,
924 max_tx_gas_limit: Option<u64>,
926 disable_balance_check: bool,
928 other_tx_types: U256,
930 max_initcode_size: usize,
932 tx_gas_limit_cap: u64,
934 eip7594: bool,
938}
939
940impl<Client, Evm> EthTransactionValidatorBuilder<Client, Evm> {
941 pub fn new(client: Client, evm_config: Evm) -> Self
951 where
952 Client: ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks>
953 + BlockReaderIdExt<Header = HeaderTy<Evm::Primitives>>,
954 Evm: ConfigureEvm,
955 {
956 let chain_spec = client.chain_spec();
957 let tip = client
958 .header_by_id(BlockId::latest())
959 .expect("failed to fetch latest header")
960 .expect("latest header is not found");
961 let evm_env =
962 evm_config.evm_env(&tip).expect("evm_env should not fail for existing blocks");
963
964 Self {
965 block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(),
966 client,
967 evm_config,
968 minimum_priority_fee: None,
969 additional_tasks: 1,
970 kzg_settings: EnvKzgSettings::Default,
971 local_transactions_config: Default::default(),
972 max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
973 tx_fee_cap: Some(1e18 as u128),
974 max_tx_gas_limit: None,
975 eip2718: true,
977 eip1559: true,
978 eip4844: true,
979 eip7702: true,
980
981 shanghai: chain_spec.is_shanghai_active_at_timestamp(tip.timestamp()),
982 cancun: chain_spec.is_cancun_active_at_timestamp(tip.timestamp()),
983 prague: chain_spec.is_prague_active_at_timestamp(tip.timestamp()),
984 osaka: chain_spec.is_osaka_active_at_timestamp(tip.timestamp()),
985
986 tip_timestamp: tip.timestamp(),
987
988 max_blob_count: chain_spec
989 .blob_params_at_timestamp(tip.timestamp())
990 .unwrap_or_else(BlobParams::prague)
991 .max_blobs_per_tx,
992
993 disable_balance_check: false,
995
996 other_tx_types: U256::ZERO,
998
999 tx_gas_limit_cap: evm_env.cfg_env.tx_gas_limit_cap(),
1000 max_initcode_size: evm_env.cfg_env.max_initcode_size(),
1001
1002 eip7594: true,
1004 }
1005 }
1006
1007 pub const fn no_cancun(self) -> Self {
1009 self.set_cancun(false)
1010 }
1011
1012 pub fn with_local_transactions_config(
1014 mut self,
1015 local_transactions_config: LocalTransactionConfig,
1016 ) -> Self {
1017 self.local_transactions_config = local_transactions_config;
1018 self
1019 }
1020
1021 pub const fn set_cancun(mut self, cancun: bool) -> Self {
1023 self.cancun = cancun;
1024 self
1025 }
1026
1027 pub const fn no_shanghai(self) -> Self {
1029 self.set_shanghai(false)
1030 }
1031
1032 pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
1034 self.shanghai = shanghai;
1035 self
1036 }
1037
1038 pub const fn no_prague(self) -> Self {
1040 self.set_prague(false)
1041 }
1042
1043 pub const fn set_prague(mut self, prague: bool) -> Self {
1045 self.prague = prague;
1046 self
1047 }
1048
1049 pub const fn no_osaka(self) -> Self {
1051 self.set_osaka(false)
1052 }
1053
1054 pub const fn set_osaka(mut self, osaka: bool) -> Self {
1056 self.osaka = osaka;
1057 self
1058 }
1059
1060 pub const fn no_eip2718(self) -> Self {
1062 self.set_eip2718(false)
1063 }
1064
1065 pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
1067 self.eip2718 = eip2718;
1068 self
1069 }
1070
1071 pub const fn no_eip1559(self) -> Self {
1073 self.set_eip1559(false)
1074 }
1075
1076 pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
1078 self.eip1559 = eip1559;
1079 self
1080 }
1081
1082 pub const fn no_eip4844(self) -> Self {
1084 self.set_eip4844(false)
1085 }
1086
1087 pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
1089 self.eip4844 = eip4844;
1090 self
1091 }
1092
1093 pub const fn no_eip7702(self) -> Self {
1095 self.set_eip7702(false)
1096 }
1097
1098 pub const fn set_eip7702(mut self, eip7702: bool) -> Self {
1100 self.eip7702 = eip7702;
1101 self
1102 }
1103
1104 pub const fn no_eip7594(self) -> Self {
1111 self.set_eip7594(false)
1112 }
1113
1114 pub const fn set_eip7594(mut self, eip7594: bool) -> Self {
1119 self.eip7594 = eip7594;
1120 self
1121 }
1122
1123 pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
1125 self.kzg_settings = kzg_settings;
1126 self
1127 }
1128
1129 pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: Option<u128>) -> Self {
1131 self.minimum_priority_fee = minimum_priority_fee;
1132 self
1133 }
1134
1135 pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
1137 self.additional_tasks = additional_tasks;
1138 self
1139 }
1140
1141 pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
1143 self.max_tx_input_bytes = max_tx_input_bytes;
1144 self
1145 }
1146
1147 pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
1151 self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
1152 self
1153 }
1154
1155 pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
1159 self.tx_fee_cap = Some(tx_fee_cap);
1160 self
1161 }
1162
1163 pub const fn with_max_tx_gas_limit(mut self, max_tx_gas_limit: Option<u64>) -> Self {
1165 self.max_tx_gas_limit = max_tx_gas_limit;
1166 self
1167 }
1168
1169 pub const fn disable_balance_check(mut self) -> Self {
1171 self.disable_balance_check = true;
1172 self
1173 }
1174
1175 pub const fn with_custom_tx_type(mut self, tx_type: u8) -> Self {
1177 self.other_tx_types.set_bit(tx_type as usize, true);
1178 self
1179 }
1180
1181 pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx, Evm>
1183 where
1184 S: BlobStore,
1185 {
1186 let Self {
1187 client,
1188 evm_config,
1189 shanghai,
1190 cancun,
1191 prague,
1192 osaka,
1193 tip_timestamp,
1194 eip2718,
1195 eip1559,
1196 eip4844,
1197 eip7702,
1198 block_gas_limit,
1199 tx_fee_cap,
1200 minimum_priority_fee,
1201 kzg_settings,
1202 local_transactions_config,
1203 max_tx_input_bytes,
1204 max_tx_gas_limit,
1205 disable_balance_check,
1206 max_blob_count,
1207 additional_tasks: _,
1208 other_tx_types,
1209 max_initcode_size,
1210 tx_gas_limit_cap,
1211 eip7594,
1212 } = self;
1213
1214 let fork_tracker = ForkTracker {
1215 shanghai: AtomicBool::new(shanghai),
1216 cancun: AtomicBool::new(cancun),
1217 prague: AtomicBool::new(prague),
1218 osaka: AtomicBool::new(osaka),
1219 tip_timestamp: AtomicU64::new(tip_timestamp),
1220 max_blob_count: AtomicU64::new(max_blob_count),
1221 max_initcode_size: AtomicUsize::new(max_initcode_size),
1222 tx_gas_limit_cap: AtomicU64::new(tx_gas_limit_cap),
1223 };
1224
1225 EthTransactionValidator {
1226 client,
1227 eip2718,
1228 eip1559,
1229 fork_tracker,
1230 eip4844,
1231 eip7702,
1232 block_gas_limit,
1233 tx_fee_cap,
1234 minimum_priority_fee,
1235 blob_store: Box::new(blob_store),
1236 kzg_settings,
1237 local_transactions_config,
1238 max_tx_input_bytes,
1239 max_tx_gas_limit,
1240 disable_balance_check,
1241 evm_config,
1242 _marker: Default::default(),
1243 validation_metrics: TxPoolValidationMetrics::default(),
1244 other_tx_types,
1245 eip7594,
1246 }
1247 }
1248
1249 pub fn build_with_tasks<Tx, T, S>(
1256 self,
1257 tasks: T,
1258 blob_store: S,
1259 ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx, Evm>>
1260 where
1261 T: TaskSpawner,
1262 S: BlobStore,
1263 {
1264 let additional_tasks = self.additional_tasks;
1265 let validator = self.build::<Tx, S>(blob_store);
1266
1267 let (tx, task) = ValidationTask::new();
1268
1269 for _ in 0..additional_tasks {
1271 let task = task.clone();
1272 tasks.spawn_blocking_task(Box::pin(async move {
1273 task.run().await;
1274 }));
1275 }
1276
1277 tasks.spawn_critical_blocking_task(
1280 "transaction-validation-service",
1281 Box::pin(async move {
1282 task.run().await;
1283 }),
1284 );
1285
1286 let to_validation_task = Arc::new(Mutex::new(tx));
1287
1288 TransactionValidationTaskExecutor { validator: Arc::new(validator), to_validation_task }
1289 }
1290}
1291
1292#[derive(Debug)]
1294pub struct ForkTracker {
1295 pub shanghai: AtomicBool,
1297 pub cancun: AtomicBool,
1299 pub prague: AtomicBool,
1301 pub osaka: AtomicBool,
1303 pub max_blob_count: AtomicU64,
1305 pub tip_timestamp: AtomicU64,
1307 pub max_initcode_size: AtomicUsize,
1309 pub tx_gas_limit_cap: AtomicU64,
1311}
1312
1313impl ForkTracker {
1314 pub fn is_shanghai_activated(&self) -> bool {
1316 self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1317 }
1318
1319 pub fn is_cancun_activated(&self) -> bool {
1321 self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1322 }
1323
1324 pub fn is_prague_activated(&self) -> bool {
1326 self.prague.load(std::sync::atomic::Ordering::Relaxed)
1327 }
1328
1329 pub fn is_osaka_activated(&self) -> bool {
1331 self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1332 }
1333
1334 pub fn tip_timestamp(&self) -> u64 {
1336 self.tip_timestamp.load(std::sync::atomic::Ordering::Relaxed)
1337 }
1338
1339 pub fn max_blob_count(&self) -> u64 {
1341 self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1342 }
1343}
1344
1345pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1349 transaction: &T,
1350 fork_tracker: &ForkTracker,
1351) -> Result<(), InvalidPoolTransactionError> {
1352 use revm_primitives::hardfork::SpecId;
1353 let spec_id = if fork_tracker.is_prague_activated() {
1354 SpecId::PRAGUE
1355 } else if fork_tracker.is_shanghai_activated() {
1356 SpecId::SHANGHAI
1357 } else {
1358 SpecId::MERGE
1359 };
1360
1361 let gas = revm_interpreter::gas::calculate_initial_tx_gas(
1362 spec_id,
1363 transaction.input(),
1364 transaction.is_create(),
1365 transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1366 transaction
1367 .access_list()
1368 .map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
1369 .unwrap_or_default() as u64,
1370 transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
1371 );
1372
1373 let gas_limit = transaction.gas_limit();
1374 if gas_limit < gas.initial_gas || gas_limit < gas.floor_gas {
1375 Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1376 } else {
1377 Ok(())
1378 }
1379}
1380
1381#[cfg(test)]
1382mod tests {
1383 use super::*;
1384 use crate::{
1385 blobstore::InMemoryBlobStore, error::PoolErrorKind, traits::PoolTransaction,
1386 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1387 };
1388 use alloy_consensus::Transaction;
1389 use alloy_eips::eip2718::Decodable2718;
1390 use alloy_primitives::{hex, U256};
1391 use reth_ethereum_primitives::PooledTransactionVariant;
1392 use reth_evm_ethereum::EthEvmConfig;
1393 use reth_primitives_traits::SignedTransaction;
1394 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1395 use revm_primitives::eip3860::MAX_INITCODE_SIZE;
1396
1397 fn test_evm_config() -> EthEvmConfig {
1398 EthEvmConfig::mainnet()
1399 }
1400
1401 fn get_transaction() -> EthPooledTransaction {
1402 let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1403
1404 let data = hex::decode(raw).unwrap();
1405 let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1406
1407 EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1408 }
1409
1410 #[tokio::test]
1412 async fn validate_transaction() {
1413 let transaction = get_transaction();
1414 let mut fork_tracker = ForkTracker {
1415 shanghai: false.into(),
1416 cancun: false.into(),
1417 prague: false.into(),
1418 osaka: false.into(),
1419 tip_timestamp: 0.into(),
1420 max_blob_count: 0.into(),
1421 max_initcode_size: AtomicUsize::new(MAX_INITCODE_SIZE),
1422 tx_gas_limit_cap: AtomicU64::new(0),
1423 };
1424
1425 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1426 assert!(res.is_ok());
1427
1428 fork_tracker.shanghai = true.into();
1429 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1430 assert!(res.is_ok());
1431
1432 let provider = MockEthProvider::default().with_genesis_block();
1433 provider.add_account(
1434 transaction.sender(),
1435 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1436 );
1437 let blob_store = InMemoryBlobStore::default();
1438 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1439 .build(blob_store.clone());
1440
1441 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1442
1443 assert!(outcome.is_valid());
1444
1445 let pool =
1446 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1447
1448 let res = pool.add_external_transaction(transaction.clone()).await;
1449 assert!(res.is_ok());
1450 let tx = pool.get(transaction.hash());
1451 assert!(tx.is_some());
1452 }
1453
1454 #[tokio::test]
1456 async fn invalid_on_gas_limit_too_high() {
1457 let transaction = get_transaction();
1458
1459 let provider = MockEthProvider::default().with_genesis_block();
1460 provider.add_account(
1461 transaction.sender(),
1462 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1463 );
1464
1465 let blob_store = InMemoryBlobStore::default();
1466 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1467 .set_block_gas_limit(1_000_000) .build(blob_store.clone());
1469
1470 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1471
1472 assert!(outcome.is_invalid());
1473
1474 let pool =
1475 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1476
1477 let res = pool.add_external_transaction(transaction.clone()).await;
1478 assert!(res.is_err());
1479 assert!(matches!(
1480 res.unwrap_err().kind,
1481 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1482 1_015_288, 1_000_000
1483 ))
1484 ));
1485 let tx = pool.get(transaction.hash());
1486 assert!(tx.is_none());
1487 }
1488
1489 #[tokio::test]
1490 async fn invalid_on_fee_cap_exceeded() {
1491 let transaction = get_transaction();
1492 let provider = MockEthProvider::default().with_genesis_block();
1493 provider.add_account(
1494 transaction.sender(),
1495 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1496 );
1497
1498 let blob_store = InMemoryBlobStore::default();
1499 let validator = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1500 .set_tx_fee_cap(100) .build(blob_store.clone());
1502
1503 let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1504 assert!(outcome.is_invalid());
1505
1506 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1507 assert!(matches!(
1508 err,
1509 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1510 if (max_tx_fee_wei > tx_fee_cap_wei)
1511 ));
1512 }
1513
1514 let pool =
1515 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1516 let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
1517 assert!(res.is_err());
1518 assert!(matches!(
1519 res.unwrap_err().kind,
1520 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
1521 ));
1522 let tx = pool.get(transaction.hash());
1523 assert!(tx.is_none());
1524 }
1525
1526 #[tokio::test]
1527 async fn valid_on_zero_fee_cap() {
1528 let transaction = get_transaction();
1529 let provider = MockEthProvider::default().with_genesis_block();
1530 provider.add_account(
1531 transaction.sender(),
1532 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1533 );
1534
1535 let blob_store = InMemoryBlobStore::default();
1536 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1537 .set_tx_fee_cap(0) .build(blob_store);
1539
1540 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1541 assert!(outcome.is_valid());
1542 }
1543
1544 #[tokio::test]
1545 async fn valid_on_normal_fee_cap() {
1546 let transaction = get_transaction();
1547 let provider = MockEthProvider::default().with_genesis_block();
1548 provider.add_account(
1549 transaction.sender(),
1550 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1551 );
1552
1553 let blob_store = InMemoryBlobStore::default();
1554 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1555 .set_tx_fee_cap(2e18 as u128) .build(blob_store);
1557
1558 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1559 assert!(outcome.is_valid());
1560 }
1561
1562 #[tokio::test]
1563 async fn invalid_on_max_tx_gas_limit_exceeded() {
1564 let transaction = get_transaction();
1565 let provider = MockEthProvider::default().with_genesis_block();
1566 provider.add_account(
1567 transaction.sender(),
1568 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1569 );
1570
1571 let blob_store = InMemoryBlobStore::default();
1572 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1573 .with_max_tx_gas_limit(Some(500_000)) .build(blob_store.clone());
1575
1576 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1577 assert!(outcome.is_invalid());
1578
1579 let pool =
1580 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1581
1582 let res = pool.add_external_transaction(transaction.clone()).await;
1583 assert!(res.is_err());
1584 assert!(matches!(
1585 res.unwrap_err().kind,
1586 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
1587 1_015_288, 500_000
1588 ))
1589 ));
1590 let tx = pool.get(transaction.hash());
1591 assert!(tx.is_none());
1592 }
1593
1594 #[tokio::test]
1595 async fn valid_on_max_tx_gas_limit_disabled() {
1596 let transaction = get_transaction();
1597 let provider = MockEthProvider::default().with_genesis_block();
1598 provider.add_account(
1599 transaction.sender(),
1600 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1601 );
1602
1603 let blob_store = InMemoryBlobStore::default();
1604 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1605 .with_max_tx_gas_limit(None) .build(blob_store);
1607
1608 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1609 assert!(outcome.is_valid());
1610 }
1611
1612 #[tokio::test]
1613 async fn valid_on_max_tx_gas_limit_within_limit() {
1614 let transaction = get_transaction();
1615 let provider = MockEthProvider::default().with_genesis_block();
1616 provider.add_account(
1617 transaction.sender(),
1618 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1619 );
1620
1621 let blob_store = InMemoryBlobStore::default();
1622 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1623 .with_max_tx_gas_limit(Some(2_000_000)) .build(blob_store);
1625
1626 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1627 assert!(outcome.is_valid());
1628 }
1629
1630 fn setup_priority_fee_test() -> (EthPooledTransaction, MockEthProvider) {
1632 let transaction = get_transaction();
1633 let provider = MockEthProvider::default().with_genesis_block();
1634 provider.add_account(
1635 transaction.sender(),
1636 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1637 );
1638 (transaction, provider)
1639 }
1640
1641 fn create_validator_with_minimum_fee(
1643 provider: MockEthProvider,
1644 minimum_priority_fee: Option<u128>,
1645 local_config: Option<LocalTransactionConfig>,
1646 ) -> EthTransactionValidator<MockEthProvider, EthPooledTransaction, EthEvmConfig> {
1647 let blob_store = InMemoryBlobStore::default();
1648 let mut builder = EthTransactionValidatorBuilder::new(provider, test_evm_config())
1649 .with_minimum_priority_fee(minimum_priority_fee);
1650
1651 if let Some(config) = local_config {
1652 builder = builder.with_local_transactions_config(config);
1653 }
1654
1655 builder.build(blob_store)
1656 }
1657
1658 #[tokio::test]
1659 async fn invalid_on_priority_fee_lower_than_configured_minimum() {
1660 let (transaction, provider) = setup_priority_fee_test();
1661
1662 assert!(transaction.is_dynamic_fee());
1664
1665 let minimum_priority_fee =
1667 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1668
1669 let validator =
1670 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1671
1672 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1674 assert!(outcome.is_invalid());
1675
1676 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1677 assert!(matches!(
1678 err,
1679 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1680 if min_fee == minimum_priority_fee
1681 ));
1682 }
1683
1684 let blob_store = InMemoryBlobStore::default();
1686 let pool =
1687 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1688
1689 let res = pool.add_external_transaction(transaction.clone()).await;
1690 assert!(res.is_err());
1691 assert!(matches!(
1692 res.unwrap_err().kind,
1693 PoolErrorKind::InvalidTransaction(
1694 InvalidPoolTransactionError::PriorityFeeBelowMinimum { .. }
1695 )
1696 ));
1697 let tx = pool.get(transaction.hash());
1698 assert!(tx.is_none());
1699
1700 let (_, local_provider) = setup_priority_fee_test();
1702 let validator_local =
1703 create_validator_with_minimum_fee(local_provider, Some(minimum_priority_fee), None);
1704
1705 let local_outcome = validator_local.validate_one(TransactionOrigin::Local, transaction);
1706 assert!(local_outcome.is_valid());
1707 }
1708
1709 #[tokio::test]
1710 async fn valid_on_priority_fee_equal_to_minimum() {
1711 let (transaction, provider) = setup_priority_fee_test();
1712
1713 let tx_priority_fee =
1715 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1716 let validator = create_validator_with_minimum_fee(provider, Some(tx_priority_fee), None);
1717
1718 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1719 assert!(outcome.is_valid());
1720 }
1721
1722 #[tokio::test]
1723 async fn valid_on_priority_fee_above_minimum() {
1724 let (transaction, provider) = setup_priority_fee_test();
1725
1726 let tx_priority_fee =
1728 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1729 let minimum_priority_fee = tx_priority_fee / 2; let validator =
1732 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1733
1734 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1735 assert!(outcome.is_valid());
1736 }
1737
1738 #[tokio::test]
1739 async fn valid_on_minimum_priority_fee_disabled() {
1740 let (transaction, provider) = setup_priority_fee_test();
1741
1742 let validator = create_validator_with_minimum_fee(provider, None, None);
1744
1745 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1746 assert!(outcome.is_valid());
1747 }
1748
1749 #[tokio::test]
1750 async fn priority_fee_validation_applies_to_private_transactions() {
1751 let (transaction, provider) = setup_priority_fee_test();
1752
1753 let minimum_priority_fee =
1755 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1756
1757 let validator =
1758 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1759
1760 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1763 assert!(outcome.is_invalid());
1764
1765 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1766 assert!(matches!(
1767 err,
1768 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1769 if min_fee == minimum_priority_fee
1770 ));
1771 }
1772 }
1773
1774 #[tokio::test]
1775 async fn valid_on_local_config_exempts_private_transactions() {
1776 let (transaction, provider) = setup_priority_fee_test();
1777
1778 let minimum_priority_fee =
1780 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1781
1782 let local_config =
1784 LocalTransactionConfig { propagate_local_transactions: true, ..Default::default() };
1785
1786 let validator = create_validator_with_minimum_fee(
1787 provider,
1788 Some(minimum_priority_fee),
1789 Some(local_config),
1790 );
1791
1792 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1796 assert!(outcome.is_invalid()); }
1798
1799 #[test]
1800 fn reject_oversized_tx() {
1801 let mut transaction = get_transaction();
1802 transaction.encoded_length = DEFAULT_MAX_TX_INPUT_BYTES + 1;
1803 let provider = MockEthProvider::default().with_genesis_block();
1804
1805 let validator = create_validator_with_minimum_fee(provider, None, None);
1807
1808 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1809 let invalid = outcome.as_invalid().unwrap();
1810 assert!(invalid.is_oversized());
1811 }
1812
1813 #[tokio::test]
1814 async fn valid_with_disabled_balance_check() {
1815 let transaction = get_transaction();
1816 let provider = MockEthProvider::default().with_genesis_block();
1817
1818 provider.add_account(
1820 transaction.sender(),
1821 ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
1822 );
1823
1824 let validator =
1826 EthTransactionValidatorBuilder::new(provider.clone(), EthEvmConfig::mainnet())
1827 .build(InMemoryBlobStore::default());
1828
1829 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1830 let expected_cost = *transaction.cost();
1831 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1832 assert!(matches!(
1833 err,
1834 InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err))
1835 if funds_err.got == alloy_primitives::U256::ZERO && funds_err.expected == expected_cost
1836 ));
1837 } else {
1838 panic!("Expected Invalid outcome with InsufficientFunds error");
1839 }
1840
1841 let validator = EthTransactionValidatorBuilder::new(provider, EthEvmConfig::mainnet())
1843 .disable_balance_check()
1844 .build(InMemoryBlobStore::default());
1845
1846 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1847 assert!(outcome.is_valid()); }
1849}