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 EthBlobTransactionSidecar, EthPoolTransaction, LocalTransactionConfig,
13 TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator,
14};
15use alloy_consensus::{
16 constants::{
17 EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
18 LEGACY_TX_TYPE_ID,
19 },
20 BlockHeader,
21};
22use alloy_eips::{
23 eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M, eip4844::env_settings::EnvKzgSettings,
24 eip7840::BlobParams,
25};
26use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
27use reth_primitives_traits::{
28 transaction::error::InvalidTransactionError, Block, GotExpected, SealedBlock,
29};
30use reth_storage_api::{StateProvider, StateProviderFactory};
31use reth_tasks::TaskSpawner;
32use std::{
33 marker::PhantomData,
34 sync::{
35 atomic::{AtomicBool, AtomicU64},
36 Arc,
37 },
38 time::Instant,
39};
40use tokio::sync::Mutex;
41
42#[derive(Debug, Clone)]
45pub struct EthTransactionValidator<Client, T> {
46 inner: Arc<EthTransactionValidatorInner<Client, T>>,
48}
49
50impl<Client, Tx> EthTransactionValidator<Client, Tx> {
51 pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
53 where
54 Client: ChainSpecProvider,
55 {
56 self.client().chain_spec()
57 }
58
59 pub fn client(&self) -> &Client {
61 &self.inner.client
62 }
63}
64
65impl<Client, Tx> EthTransactionValidator<Client, Tx>
66where
67 Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
68 Tx: EthPoolTransaction,
69{
70 pub fn validate_one(
74 &self,
75 origin: TransactionOrigin,
76 transaction: Tx,
77 ) -> TransactionValidationOutcome<Tx> {
78 self.inner.validate_one(origin, transaction)
79 }
80
81 pub fn validate_one_with_state(
88 &self,
89 origin: TransactionOrigin,
90 transaction: Tx,
91 state: &mut Option<Box<dyn StateProvider>>,
92 ) -> TransactionValidationOutcome<Tx> {
93 self.inner.validate_one_with_provider(origin, transaction, state)
94 }
95
96 pub fn validate_all(
102 &self,
103 transactions: Vec<(TransactionOrigin, Tx)>,
104 ) -> Vec<TransactionValidationOutcome<Tx>> {
105 self.inner.validate_batch(transactions)
106 }
107}
108
109impl<Client, Tx> TransactionValidator for EthTransactionValidator<Client, Tx>
110where
111 Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
112 Tx: EthPoolTransaction,
113{
114 type Transaction = Tx;
115
116 async fn validate_transaction(
117 &self,
118 origin: TransactionOrigin,
119 transaction: Self::Transaction,
120 ) -> TransactionValidationOutcome<Self::Transaction> {
121 self.validate_one(origin, transaction)
122 }
123
124 async fn validate_transactions(
125 &self,
126 transactions: Vec<(TransactionOrigin, Self::Transaction)>,
127 ) -> Vec<TransactionValidationOutcome<Self::Transaction>> {
128 self.validate_all(transactions)
129 }
130
131 fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>)
132 where
133 B: Block,
134 {
135 self.inner.on_new_head_block(new_tip_block.header())
136 }
137}
138
139#[derive(Debug)]
154pub(crate) struct EthTransactionValidatorInner<Client, T> {
155 client: Client,
157 blob_store: Box<dyn BlobStore>,
159 fork_tracker: ForkTracker,
161 eip2718: bool,
163 eip1559: bool,
165 eip4844: bool,
167 eip7702: bool,
169 block_gas_limit: AtomicU64,
171 tx_fee_cap: Option<u128>,
173 minimum_priority_fee: Option<u128>,
175 kzg_settings: EnvKzgSettings,
177 local_transactions_config: LocalTransactionConfig,
179 max_tx_input_bytes: usize,
181 _marker: PhantomData<T>,
183 validation_metrics: TxPoolValidationMetrics,
185}
186
187impl<Client: ChainSpecProvider, Tx> EthTransactionValidatorInner<Client, Tx> {
190 pub(crate) fn chain_id(&self) -> u64 {
192 self.client.chain_spec().chain().id()
193 }
194}
195
196impl<Client, Tx> EthTransactionValidatorInner<Client, Tx>
197where
198 Client: ChainSpecProvider<ChainSpec: EthereumHardforks> + StateProviderFactory,
199 Tx: EthPoolTransaction,
200{
201 fn chain_spec(&self) -> Arc<Client::ChainSpec> {
203 self.client.chain_spec()
204 }
205
206 fn validate_one_with_provider(
210 &self,
211 origin: TransactionOrigin,
212 mut transaction: Tx,
213 maybe_state: &mut Option<Box<dyn StateProvider>>,
214 ) -> TransactionValidationOutcome<Tx> {
215 match transaction.ty() {
217 LEGACY_TX_TYPE_ID => {
218 }
220 EIP2930_TX_TYPE_ID => {
221 if !self.eip2718 {
223 return TransactionValidationOutcome::Invalid(
224 transaction,
225 InvalidTransactionError::Eip2930Disabled.into(),
226 )
227 }
228 }
229 EIP1559_TX_TYPE_ID => {
230 if !self.eip1559 {
232 return TransactionValidationOutcome::Invalid(
233 transaction,
234 InvalidTransactionError::Eip1559Disabled.into(),
235 )
236 }
237 }
238 EIP4844_TX_TYPE_ID => {
239 if !self.eip4844 {
241 return TransactionValidationOutcome::Invalid(
242 transaction,
243 InvalidTransactionError::Eip4844Disabled.into(),
244 )
245 }
246 }
247 EIP7702_TX_TYPE_ID => {
248 if !self.eip7702 {
250 return TransactionValidationOutcome::Invalid(
251 transaction,
252 InvalidTransactionError::Eip7702Disabled.into(),
253 )
254 }
255 }
256
257 _ => {
258 return TransactionValidationOutcome::Invalid(
259 transaction,
260 InvalidTransactionError::TxTypeNotSupported.into(),
261 )
262 }
263 };
264
265 let tx_input_len = transaction.input().len();
267 if tx_input_len > self.max_tx_input_bytes {
268 return TransactionValidationOutcome::Invalid(
269 transaction,
270 InvalidPoolTransactionError::OversizedData(tx_input_len, self.max_tx_input_bytes),
271 )
272 }
273
274 if self.fork_tracker.is_shanghai_activated() {
276 if let Err(err) = transaction.ensure_max_init_code_size(MAX_INIT_CODE_BYTE_SIZE) {
277 return TransactionValidationOutcome::Invalid(transaction, err)
278 }
279 }
280
281 let transaction_gas_limit = transaction.gas_limit();
283 let block_gas_limit = self.max_gas_limit();
284 if transaction_gas_limit > block_gas_limit {
285 return TransactionValidationOutcome::Invalid(
286 transaction,
287 InvalidPoolTransactionError::ExceedsGasLimit(
288 transaction_gas_limit,
289 block_gas_limit,
290 ),
291 )
292 }
293
294 if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) {
296 return TransactionValidationOutcome::Invalid(
297 transaction,
298 InvalidTransactionError::TipAboveFeeCap.into(),
299 )
300 }
301
302 let is_local = self.local_transactions_config.is_local(origin, transaction.sender_ref());
304
305 if is_local {
308 match self.tx_fee_cap {
309 Some(0) | None => {} Some(tx_fee_cap_wei) => {
311 let gas_price = transaction.max_fee_per_gas();
314 let max_tx_fee_wei = gas_price.saturating_mul(transaction.gas_limit() as u128);
315 if max_tx_fee_wei > tx_fee_cap_wei {
316 return TransactionValidationOutcome::Invalid(
317 transaction,
318 InvalidPoolTransactionError::ExceedsFeeCap {
319 max_tx_fee_wei,
320 tx_fee_cap_wei,
321 },
322 );
323 }
324 }
325 }
326 }
327
328 if !is_local &&
331 transaction.is_dynamic_fee() &&
332 transaction.max_priority_fee_per_gas() < self.minimum_priority_fee
333 {
334 return TransactionValidationOutcome::Invalid(
335 transaction,
336 InvalidPoolTransactionError::Underpriced,
337 )
338 }
339
340 if let Some(chain_id) = transaction.chain_id() {
342 if chain_id != self.chain_id() {
343 return TransactionValidationOutcome::Invalid(
344 transaction,
345 InvalidTransactionError::ChainIdMismatch.into(),
346 )
347 }
348 }
349
350 if transaction.is_eip7702() {
351 if !self.fork_tracker.is_prague_activated() {
353 return TransactionValidationOutcome::Invalid(
354 transaction,
355 InvalidTransactionError::TxTypeNotSupported.into(),
356 )
357 }
358
359 if transaction.authorization_list().is_none_or(|l| l.is_empty()) {
360 return TransactionValidationOutcome::Invalid(
361 transaction,
362 Eip7702PoolTransactionError::MissingEip7702AuthorizationList.into(),
363 )
364 }
365 }
366
367 if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
368 return TransactionValidationOutcome::Invalid(transaction, err)
369 }
370
371 if transaction.is_eip4844() {
373 if !self.fork_tracker.is_cancun_activated() {
375 return TransactionValidationOutcome::Invalid(
376 transaction,
377 InvalidTransactionError::TxTypeNotSupported.into(),
378 )
379 }
380
381 let blob_count =
382 transaction.blob_versioned_hashes().map(|b| b.len() as u64).unwrap_or(0);
383 if blob_count == 0 {
384 return TransactionValidationOutcome::Invalid(
386 transaction,
387 InvalidPoolTransactionError::Eip4844(
388 Eip4844PoolTransactionError::NoEip4844Blobs,
389 ),
390 )
391 }
392
393 let max_blob_count = self.fork_tracker.max_blob_count();
394 if blob_count > max_blob_count {
395 return TransactionValidationOutcome::Invalid(
396 transaction,
397 InvalidPoolTransactionError::Eip4844(
398 Eip4844PoolTransactionError::TooManyEip4844Blobs {
399 have: blob_count,
400 permitted: max_blob_count,
401 },
402 ),
403 )
404 }
405 }
406
407 if maybe_state.is_none() {
409 match self.client.latest() {
410 Ok(new_state) => {
411 *maybe_state = Some(new_state);
412 }
413 Err(err) => {
414 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
415 }
416 }
417 }
418
419 let state = maybe_state.as_deref().expect("provider is set");
420
421 let account = match state.basic_account(transaction.sender_ref()) {
423 Ok(account) => account.unwrap_or_default(),
424 Err(err) => {
425 return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err))
426 }
427 };
428
429 if let Some(code_hash) = &account.bytecode_hash {
436 let is_eip7702 = if self.fork_tracker.is_prague_activated() {
437 match state.bytecode_by_hash(code_hash) {
438 Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(),
439 Err(err) => {
440 return TransactionValidationOutcome::Error(
441 *transaction.hash(),
442 Box::new(err),
443 )
444 }
445 }
446 } else {
447 false
448 };
449
450 if !is_eip7702 {
451 return TransactionValidationOutcome::Invalid(
452 transaction,
453 InvalidTransactionError::SignerAccountHasBytecode.into(),
454 )
455 }
456 }
457
458 let tx_nonce = transaction.nonce();
459
460 if tx_nonce < account.nonce {
462 return TransactionValidationOutcome::Invalid(
463 transaction,
464 InvalidTransactionError::NonceNotConsistent { tx: tx_nonce, state: account.nonce }
465 .into(),
466 )
467 }
468
469 let cost = transaction.cost();
470
471 if cost > &account.balance {
473 let expected = *cost;
474 return TransactionValidationOutcome::Invalid(
475 transaction,
476 InvalidTransactionError::InsufficientFunds(
477 GotExpected { got: account.balance, expected }.into(),
478 )
479 .into(),
480 )
481 }
482
483 let mut maybe_blob_sidecar = None;
484
485 if transaction.is_eip4844() {
487 match transaction.take_blob() {
489 EthBlobTransactionSidecar::None => {
490 return TransactionValidationOutcome::Invalid(
492 transaction,
493 InvalidTransactionError::TxTypeNotSupported.into(),
494 )
495 }
496 EthBlobTransactionSidecar::Missing => {
497 if matches!(self.blob_store.contains(*transaction.hash()), Ok(true)) {
502 } else {
504 return TransactionValidationOutcome::Invalid(
505 transaction,
506 InvalidPoolTransactionError::Eip4844(
507 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
508 ),
509 )
510 }
511 }
512 EthBlobTransactionSidecar::Present(blob) => {
513 let now = Instant::now();
514 if let Err(err) = transaction.validate_blob(&blob, self.kzg_settings.get()) {
516 return TransactionValidationOutcome::Invalid(
517 transaction,
518 InvalidPoolTransactionError::Eip4844(
519 Eip4844PoolTransactionError::InvalidEip4844Blob(err),
520 ),
521 )
522 }
523 self.validation_metrics.blob_validation_duration.record(now.elapsed());
525 maybe_blob_sidecar = Some(blob);
527 }
528 }
529 }
530
531 TransactionValidationOutcome::Valid {
533 balance: account.balance,
534 state_nonce: account.nonce,
535 transaction: ValidTransaction::new(transaction, maybe_blob_sidecar),
536 propagate: match origin {
538 TransactionOrigin::External => true,
539 TransactionOrigin::Local => {
540 self.local_transactions_config.propagate_local_transactions
541 }
542 TransactionOrigin::Private => false,
543 },
544 }
545 }
546
547 fn validate_one(
549 &self,
550 origin: TransactionOrigin,
551 transaction: Tx,
552 ) -> TransactionValidationOutcome<Tx> {
553 let mut provider = None;
554 self.validate_one_with_provider(origin, transaction, &mut provider)
555 }
556
557 fn validate_batch(
559 &self,
560 transactions: Vec<(TransactionOrigin, Tx)>,
561 ) -> Vec<TransactionValidationOutcome<Tx>> {
562 let mut provider = None;
563 transactions
564 .into_iter()
565 .map(|(origin, tx)| self.validate_one_with_provider(origin, tx, &mut provider))
566 .collect()
567 }
568
569 fn on_new_head_block<T: BlockHeader>(&self, new_tip_block: &T) {
570 if self.chain_spec().is_cancun_active_at_timestamp(new_tip_block.timestamp()) {
572 self.fork_tracker.cancun.store(true, std::sync::atomic::Ordering::Relaxed);
573 }
574
575 if self.chain_spec().is_shanghai_active_at_timestamp(new_tip_block.timestamp()) {
576 self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
577 }
578
579 if self.chain_spec().is_prague_active_at_timestamp(new_tip_block.timestamp()) {
580 self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
581 }
582
583 if let Some(blob_params) =
584 self.chain_spec().blob_params_at_timestamp(new_tip_block.timestamp())
585 {
586 self.fork_tracker
587 .max_blob_count
588 .store(blob_params.max_blob_count, std::sync::atomic::Ordering::Relaxed);
589 }
590
591 self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed);
592 }
593
594 fn max_gas_limit(&self) -> u64 {
595 self.block_gas_limit.load(std::sync::atomic::Ordering::Relaxed)
596 }
597}
598
599#[derive(Debug)]
601pub struct EthTransactionValidatorBuilder<Client> {
602 client: Client,
603 shanghai: bool,
605 cancun: bool,
607 prague: bool,
609 max_blob_count: u64,
611 eip2718: bool,
613 eip1559: bool,
615 eip4844: bool,
617 eip7702: bool,
619 block_gas_limit: AtomicU64,
621 tx_fee_cap: Option<u128>,
623 minimum_priority_fee: Option<u128>,
625 additional_tasks: usize,
629
630 kzg_settings: EnvKzgSettings,
632 local_transactions_config: LocalTransactionConfig,
634 max_tx_input_bytes: usize,
636}
637
638impl<Client> EthTransactionValidatorBuilder<Client> {
639 pub fn new(client: Client) -> Self {
648 Self {
649 block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(),
650 client,
651 minimum_priority_fee: None,
652 additional_tasks: 1,
653 kzg_settings: EnvKzgSettings::Default,
654 local_transactions_config: Default::default(),
655 max_tx_input_bytes: DEFAULT_MAX_TX_INPUT_BYTES,
656 tx_fee_cap: Some(1e18 as u128),
657 eip2718: true,
659 eip1559: true,
660 eip4844: true,
661 eip7702: true,
662
663 shanghai: true,
665
666 cancun: true,
668
669 prague: false,
671
672 max_blob_count: BlobParams::cancun().max_blob_count,
674 }
675 }
676
677 pub const fn no_cancun(self) -> Self {
679 self.set_cancun(false)
680 }
681
682 pub fn with_local_transactions_config(
684 mut self,
685 local_transactions_config: LocalTransactionConfig,
686 ) -> Self {
687 self.local_transactions_config = local_transactions_config;
688 self
689 }
690
691 pub const fn set_cancun(mut self, cancun: bool) -> Self {
693 self.cancun = cancun;
694 self
695 }
696
697 pub const fn no_shanghai(self) -> Self {
699 self.set_shanghai(false)
700 }
701
702 pub const fn set_shanghai(mut self, shanghai: bool) -> Self {
704 self.shanghai = shanghai;
705 self
706 }
707
708 pub const fn no_prague(self) -> Self {
710 self.set_prague(false)
711 }
712
713 pub const fn set_prague(mut self, prague: bool) -> Self {
715 self.prague = prague;
716 self
717 }
718
719 pub const fn no_eip2718(self) -> Self {
721 self.set_eip2718(false)
722 }
723
724 pub const fn set_eip2718(mut self, eip2718: bool) -> Self {
726 self.eip2718 = eip2718;
727 self
728 }
729
730 pub const fn no_eip1559(self) -> Self {
732 self.set_eip1559(false)
733 }
734
735 pub const fn set_eip1559(mut self, eip1559: bool) -> Self {
737 self.eip1559 = eip1559;
738 self
739 }
740
741 pub const fn no_eip4844(self) -> Self {
743 self.set_eip4844(false)
744 }
745
746 pub const fn set_eip4844(mut self, eip4844: bool) -> Self {
748 self.eip4844 = eip4844;
749 self
750 }
751
752 pub fn kzg_settings(mut self, kzg_settings: EnvKzgSettings) -> Self {
754 self.kzg_settings = kzg_settings;
755 self
756 }
757
758 pub const fn with_minimum_priority_fee(mut self, minimum_priority_fee: u128) -> Self {
760 self.minimum_priority_fee = Some(minimum_priority_fee);
761 self
762 }
763
764 pub const fn with_additional_tasks(mut self, additional_tasks: usize) -> Self {
766 self.additional_tasks = additional_tasks;
767 self
768 }
769
770 pub fn with_head_timestamp(mut self, timestamp: u64) -> Self
774 where
775 Client: ChainSpecProvider<ChainSpec: EthereumHardforks>,
776 {
777 self.cancun = self.client.chain_spec().is_cancun_active_at_timestamp(timestamp);
778 self.shanghai = self.client.chain_spec().is_shanghai_active_at_timestamp(timestamp);
779 self.prague = self.client.chain_spec().is_prague_active_at_timestamp(timestamp);
780 self.max_blob_count = self
781 .client
782 .chain_spec()
783 .blob_params_at_timestamp(timestamp)
784 .unwrap_or_else(BlobParams::cancun)
785 .max_blob_count;
786 self
787 }
788
789 pub const fn with_max_tx_input_bytes(mut self, max_tx_input_bytes: usize) -> Self {
791 self.max_tx_input_bytes = max_tx_input_bytes;
792 self
793 }
794
795 pub fn set_block_gas_limit(self, block_gas_limit: u64) -> Self {
799 self.block_gas_limit.store(block_gas_limit, std::sync::atomic::Ordering::Relaxed);
800 self
801 }
802
803 pub const fn set_tx_fee_cap(mut self, tx_fee_cap: u128) -> Self {
807 self.tx_fee_cap = Some(tx_fee_cap);
808 self
809 }
810
811 pub fn build<Tx, S>(self, blob_store: S) -> EthTransactionValidator<Client, Tx>
813 where
814 S: BlobStore,
815 {
816 let Self {
817 client,
818 shanghai,
819 cancun,
820 prague,
821 eip2718,
822 eip1559,
823 eip4844,
824 eip7702,
825 block_gas_limit,
826 tx_fee_cap,
827 minimum_priority_fee,
828 kzg_settings,
829 local_transactions_config,
830 max_tx_input_bytes,
831 ..
832 } = self;
833
834 let max_blob_count = if prague {
835 BlobParams::prague().max_blob_count
836 } else {
837 BlobParams::cancun().max_blob_count
838 };
839
840 let fork_tracker = ForkTracker {
841 shanghai: AtomicBool::new(shanghai),
842 cancun: AtomicBool::new(cancun),
843 prague: AtomicBool::new(prague),
844 max_blob_count: AtomicU64::new(max_blob_count),
845 };
846
847 let inner = EthTransactionValidatorInner {
848 client,
849 eip2718,
850 eip1559,
851 fork_tracker,
852 eip4844,
853 eip7702,
854 block_gas_limit,
855 tx_fee_cap,
856 minimum_priority_fee,
857 blob_store: Box::new(blob_store),
858 kzg_settings,
859 local_transactions_config,
860 max_tx_input_bytes,
861 _marker: Default::default(),
862 validation_metrics: TxPoolValidationMetrics::default(),
863 };
864
865 EthTransactionValidator { inner: Arc::new(inner) }
866 }
867
868 pub fn build_with_tasks<Tx, T, S>(
875 self,
876 tasks: T,
877 blob_store: S,
878 ) -> TransactionValidationTaskExecutor<EthTransactionValidator<Client, Tx>>
879 where
880 T: TaskSpawner,
881 S: BlobStore,
882 {
883 let additional_tasks = self.additional_tasks;
884 let validator = self.build(blob_store);
885
886 let (tx, task) = ValidationTask::new();
887
888 for _ in 0..additional_tasks {
890 let task = task.clone();
891 tasks.spawn_blocking(Box::pin(async move {
892 task.run().await;
893 }));
894 }
895
896 tasks.spawn_critical_blocking(
899 "transaction-validation-service",
900 Box::pin(async move {
901 task.run().await;
902 }),
903 );
904
905 let to_validation_task = Arc::new(Mutex::new(tx));
906
907 TransactionValidationTaskExecutor { validator, to_validation_task }
908 }
909}
910
911#[derive(Debug)]
913pub struct ForkTracker {
914 pub shanghai: AtomicBool,
916 pub cancun: AtomicBool,
918 pub prague: AtomicBool,
920 pub max_blob_count: AtomicU64,
922}
923
924impl ForkTracker {
925 pub fn is_shanghai_activated(&self) -> bool {
927 self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
928 }
929
930 pub fn is_cancun_activated(&self) -> bool {
932 self.cancun.load(std::sync::atomic::Ordering::Relaxed)
933 }
934
935 pub fn is_prague_activated(&self) -> bool {
937 self.prague.load(std::sync::atomic::Ordering::Relaxed)
938 }
939
940 pub fn max_blob_count(&self) -> u64 {
942 self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed)
943 }
944}
945
946pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
950 transaction: &T,
951 fork_tracker: &ForkTracker,
952) -> Result<(), InvalidPoolTransactionError> {
953 use revm_primitives::hardfork::SpecId;
954 let spec_id = if fork_tracker.is_prague_activated() {
955 SpecId::PRAGUE
956 } else if fork_tracker.is_shanghai_activated() {
957 SpecId::SHANGHAI
958 } else {
959 SpecId::MERGE
960 };
961
962 let gas = revm_interpreter::gas::calculate_initial_tx_gas(
963 spec_id,
964 transaction.input(),
965 transaction.is_create(),
966 transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64,
967 transaction
968 .access_list()
969 .map(|l| l.iter().map(|i| i.storage_keys.len()).sum::<usize>())
970 .unwrap_or_default() as u64,
971 transaction.authorization_list().map(|l| l.len()).unwrap_or_default() as u64,
972 );
973
974 let gas_limit = transaction.gas_limit();
975 if gas_limit < gas.initial_gas || gas_limit < gas.floor_gas {
976 Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
977 } else {
978 Ok(())
979 }
980}
981
982#[cfg(test)]
983mod tests {
984 use super::*;
985 use crate::{
986 blobstore::InMemoryBlobStore, error::PoolErrorKind, traits::PoolTransaction,
987 CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionPool,
988 };
989 use alloy_consensus::Transaction;
990 use alloy_eips::eip2718::Decodable2718;
991 use alloy_primitives::{hex, U256};
992 use reth_ethereum_primitives::PooledTransaction;
993 use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
994
995 fn get_transaction() -> EthPooledTransaction {
996 let raw = "0x02f914950181ad84b2d05e0085117553845b830f7df88080b9143a6040608081523462000414576200133a803803806200001e8162000419565b9283398101608082820312620004145781516001600160401b03908181116200041457826200004f9185016200043f565b92602092838201519083821162000414576200006d9183016200043f565b8186015190946001600160a01b03821692909183900362000414576060015190805193808511620003145760038054956001938488811c9816801562000409575b89891014620003f3578190601f988981116200039d575b50899089831160011462000336576000926200032a575b505060001982841b1c191690841b1781555b8751918211620003145760049788548481811c9116801562000309575b89821014620002f457878111620002a9575b5087908784116001146200023e5793839491849260009562000232575b50501b92600019911b1c19161785555b6005556007805460ff60a01b19169055600880546001600160a01b0319169190911790553015620001f3575060025469d3c21bcecceda100000092838201809211620001de57506000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9160025530835282815284832084815401905584519384523093a351610e889081620004b28239f35b601190634e487b7160e01b6000525260246000fd5b90606493519262461bcd60e51b845283015260248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152fd5b0151935038806200013a565b9190601f198416928a600052848a6000209460005b8c8983831062000291575050501062000276575b50505050811b0185556200014a565b01519060f884600019921b161c191690553880808062000267565b86860151895590970196948501948893500162000253565b89600052886000208880860160051c8201928b8710620002ea575b0160051c019085905b828110620002dd5750506200011d565b60008155018590620002cd565b92508192620002c4565b60228a634e487b7160e01b6000525260246000fd5b90607f16906200010b565b634e487b7160e01b600052604160045260246000fd5b015190503880620000dc565b90869350601f19831691856000528b6000209260005b8d8282106200038657505084116200036d575b505050811b018155620000ee565b015160001983861b60f8161c191690553880806200035f565b8385015186558a979095019493840193016200034c565b90915083600052896000208980850160051c8201928c8610620003e9575b918891869594930160051c01915b828110620003d9575050620000c5565b60008155859450889101620003c9565b92508192620003bb565b634e487b7160e01b600052602260045260246000fd5b97607f1697620000ae565b600080fd5b6040519190601f01601f191682016001600160401b038111838210176200031457604052565b919080601f84011215620004145782516001600160401b038111620003145760209062000475601f8201601f1916830162000419565b92818452828287010111620004145760005b8181106200049d57508260009394955001015290565b85810183015184820184015282016200048756fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314610a1c57508163095ea7b3146109f257816318160ddd146109d35781631b4c84d2146109ac57816323b872dd14610833578163313ce5671461081757816339509351146107c357816370a082311461078c578163715018a6146107685781638124f7ac146107495781638da5cb5b1461072057816395d89b411461061d578163a457c2d714610575578163a9059cbb146104e4578163c9567bf914610120575063dd62ed3e146100d557600080fd5b3461011c578060031936011261011c57806020926100f1610b5a565b6100f9610b75565b6001600160a01b0391821683526001865283832091168252845220549051908152f35b5080fd5b905082600319360112610338576008546001600160a01b039190821633036104975760079283549160ff8360a01c1661045557737a250d5630b4cf539739df2c5dacb4c659f2488d92836bffffffffffffffffffffffff60a01b8092161786553087526020938785528388205430156104065730895260018652848920828a52865280858a205584519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925863092a38554835163c45a015560e01b815290861685828581845afa9182156103dd57849187918b946103e7575b5086516315ab88c960e31b815292839182905afa9081156103dd576044879289928c916103c0575b508b83895196879586946364e329cb60e11b8652308c870152166024850152165af19081156103b6579086918991610389575b50169060065416176006558385541660604730895288865260c4858a20548860085416928751958694859363f305d71960e01b8552308a86015260248501528d60448501528d606485015260848401524260a48401525af1801561037f579084929161034c575b50604485600654169587541691888551978894859363095ea7b360e01b855284015260001960248401525af1908115610343575061030c575b5050805460ff60a01b1916600160a01b17905580f35b81813d831161033c575b6103208183610b8b565b8101031261033857518015150361011c5738806102f6565b8280fd5b503d610316565b513d86823e3d90fd5b6060809293503d8111610378575b6103648183610b8b565b81010312610374578290386102bd565b8580fd5b503d61035a565b83513d89823e3d90fd5b6103a99150863d88116103af575b6103a18183610b8b565b810190610e33565b38610256565b503d610397565b84513d8a823e3d90fd5b6103d79150843d86116103af576103a18183610b8b565b38610223565b85513d8b823e3d90fd5b6103ff919450823d84116103af576103a18183610b8b565b92386101fb565b845162461bcd60e51b81528085018790526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b6020606492519162461bcd60e51b8352820152601760248201527f74726164696e6720697320616c7265616479206f70656e0000000000000000006044820152fd5b608490602084519162461bcd60e51b8352820152602160248201527f4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6044820152603760f91b6064820152fd5b9050346103385781600319360112610338576104fe610b5a565b9060243593303303610520575b602084610519878633610bc3565b5160018152f35b600594919454808302908382041483151715610562576127109004820391821161054f5750925080602061050b565b634e487b7160e01b815260118552602490fd5b634e487b7160e01b825260118652602482fd5b9050823461061a578260031936011261061a57610590610b5a565b918360243592338152600160205281812060018060a01b03861682526020522054908282106105c9576020856105198585038733610d31565b608490602086519162461bcd60e51b8352820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b6064820152fd5b80fd5b83833461011c578160031936011261011c57805191809380549160019083821c92828516948515610716575b6020958686108114610703578589529081156106df5750600114610687575b6106838787610679828c0383610b8b565b5191829182610b11565b0390f35b81529295507f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b5b8284106106cc57505050826106839461067992820101948680610668565b80548685018801529286019281016106ae565b60ff19168887015250505050151560051b8301019250610679826106838680610668565b634e487b7160e01b845260228352602484fd5b93607f1693610649565b50503461011c578160031936011261011c5760085490516001600160a01b039091168152602090f35b50503461011c578160031936011261011c576020906005549051908152f35b833461061a578060031936011261061a57600880546001600160a01b031916905580f35b50503461011c57602036600319011261011c5760209181906001600160a01b036107b4610b5a565b16815280845220549051908152f35b82843461061a578160031936011261061a576107dd610b5a565b338252600160209081528383206001600160a01b038316845290528282205460243581019290831061054f57602084610519858533610d31565b50503461011c578160031936011261011c576020905160128152f35b83833461011c57606036600319011261011c5761084e610b5a565b610856610b75565b6044359160018060a01b0381169485815260209560018752858220338352875285822054976000198903610893575b505050906105199291610bc3565b85891061096957811561091a5733156108cc5750948481979861051997845260018a528284203385528a52039120558594938780610885565b865162461bcd60e51b8152908101889052602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b865162461bcd60e51b81529081018890526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b865162461bcd60e51b8152908101889052601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606490fd5b50503461011c578160031936011261011c5760209060ff60075460a01c1690519015158152f35b50503461011c578160031936011261011c576020906002549051908152f35b50503461011c578060031936011261011c57602090610519610a12610b5a565b6024359033610d31565b92915034610b0d5783600319360112610b0d57600354600181811c9186908281168015610b03575b6020958686108214610af05750848852908115610ace5750600114610a75575b6106838686610679828b0383610b8b565b929550600383527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b5b828410610abb575050508261068394610679928201019438610a64565b8054868501880152928601928101610a9e565b60ff191687860152505050151560051b83010192506106798261068338610a64565b634e487b7160e01b845260229052602483fd5b93607f1693610a44565b8380fd5b6020808252825181830181905290939260005b828110610b4657505060409293506000838284010152601f8019910116010190565b818101860151848201604001528501610b24565b600435906001600160a01b0382168203610b7057565b600080fd5b602435906001600160a01b0382168203610b7057565b90601f8019910116810190811067ffffffffffffffff821117610bad57604052565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03908116918215610cde5716918215610c8d57600082815280602052604081205491808310610c3957604082827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef958760209652828652038282205586815220818154019055604051908152a3565b60405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608490fd5b6001600160a01b03908116918215610de25716918215610d925760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925918360005260018252604060002085600052825280604060002055604051908152a3565b60405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608490fd5b90816020910312610b7057516001600160a01b0381168103610b70579056fea2646970667358221220285c200b3978b10818ff576bb83f2dc4a2a7c98dfb6a36ea01170de792aa652764736f6c63430008140033000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000d3fd4f95820a9aa848ce716d6c200eaefb9a2e4900000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000003543131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035431310000000000000000000000000000000000000000000000000000000000c001a04e551c75810ffdfe6caff57da9f5a8732449f42f0f4c57f935b05250a76db3b6a046cd47e6d01914270c1ec0d9ac7fae7dfb240ec9a8b6ec7898c4d6aa174388f2";
997
998 let data = hex::decode(raw).unwrap();
999 let tx = PooledTransaction::decode_2718(&mut data.as_ref()).unwrap();
1000
1001 EthPooledTransaction::from_pooled(tx.try_into_recovered().unwrap())
1002 }
1003
1004 #[tokio::test]
1006 async fn validate_transaction() {
1007 let transaction = get_transaction();
1008 let mut fork_tracker = ForkTracker {
1009 shanghai: false.into(),
1010 cancun: false.into(),
1011 prague: false.into(),
1012 max_blob_count: 0.into(),
1013 };
1014
1015 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1016 assert!(res.is_ok());
1017
1018 fork_tracker.shanghai = true.into();
1019 let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
1020 assert!(res.is_ok());
1021
1022 let provider = MockEthProvider::default();
1023 provider.add_account(
1024 transaction.sender(),
1025 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1026 );
1027 let blob_store = InMemoryBlobStore::default();
1028 let validator = EthTransactionValidatorBuilder::new(provider).build(blob_store.clone());
1029
1030 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1031
1032 assert!(outcome.is_valid());
1033
1034 let pool =
1035 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1036
1037 let res = pool.add_external_transaction(transaction.clone()).await;
1038 assert!(res.is_ok());
1039 let tx = pool.get(transaction.hash());
1040 assert!(tx.is_some());
1041 }
1042
1043 #[tokio::test]
1045 async fn invalid_on_gas_limit_too_high() {
1046 let transaction = get_transaction();
1047
1048 let provider = MockEthProvider::default();
1049 provider.add_account(
1050 transaction.sender(),
1051 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1052 );
1053
1054 let blob_store = InMemoryBlobStore::default();
1055 let validator = EthTransactionValidatorBuilder::new(provider)
1056 .set_block_gas_limit(1_000_000) .build(blob_store.clone());
1058
1059 let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone());
1060
1061 assert!(outcome.is_invalid());
1062
1063 let pool =
1064 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1065
1066 let res = pool.add_external_transaction(transaction.clone()).await;
1067 assert!(res.is_err());
1068 assert!(matches!(
1069 res.unwrap_err().kind,
1070 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsGasLimit(
1071 1_015_288, 1_000_000
1072 ))
1073 ));
1074 let tx = pool.get(transaction.hash());
1075 assert!(tx.is_none());
1076 }
1077
1078 #[tokio::test]
1079 async fn invalid_on_fee_cap_exceeded() {
1080 let transaction = get_transaction();
1081 let provider = MockEthProvider::default();
1082 provider.add_account(
1083 transaction.sender(),
1084 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1085 );
1086
1087 let blob_store = InMemoryBlobStore::default();
1088 let validator = EthTransactionValidatorBuilder::new(provider)
1089 .set_tx_fee_cap(100) .build(blob_store.clone());
1091
1092 let outcome = validator.validate_one(TransactionOrigin::Local, transaction.clone());
1093 assert!(outcome.is_invalid());
1094
1095 if let TransactionValidationOutcome::Invalid(_, err) = outcome {
1096 assert!(matches!(
1097 err,
1098 InvalidPoolTransactionError::ExceedsFeeCap { max_tx_fee_wei, tx_fee_cap_wei }
1099 if (max_tx_fee_wei > tx_fee_cap_wei)
1100 ));
1101 }
1102
1103 let pool =
1104 Pool::new(validator, CoinbaseTipOrdering::default(), blob_store, Default::default());
1105 let res = pool.add_transaction(TransactionOrigin::Local, transaction.clone()).await;
1106 assert!(res.is_err());
1107 assert!(matches!(
1108 res.unwrap_err().kind,
1109 PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::ExceedsFeeCap { .. })
1110 ));
1111 let tx = pool.get(transaction.hash());
1112 assert!(tx.is_none());
1113 }
1114
1115 #[tokio::test]
1116 async fn valid_on_zero_fee_cap() {
1117 let transaction = get_transaction();
1118 let provider = MockEthProvider::default();
1119 provider.add_account(
1120 transaction.sender(),
1121 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1122 );
1123
1124 let blob_store = InMemoryBlobStore::default();
1125 let validator = EthTransactionValidatorBuilder::new(provider)
1126 .set_tx_fee_cap(0) .build(blob_store);
1128
1129 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1130 assert!(outcome.is_valid());
1131 }
1132
1133 #[tokio::test]
1134 async fn valid_on_normal_fee_cap() {
1135 let transaction = get_transaction();
1136 let provider = MockEthProvider::default();
1137 provider.add_account(
1138 transaction.sender(),
1139 ExtendedAccount::new(transaction.nonce(), U256::MAX),
1140 );
1141
1142 let blob_store = InMemoryBlobStore::default();
1143 let validator = EthTransactionValidatorBuilder::new(provider)
1144 .set_tx_fee_cap(2e18 as u128) .build(blob_store);
1146
1147 let outcome = validator.validate_one(TransactionOrigin::Local, transaction);
1148 assert!(outcome.is_valid());
1149 }
1150}