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 std::{
36 marker::PhantomData,
37 sync::{
38 atomic::{AtomicBool, AtomicU64},
39 Arc,
40 },
41 time::Instant,
42};
43use tokio::sync::Mutex;
44
45#[derive(Debug)]
60pub struct EthTransactionValidator<Client, T> {
61 client: Client,
63 blob_store: Box<dyn BlobStore>,
65 fork_tracker: ForkTracker,
67 eip2718: bool,
69 eip1559: bool,
71 eip4844: bool,
73 eip7702: bool,
75 block_gas_limit: AtomicU64,
77 tx_fee_cap: Option<u128>,
79 minimum_priority_fee: Option<u128>,
81 kzg_settings: EnvKzgSettings,
83 local_transactions_config: LocalTransactionConfig,
85 max_tx_input_bytes: usize,
87 max_tx_gas_limit: Option<u64>,
89 disable_balance_check: bool,
91 _marker: PhantomData<T>,
93 validation_metrics: TxPoolValidationMetrics,
95}
96
97impl<Client, Tx> EthTransactionValidator<Client, Tx> {
98 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
100 where
101 Client: ChainSpecProvider,
102 {
103 self.client().chain_spec()
104 }
105
106 pub fn chain_id(&self) -> u64
108 where
109 Client: ChainSpecProvider,
110 {
111 self.client().chain_spec().chain().id()
112 }
113
114 pub const fn client(&self) -> &Client {
116 &self.client
117 }
118
119 pub const fn fork_tracker(&self) -> &ForkTracker {
121 &self.fork_tracker
122 }
123
124 pub const fn eip2718(&self) -> bool {
126 self.eip2718
127 }
128
129 pub const fn eip1559(&self) -> bool {
131 self.eip1559
132 }
133
134 pub const fn eip4844(&self) -> bool {
136 self.eip4844
137 }
138
139 pub const fn eip7702(&self) -> bool {
141 self.eip7702
142 }
143
144 pub const fn tx_fee_cap(&self) -> &Option<u128> {
146 &self.tx_fee_cap
147 }
148
149 pub const fn minimum_priority_fee(&self) -> &Option<u128> {
151 &self.minimum_priority_fee
152 }
153
154 pub const fn kzg_settings(&self) -> &EnvKzgSettings {
156 &self.kzg_settings
157 }
158
159 pub const fn local_transactions_config(&self) -> &LocalTransactionConfig {
161 &self.local_transactions_config
162 }
163
164 pub const fn max_tx_input_bytes(&self) -> usize {
167 self.max_tx_input_bytes
168 }
169
170 pub const fn disable_balance_check(&self) -> bool {
172 self.disable_balance_check
173 }
174}
175
176impl<Client, Tx> EthTransactionValidator<Client, Tx>
177where
178 Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
179 Tx: EthPoolTransaction,
180{
181 pub fn block_gas_limit(&self) -> u64 {
183 self.max_gas_limit()
184 }
185
186 pub fn validate_one(
190 &self,
191 origin: TransactionOrigin,
192 transaction: Tx,
193 ) -> TransactionValidationOutcome<Tx> {
194 self.validate_one_with_provider(origin, transaction, &mut None)
195 }
196
197 pub fn validate_one_with_state(
204 &self,
205 origin: TransactionOrigin,
206 transaction: Tx,
207 state: &mut Option<Box<dyn AccountInfoReader>>,
208 ) -> TransactionValidationOutcome<Tx> {
209 self.validate_one_with_provider(origin, transaction, state)
210 }
211
212 fn validate_one_with_provider(
216 &self,
217 origin: TransactionOrigin,
218 transaction: Tx,
219 maybe_state: &mut Option<Box<dyn AccountInfoReader>>,
220 ) -> TransactionValidationOutcome<Tx> {
221 match self.validate_one_no_state(origin, transaction) {
222 Ok(transaction) => {
223 if maybe_state.is_none() {
226 match self.client.latest() {
227 Ok(new_state) => {
228 *maybe_state = Some(Box::new(new_state));
229 }
230 Err(err) => {
231 return TransactionValidationOutcome::Error(
232 *transaction.hash(),
233 Box::new(err),
234 )
235 }
236 }
237 }
238
239 let state = maybe_state.as_deref().expect("provider is set");
240
241 self.validate_one_against_state(origin, transaction, state)
242 }
243 Err(invalid_outcome) => invalid_outcome,
244 }
245 }
246
247 fn validate_one_no_state(
251 &self,
252 origin: TransactionOrigin,
253 transaction: Tx,
254 ) -> Result<Tx, TransactionValidationOutcome<Tx>> {
255 match transaction.ty() {
257 LEGACY_TX_TYPE_ID => {
258 }
260 EIP2930_TX_TYPE_ID => {
261 if !self.eip2718 {
263 return Err(TransactionValidationOutcome::Invalid(
264 transaction,
265 InvalidTransactionError::Eip2930Disabled.into(),
266 ))
267 }
268 }
269 EIP1559_TX_TYPE_ID => {
270 if !self.eip1559 {
272 return Err(TransactionValidationOutcome::Invalid(
273 transaction,
274 InvalidTransactionError::Eip1559Disabled.into(),
275 ))
276 }
277 }
278 EIP4844_TX_TYPE_ID => {
279 if !self.eip4844 {
281 return Err(TransactionValidationOutcome::Invalid(
282 transaction,
283 InvalidTransactionError::Eip4844Disabled.into(),
284 ))
285 }
286 }
287 EIP7702_TX_TYPE_ID => {
288 if !self.eip7702 {
290 return Err(TransactionValidationOutcome::Invalid(
291 transaction,
292 InvalidTransactionError::Eip7702Disabled.into(),
293 ))
294 }
295 }
296
297 _ => {
298 return Err(TransactionValidationOutcome::Invalid(
299 transaction,
300 InvalidTransactionError::TxTypeNotSupported.into(),
301 ))
302 }
303 };
304
305 let tx_nonce = transaction.nonce();
307 if tx_nonce == u64::MAX {
308 return Err(TransactionValidationOutcome::Invalid(
309 transaction,
310 InvalidPoolTransactionError::Eip2681,
311 ))
312 }
313
314 if transaction.is_eip4844() {
316 let tx_input_len = transaction.input().len();
321 if tx_input_len > self.max_tx_input_bytes {
322 return Err(TransactionValidationOutcome::Invalid(
323 transaction,
324 InvalidPoolTransactionError::OversizedData(
325 tx_input_len,
326 self.max_tx_input_bytes,
327 ),
328 ))
329 }
330 } else {
331 let tx_size = transaction.encoded_length();
333 if tx_size > self.max_tx_input_bytes {
334 return Err(TransactionValidationOutcome::Invalid(
335 transaction,
336 InvalidPoolTransactionError::OversizedData(tx_size, self.max_tx_input_bytes),
337 ))
338 }
339 }
340
341 if self.fork_tracker.is_shanghai_activated() {
343 if let Err(err) = transaction.ensure_max_init_code_size(MAX_INIT_CODE_BYTE_SIZE) {
344 return Err(TransactionValidationOutcome::Invalid(transaction, err))
345 }
346 }
347
348 let transaction_gas_limit = transaction.gas_limit();
350 let block_gas_limit = self.max_gas_limit();
351 if transaction_gas_limit > block_gas_limit {
352 return Err(TransactionValidationOutcome::Invalid(
353 transaction,
354 InvalidPoolTransactionError::ExceedsGasLimit(
355 transaction_gas_limit,
356 block_gas_limit,
357 ),
358 ))
359 }
360
361 if let Some(max_tx_gas_limit) = self.max_tx_gas_limit {
363 if transaction_gas_limit > max_tx_gas_limit {
364 return Err(TransactionValidationOutcome::Invalid(
365 transaction,
366 InvalidPoolTransactionError::MaxTxGasLimitExceeded(
367 transaction_gas_limit,
368 max_tx_gas_limit,
369 ),
370 ))
371 }
372 }
373
374 if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
376 return Err(TransactionValidationOutcome::Invalid(
377 transaction,
378 InvalidTransactionError::TipAboveFeeCap.into(),
379 ))
380 }
381
382 let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
384
385 if is_local {
388 match self.tx_fee_cap {
389 Some(0) | None => {} Some(tx_fee_cap_wei) => {
391 let gas_price = transaction.max_fee_per_gas();
394 let max_tx_fee_wei = gas_price.saturating_mul(transaction.gas_limit() as u128);
395 if max_tx_fee_wei > tx_fee_cap_wei {
396 return Err(TransactionValidationOutcome::Invalid(
397 transaction,
398 InvalidPoolTransactionError::ExceedsFeeCap {
399 max_tx_fee_wei,
400 tx_fee_cap_wei,
401 },
402 ))
403 }
404 }
405 }
406 }
407
408 if !is_local &&
411 transaction.is_dynamic_fee() &&
412 transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
413 {
414 return Err(TransactionValidationOutcome::Invalid(
415 transaction,
416 InvalidPoolTransactionError::PriorityFeeBelowMinimum {
417 minimum_priority_fee: self
418 .minimum_priority_fee
419 .expect("minimum priority fee is expected inside if statement"),
420 },
421 ))
422 }
423
424 if let Some(chain_id) = transaction.chain_id() {
426 if chain_id != self.chain_id() {
427 return Err(TransactionValidationOutcome::Invalid(
428 transaction,
429 InvalidTransactionError::ChainIdMismatch.into(),
430 ))
431 }
432 }
433
434 if transaction.is_eip7702() {
435 if !self.fork_tracker.is_prague_activated() {
437 return Err(TransactionValidationOutcome::Invalid(
438 transaction,
439 InvalidTransactionError::TxTypeNotSupported.into(),
440 ))
441 }
442
443 if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
444 return Err(TransactionValidationOutcome::Invalid(
445 transaction,
446 Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into(),
447 ))
448 }
449 }
450
451 if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
452 return Err(TransactionValidationOutcome::Invalid(transaction, err))
453 }
454
455 if transaction.is_eip4844() {
457 if !self.fork_tracker.is_cancun_activated() {
459 return Err(TransactionValidationOutcome::Invalid(
460 transaction,
461 InvalidTransactionError::TxTypeNotSupported.into(),
462 ))
463 }
464
465 let blob_count = transaction.blob_count().unwrap_or(0);
466 if blob_count == 0 {
467 return Err(TransactionValidationOutcome::Invalid(
469 transaction,
470 InvalidPoolTransactionError::Eip4844(
471 Eip4844PoolTransactionError::NoEip4844Blobs,
472 ),
473 ))
474 }
475
476 let max_blob_count = self.fork_tracker.max_blob_count();
477 if blob_count > max_blob_count {
478 return Err(TransactionValidationOutcome::Invalid(
479 transaction,
480 InvalidPoolTransactionError::Eip4844(
481 Eip4844PoolTransactionError::TooManyEip4844Blobs {
482 have: blob_count,
483 permitted: max_blob_count,
484 },
485 ),
486 ))
487 }
488 }
489
490 if self.fork_tracker.is_osaka_activated() &&
492 transaction.gas_limit() > MAX_TX_GAS_LIMIT_OSAKA
493 {
494 return Err(TransactionValidationOutcome::Invalid(
495 transaction,
496 InvalidTransactionError::GasLimitTooHigh.into(),
497 ))
498 }
499
500 Ok(transaction)
501 }
502
503 fn validate_one_against_state<P>(
505 &self,
506 origin: TransactionOrigin,
507 mut transaction: Tx,
508 state: P,
509 ) -> TransactionValidationOutcome<Tx>
510 where
511 P: AccountInfoReader,
512 {
513 let account = match state.basic_account(transaction.sender_ref()) {
515 Ok(account) => account.unwrap_or_default(),
516 Err(err) => {
517 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
518 }
519 };
520
521 match self.validate_sender_bytecode(&transaction, &account, &state) {
523 Err(outcome) => return outcome,
524 Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err),
525 _ => {}
526 };
527
528 if let Err(err) = self.validate_sender_nonce(&transaction, &account) {
530 return TransactionValidationOutcome::Invalid(transaction, err)
531 }
532
533 if let Err(err) = self.validate_sender_balance(&transaction, &account) {
535 return TransactionValidationOutcome::Invalid(transaction, err)
536 }
537
538 let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) {
540 Err(err) => return TransactionValidationOutcome::Invalid(transaction, err),
541 Ok(sidecar) => sidecar,
542 };
543
544 let authorities = self.recover_authorities(&transaction);
545 TransactionValidationOutcome::Valid {
547 balance: account.balance,
548 state_nonce: account.nonce,
549 bytecode_hash: account.bytecode_hash,
550 transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
551 propagate: match origin {
553 TransactionOrigin::External => true,
554 TransactionOrigin::Local => {
555 self.local_transactions_config.propagate_local_transactions
556 }
557 TransactionOrigin::Private => false,
558 },
559 authorities,
560 }
561 }
562
563 pub fn validate_sender_bytecode(
565 &self,
566 transaction: &Tx,
567 sender: &Account,
568 state: impl BytecodeReader,
569 ) -> Result<Result<(), InvalidPoolTransactionError>, TransactionValidationOutcome<Tx>> {
570 if let Some(code_hash) = &sender.bytecode_hash {
577 let is_eip7702 = if self.fork_tracker.is_prague_activated() {
578 match state.bytecode_by_hash(code_hash) {
579 Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
580 Err(err) => {
581 return Err(TransactionValidationOutcome::Error(
582 *transaction.hash(),
583 Box::new(err),
584 ))
585 }
586 }
587 } else {
588 false
589 };
590
591 if !is_eip7702 {
592 return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into()))
593 }
594 }
595 Ok(Ok(()))
596 }
597
598 pub fn validate_sender_nonce(
600 &self,
601 transaction: &Tx,
602 sender: &Account,
603 ) -> Result<(), InvalidPoolTransactionError> {
604 let tx_nonce = transaction.nonce();
605
606 if tx_nonce < sender.nonce {
607 return Err(InvalidTransactionError::NonceNotConsistent {
608 tx: tx_nonce,
609 state: sender.nonce,
610 }
611 .into())
612 }
613 Ok(())
614 }
615
616 pub fn validate_sender_balance(
618 &self,
619 transaction: &Tx,
620 sender: &Account,
621 ) -> Result<(), InvalidPoolTransactionError> {
622 let cost = transaction.cost();
623
624 if !self.disable_balance_check && cost > &sender.balance {
625 let expected = *cost;
626 return Err(InvalidTransactionError::InsufficientFunds(
627 GotExpected { got: sender.balance, expected }.into(),
628 )
629 .into())
630 }
631 Ok(())
632 }
633
634 pub fn validate_eip4844(
636 &self,
637 transaction: &mut Tx,
638 ) -> Result<Option<BlobTransactionSidecarVariant>, InvalidPoolTransactionError> {
639 let mut maybe_blob_sidecar = None;
640
641 if transaction.is_eip4844() {
643 match transaction.take_blob() {
645 EthBlobTransactionSidecar::None => {
646 return Err(InvalidTransactionError::TxTypeNotSupported.into())
648 }
649 EthBlobTransactionSidecar::Missing => {
650 if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) {
655 } else {
657 return Err(InvalidPoolTransactionError::Eip4844(
658 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
659 ))
660 }
661 }
662 EthBlobTransactionSidecar::Present(sidecar) => {
663 let now = Instant::now();
664
665 if self.fork_tracker.is_osaka_activated() {
666 if sidecar.is_eip4844() {
667 return Err(InvalidPoolTransactionError::Eip4844(
668 Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka,
669 ))
670 }
671 } else if sidecar.is_eip7594() {
672 return Err(InvalidPoolTransactionError::Eip4844(
673 Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka,
674 ))
675 }
676
677 if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) {
679 return Err(InvalidPoolTransactionError::Eip4844(
680 Eip4844PoolTransactionError::InvalidEip4844Blob(err),
681 ))
682 }
683 self.validation_metrics.blob_validation_duration.record(now.elapsed());
685 maybe_blob_sidecar = Some(sidecar);
687 }
688 }
689 }
690 Ok(maybe_blob_sidecar)
691 }
692
693 fn recover_authorities(&self, transaction: &Tx) -> std::option::Option<Vec<Address>> {
695 transaction
696 .authorization_list()
697 .map(|auths| auths.iter().flat_map(|auth| auth.recover_authority()).collect::<Vec<_>>())
698 }
699
700 fn validate_batch(
702 &self,
703 transactions: Vec<(TransactionOrigin, Tx)>,
704 ) -> Vec<TransactionValidationOutcome<Tx>> {
705 let mut provider = None;
706 transactions
707 .into_iter()
708 .map(|(origin, tx)| self.validate_one_with_provider(origin, tx, &mut provider))
709 .collect()
710 }
711
712 fn validate_batch_with_origin(
714 &self,
715 origin: TransactionOrigin,
716 transactions: impl IntoIterator<Item = Tx> + Send,
717 ) -> Vec<TransactionValidationOutcome<Tx>> {
718 let mut provider = None;
719 transactions
720 .into_iter()
721 .map(|tx| self.validate_one_with_provider(origin, tx, &mut provider))
722 .collect()
723 }
724
725 fn on_new_head_block<T: BlockHeader>(&self, new_tip_block: &T) {
726 if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
728 self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
729 }
730
731 if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
732 self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
733 }
734
735 if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
736 self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
737 }
738
739 if self.chain_spec().is_osaka_active_at_timestamp(new_tip_block.timestamp()) {
740 self.fork_tracker.osaka.store(true, std::sync::atomic::Ordering::Relaxed);
741 }
742
743 if let Some(blob_params) =
744 self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
745 {
746 self.fork_tracker
747 .max_blob_count
748 .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed);
749 }
750
751 self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
752 }
753
754 fn max_gas_limit(&self) -> u64 {
755 self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
756 }
757}
758
759impl<Client, Tx> TransactionValidator for EthTransactionValidator<Client, Tx>
760where
761 Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
762 Tx: EthPoolTransaction,
763{
764 type Transaction = Tx;
765
766 async fn validate_transaction(
767 &self,
768 origin: TransactionOrigin,
769 transaction: Self::Transaction,
770 ) -> TransactionValidationOutcome<Self::Transaction> {
771 self.validate_one(origin, transaction)
772 }
773
774 async fn validate_transactions(
775 &self,
776 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
777 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
778 self.validate_batch(transactions)
779 }
780
781 async fn validate_transactions_with_origin(
782 &self,
783 origin: TransactionOrigin,
784 transactions: impl IntoIterator<Item = Self::Transaction> + Send,
785 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
786 self.validate_batch_with_origin(origin, transactions)
787 }
788
789 fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>)
790 where
791 B: Block,
792 {
793 self.on_new_head_block(new_tip_block.header())
794 }
795}
796
797#[derive(Debug)]
799pub struct EthTransactionValidatorBuilder<Client> {
800 client: Client,
801 shanghai: bool,
803 cancun: bool,
805 prague: bool,
807 osaka: bool,
809 max_blob_count: u64,
811 eip2718: bool,
813 eip1559: bool,
815 eip4844: bool,
817 eip7702: bool,
819 block_gas_limit: AtomicU64,
821 tx_fee_cap: Option<u128>,
823 minimum_priority_fee: Option<u128>,
825 additional_tasks: usize,
829
830 kzg_settings: EnvKzgSettings,
832 local_transactions_config: LocalTransactionConfig,
834 max_tx_input_bytes: usize,
836 max_tx_gas_limit: Option<u64>,
838 disable_balance_check: bool,
840}
841
842impl<Client> EthTransactionValidatorBuilder<Client> {
843 pub fn new(client: Client) -> Self {
853 Self {
854 block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(),
855 client,
856 minimum_priority_fee: None,
857 additional_tasks: 1,
858 kzg_settings: EnvKzgSettings::Default,
859 local_transactions_config: Default::default(),
860 max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
861 tx_fee_cap: Some(1e18 as u128),
862 max_tx_gas_limit: None,
863 eip2718: true,
865 eip1559: true,
866 eip4844: true,
867 eip7702: true,
868
869 shanghai: true,
871
872 cancun: true,
874
875 prague: true,
877
878 osaka: false,
880
881 max_blob_count: BlobParams::prague().max_blobs_per_tx,
883
884 disable_balance_check: false,
886 }
887 }
888
889 pub const fn no_cancun(self) -> Self {
891 self.set_cancun(false)
892 }
893
894 pub fn with_local_transactions_config(
896 mut self,
897 local_transactions_config: LocalTransactionConfig,
898 ) -> Self {
899 self.local_transactions_config = local_transactions_config;
900 self
901 }
902
903 pub const fn set_cancun(mut self, cancun: bool) -> Self {
905 self.cancun = cancun;
906 self
907 }
908
909 pub const fn no_shanghai(self) -> Self {
911 self.set_shanghai(false)
912 }
913
914 pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
916 self.shanghai = shanghai;
917 self
918 }
919
920 pub const fn no_prague(self) -> Self {
922 self.set_prague(false)
923 }
924
925 pub const fn set_prague(mut self, prague: bool) -> Self {
927 self.prague = prague;
928 self
929 }
930
931 pub const fn no_osaka(self) -> Self {
933 self.set_osaka(false)
934 }
935
936 pub const fn set_osaka(mut self, osaka: bool) -> Self {
938 self.osaka = osaka;
939 self
940 }
941
942 pub const fn no_eip2718(self) -> Self {
944 self.set_eip2718(false)
945 }
946
947 pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
949 self.eip2718 = eip2718;
950 self
951 }
952
953 pub const fn no_eip1559(self) -> Self {
955 self.set_eip1559(false)
956 }
957
958 pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
960 self.eip1559 = eip1559;
961 self
962 }
963
964 pub const fn no_eip4844(self) -> Self {
966 self.set_eip4844(false)
967 }
968
969 pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
971 self.eip4844 = eip4844;
972 self
973 }
974
975 pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
977 self.kzg_settings = kzg_settings;
978 self
979 }
980
981 pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: Option<u128>) -> Self {
983 self.minimum_priority_fee = minimum_priority_fee;
984 self
985 }
986
987 pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
989 self.additional_tasks = additional_tasks;
990 self
991 }
992
993 pub fn with_head_timestamp(mut self, timestamp: u64) -> Self
998 where
999 Client: ChainSpecProvider<ChainSpec: EthereumHardforks>,
1000 {
1001 self.shanghai = self.client.chain_spec().is_shanghai_active_at_timestamp(timestamp);
1002 self.cancun = self.client.chain_spec().is_cancun_active_at_timestamp(timestamp);
1003 self.prague = self.client.chain_spec().is_prague_active_at_timestamp(timestamp);
1004 self.osaka = self.client.chain_spec().is_osaka_active_at_timestamp(timestamp);
1005 self.max_blob_count = self
1006 .client
1007 .chain_spec()
1008 .blob_params_at_timestamp(timestamp)
1009 .unwrap_or_else(BlobParams::cancun)
1010 .max_blobs_per_tx;
1011 self
1012 }
1013
1014 pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
1016 self.max_tx_input_bytes = max_tx_input_bytes;
1017 self
1018 }
1019
1020 pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
1024 self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
1025 self
1026 }
1027
1028 pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
1032 self.tx_fee_cap = Some(tx_fee_cap);
1033 self
1034 }
1035
1036 pub const fn with_max_tx_gas_limit(mut self, max_tx_gas_limit: Option<u64>) -> Self {
1038 self.max_tx_gas_limit = max_tx_gas_limit;
1039 self
1040 }
1041
1042 pub const fn disable_balance_check(mut self) -> Self {
1044 self.disable_balance_check = true;
1045 self
1046 }
1047
1048 pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx>
1050 where
1051 S: BlobStore,
1052 {
1053 let Self {
1054 client,
1055 shanghai,
1056 cancun,
1057 prague,
1058 osaka,
1059 eip2718,
1060 eip1559,
1061 eip4844,
1062 eip7702,
1063 block_gas_limit,
1064 tx_fee_cap,
1065 minimum_priority_fee,
1066 kzg_settings,
1067 local_transactions_config,
1068 max_tx_input_bytes,
1069 max_tx_gas_limit,
1070 disable_balance_check,
1071 max_blob_count,
1072 additional_tasks: _,
1073 } = self;
1074
1075 let fork_tracker = ForkTracker {
1076 shanghai: AtomicBool::new(shanghai),
1077 cancun: AtomicBool::new(cancun),
1078 prague: AtomicBool::new(prague),
1079 osaka: AtomicBool::new(osaka),
1080 max_blob_count: AtomicU64::new(max_blob_count),
1081 };
1082
1083 EthTransactionValidator {
1084 client,
1085 eip2718,
1086 eip1559,
1087 fork_tracker,
1088 eip4844,
1089 eip7702,
1090 block_gas_limit,
1091 tx_fee_cap,
1092 minimum_priority_fee,
1093 blob_store: Box::new(blob_store),
1094 kzg_settings,
1095 local_transactions_config,
1096 max_tx_input_bytes,
1097 max_tx_gas_limit,
1098 disable_balance_check,
1099 _marker: Default::default(),
1100 validation_metrics: TxPoolValidationMetrics::default(),
1101 }
1102 }
1103
1104 pub fn build_with_tasks<Tx, T, S>(
1111 self,
1112 tasks: T,
1113 blob_store: S,
1114 ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx>>
1115 where
1116 T: TaskSpawner,
1117 S: BlobStore,
1118 {
1119 let additional_tasks = self.additional_tasks;
1120 let validator = self.build(blob_store);
1121
1122 let (tx, task) = ValidationTask::new();
1123
1124 for _ in 0..additional_tasks {
1126 let task = task.clone();
1127 tasks.spawn_blocking(Box::pin(async move {
1128 task.run().await;
1129 }));
1130 }
1131
1132 tasks.spawn_critical_blocking(
1135 "transaction-validation-service",
1136 Box::pin(async move {
1137 task.run().await;
1138 }),
1139 );
1140
1141 let to_validation_task = Arc::new(Mutex::new(tx));
1142
1143 TransactionValidationTaskExecutor { validator: Arc::new(validator), to_validation_task }
1144 }
1145}
1146
1147#[derive(Debug)]
1149pub struct ForkTracker {
1150 pub shanghai: AtomicBool,
1152 pub cancun: AtomicBool,
1154 pub prague: AtomicBool,
1156 pub osaka: AtomicBool,
1158 pub max_blob_count: AtomicU64,
1160}
1161
1162impl ForkTracker {
1163 pub fn is_shanghai_activated(&self) -> bool {
1165 self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
1166 }
1167
1168 pub fn is_cancun_activated(&self) -> bool {
1170 self.cancun.load(std::sync::atomic::Ordering::Relaxed)
1171 }
1172
1173 pub fn is_prague_activated(&self) -> bool {
1175 self.prague.load(std::sync::atomic::Ordering::Relaxed)
1176 }
1177
1178 pub fn is_osaka_activated(&self) -> bool {
1180 self.osaka.load(std::sync::atomic::Ordering::Relaxed)
1181 }
1182
1183 pub fn max_blob_count(&self) -> u64 {
1185 self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
1186 }
1187}
1188
1189pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
1193 transaction: &T,
1194 fork_tracker: &ForkTracker,
1195) -> Result<(), InvalidPoolTransactionError> {
1196 use revm_primitives::hardfork::SpecId;
1197 let spec_id = if fork_tracker.is_prague_activated() {
1198 SpecId::PRAGUE
1199 } else if fork_tracker.is_shanghai_activated() {
1200 SpecId::SHANGHAI
1201 } else {
1202 SpecId::MERGE
1203 };
1204
1205 let gas = revm_interpreter::gas::calculate_initial_tx_gas(
1206 spec_id,
1207 transaction.input(),
1208 transaction.is_create(),
1209 transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
1210 transaction
1211 .access_list()
1212 .map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
1213 .unwrap_or_default() as u64,
1214 transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
1215 );
1216
1217 let gas_limit = transaction.gas_limit();
1218 if gas_limit < gas.initial_gas || gas_limit < gas.floor_gas {
1219 Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
1220 } else {
1221 Ok(())
1222 }
1223}
1224
1225#[cfg(test)]
1226mod tests {
1227 use super::*;
1228 use crate::{
1229 blobstore::InMemoryBlobStore, error::PoolErrorKind, traits::PoolTransaction,
1230 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
1231 };
1232 use alloy_consensus::Transaction;
1233 use alloy_eips::eip2718::Decodable2718;
1234 use alloy_primitives::{hex, U256};
1235 use reth_ethereum_primitives::PooledTransactionVariant;
1236 use reth_primitives_traits::SignedTransaction;
1237 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
1238
1239 fn get_transaction() -> EthPooledTransaction {
1240 let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
1241
1242 let data = hex::decode(raw).unwrap();
1243 let tx = PooledTransactionVariant::decode_2718(&mut data.as_ref()).unwrap();
1244
1245 EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1246 }
1247
1248 #[tokio::test]
1250 async fn validate_transaction() {
1251 let transaction = get_transaction();
1252 let mut fork_tracker = ForkTracker {
1253 shanghai: false.into(),
1254 cancun: false.into(),
1255 prague: false.into(),
1256 osaka: false.into(),
1257 max_blob_count: 0.into(),
1258 };
1259
1260 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1261 assert!(res.is_ok());
1262
1263 fork_tracker.shanghai = true.into();
1264 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1265 assert!(res.is_ok());
1266
1267 let provider = MockEthProvider::default();
1268 provider.add_account(
1269 transaction.sender(),
1270 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1271 );
1272 let blob_store = InMemoryBlobStore::default();
1273 let validator = EthTransactionValidatorBuilder::new(provider).build(blob_store.clone());
1274
1275 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1276
1277 assert!(outcome.is_valid());
1278
1279 let pool =
1280 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1281
1282 let res = pool.add_external_transaction(transaction.clone()).await;
1283 assert!(res.is_ok());
1284 let tx = pool.get(transaction.hash());
1285 assert!(tx.is_some());
1286 }
1287
1288 #[tokio::test]
1290 async fn invalid_on_gas_limit_too_high() {
1291 let transaction = get_transaction();
1292
1293 let provider = MockEthProvider::default();
1294 provider.add_account(
1295 transaction.sender(),
1296 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1297 );
1298
1299 let blob_store = InMemoryBlobStore::default();
1300 let validator = EthTransactionValidatorBuilder::new(provider)
1301 .set_block_gas_limit(1_000_000) .build(blob_store.clone());
1303
1304 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1305
1306 assert!(outcome.is_invalid());
1307
1308 let pool =
1309 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1310
1311 let res = pool.add_external_transaction(transaction.clone()).await;
1312 assert!(res.is_err());
1313 assert!(matches!(
1314 res.unwrap_err().kind,
1315 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1316 1_015_288, 1_000_000
1317 ))
1318 ));
1319 let tx = pool.get(transaction.hash());
1320 assert!(tx.is_none());
1321 }
1322
1323 #[tokio::test]
1324 async fn invalid_on_fee_cap_exceeded() {
1325 let transaction = get_transaction();
1326 let provider = MockEthProvider::default();
1327 provider.add_account(
1328 transaction.sender(),
1329 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1330 );
1331
1332 let blob_store = InMemoryBlobStore::default();
1333 let validator = EthTransactionValidatorBuilder::new(provider)
1334 .set_tx_fee_cap(100) .build(blob_store.clone());
1336
1337 let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1338 assert!(outcome.is_invalid());
1339
1340 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1341 assert!(matches!(
1342 err,
1343 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1344 if (max_tx_fee_wei > tx_fee_cap_wei)
1345 ));
1346 }
1347
1348 let pool =
1349 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1350 let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
1351 assert!(res.is_err());
1352 assert!(matches!(
1353 res.unwrap_err().kind,
1354 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
1355 ));
1356 let tx = pool.get(transaction.hash());
1357 assert!(tx.is_none());
1358 }
1359
1360 #[tokio::test]
1361 async fn valid_on_zero_fee_cap() {
1362 let transaction = get_transaction();
1363 let provider = MockEthProvider::default();
1364 provider.add_account(
1365 transaction.sender(),
1366 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1367 );
1368
1369 let blob_store = InMemoryBlobStore::default();
1370 let validator = EthTransactionValidatorBuilder::new(provider)
1371 .set_tx_fee_cap(0) .build(blob_store);
1373
1374 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1375 assert!(outcome.is_valid());
1376 }
1377
1378 #[tokio::test]
1379 async fn valid_on_normal_fee_cap() {
1380 let transaction = get_transaction();
1381 let provider = MockEthProvider::default();
1382 provider.add_account(
1383 transaction.sender(),
1384 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1385 );
1386
1387 let blob_store = InMemoryBlobStore::default();
1388 let validator = EthTransactionValidatorBuilder::new(provider)
1389 .set_tx_fee_cap(2e18 as u128) .build(blob_store);
1391
1392 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1393 assert!(outcome.is_valid());
1394 }
1395
1396 #[tokio::test]
1397 async fn invalid_on_max_tx_gas_limit_exceeded() {
1398 let transaction = get_transaction();
1399 let provider = MockEthProvider::default();
1400 provider.add_account(
1401 transaction.sender(),
1402 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1403 );
1404
1405 let blob_store = InMemoryBlobStore::default();
1406 let validator = EthTransactionValidatorBuilder::new(provider)
1407 .with_max_tx_gas_limit(Some(500_000)) .build(blob_store.clone());
1409
1410 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1411 assert!(outcome.is_invalid());
1412
1413 let pool =
1414 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1415
1416 let res = pool.add_external_transaction(transaction.clone()).await;
1417 assert!(res.is_err());
1418 assert!(matches!(
1419 res.unwrap_err().kind,
1420 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::MaxTxGasLimitExceeded(
1421 1_015_288, 500_000
1422 ))
1423 ));
1424 let tx = pool.get(transaction.hash());
1425 assert!(tx.is_none());
1426 }
1427
1428 #[tokio::test]
1429 async fn valid_on_max_tx_gas_limit_disabled() {
1430 let transaction = get_transaction();
1431 let provider = MockEthProvider::default();
1432 provider.add_account(
1433 transaction.sender(),
1434 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1435 );
1436
1437 let blob_store = InMemoryBlobStore::default();
1438 let validator = EthTransactionValidatorBuilder::new(provider)
1439 .with_max_tx_gas_limit(None) .build(blob_store);
1441
1442 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1443 assert!(outcome.is_valid());
1444 }
1445
1446 #[tokio::test]
1447 async fn valid_on_max_tx_gas_limit_within_limit() {
1448 let transaction = get_transaction();
1449 let provider = MockEthProvider::default();
1450 provider.add_account(
1451 transaction.sender(),
1452 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1453 );
1454
1455 let blob_store = InMemoryBlobStore::default();
1456 let validator = EthTransactionValidatorBuilder::new(provider)
1457 .with_max_tx_gas_limit(Some(2_000_000)) .build(blob_store);
1459
1460 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1461 assert!(outcome.is_valid());
1462 }
1463
1464 fn setup_priority_fee_test() -> (EthPooledTransaction, MockEthProvider) {
1466 let transaction = get_transaction();
1467 let provider = MockEthProvider::default();
1468 provider.add_account(
1469 transaction.sender(),
1470 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1471 );
1472 (transaction, provider)
1473 }
1474
1475 fn create_validator_with_minimum_fee(
1477 provider: MockEthProvider,
1478 minimum_priority_fee: Option<u128>,
1479 local_config: Option<LocalTransactionConfig>,
1480 ) -> EthTransactionValidator<MockEthProvider, EthPooledTransaction> {
1481 let blob_store = InMemoryBlobStore::default();
1482 let mut builder = EthTransactionValidatorBuilder::new(provider)
1483 .with_minimum_priority_fee(minimum_priority_fee);
1484
1485 if let Some(config) = local_config {
1486 builder = builder.with_local_transactions_config(config);
1487 }
1488
1489 builder.build(blob_store)
1490 }
1491
1492 #[tokio::test]
1493 async fn invalid_on_priority_fee_lower_than_configured_minimum() {
1494 let (transaction, provider) = setup_priority_fee_test();
1495
1496 assert!(transaction.is_dynamic_fee());
1498
1499 let minimum_priority_fee =
1501 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1502
1503 let validator =
1504 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1505
1506 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1508 assert!(outcome.is_invalid());
1509
1510 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1511 assert!(matches!(
1512 err,
1513 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1514 if min_fee == minimum_priority_fee
1515 ));
1516 }
1517
1518 let blob_store = InMemoryBlobStore::default();
1520 let pool =
1521 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1522
1523 let res = pool.add_external_transaction(transaction.clone()).await;
1524 assert!(res.is_err());
1525 assert!(matches!(
1526 res.unwrap_err().kind,
1527 PoolErrorKind::InvalidTransaction(
1528 InvalidPoolTransactionError::PriorityFeeBelowMinimum { .. }
1529 )
1530 ));
1531 let tx = pool.get(transaction.hash());
1532 assert!(tx.is_none());
1533
1534 let (_, local_provider) = setup_priority_fee_test();
1536 let validator_local =
1537 create_validator_with_minimum_fee(local_provider, Some(minimum_priority_fee), None);
1538
1539 let local_outcome = validator_local.validate_one(TransactionOrigin::Local, transaction);
1540 assert!(local_outcome.is_valid());
1541 }
1542
1543 #[tokio::test]
1544 async fn valid_on_priority_fee_equal_to_minimum() {
1545 let (transaction, provider) = setup_priority_fee_test();
1546
1547 let tx_priority_fee =
1549 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1550 let validator = create_validator_with_minimum_fee(provider, Some(tx_priority_fee), None);
1551
1552 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1553 assert!(outcome.is_valid());
1554 }
1555
1556 #[tokio::test]
1557 async fn valid_on_priority_fee_above_minimum() {
1558 let (transaction, provider) = setup_priority_fee_test();
1559
1560 let tx_priority_fee =
1562 transaction.max_priority_fee_per_gas().expect("priority fee is expected");
1563 let minimum_priority_fee = tx_priority_fee / 2; let validator =
1566 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1567
1568 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1569 assert!(outcome.is_valid());
1570 }
1571
1572 #[tokio::test]
1573 async fn valid_on_minimum_priority_fee_disabled() {
1574 let (transaction, provider) = setup_priority_fee_test();
1575
1576 let validator = create_validator_with_minimum_fee(provider, None, None);
1578
1579 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1580 assert!(outcome.is_valid());
1581 }
1582
1583 #[tokio::test]
1584 async fn priority_fee_validation_applies_to_private_transactions() {
1585 let (transaction, provider) = setup_priority_fee_test();
1586
1587 let minimum_priority_fee =
1589 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1590
1591 let validator =
1592 create_validator_with_minimum_fee(provider, Some(minimum_priority_fee), None);
1593
1594 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1597 assert!(outcome.is_invalid());
1598
1599 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1600 assert!(matches!(
1601 err,
1602 InvalidPoolTransactionError::PriorityFeeBelowMinimum { minimum_priority_fee: min_fee }
1603 if min_fee == minimum_priority_fee
1604 ));
1605 }
1606 }
1607
1608 #[tokio::test]
1609 async fn valid_on_local_config_exempts_private_transactions() {
1610 let (transaction, provider) = setup_priority_fee_test();
1611
1612 let minimum_priority_fee =
1614 transaction.max_priority_fee_per_gas().expect("priority fee is expected") * 2;
1615
1616 let local_config =
1618 LocalTransactionConfig { propagate_local_transactions: true, ..Default::default() };
1619
1620 let validator = create_validator_with_minimum_fee(
1621 provider,
1622 Some(minimum_priority_fee),
1623 Some(local_config),
1624 );
1625
1626 let outcome = validator.validate_one(TransactionOrigin::Private, transaction);
1630 assert!(outcome.is_invalid()); }
1632
1633 #[test]
1634 fn reject_oversized_tx() {
1635 let mut transaction = get_transaction();
1636 transaction.encoded_length = DEFAULT_MAX_TX_INPUT_BYTES + 1;
1637 let provider = MockEthProvider::default();
1638
1639 let validator = create_validator_with_minimum_fee(provider, None, None);
1641
1642 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1643 let invalid = outcome.as_invalid().unwrap();
1644 assert!(invalid.is_oversized());
1645 }
1646
1647 #[tokio::test]
1648 async fn valid_with_disabled_balance_check() {
1649 let transaction = get_transaction();
1650 let provider = MockEthProvider::default();
1651
1652 provider.add_account(
1654 transaction.sender(),
1655 ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO),
1656 );
1657
1658 let validator = EthTransactionValidatorBuilder::new(provider.clone())
1660 .build(InMemoryBlobStore::default());
1661
1662 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1663 let expected_cost = *transaction.cost();
1664 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1665 assert!(matches!(
1666 err,
1667 InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err))
1668 if funds_err.got == alloy_primitives::U256::ZERO && funds_err.expected == expected_cost
1669 ));
1670 } else {
1671 panic!("Expected Invalid outcome with InsufficientFunds error");
1672 }
1673
1674 let validator = EthTransactionValidatorBuilder::new(provider)
1676 .disable_balance_check() .build(InMemoryBlobStore::default());
1678
1679 let outcome = validator.validate_one(TransactionOrigin::External, transaction);
1680 assert!(outcome.is_valid()); }
1682}