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, MAX_INIT_CODE_BYTE_SIZE},
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,
27};
28use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
29use reth_primitives_traits::{
30 constants::MAX_TX_GAS_LIMIT_OSAKA, transaction::error::InvalidTransactionError, Account, Block,
31 GotExpected, SealedBlock,
32};
33use reth_storage_api::{AccountInfoReader, BytecodeReader, StateProviderFactory};
34use reth_tasks::TaskSpawner;
35use revm_primitives::U256;
36use std::{
37 marker::PhantomData,
38 sync::{
39 atomic::{AtomicBool, AtomicU64},
40 Arc,
41 },
42 time::{Instant, SystemTime},
43};
44use tokio::sync::Mutex;
45
46#[derive(Debug)]
61pub struct EthTransactionValidator<Client, T> {
62 client: Client,
64 blob_store: Box<dyn BlobStore>,
66 fork_tracker: ForkTracker,
68 eip2718: bool,
70 eip1559: bool,
72 eip4844: bool,
74 eip7702: bool,
76 block_gas_limit: AtomicU64,
78 tx_fee_cap: Option<u128>,
80 minimum_priority_fee: Option<u128>,
82 kzg_settings: EnvKzgSettings,
84 local_transactions_config: LocalTransactionConfig,
86 max_tx_input_bytes: usize,
88 max_tx_gas_limit: Option<u64>,
90 disable_balance_check: bool,
92 _marker: PhantomData<T>,
94 validation_metrics: TxPoolValidationMetrics,
96 other_tx_types: U256,
98}
99
100impl<Client, Tx> EthTransactionValidator<Client, Tx> {
101 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
103 where
104 Client: ChainSpecProvider,
105 {
106 self.client().chain_spec()
107 }
108
109 pub fn chain_id(&self) -> u64
111 where
112 Client: ChainSpecProvider,
113 {
114 self.client().chain_spec().chain().id()
115 }
116
117 pub const fn client(&self) -> &Client {
119 &self.client
120 }
121
122 pub const fn fork_tracker(&self) -> &ForkTracker {
124 &self.fork_tracker
125 }
126
127 pub const fn eip2718(&self) -> bool {
129 self.eip2718
130 }
131
132 pub const fn eip1559(&self) -> bool {
134 self.eip1559
135 }
136
137 pub const fn eip4844(&self) -> bool {
139 self.eip4844
140 }
141
142 pub const fn eip7702(&self) -> bool {
144 self.eip7702
145 }
146
147 pub const fn tx_fee_cap(&self) -> &Option<u128> {
149 &self.tx_fee_cap
150 }
151
152 pub const fn minimum_priority_fee(&self) -> &Option<u128> {
154 &self.minimum_priority_fee
155 }
156
157 pub const fn kzg_settings(&self) -> &EnvKzgSettings {
159 &self.kzg_settings
160 }
161
162 pub const fn local_transactions_config(&self) -> &LocalTransactionConfig {
164 &self.local_transactions_config
165 }
166
167 pub const fn max_tx_input_bytes(&self) -> usize {
170 self.max_tx_input_bytes
171 }
172
173 pub const fn disable_balance_check(&self) -> bool {
175 self.disable_balance_check
176 }
177}
178
179impl<Client, Tx> EthTransactionValidator<Client, Tx>
180where
181 Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
182 Tx: EthPoolTransaction,
183{
184 pub fn block_gas_limit(&self) -> u64 {
186 self.max_gas_limit()
187 }
188
189 pub fn validate_one(
193 &self,
194 origin: TransactionOrigin,
195 transaction: Tx,
196 ) -> TransactionValidationOutcome<Tx> {
197 self.validate_one_with_provider(origin, transaction, &mut None)
198 }
199
200 pub fn validate_one_with_state(
207 &self,
208 origin: TransactionOrigin,
209 transaction: Tx,
210 state: &mut Option<Box<dyn AccountInfoReader>>,
211 ) -> TransactionValidationOutcome<Tx> {
212 self.validate_one_with_provider(origin, transaction, state)
213 }
214
215 fn validate_one_with_provider(
219 &self,
220 origin: TransactionOrigin,
221 transaction: Tx,
222 maybe_state: &mut Option<Box<dyn AccountInfoReader>>,
223 ) -> TransactionValidationOutcome<Tx> {
224 match self.validate_one_no_state(origin, transaction) {
225 Ok(transaction) => {
226 if maybe_state.is_none() {
229 match self.client.latest() {
230 Ok(new_state) => {
231 *maybe_state = Some(Box::new(new_state));
232 }
233 Err(err) => {
234 return TransactionValidationOutcome::Error(
235 *transaction.hash(),
236 Box::new(err),
237 )
238 }
239 }
240 }
241
242 let state = maybe_state.as_deref().expect("provider is set");
243
244 self.validate_one_against_state(origin, transaction, state)
245 }
246 Err(invalid_outcome) => invalid_outcome,
247 }
248 }
249
250 pub fn validate_one_with_state_provider(
252 &self,
253 origin: TransactionOrigin,
254 transaction: Tx,
255 state: impl AccountInfoReader,
256 ) -> TransactionValidationOutcome<Tx> {
257 let tx = match self.validate_one_no_state(origin, transaction) {
258 Ok(tx) => tx,
259 Err(invalid_outcome) => return invalid_outcome,
260 };
261 self.validate_one_against_state(origin, tx, state)
262 }
263
264 fn validate_one_no_state(
268 &self,
269 origin: TransactionOrigin,
270 transaction: Tx,
271 ) -> Result<Tx, TransactionValidationOutcome<Tx>> {
272 match transaction.ty() {
274 LEGACY_TX_TYPE_ID => {
275 }
277 EIP2930_TX_TYPE_ID => {
278 if !self.eip2718 {
280 return Err(TransactionValidationOutcome::Invalid(
281 transaction,
282 InvalidTransactionError::Eip2930Disabled.into(),
283 ))
284 }
285 }
286 EIP1559_TX_TYPE_ID => {
287 if !self.eip1559 {
289 return Err(TransactionValidationOutcome::Invalid(
290 transaction,
291 InvalidTransactionError::Eip1559Disabled.into(),
292 ))
293 }
294 }
295 EIP4844_TX_TYPE_ID => {
296 if !self.eip4844 {
298 return Err(TransactionValidationOutcome::Invalid(
299 transaction,
300 InvalidTransactionError::Eip4844Disabled.into(),
301 ))
302 }
303 }
304 EIP7702_TX_TYPE_ID => {
305 if !self.eip7702 {
307 return Err(TransactionValidationOutcome::Invalid(
308 transaction,
309 InvalidTransactionError::Eip7702Disabled.into(),
310 ))
311 }
312 }
313
314 ty if !self.other_tx_types.bit(ty as usize) => {
315 return Err(TransactionValidationOutcome::Invalid(
316 transaction,
317 InvalidTransactionError::TxTypeNotSupported.into(),
318 ))
319 }
320
321 _ => {}
322 };
323
324 let tx_nonce = transaction.nonce();
326 if tx_nonce == u64::MAX {
327 return Err(TransactionValidationOutcome::Invalid(
328 transaction,
329 InvalidPoolTransactionError::Eip2681,
330 ))
331 }
332
333 if transaction.is_eip4844() {
335 let tx_input_len = transaction.input().len();
340 if tx_input_len > self.max_tx_input_bytes {
341 return Err(TransactionValidationOutcome::Invalid(
342 transaction,
343 InvalidPoolTransactionError::OversizedData {
344 size: tx_input_len,
345 limit: self.max_tx_input_bytes,
346 },
347 ))
348 }
349 } else {
350 let tx_size = transaction.encoded_length();
352 if tx_size > self.max_tx_input_bytes {
353 return Err(TransactionValidationOutcome::Invalid(
354 transaction,
355 InvalidPoolTransactionError::OversizedData {
356 size: tx_size,
357 limit: self.max_tx_input_bytes,
358 },
359 ))
360 }
361 }
362
363 if self.fork_tracker.is_shanghai_activated() &&
365 let Err(err) = transaction.ensure_max_init_code_size(MAX_INIT_CODE_BYTE_SIZE)
366 {
367 return Err(TransactionValidationOutcome::Invalid(transaction, err))
368 }
369
370 let transaction_gas_limit = transaction.gas_limit();
372 let block_gas_limit = self.max_gas_limit();
373 if transaction_gas_limit > block_gas_limit {
374 return Err(TransactionValidationOutcome::Invalid(
375 transaction,
376 InvalidPoolTransactionError::ExceedsGasLimit(
377 transaction_gas_limit,
378 block_gas_limit,
379 ),
380 ))
381 }
382
383 if let Some(max_tx_gas_limit) = self.max_tx_gas_limit &&
385 transaction_gas_limit > max_tx_gas_limit
386 {
387 return Err(TransactionValidationOutcome::Invalid(
388 transaction,
389 InvalidPoolTransactionError::MaxTxGasLimitExceeded(
390 transaction_gas_limit,
391 max_tx_gas_limit,
392 ),
393 ))
394 }
395
396 if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
398 return Err(TransactionValidationOutcome::Invalid(
399 transaction,
400 InvalidTransactionError::TipAboveFeeCap.into(),
401 ))
402 }
403
404 let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
406
407 if is_local {
410 match self.tx_fee_cap {
411 Some(0) | None => {} Some(tx_fee_cap_wei) => {
413 let max_tx_fee_wei = transaction.cost().saturating_sub(transaction.value());
414 if max_tx_fee_wei > tx_fee_cap_wei {
415 return Err(TransactionValidationOutcome::Invalid(
416 transaction,
417 InvalidPoolTransactionError::ExceedsFeeCap {
418 max_tx_fee_wei: max_tx_fee_wei.saturating_to(),
419 tx_fee_cap_wei,
420 },
421 ))
422 }
423 }
424 }
425 }
426
427 if !is_local &&
430 transaction.is_dynamic_fee() &&
431 transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
432 {
433 return Err(TransactionValidationOutcome::Invalid(
434 transaction,
435 InvalidPoolTransactionError::PriorityFeeBelowMinimum {
436 minimum_priority_fee: self
437 .minimum_priority_fee
438 .expect("minimum priority fee is expected inside if statement"),
439 },
440 ))
441 }
442
443 if let Some(chain_id) = transaction.chain_id() &&
445 chain_id != self.chain_id()
446 {
447 return Err(TransactionValidationOutcome::Invalid(
448 transaction,
449 InvalidTransactionError::ChainIdMismatch.into(),
450 ))
451 }
452
453 if transaction.is_eip7702() {
454 if !self.fork_tracker.is_prague_activated() {
456 return Err(TransactionValidationOutcome::Invalid(
457 transaction,
458 InvalidTransactionError::TxTypeNotSupported.into(),
459 ))
460 }
461
462 if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
463 return Err(TransactionValidationOutcome::Invalid(
464 transaction,
465 Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into(),
466 ))
467 }
468 }
469
470 if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
471 return Err(TransactionValidationOutcome::Invalid(transaction, err))
472 }
473
474 if transaction.is_eip4844() {
476 if !self.fork_tracker.is_cancun_activated() {
478 return Err(TransactionValidationOutcome::Invalid(
479 transaction,
480 InvalidTransactionError::TxTypeNotSupported.into(),
481 ))
482 }
483
484 let blob_count = transaction.blob_count().unwrap_or(0);
485 if blob_count == 0 {
486 return Err(TransactionValidationOutcome::Invalid(
488 transaction,
489 InvalidPoolTransactionError::Eip4844(
490 Eip4844PoolTransactionError::NoEip4844Blobs,
491 ),
492 ))
493 }
494
495 let max_blob_count = self.fork_tracker.max_blob_count();
496 if blob_count > max_blob_count {
497 return Err(TransactionValidationOutcome::Invalid(
498 transaction,
499 InvalidPoolTransactionError::Eip4844(
500 Eip4844PoolTransactionError::TooManyEip4844Blobs {
501 have: blob_count,
502 permitted: max_blob_count,
503 },
504 ),
505 ))
506 }
507 }
508
509 if self.fork_tracker.is_osaka_activated() &&
511 transaction.gas_limit() > MAX_TX_GAS_LIMIT_OSAKA
512 {
513 return Err(TransactionValidationOutcome::Invalid(
514 transaction,
515 InvalidTransactionError::GasLimitTooHigh.into(),
516 ))
517 }
518
519 Ok(transaction)
520 }
521
522 fn validate_one_against_state<P>(
524 &self,
525 origin: TransactionOrigin,
526 mut transaction: Tx,
527 state: P,
528 ) -> TransactionValidationOutcome<Tx>
529 where
530 P: AccountInfoReader,
531 {
532 let account = match state.basic_account(transaction.sender_ref()) {
534 Ok(account) => account.unwrap_or_default(),
535 Err(err) => {
536 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
537 }
538 };
539
540 match self.validate_sender_bytecode(&transaction, &account, &state) {
542 Err(outcome) => return outcome,
543 Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err),
544 _ => {}
545 };
546
547 if transaction.requires_nonce_check() &&
549 let Err(err) = self.validate_sender_nonce(&transaction, &account)
550 {
551 return TransactionValidationOutcome::Invalid(transaction, err)
552 }
553
554 if let Err(err) = self.validate_sender_balance(&transaction, &account) {
556 return TransactionValidationOutcome::Invalid(transaction, err)
557 }
558
559 let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) {
561 Err(err) => return TransactionValidationOutcome::Invalid(transaction, err),
562 Ok(sidecar) => sidecar,
563 };
564
565 let authorities = self.recover_authorities(&transaction);
566 TransactionValidationOutcome::Valid {
568 balance: account.balance,
569 state_nonce: account.nonce,
570 bytecode_hash: account.bytecode_hash,
571 transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
572 propagate: match origin {
574 TransactionOrigin::External => true,
575 TransactionOrigin::Local => {
576 self.local_transactions_config.propagate_local_transactions
577 }
578 TransactionOrigin::Private => false,
579 },
580 authorities,
581 }
582 }
583
584 pub fn validate_sender_bytecode(
586 &self,
587 transaction: &Tx,
588 sender: &Account,
589 state: impl BytecodeReader,
590 ) -> Result<Result<(), InvalidPoolTransactionError>, TransactionValidationOutcome<Tx>> {
591 if let Some(code_hash) = &sender.bytecode_hash {
598 let is_eip7702 = if self.fork_tracker.is_prague_activated() {
599 match state.bytecode_by_hash(code_hash) {
600 Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
601 Err(err) => {
602 return Err(TransactionValidationOutcome::Error(
603 *transaction.hash(),
604 Box::new(err),
605 ))
606 }
607 }
608 } else {
609 false
610 };
611
612 if !is_eip7702 {
613 return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into()))
614 }
615 }
616 Ok(Ok(()))
617 }
618
619 pub fn validate_sender_nonce(
621 &self,
622 transaction: &Tx,
623 sender: &Account,
624 ) -> Result<(), InvalidPoolTransactionError> {
625 let tx_nonce = transaction.nonce();
626
627 if tx_nonce < sender.nonce {
628 return Err(InvalidTransactionError::NonceNotConsistent {
629 tx: tx_nonce,
630 state: sender.nonce,
631 }
632 .into())
633 }
634 Ok(())
635 }
636
637 pub fn validate_sender_balance(
639 &self,
640 transaction: &Tx,
641 sender: &Account,
642 ) -> Result<(), InvalidPoolTransactionError> {
643 let cost = transaction.cost();
644
645 if !self.disable_balance_check && cost > &sender.balance {
646 let expected = *cost;
647 return Err(InvalidTransactionError::InsufficientFunds(
648 GotExpected { got: sender.balance, expected }.into(),
649 )
650 .into())
651 }
652 Ok(())
653 }
654
655 pub fn validate_eip4844(
657 &self,
658 transaction: &mut Tx,
659 ) -> Result<Option<BlobTransactionSidecarVariant>, InvalidPoolTransactionError> {
660 let mut maybe_blob_sidecar = None;
661
662 if transaction.is_eip4844() {
664 match transaction.take_blob() {
666 EthBlobTransactionSidecar::None => {
667 return Err(InvalidTransactionError::TxTypeNotSupported.into())
669 }
670 EthBlobTransactionSidecar::Missing => {
671 if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) {
676 } else {
678 return Err(InvalidPoolTransactionError::Eip4844(
679 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
680 ))
681 }
682 }
683 EthBlobTransactionSidecar::Present(sidecar) => {
684 let now = Instant::now();
685
686 if self.fork_tracker.is_osaka_activated() {
687 if sidecar.is_eip4844() {
688 return Err(InvalidPoolTransactionError::Eip4844(
689 Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
690 ))
691 }
692 } else if sidecar.is_eip7594() && !self.allow_7594_sidecars() {
693 return Err(InvalidPoolTransactionError::Eip4844(
694 Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
695 ))
696 }
697
698 if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
700 return Err(InvalidPoolTransactionError::Eip4844(
701 Eip4844PoolTransactionError::InvalidEip4844Blob(err),
702 ))
703 }
704 self.validation_metrics.blob_validation_duration.record(now.elapsed());
706 maybe_blob_sidecar = Some(sidecar);
708 }
709 }
710 }
711 Ok(maybe_blob_sidecar)
712 }
713
714 fn recover_authorities(&self, transaction: &Tx) -> std::option::Option<Vec<Address>> {
716 transaction
717 .authorization_list()
718 .map(|auths| auths.iter().flat_map(|auth| auth.recover_authority()).collect::<Vec<_>>())
719 }
720
721 fn validate_batch(
723 &self,
724 transactions: Vec<(TransactionOrigin, Tx)>,
725 ) -> Vec<TransactionValidationOutcome<Tx>> {
726 let mut provider = None;
727 transactions
728 .into_iter()
729 .map(|(origin, tx)| self.validate_one_with_provider(origin, tx, &mut provider))
730 .collect()
731 }
732
733 fn validate_batch_with_origin(
735 &self,
736 origin: TransactionOrigin,
737 transactions: impl IntoIterator<Item = Tx> + Send,
738 ) -> Vec<TransactionValidationOutcome<Tx>> {
739 let mut provider = None;
740 transactions
741 .into_iter()
742 .map(|tx| self.validate_one_with_provider(origin, tx, &mut provider))
743 .collect()
744 }
745
746 fn on_new_head_block<T: BlockHeader>(&self, new_tip_block: &T) {
747 if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
749 self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
750 }
751
752 if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
753 self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
754 }
755
756 if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
757 self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
758 }
759
760 if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
761 self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
762 }
763
764 self.fork_tracker
765 .tip_timestamp
766 .store(new_tip_block.timestamp(), std::sync::atomic::Ordering::Relaxed);
767
768 if let Some(blob_params) =
769 self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
770 {
771 self.fork_tracker
772 .max_blob_count
773 .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed);
774 }
775
776 self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
777 }
778
779 fn max_gas_limit(&self) -> u64 {
780 self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
781 }
782
783 fn allow_7594_sidecars(&self) -> bool {
785 let tip_timestamp = self.fork_tracker.tip_timestamp();
786
787 if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(12)) {
789 true
790 } else if self.chain_spec().is_osaka_active_at_timestamp(tip_timestamp.saturating_add(24)) {
791 let current_timestamp =
792 SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
793
794 current_timestamp >= tip_timestamp.saturating_add(4)
796 } else {
797 false
798 }
799 }
800}
801
802impl<Client, Tx> TransactionValidator for EthTransactionValidator<Client, Tx>
803where
804 Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
805 Tx: EthPoolTransaction,
806{
807 type Transaction = Tx;
808
809 async fn validate_transaction(
810 &self,
811 origin: TransactionOrigin,
812 transaction: Self::Transaction,
813 ) -> TransactionValidationOutcome<Self::Transaction> {
814 self.validate_one(origin, transaction)
815 }
816
817 async fn validate_transactions(
818 &self,
819 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
820 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
821 self.validate_batch(transactions)
822 }
823
824 async fn validate_transactions_with_origin(
825 &self,
826 origin: TransactionOrigin,
827 transactions: impl IntoIterator<Item = Self::Transaction> + Send,
828 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
829 self.validate_batch_with_origin(origin, transactions)
830 }
831
832 fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>)
833 where
834 B: Block,
835 {
836 self.on_new_head_block(new_tip_block.header())
837 }
838}
839
840#[derive(Debug)]
842pub struct EthTransactionValidatorBuilder<Client> {
843 client: Client,
844 shanghai: bool,
846 cancun: bool,
848 prague: bool,
850 osaka: bool,
852 tip_timestamp: u64,
854 max_blob_count: u64,
856 eip2718: bool,
858 eip1559: bool,
860 eip4844: bool,
862 eip7702: bool,
864 block_gas_limit: AtomicU64,
866 tx_fee_cap: Option<u128>,
868 minimum_priority_fee: Option<u128>,
870 additional_tasks: usize,
874
875 kzg_settings: EnvKzgSettings,
877 local_transactions_config: LocalTransactionConfig,
879 max_tx_input_bytes: usize,
881 max_tx_gas_limit: Option<u64>,
883 disable_balance_check: bool,
885 other_tx_types: U256,
887}
888
889impl<Client> EthTransactionValidatorBuilder<Client> {
890 pub fn new(client: Client) -> Self {
900 Self {
901 block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(),
902 client,
903 minimum_priority_fee: None,
904 additional_tasks: 1,
905 kzg_settings: EnvKzgSettings::Default,
906 local_transactions_config: Default::default(),
907 max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
908 tx_fee_cap: Some(1e18 as u128),
909 max_tx_gas_limit: None,
910 eip2718: true,
912 eip1559: true,
913 eip4844: true,
914 eip7702: true,
915
916 shanghai: true,
918
919 cancun: true,
921
922 prague: true,
924
925 osaka: false,
927
928 tip_timestamp: 0,
929
930 max_blob_count: BlobParams::prague().max_blobs_per_tx,
932
933 disable_balance_check: false,
935
936 other_tx_types: U256::ZERO,
938 }
939 }
940
941 pub const fn no_cancun(self) -> Self {
943 self.set_cancun(false)
944 }
945
946 pub fn with_local_transactions_config(
948 mut self,
949 local_transactions_config: LocalTransactionConfig,
950 ) -> Self {
951 self.local_transactions_config = local_transactions_config;
952 self
953 }
954
955 pub const fn set_cancun(mut self, cancun: bool) -> Self {
957 self.cancun = cancun;
958 self
959 }
960
961 pub const fn no_shanghai(self) -> Self {
963 self.set_shanghai(false)
964 }
965
966 pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
968 self.shanghai = shanghai;
969 self
970 }
971
972 pub const fn no_prague(self) -> Self {
974 self.set_prague(false)
975 }
976
977 pub const fn set_prague(mut self, prague: bool) -> Self {
979 self.prague = prague;
980 self
981 }
982
983 pub const fn no_osaka(self) -> Self {
985 self.set_osaka(false)
986 }
987
988 pub const fn set_osaka(mut self, osaka: bool) -> Self {
990 self.osaka = osaka;
991 self
992 }
993
994 pub const fn no_eip2718(self) -> Self {
996 self.set_eip2718(false)
997 }
998
999 pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
1001 self.eip2718 = eip2718;
1002 self
1003 }
1004
1005 pub const fn no_eip1559(self) -> Self {
1007 self.set_eip1559(false)
1008 }
1009
1010 pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
1012 self.eip1559 = eip1559;
1013 self
1014 }
1015
1016 pub const fn no_eip4844(self) -> Self {
1018 self.set_eip4844(false)
1019 }
1020
1021 pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
1023 self.eip4844 = eip4844;
1024 self
1025 }
1026
1027 pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
1029 self.kzg_settings = kzg_settings;
1030 self
1031 }
1032
1033 pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: Option<u128>) -> Self {
1035 self.minimum_priority_fee = minimum_priority_fee;
1036 self
1037 }
1038
1039 pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
1041 self.additional_tasks = additional_tasks;
1042 self
1043 }
1044
1045 pub fn with_head_timestamp(mut self, timestamp: u64) -> Self
1050 where
1051 Client: ChainSpecProvider<ChainSpec: EthereumHardforks>,
1052 {
1053 self.shanghai = self.client.chain_spec().is_shanghai_active_at_timestamp(timestamp);
1054 self.cancun = self.client.chain_spec().is_cancun_active_at_timestamp(timestamp);
1055 self.prague = self.client.chain_spec().is_prague_active_at_timestamp(timestamp);
1056 self.osaka = self.client.chain_spec().is_osaka_active_at_timestamp(timestamp);
1057 self.tip_timestamp = timestamp;
1058 self.max_blob_count = self
1059 .client
1060 .chain_spec()
1061 .blob_params_at_timestamp(timestamp)
1062 .unwrap_or_else(BlobParams::cancun)
1063 .max_blobs_per_tx;
1064 self
1065 }
1066
1067 pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
1069 self.max_tx_input_bytes = max_tx_input_bytes;
1070 self
1071 }
1072
1073 pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
1077 self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
1078 self
1079 }
1080
1081 pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
1085 self.tx_fee_cap = Some(tx_fee_cap);
1086 self
1087 }
1088
1089 pub const fn with_max_tx_gas_limit(mut self, max_tx_gas_limit: Option<u64>) -> Self {
1091 self.max_tx_gas_limit = max_tx_gas_limit;
1092 self
1093 }
1094
1095 pub const fn disable_balance_check(mut self) -> Self {
1097 self.disable_balance_check = true;
1098 self
1099 }
1100
1101 pub const fn with_custom_tx_type(mut self, tx_type: u8) -> Self {
1103 self.other_tx_types.set_bit(tx_type as usize, true);
1104 self
1105 }
1106
1107 pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx>
1109 where
1110 S: BlobStore,
1111 {
1112 let Self {
1113 client,
1114 shanghai,
1115 cancun,
1116 prague,
1117 osaka,
1118 tip_timestamp,
1119 eip2718,
1120 eip1559,
1121 eip4844,
1122 eip7702,
1123 block_gas_limit,
1124 tx_fee_cap,
1125 minimum_priority_fee,
1126 kzg_settings,
1127 local_transactions_config,
1128 max_tx_input_bytes,
1129 max_tx_gas_limit,
1130 disable_balance_check,
1131 max_blob_count,
1132 additional_tasks: _,
1133 other_tx_types,
1134 } = self;
1135
1136 let fork_tracker = ForkTracker {
1137 shanghai: AtomicBool::new(shanghai),
1138 cancun: AtomicBool::new(cancun),
1139 prague: AtomicBool::new(prague),
1140 osaka: AtomicBool::new(osaka),
1141 tip_timestamp: AtomicU64::new(tip_timestamp),
1142 max_blob_count: AtomicU64::new(max_blob_count),
1143 };
1144
1145 EthTransactionValidator {
1146 client,
1147 eip2718,
1148 eip1559,
1149 fork_tracker,
1150 eip4844,
1151 eip7702,
1152 block_gas_limit,
1153 tx_fee_cap,
1154 minimum_priority_fee,
1155 blob_store: Box::new(blob_store),
1156 kzg_settings,
1157 local_transactions_config,
1158 max_tx_input_bytes,
1159 max_tx_gas_limit,
1160 disable_balance_check,
1161 _marker: Default::default(),
1162 validation_metrics: TxPoolValidationMetrics::default(),
1163 other_tx_types,
1164 }
1165 }
1166
1167 pub fn build_with_tasks<Tx, T, S>(
1174 self,
1175 tasks: T,
1176 blob_store: S,
1177 ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx>>
1178 where
1179 T: TaskSpawner,
1180 S: BlobStore,
1181 {
1182 let additional_tasks = self.additional_tasks;
1183 let validator = self.build(blob_store);
1184
1185 let (tx, task) = ValidationTask::new();
1186
1187 for _ in 0..additional_tasks {
1189 let task = task.clone();
1190 tasks.spawn_blocking(Box::pin(async move {
1191 task.run().await;
1192 }));
1193 }
1194
1195 tasks.spawn_critical_blocking(
1198 "transaction-validation-service",
1199 Box::pin(async move {
1200 task.run().await;
1201 }),
1202 );
1203
1204 let to_validation_task = Arc::new(Mutex::new(tx));
1205
1206 TransactionValidationTaskExecutor { validator: Arc::new(validator), to_validation_task }
1207 }
1208}
1209
1210#[derive(Debug)]
1212pub struct ForkTracker {
1213 pub shanghai: AtomicBool,
1215 pub cancun: AtomicBool,
1217 pub prague: AtomicBool,
1219 pub osaka: AtomicBool,
1221 pub max_blob_count: AtomicU64,
1223 pub tip_timestamp: AtomicU64,
1225}
1226
1227impl ForkTracker {
1228 pub fn is_shanghai_activated(&self) -> bool {
1230 self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1231 }
1232
1233 pub fn is_cancun_activated(&self) -> bool {
1235 self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1236 }
1237
1238 pub fn is_prague_activated(&self) -> bool {
1240 self.prague.load(std::sync::atomic::Ordering::Relaxed)
1241 }
1242
1243 pub fn is_osaka_activated(&self) -> bool {
1245 self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1246 }
1247
1248 pub fn tip_timestamp(&self) -> u64 {
1250 self.tip_timestamp.load(std::sync::atomic::Ordering::Relaxed)
1251 }
1252
1253 pub fn max_blob_count(&self) -> u64 {
1255 self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1256 }
1257}
1258
1259pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1263 transaction: &T,
1264 fork_tracker: &ForkTracker,
1265) -> Result<(), InvalidPoolTransactionError> {
1266 use revm_primitives::hardfork::SpecId;
1267 let spec_id = if fork_tracker.is_prague_activated() {
1268 SpecId::PRAGUE
1269 } else if fork_tracker.is_shanghai_activated() {
1270 SpecId::SHANGHAI
1271 } else {
1272 SpecId::MERGE
1273 };
1274
1275 let gas = revm_interpreter::gas::calculate_initial_tx_gas(
1276 spec_id,
1277 transaction.input(),
1278 transaction.is_create(),
1279 transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1280 transaction
1281 .access_list()
1282 .map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
1283 .unwrap_or_default() as u64,
1284 transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
1285 );
1286
1287 let gas_limit = transaction.gas_limit();
1288 if gas_limit < gas.initial_gas || gas_limit < gas.floor_gas {
1289 Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1290 } else {
1291 Ok(())
1292 }
1293}
1294
1295#[cfg(test)]
1296mod tests {
1297 use super::*;
1298 use crate::{
1299 blobstore::InMemoryBlobStore, error::PoolErrorKind, traits::PoolTransaction,
1300 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1301 };
1302 use alloy_consensus::Transaction;
1303 use alloy_eips::eip2718::Decodable2718;
1304 use alloy_primitives::{hex, U256};
1305 use reth_ethereum_primitives::PooledTransactionVariant;
1306 use reth_primitives_traits::SignedTransaction;
1307 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1308
1309 fn get_transaction() -> EthPooledTransaction {
1310 let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1311
1312 let data = hex::decode(raw).unwrap();
1313 let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1314
1315 EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1316 }
1317
1318 #[tokio::test]
1320 async fn validate_transaction() {
1321 let transaction = get_transaction();
1322 let mut fork_tracker = ForkTracker {
1323 shanghai: false.into(),
1324 cancun: false.into(),
1325 prague: false.into(),
1326 osaka: false.into(),
1327 tip_timestamp: 0.into(),
1328 max_blob_count: 0.into(),
1329 };
1330
1331 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1332 assert!(res.is_ok());
1333
1334 fork_tracker.shanghai = true.into();
1335 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1336 assert!(res.is_ok());
1337
1338 let provider = MockEthProvider::default();
1339 provider.add_account(
1340 transaction.sender(),
1341 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1342 );
1343 let blob_store = InMemoryBlobStore::default();
1344 let validator = EthTransactionValidatorBuilder::new(provider).build(blob_store.clone());
1345
1346 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1347
1348 assert!(outcome.is_valid());
1349
1350 let pool =
1351 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1352
1353 let res = pool.add_external_transaction(transaction.clone()).await;
1354 assert!(res.is_ok());
1355 let tx = pool.get(transaction.hash());
1356 assert!(tx.is_some());
1357 }
1358
1359 #[tokio::test]
1361 async fn invalid_on_gas_limit_too_high() {
1362 let transaction = get_transaction();
1363
1364 let provider = MockEthProvider::default();
1365 provider.add_account(
1366 transaction.sender(),
1367 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1368 );
1369
1370 let blob_store = InMemoryBlobStore::default();
1371 let validator = EthTransactionValidatorBuilder::new(provider)
1372 .set_block_gas_limit(1_000_000) .build(blob_store.clone());
1374
1375 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1376
1377 assert!(outcome.is_invalid());
1378
1379 let pool =
1380 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1381
1382 let res = pool.add_external_transaction(transaction.clone()).await;
1383 assert!(res.is_err());
1384 assert!(matches!(
1385 res.unwrap_err().kind,
1386 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1387 1_015_288, 1_000_000
1388 ))
1389 ));
1390 let tx = pool.get(transaction.hash());
1391 assert!(tx.is_none());
1392 }
1393
1394 #[tokio::test]
1395 async fn invalid_on_fee_cap_exceeded() {
1396 let transaction = get_transaction();
1397 let provider = MockEthProvider::default();
1398 provider.add_account(
1399 transaction.sender(),
1400 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1401 );
1402
1403 let blob_store = InMemoryBlobStore::default();
1404 let validator = EthTransactionValidatorBuilder::new(provider)
1405 .set_tx_fee_cap(100) .build(blob_store.clone());
1407
1408 let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1409 assert!(outcome.is_invalid());
1410
1411 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1412 assert!(matches!(
1413 err,
1414 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1415 if (max_tx_fee_wei > tx_fee_cap_wei)
1416 ));
1417 }
1418
1419 let pool =
1420 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1421 let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
1422 assert!(res.is_err());
1423 assert!(matches!(
1424 res.unwrap_err().kind,
1425 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
1426 ));
1427 let tx = pool.get(transaction.hash());
1428 assert!(tx.is_none());
1429 }
1430
1431 #[tokio::test]
1432 async fn valid_on_zero_fee_cap() {
1433 let transaction = get_transaction();
1434 let provider = MockEthProvider::default();
1435 provider.add_account(
1436 transaction.sender(),
1437 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1438 );
1439
1440 let blob_store = InMemoryBlobStore::default();
1441 let validator = EthTransactionValidatorBuilder::new(provider)
1442 .set_tx_fee_cap(0) .build(blob_store);
1444
1445 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1446 assert!(outcome.is_valid());
1447 }
1448
1449 #[tokio::test]
1450 async fn valid_on_normal_fee_cap() {
1451 let transaction = get_transaction();
1452 let provider = MockEthProvider::default();
1453 provider.add_account(
1454 transaction.sender(),
1455 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1456 );
1457
1458 let blob_store = InMemoryBlobStore::default();
1459 let validator = EthTransactionValidatorBuilder::new(provider)
1460 .set_tx_fee_cap(2e18 as u128) .build(blob_store);
1462
1463 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1464 assert!(outcome.is_valid());
1465 }
1466
1467 #[tokio::test]
1468 async fn invalid_on_max_tx_gas_limit_exceeded() {
1469 let transaction = get_transaction();
1470 let provider = MockEthProvider::default();
1471 provider.add_account(
1472 transaction.sender(),
1473 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1474 );
1475
1476 let blob_store = InMemoryBlobStore::default();
1477 let validator = EthTransactionValidatorBuilder::new(provider)
1478 .with_max_tx_gas_limit(Some(500_000)) .build(blob_store.clone());
1480
1481 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1482 assert!(outcome.is_invalid());
1483
1484 let pool =
1485 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1486
1487 let res = pool.add_external_transaction(transaction.clone()).await;
1488 assert!(res.is_err());
1489 assert!(matches!(
1490 res.unwrap_err().kind,
1491 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
1492 1_015_288, 500_000
1493 ))
1494 ));
1495 let tx = pool.get(transaction.hash());
1496 assert!(tx.is_none());
1497 }
1498
1499 #[tokio::test]
1500 async fn valid_on_max_tx_gas_limit_disabled() {
1501 let transaction = get_transaction();
1502 let provider = MockEthProvider::default();
1503 provider.add_account(
1504 transaction.sender(),
1505 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1506 );
1507
1508 let blob_store = InMemoryBlobStore::default();
1509 let validator = EthTransactionValidatorBuilder::new(provider)
1510 .with_max_tx_gas_limit(None) .build(blob_store);
1512
1513 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1514 assert!(outcome.is_valid());
1515 }
1516
1517 #[tokio::test]
1518 async fn valid_on_max_tx_gas_limit_within_limit() {
1519 let transaction = get_transaction();
1520 let provider = MockEthProvider::default();
1521 provider.add_account(
1522 transaction.sender(),
1523 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1524 );
1525
1526 let blob_store = InMemoryBlobStore::default();
1527 let validator = EthTransactionValidatorBuilder::new(provider)
1528 .with_max_tx_gas_limit(Some(2_000_000)) .build(blob_store);
1530
1531 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1532 assert!(outcome.is_valid());
1533 }
1534
1535 fn setup_priority_fee_test() -> (EthPooledTransaction, MockEthProvider) {
1537 let transaction = get_transaction();
1538 let provider = MockEthProvider::default();
1539 provider.add_account(
1540 transaction.sender(),
1541 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1542 );
1543 (transaction, provider)
1544 }
1545
1546 fn create_validator_with_minimum_fee(
1548 provider: MockEthProvider,
1549 minimum_priority_fee: Option<u128>,
1550 local_config: Option<LocalTransactionConfig>,
1551 ) -> EthTransactionValidator<MockEthProvider, EthPooledTransaction> {
1552 let blob_store = InMemoryBlobStore::default();
1553 let mut builder = EthTransactionValidatorBuilder::new(provider)
1554 .with_minimum_priority_fee(minimum_priority_fee);
1555
1556 if let Some(config) = local_config {
1557 builder = builder.with_local_transactions_config(config);
1558 }
1559
1560 builder.build(blob_store)
1561 }
1562
1563 #[tokio::test]
1564 async fn invalid_on_priority_fee_lower_than_configured_minimum() {
1565 let (transaction, provider) = setup_priority_fee_test();
1566
1567 assert!(transaction.is_dynamic_fee());
1569
1570 let minimum_priority_fee =
1572 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1573
1574 let validator =
1575 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1576
1577 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1579 assert!(outcome.is_invalid());
1580
1581 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1582 assert!(matches!(
1583 err,
1584 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1585 if min_fee == minimum_priority_fee
1586 ));
1587 }
1588
1589 let blob_store = InMemoryBlobStore::default();
1591 let pool =
1592 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1593
1594 let res = pool.add_external_transaction(transaction.clone()).await;
1595 assert!(res.is_err());
1596 assert!(matches!(
1597 res.unwrap_err().kind,
1598 PoolErrorKind::InvalidTransaction(
1599 InvalidPoolTransactionError::PriorityFeeBelowMinimum { .. }
1600 )
1601 ));
1602 let tx = pool.get(transaction.hash());
1603 assert!(tx.is_none());
1604
1605 let (_, local_provider) = setup_priority_fee_test();
1607 let validator_local =
1608 create_validator_with_minimum_fee(local_provider, Some(minimum_priority_fee), None);
1609
1610 let local_outcome = validator_local.validate_one(TransactionOrigin::Local, transaction);
1611 assert!(local_outcome.is_valid());
1612 }
1613
1614 #[tokio::test]
1615 async fn valid_on_priority_fee_equal_to_minimum() {
1616 let (transaction, provider) = setup_priority_fee_test();
1617
1618 let tx_priority_fee =
1620 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1621 let validator = create_validator_with_minimum_fee(provider, Some(tx_priority_fee), None);
1622
1623 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1624 assert!(outcome.is_valid());
1625 }
1626
1627 #[tokio::test]
1628 async fn valid_on_priority_fee_above_minimum() {
1629 let (transaction, provider) = setup_priority_fee_test();
1630
1631 let tx_priority_fee =
1633 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1634 let minimum_priority_fee = tx_priority_fee / 2; let validator =
1637 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1638
1639 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1640 assert!(outcome.is_valid());
1641 }
1642
1643 #[tokio::test]
1644 async fn valid_on_minimum_priority_fee_disabled() {
1645 let (transaction, provider) = setup_priority_fee_test();
1646
1647 let validator = create_validator_with_minimum_fee(provider, None, None);
1649
1650 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1651 assert!(outcome.is_valid());
1652 }
1653
1654 #[tokio::test]
1655 async fn priority_fee_validation_applies_to_private_transactions() {
1656 let (transaction, provider) = setup_priority_fee_test();
1657
1658 let minimum_priority_fee =
1660 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1661
1662 let validator =
1663 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1664
1665 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1668 assert!(outcome.is_invalid());
1669
1670 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1671 assert!(matches!(
1672 err,
1673 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1674 if min_fee == minimum_priority_fee
1675 ));
1676 }
1677 }
1678
1679 #[tokio::test]
1680 async fn valid_on_local_config_exempts_private_transactions() {
1681 let (transaction, provider) = setup_priority_fee_test();
1682
1683 let minimum_priority_fee =
1685 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1686
1687 let local_config =
1689 LocalTransactionConfig { propagate_local_transactions: true, ..Default::default() };
1690
1691 let validator = create_validator_with_minimum_fee(
1692 provider,
1693 Some(minimum_priority_fee),
1694 Some(local_config),
1695 );
1696
1697 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1701 assert!(outcome.is_invalid()); }
1703
1704 #[test]
1705 fn reject_oversized_tx() {
1706 let mut transaction = get_transaction();
1707 transaction.encoded_length = DEFAULT_MAX_TX_INPUT_BYTES + 1;
1708 let provider = MockEthProvider::default();
1709
1710 let validator = create_validator_with_minimum_fee(provider, None, None);
1712
1713 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1714 let invalid = outcome.as_invalid().unwrap();
1715 assert!(invalid.is_oversized());
1716 }
1717
1718 #[tokio::test]
1719 async fn valid_with_disabled_balance_check() {
1720 let transaction = get_transaction();
1721 let provider = MockEthProvider::default();
1722
1723 provider.add_account(
1725 transaction.sender(),
1726 ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
1727 );
1728
1729 let validator = EthTransactionValidatorBuilder::new(provider.clone())
1731 .build(InMemoryBlobStore::default());
1732
1733 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1734 let expected_cost = *transaction.cost();
1735 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1736 assert!(matches!(
1737 err,
1738 InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err))
1739 if funds_err.got == alloy_primitives::U256::ZERO && funds_err.expected == expected_cost
1740 ));
1741 } else {
1742 panic!("Expected Invalid outcome with InsufficientFunds error");
1743 }
1744
1745 let validator = EthTransactionValidatorBuilder::new(provider)
1747 .disable_balance_check() .build(InMemoryBlobStore::default());
1749
1750 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1751 assert!(outcome.is_valid()); }
1753}