1use crate::{
4 error::RawPoolTransactionError,
5 identifier::{SenderIdentifiers, TransactionId},
6 pool::txpool::TxPool,
7 traits::TransactionOrigin,
8 CoinbaseTipOrdering, EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction,
9 ValidPoolTransaction,
10};
11use alloy_consensus::{
12 constants::{
13 EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
14 LEGACY_TX_TYPE_ID,
15 },
16 EthereumTxEnvelope, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702,
17 TxEnvelope, TxLegacy, TxType, Typed2718,
18};
19use alloy_eips::{
20 eip1559::{calc_effective_gas_price, MIN_PROTOCOL_BASE_FEE},
21 eip2718::Encodable2718,
22 eip2930::AccessList,
23 eip4844::{BlobTransactionSidecar, BlobTransactionValidationError, DATA_GAS_PER_BLOB},
24 eip7594::BlobTransactionSidecarVariant,
25 eip7702::SignedAuthorization,
26};
27use alloy_primitives::{Address, Bytes, ChainId, Signature, TxHash, TxKind, B256, U256};
28use paste::paste;
29use rand::{distr::Uniform, prelude::Distribution};
30use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
31use reth_primitives_traits::{
32 transaction::error::TryFromRecoveredTransactionError, InMemorySize, Recovered,
33 SignedTransaction,
34};
35
36use alloy_consensus::error::ValueError;
37use alloy_eips::eip4844::env_settings::KzgSettings;
38use rand::distr::weighted::WeightedIndex;
39use std::{ops::Range, sync::Arc, time::Instant, vec::IntoIter};
40
41pub type MockTxPool = TxPool<MockOrdering>;
45
46pub type MockValidTx = ValidPoolTransaction<MockTransaction>;
51
52pub fn mock_tx_pool() -> MockTxPool {
54 MockTxPool::new(Default::default(), Default::default())
55}
56
57macro_rules! set_value {
59 (&mut $this:expr => $field:ident) => {{
61 let new_value = $field;
62 match $this {
63 MockTransaction::Legacy { $field, .. } => {
64 *$field = new_value;
65 }
66 MockTransaction::Eip1559 { $field, .. } => {
67 *$field = new_value;
68 }
69 MockTransaction::Eip4844 { $field, .. } => {
70 *$field = new_value;
71 }
72 MockTransaction::Eip2930 { $field, .. } => {
73 *$field = new_value;
74 }
75 MockTransaction::Eip7702 { $field, .. } => {
76 *$field = new_value;
77 }
78 }
79 $this.update_cost();
81 }};
82 ($this:expr => $field:ident) => {{
84 let new_value = $field;
85 match $this {
86 MockTransaction::Legacy { ref mut $field, .. } |
87 MockTransaction::Eip1559 { ref mut $field, .. } |
88 MockTransaction::Eip4844 { ref mut $field, .. } |
89 MockTransaction::Eip2930 { ref mut $field, .. } |
90 MockTransaction::Eip7702 { ref mut $field, .. } => {
91 *$field = new_value;
92 }
93 }
94 $this.update_cost();
96 }};
97}
98
99macro_rules! get_value {
101 ($this:tt => $field:ident) => {
102 match $this {
103 MockTransaction::Legacy { $field, .. } |
104 MockTransaction::Eip1559 { $field, .. } |
105 MockTransaction::Eip4844 { $field, .. } |
106 MockTransaction::Eip2930 { $field, .. } |
107 MockTransaction::Eip7702 { $field, .. } => $field,
108 }
109 };
110}
111
112macro_rules! make_setters_getters {
114 ($($name:ident => $t:ty);*) => {
115 paste! {$(
116 pub fn [<set_ $name>](&mut self, $name: $t) -> &mut Self {
118 set_value!(&mut self => $name);
119 self
120 }
121
122 pub fn [<with_ $name>](mut self, $name: $t) -> Self {
124 set_value!(self => $name);
125 self
126 }
127
128 pub const fn [<get_ $name>](&self) -> &$t {
130 get_value!(self => $name)
131 }
132 )*}
133 };
134}
135
136#[derive(Debug, Clone, Eq, PartialEq)]
138pub enum MockTransaction {
139 Legacy {
141 chain_id: Option<ChainId>,
143 hash: B256,
145 sender: Address,
147 nonce: u64,
149 gas_price: u128,
151 gas_limit: u64,
153 to: TxKind,
155 value: U256,
157 input: Bytes,
159 size: usize,
161 cost: U256,
163 },
164 Eip2930 {
166 chain_id: ChainId,
168 hash: B256,
170 sender: Address,
172 nonce: u64,
174 to: TxKind,
176 gas_limit: u64,
178 input: Bytes,
180 value: U256,
182 gas_price: u128,
184 access_list: AccessList,
186 size: usize,
188 cost: U256,
190 },
191 Eip1559 {
193 chain_id: ChainId,
195 hash: B256,
197 sender: Address,
199 nonce: u64,
201 max_fee_per_gas: u128,
203 max_priority_fee_per_gas: u128,
205 gas_limit: u64,
207 to: TxKind,
209 value: U256,
211 access_list: AccessList,
213 input: Bytes,
215 size: usize,
217 cost: U256,
219 },
220 Eip4844 {
222 chain_id: ChainId,
224 hash: B256,
226 sender: Address,
228 nonce: u64,
230 max_fee_per_gas: u128,
232 max_priority_fee_per_gas: u128,
234 max_fee_per_blob_gas: u128,
236 gas_limit: u64,
238 to: Address,
240 value: U256,
242 access_list: AccessList,
244 input: Bytes,
246 sidecar: BlobTransactionSidecarVariant,
248 blob_versioned_hashes: Vec<B256>,
250 size: usize,
252 cost: U256,
254 },
255 Eip7702 {
257 chain_id: ChainId,
259 hash: B256,
261 sender: Address,
263 nonce: u64,
265 max_fee_per_gas: u128,
267 max_priority_fee_per_gas: u128,
269 gas_limit: u64,
271 to: Address,
273 value: U256,
275 access_list: AccessList,
277 authorization_list: Vec<SignedAuthorization>,
279 input: Bytes,
281 size: usize,
283 cost: U256,
285 },
286}
287
288impl MockTransaction {
291 make_setters_getters! {
292 nonce => u64;
293 hash => B256;
294 sender => Address;
295 gas_limit => u64;
296 value => U256;
297 input => Bytes;
298 size => usize
299 }
300
301 pub fn legacy() -> Self {
303 Self::Legacy {
304 chain_id: Some(1),
305 hash: B256::random(),
306 sender: Address::random(),
307 nonce: 0,
308 gas_price: 0,
309 gas_limit: 0,
310 to: Address::random().into(),
311 value: Default::default(),
312 input: Default::default(),
313 size: Default::default(),
314 cost: U256::ZERO,
315 }
316 }
317
318 pub fn eip2930() -> Self {
320 Self::Eip2930 {
321 chain_id: 1,
322 hash: B256::random(),
323 sender: Address::random(),
324 nonce: 0,
325 to: Address::random().into(),
326 gas_limit: 0,
327 input: Bytes::new(),
328 value: Default::default(),
329 gas_price: 0,
330 access_list: Default::default(),
331 size: Default::default(),
332 cost: U256::ZERO,
333 }
334 }
335
336 pub fn eip1559() -> Self {
338 Self::Eip1559 {
339 chain_id: 1,
340 hash: B256::random(),
341 sender: Address::random(),
342 nonce: 0,
343 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
344 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
345 gas_limit: 0,
346 to: Address::random().into(),
347 value: Default::default(),
348 input: Bytes::new(),
349 access_list: Default::default(),
350 size: Default::default(),
351 cost: U256::ZERO,
352 }
353 }
354
355 pub fn eip7702() -> Self {
357 Self::Eip7702 {
358 chain_id: 1,
359 hash: B256::random(),
360 sender: Address::random(),
361 nonce: 0,
362 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
363 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
364 gas_limit: 0,
365 to: Address::random(),
366 value: Default::default(),
367 input: Bytes::new(),
368 access_list: Default::default(),
369 authorization_list: vec![],
370 size: Default::default(),
371 cost: U256::ZERO,
372 }
373 }
374
375 pub fn eip4844() -> Self {
377 Self::Eip4844 {
378 chain_id: 1,
379 hash: B256::random(),
380 sender: Address::random(),
381 nonce: 0,
382 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
383 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
384 max_fee_per_blob_gas: DATA_GAS_PER_BLOB as u128,
385 gas_limit: 0,
386 to: Address::random(),
387 value: Default::default(),
388 input: Bytes::new(),
389 access_list: Default::default(),
390 sidecar: BlobTransactionSidecarVariant::Eip4844(Default::default()),
391 blob_versioned_hashes: Default::default(),
392 size: Default::default(),
393 cost: U256::ZERO,
394 }
395 }
396
397 pub fn eip4844_with_sidecar(sidecar: BlobTransactionSidecarVariant) -> Self {
399 let mut transaction = Self::eip4844();
400 if let Self::Eip4844 { sidecar: existing_sidecar, blob_versioned_hashes, .. } =
401 &mut transaction
402 {
403 *blob_versioned_hashes = sidecar.versioned_hashes().collect();
404 *existing_sidecar = sidecar;
405 }
406 transaction
407 }
408
409 pub fn new_from_type(tx_type: TxType) -> Self {
418 match tx_type {
419 TxType::Legacy => Self::legacy(),
420 TxType::Eip2930 => Self::eip2930(),
421 TxType::Eip1559 => Self::eip1559(),
422 TxType::Eip4844 => Self::eip4844(),
423 TxType::Eip7702 => Self::eip7702(),
424 }
425 }
426
427 pub const fn with_blob_fee(mut self, val: u128) -> Self {
429 self.set_blob_fee(val);
430 self
431 }
432
433 pub fn with_blob_hashes(mut self, count: usize) -> Self {
435 if let Self::Eip4844 { blob_versioned_hashes, .. } = &mut self {
436 *blob_versioned_hashes = (0..count).map(|_| B256::random()).collect();
437 }
438 self
439 }
440
441 pub const fn set_blob_fee(&mut self, val: u128) -> &mut Self {
443 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = self {
444 *max_fee_per_blob_gas = val;
445 }
446 self
447 }
448
449 pub const fn set_priority_fee(&mut self, val: u128) -> &mut Self {
451 if let Self::Eip1559 { max_priority_fee_per_gas, .. } |
452 Self::Eip4844 { max_priority_fee_per_gas, .. } = self
453 {
454 *max_priority_fee_per_gas = val;
455 }
456 self
457 }
458
459 pub const fn with_priority_fee(mut self, val: u128) -> Self {
461 self.set_priority_fee(val);
462 self
463 }
464
465 pub const fn get_priority_fee(&self) -> Option<u128> {
467 match self {
468 Self::Eip1559 { max_priority_fee_per_gas, .. } |
469 Self::Eip4844 { max_priority_fee_per_gas, .. } |
470 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
471 _ => None,
472 }
473 }
474
475 pub const fn set_max_fee(&mut self, val: u128) -> &mut Self {
477 if let Self::Eip1559 { max_fee_per_gas, .. } |
478 Self::Eip4844 { max_fee_per_gas, .. } |
479 Self::Eip7702 { max_fee_per_gas, .. } = self
480 {
481 *max_fee_per_gas = val;
482 }
483 self
484 }
485
486 pub const fn with_max_fee(mut self, val: u128) -> Self {
488 self.set_max_fee(val);
489 self
490 }
491
492 pub const fn get_max_fee(&self) -> Option<u128> {
494 match self {
495 Self::Eip1559 { max_fee_per_gas, .. } |
496 Self::Eip4844 { max_fee_per_gas, .. } |
497 Self::Eip7702 { max_fee_per_gas, .. } => Some(*max_fee_per_gas),
498 _ => None,
499 }
500 }
501
502 pub fn set_accesslist(&mut self, list: AccessList) -> &mut Self {
504 match self {
505 Self::Legacy { .. } => {}
506 Self::Eip1559 { access_list: accesslist, .. } |
507 Self::Eip4844 { access_list: accesslist, .. } |
508 Self::Eip2930 { access_list: accesslist, .. } |
509 Self::Eip7702 { access_list: accesslist, .. } => {
510 *accesslist = list;
511 }
512 }
513 self
514 }
515
516 pub fn set_authorization_list(&mut self, list: Vec<SignedAuthorization>) -> &mut Self {
518 if let Self::Eip7702 { authorization_list, .. } = self {
519 *authorization_list = list;
520 }
521
522 self
523 }
524
525 pub const fn set_gas_price(&mut self, val: u128) -> &mut Self {
527 match self {
528 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
529 *gas_price = val;
530 }
531 Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
532 Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
533 Self::Eip7702 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
534 *max_fee_per_gas = val;
535 *max_priority_fee_per_gas = val;
536 }
537 }
538 self
539 }
540
541 pub const fn with_gas_price(mut self, val: u128) -> Self {
543 match self {
544 Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
545 *gas_price = val;
546 }
547 Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
548 Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
549 Self::Eip7702 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
550 *max_fee_per_gas = val;
551 *max_priority_fee_per_gas = val;
552 }
553 }
554 self
555 }
556
557 pub const fn get_gas_price(&self) -> u128 {
559 match self {
560 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
561 Self::Eip1559 { max_fee_per_gas, .. } |
562 Self::Eip4844 { max_fee_per_gas, .. } |
563 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
564 }
565 }
566
567 pub fn prev(&self) -> Self {
569 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
570 }
571
572 pub fn next(&self) -> Self {
574 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
575 }
576
577 pub fn skip(&self, skip: u64) -> Self {
579 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
580 }
581
582 pub fn inc_nonce(self) -> Self {
584 let nonce = self.get_nonce() + 1;
585 self.with_nonce(nonce)
586 }
587
588 pub fn rng_hash(self) -> Self {
590 self.with_hash(B256::random())
591 }
592
593 pub fn inc_price(&self) -> Self {
595 self.inc_price_by(1)
596 }
597
598 pub fn inc_price_by(&self, value: u128) -> Self {
600 self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
601 }
602
603 pub fn decr_price(&self) -> Self {
605 self.decr_price_by(1)
606 }
607
608 pub fn decr_price_by(&self, value: u128) -> Self {
610 self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
611 }
612
613 pub fn inc_value(&self) -> Self {
615 self.clone().with_value(self.get_value().checked_add(U256::ONE).unwrap())
616 }
617
618 pub fn inc_limit(&self) -> Self {
620 self.clone().with_gas_limit(self.get_gas_limit() + 1)
621 }
622
623 pub fn inc_blob_fee(&self) -> Self {
627 self.inc_blob_fee_by(1)
628 }
629
630 pub fn inc_blob_fee_by(&self, value: u128) -> Self {
634 let mut this = self.clone();
635 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
636 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
637 }
638 this
639 }
640
641 pub fn decr_blob_fee(&self) -> Self {
645 self.decr_price_by(1)
646 }
647
648 pub fn decr_blob_fee_by(&self, value: u128) -> Self {
652 let mut this = self.clone();
653 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
654 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
655 }
656 this
657 }
658
659 pub const fn tx_type(&self) -> u8 {
661 match self {
662 Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
663 Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
664 Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
665 Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
666 Self::Eip7702 { .. } => EIP7702_TX_TYPE_ID,
667 }
668 }
669
670 pub const fn is_legacy(&self) -> bool {
672 matches!(self, Self::Legacy { .. })
673 }
674
675 pub const fn is_eip1559(&self) -> bool {
677 matches!(self, Self::Eip1559 { .. })
678 }
679
680 pub const fn is_eip4844(&self) -> bool {
682 matches!(self, Self::Eip4844 { .. })
683 }
684
685 pub const fn is_eip2930(&self) -> bool {
687 matches!(self, Self::Eip2930 { .. })
688 }
689
690 pub const fn is_eip7702(&self) -> bool {
692 matches!(self, Self::Eip7702 { .. })
693 }
694
695 fn update_cost(&mut self) {
696 match self {
697 Self::Legacy { cost, gas_limit, gas_price, value, .. } |
698 Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
699 *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
700 }
701 Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
702 Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } |
703 Self::Eip7702 { cost, gas_limit, max_fee_per_gas, value, .. } => {
704 *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
705 }
706 };
707 }
708}
709
710impl PoolTransaction for MockTransaction {
711 type TryFromConsensusError = ValueError<EthereumTxEnvelope<TxEip4844>>;
712
713 type Consensus = TransactionSigned;
714
715 type Pooled = PooledTransactionVariant;
716
717 fn encoded_2718_consensus(&self) -> Bytes {
718 self.clone_into_consensus().encoded_2718().into()
719 }
720
721 fn consensus_ref(&self) -> Recovered<&Self::Consensus> {
722 unimplemented!("mock transaction does not wrap a consensus transaction")
723 }
724
725 fn into_consensus(self) -> Recovered<Self::Consensus> {
726 self.into()
727 }
728
729 fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self {
730 pooled.into()
731 }
732
733 fn recover_raw_transaction(data: &[u8]) -> Result<Self, RawPoolTransactionError> {
734 let mut transaction = Self::try_recover(Self::decode_raw_transaction(data)?)
735 .map_err(|_| RawPoolTransactionError::InvalidTransactionSignature)?;
736 transaction.set_size(data.len());
737 Ok(transaction)
738 }
739
740 fn recover_raw_transaction_with_cache(
741 data: &[u8],
742 cache: &reth_evm::SenderRecoveryCache,
743 ) -> Result<Self, RawPoolTransactionError> {
744 let mut transaction =
745 Self::try_recover_with_cache(Self::decode_raw_transaction(data)?, cache)
746 .map_err(|_| RawPoolTransactionError::InvalidTransactionSignature)?;
747 transaction.set_size(data.len());
748 Ok(transaction)
749 }
750
751 fn hash(&self) -> &TxHash {
752 self.get_hash()
753 }
754
755 fn sender(&self) -> Address {
756 *self.get_sender()
757 }
758
759 fn sender_ref(&self) -> &Address {
760 self.get_sender()
761 }
762
763 fn cost(&self) -> &U256 {
768 match self {
769 Self::Legacy { cost, .. } |
770 Self::Eip2930 { cost, .. } |
771 Self::Eip1559 { cost, .. } |
772 Self::Eip4844 { cost, .. } |
773 Self::Eip7702 { cost, .. } => cost,
774 }
775 }
776
777 fn encoded_length(&self) -> usize {
779 self.size()
780 }
781}
782
783impl InMemorySize for MockTransaction {
784 fn size(&self) -> usize {
785 *self.get_size()
786 }
787}
788
789impl Typed2718 for MockTransaction {
790 fn ty(&self) -> u8 {
791 match self {
792 Self::Legacy { .. } => TxType::Legacy.into(),
793 Self::Eip1559 { .. } => TxType::Eip1559.into(),
794 Self::Eip4844 { .. } => TxType::Eip4844.into(),
795 Self::Eip2930 { .. } => TxType::Eip2930.into(),
796 Self::Eip7702 { .. } => TxType::Eip7702.into(),
797 }
798 }
799}
800
801impl alloy_consensus::Transaction for MockTransaction {
802 fn chain_id(&self) -> Option<u64> {
803 match self {
804 Self::Legacy { chain_id, .. } => *chain_id,
805 Self::Eip1559 { chain_id, .. } |
806 Self::Eip4844 { chain_id, .. } |
807 Self::Eip2930 { chain_id, .. } |
808 Self::Eip7702 { chain_id, .. } => Some(*chain_id),
809 }
810 }
811
812 fn nonce(&self) -> u64 {
813 *self.get_nonce()
814 }
815
816 fn gas_limit(&self) -> u64 {
817 *self.get_gas_limit()
818 }
819
820 fn gas_price(&self) -> Option<u128> {
821 match self {
822 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => Some(*gas_price),
823 _ => None,
824 }
825 }
826
827 fn max_fee_per_gas(&self) -> u128 {
828 match self {
829 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
830 Self::Eip1559 { max_fee_per_gas, .. } |
831 Self::Eip4844 { max_fee_per_gas, .. } |
832 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
833 }
834 }
835
836 fn max_priority_fee_per_gas(&self) -> Option<u128> {
837 match self {
838 Self::Legacy { .. } | Self::Eip2930 { .. } => None,
839 Self::Eip1559 { max_priority_fee_per_gas, .. } |
840 Self::Eip4844 { max_priority_fee_per_gas, .. } |
841 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
842 }
843 }
844
845 fn max_fee_per_blob_gas(&self) -> Option<u128> {
846 match self {
847 Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
848 _ => None,
849 }
850 }
851
852 fn priority_fee_or_price(&self) -> u128 {
853 match self {
854 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
855 Self::Eip1559 { max_priority_fee_per_gas, .. } |
856 Self::Eip4844 { max_priority_fee_per_gas, .. } |
857 Self::Eip7702 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
858 }
859 }
860
861 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
862 match self.max_priority_fee_per_gas() {
863 Some(max_tip) => calc_effective_gas_price(self.max_fee_per_gas(), max_tip, base_fee),
864 None => self.max_fee_per_gas(),
865 }
866 }
867
868 fn is_dynamic_fee(&self) -> bool {
869 !matches!(self, Self::Legacy { .. } | Self::Eip2930 { .. })
870 }
871
872 fn kind(&self) -> TxKind {
873 match self {
874 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
875 Self::Eip4844 { to, .. } | Self::Eip7702 { to, .. } => TxKind::Call(*to),
876 }
877 }
878
879 fn is_create(&self) -> bool {
880 match self {
881 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
882 to.is_create()
883 }
884 Self::Eip4844 { .. } | Self::Eip7702 { .. } => false,
885 }
886 }
887
888 fn value(&self) -> U256 {
889 match self {
890 Self::Legacy { value, .. } |
891 Self::Eip1559 { value, .. } |
892 Self::Eip2930 { value, .. } |
893 Self::Eip4844 { value, .. } |
894 Self::Eip7702 { value, .. } => *value,
895 }
896 }
897
898 fn input(&self) -> &Bytes {
899 self.get_input()
900 }
901
902 fn access_list(&self) -> Option<&AccessList> {
903 match self {
904 Self::Legacy { .. } => None,
905 Self::Eip1559 { access_list: accesslist, .. } |
906 Self::Eip4844 { access_list: accesslist, .. } |
907 Self::Eip2930 { access_list: accesslist, .. } |
908 Self::Eip7702 { access_list: accesslist, .. } => Some(accesslist),
909 }
910 }
911
912 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
913 match self {
914 Self::Eip4844 { blob_versioned_hashes, .. } => Some(blob_versioned_hashes),
915 _ => None,
916 }
917 }
918
919 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
920 match self {
921 Self::Eip7702 { authorization_list, .. } => Some(authorization_list),
922 _ => None,
923 }
924 }
925}
926
927impl EthPoolTransaction for MockTransaction {
928 fn take_blob(&mut self) -> EthBlobTransactionSidecar {
929 match self {
930 Self::Eip4844 { sidecar, .. } => {
931 EthBlobTransactionSidecar::Present(sidecar.clone().into())
932 }
933 _ => EthBlobTransactionSidecar::None,
934 }
935 }
936
937 fn try_into_pooled_eip4844(
938 self,
939 sidecar: Arc<BlobTransactionSidecarVariant>,
940 ) -> Option<Recovered<Self::Pooled>> {
941 let (tx, signer) = self.into_consensus().into_parts();
942 tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
943 .map(|tx| tx.with_signer(signer))
944 .ok()
945 }
946
947 fn try_from_eip4844(
948 tx: Recovered<Self::Consensus>,
949 sidecar: BlobTransactionSidecarVariant,
950 ) -> Option<Self> {
951 let (tx, signer) = tx.into_parts();
952 tx.try_into_pooled_eip4844(sidecar)
953 .map(|tx| tx.with_signer(signer))
954 .ok()
955 .map(Self::from_pooled)
956 }
957
958 fn validate_blob(
959 &self,
960 _blob: &BlobTransactionSidecarVariant,
961 _settings: &KzgSettings,
962 ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
963 match &self {
964 Self::Eip4844 { .. } => Ok(()),
965 _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
966 }
967 }
968}
969
970impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
971 type Error = TryFromRecoveredTransactionError;
972
973 fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
974 let (transaction, sender) = tx.into_parts();
975 let hash = *transaction.tx_hash();
976 let size = transaction.size();
977
978 match transaction.into_typed_transaction() {
979 Transaction::Legacy(TxLegacy {
980 chain_id,
981 nonce,
982 gas_price,
983 gas_limit,
984 to,
985 value,
986 input,
987 }) => Ok(Self::Legacy {
988 chain_id,
989 hash,
990 sender,
991 nonce,
992 gas_price,
993 gas_limit,
994 to,
995 value,
996 input,
997 size,
998 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
999 }),
1000 Transaction::Eip2930(TxEip2930 {
1001 chain_id,
1002 nonce,
1003 gas_price,
1004 gas_limit,
1005 to,
1006 value,
1007 input,
1008 access_list,
1009 }) => Ok(Self::Eip2930 {
1010 chain_id,
1011 hash,
1012 sender,
1013 nonce,
1014 gas_price,
1015 gas_limit,
1016 to,
1017 value,
1018 input,
1019 access_list,
1020 size,
1021 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
1022 }),
1023 Transaction::Eip1559(TxEip1559 {
1024 chain_id,
1025 nonce,
1026 gas_limit,
1027 max_fee_per_gas,
1028 max_priority_fee_per_gas,
1029 to,
1030 value,
1031 input,
1032 access_list,
1033 }) => Ok(Self::Eip1559 {
1034 chain_id,
1035 hash,
1036 sender,
1037 nonce,
1038 max_fee_per_gas,
1039 max_priority_fee_per_gas,
1040 gas_limit,
1041 to,
1042 value,
1043 input,
1044 access_list,
1045 size,
1046 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1047 }),
1048 Transaction::Eip4844(TxEip4844 {
1049 chain_id,
1050 nonce,
1051 gas_limit,
1052 max_fee_per_gas,
1053 max_priority_fee_per_gas,
1054 to,
1055 value,
1056 input,
1057 access_list,
1058 blob_versioned_hashes: _,
1059 max_fee_per_blob_gas,
1060 }) => Ok(Self::Eip4844 {
1061 chain_id,
1062 hash,
1063 sender,
1064 nonce,
1065 max_fee_per_gas,
1066 max_priority_fee_per_gas,
1067 max_fee_per_blob_gas,
1068 gas_limit,
1069 to,
1070 value,
1071 input,
1072 access_list,
1073 sidecar: BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default()),
1074 blob_versioned_hashes: Default::default(),
1075 size,
1076 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1077 }),
1078 Transaction::Eip7702(TxEip7702 {
1079 chain_id,
1080 nonce,
1081 gas_limit,
1082 max_fee_per_gas,
1083 max_priority_fee_per_gas,
1084 to,
1085 value,
1086 access_list,
1087 authorization_list,
1088 input,
1089 }) => Ok(Self::Eip7702 {
1090 chain_id,
1091 hash,
1092 sender,
1093 nonce,
1094 max_fee_per_gas,
1095 max_priority_fee_per_gas,
1096 gas_limit,
1097 to,
1098 value,
1099 input,
1100 access_list,
1101 authorization_list,
1102 size,
1103 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1104 }),
1105 }
1106 }
1107}
1108
1109impl TryFrom<Recovered<TxEnvelope>> for MockTransaction {
1110 type Error = TryFromRecoveredTransactionError;
1111
1112 fn try_from(tx: Recovered<TxEnvelope>) -> Result<Self, Self::Error> {
1113 let (transaction, sender) = tx.into_parts();
1114 let hash = *transaction.tx_hash();
1115 let size = transaction.size();
1116
1117 match transaction {
1118 EthereumTxEnvelope::Legacy(signed_tx) => {
1119 let tx = signed_tx.strip_signature();
1120 Ok(Self::Legacy {
1121 chain_id: tx.chain_id,
1122 hash,
1123 sender,
1124 nonce: tx.nonce,
1125 gas_price: tx.gas_price,
1126 gas_limit: tx.gas_limit,
1127 to: tx.to,
1128 value: tx.value,
1129 input: tx.input,
1130 size,
1131 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1132 })
1133 }
1134 EthereumTxEnvelope::Eip2930(signed_tx) => {
1135 let tx = signed_tx.strip_signature();
1136 Ok(Self::Eip2930 {
1137 chain_id: tx.chain_id,
1138 hash,
1139 sender,
1140 nonce: tx.nonce,
1141 gas_price: tx.gas_price,
1142 gas_limit: tx.gas_limit,
1143 to: tx.to,
1144 value: tx.value,
1145 input: tx.input,
1146 access_list: tx.access_list,
1147 size,
1148 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1149 })
1150 }
1151 EthereumTxEnvelope::Eip1559(signed_tx) => {
1152 let tx = signed_tx.strip_signature();
1153 Ok(Self::Eip1559 {
1154 chain_id: tx.chain_id,
1155 hash,
1156 sender,
1157 nonce: tx.nonce,
1158 max_fee_per_gas: tx.max_fee_per_gas,
1159 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1160 gas_limit: tx.gas_limit,
1161 to: tx.to,
1162 value: tx.value,
1163 input: tx.input,
1164 access_list: tx.access_list,
1165 size,
1166 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1167 })
1168 }
1169 EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1170 TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1171 chain_id: tx.chain_id,
1172 hash,
1173 sender,
1174 nonce: tx.nonce,
1175 max_fee_per_gas: tx.max_fee_per_gas,
1176 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1177 max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1178 gas_limit: tx.gas_limit,
1179 to: tx.to,
1180 value: tx.value,
1181 input: tx.input.clone(),
1182 access_list: tx.access_list.clone(),
1183 sidecar: BlobTransactionSidecarVariant::Eip4844(
1184 BlobTransactionSidecar::default(),
1185 ),
1186 blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1187 size,
1188 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1189 }),
1190 tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1191 },
1192 EthereumTxEnvelope::Eip7702(signed_tx) => {
1193 let tx = signed_tx.strip_signature();
1194 Ok(Self::Eip7702 {
1195 chain_id: tx.chain_id,
1196 hash,
1197 sender,
1198 nonce: tx.nonce,
1199 max_fee_per_gas: tx.max_fee_per_gas,
1200 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1201 gas_limit: tx.gas_limit,
1202 to: tx.to,
1203 value: tx.value,
1204 access_list: tx.access_list,
1205 authorization_list: tx.authorization_list,
1206 input: tx.input,
1207 size,
1208 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1209 })
1210 }
1211 }
1212 }
1213}
1214
1215impl From<Recovered<PooledTransactionVariant>> for MockTransaction {
1216 fn from(tx: Recovered<PooledTransactionVariant>) -> Self {
1217 tx.convert::<TransactionSigned>().try_into().expect(
1218 "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1219 )
1220 }
1221}
1222
1223impl From<MockTransaction> for Recovered<TransactionSigned> {
1224 fn from(tx: MockTransaction) -> Self {
1225 let hash = *tx.hash();
1226 let sender = tx.sender();
1227 let tx = Transaction::from(tx);
1228 let tx: TransactionSigned =
1229 Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1230 Self::new_unchecked(tx, sender)
1231 }
1232}
1233
1234impl From<MockTransaction> for Transaction {
1235 fn from(mock: MockTransaction) -> Self {
1236 match mock {
1237 MockTransaction::Legacy {
1238 chain_id,
1239 nonce,
1240 gas_price,
1241 gas_limit,
1242 to,
1243 value,
1244 input,
1245 ..
1246 } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1247 MockTransaction::Eip2930 {
1248 chain_id,
1249 nonce,
1250 gas_price,
1251 gas_limit,
1252 to,
1253 value,
1254 access_list,
1255 input,
1256 ..
1257 } => Self::Eip2930(TxEip2930 {
1258 chain_id,
1259 nonce,
1260 gas_price,
1261 gas_limit,
1262 to,
1263 value,
1264 access_list,
1265 input,
1266 }),
1267 MockTransaction::Eip1559 {
1268 chain_id,
1269 nonce,
1270 gas_limit,
1271 max_fee_per_gas,
1272 max_priority_fee_per_gas,
1273 to,
1274 value,
1275 access_list,
1276 input,
1277 ..
1278 } => Self::Eip1559(TxEip1559 {
1279 chain_id,
1280 nonce,
1281 gas_limit,
1282 max_fee_per_gas,
1283 max_priority_fee_per_gas,
1284 to,
1285 value,
1286 access_list,
1287 input,
1288 }),
1289 MockTransaction::Eip4844 {
1290 chain_id,
1291 nonce,
1292 gas_limit,
1293 max_fee_per_gas,
1294 max_priority_fee_per_gas,
1295 to,
1296 value,
1297 access_list,
1298 sidecar,
1299 max_fee_per_blob_gas,
1300 input,
1301 ..
1302 } => Self::Eip4844(TxEip4844 {
1303 chain_id,
1304 nonce,
1305 gas_limit,
1306 max_fee_per_gas,
1307 max_priority_fee_per_gas,
1308 to,
1309 value,
1310 access_list,
1311 blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1312 max_fee_per_blob_gas,
1313 input,
1314 }),
1315 MockTransaction::Eip7702 {
1316 chain_id,
1317 nonce,
1318 gas_limit,
1319 max_fee_per_gas,
1320 max_priority_fee_per_gas,
1321 to,
1322 value,
1323 access_list,
1324 input,
1325 authorization_list,
1326 ..
1327 } => Self::Eip7702(TxEip7702 {
1328 chain_id,
1329 nonce,
1330 gas_limit,
1331 max_fee_per_gas,
1332 max_priority_fee_per_gas,
1333 to,
1334 value,
1335 access_list,
1336 authorization_list,
1337 input,
1338 }),
1339 }
1340 }
1341}
1342
1343#[cfg(any(test, feature = "arbitrary"))]
1344impl proptest::arbitrary::Arbitrary for MockTransaction {
1345 type Parameters = ();
1346 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1347 use proptest::prelude::Strategy;
1348 use proptest_arbitrary_interop::arb;
1349
1350 arb::<(TransactionSigned, Address)>()
1351 .prop_map(|(signed_transaction, signer)| {
1352 Recovered::new_unchecked(signed_transaction, signer)
1353 .try_into()
1354 .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1355 })
1356 .boxed()
1357 }
1358
1359 type Strategy = proptest::strategy::BoxedStrategy<Self>;
1360}
1361
1362#[derive(Debug, Default)]
1364pub struct MockTransactionFactory {
1365 pub(crate) ids: SenderIdentifiers,
1366}
1367
1368impl MockTransactionFactory {
1371 pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1373 let sender = self.ids.sender_id_or_create(tx.sender());
1374 TransactionId::new(sender, *tx.get_nonce())
1375 }
1376
1377 pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1379 self.validated_with_origin(TransactionOrigin::External, transaction)
1380 }
1381
1382 pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1384 Arc::new(self.validated(transaction))
1385 }
1386
1387 pub fn validated_with_origin(
1389 &mut self,
1390 origin: TransactionOrigin,
1391 transaction: MockTransaction,
1392 ) -> MockValidTx {
1393 MockValidTx {
1394 propagate: false,
1395 transaction_id: self.tx_id(&transaction),
1396 transaction,
1397 timestamp: Instant::now(),
1398 origin,
1399 authority_ids: None,
1400 }
1401 }
1402
1403 pub fn create_legacy(&mut self) -> MockValidTx {
1405 self.validated(MockTransaction::legacy())
1406 }
1407
1408 pub fn create_eip1559(&mut self) -> MockValidTx {
1410 self.validated(MockTransaction::eip1559())
1411 }
1412
1413 pub fn create_eip4844(&mut self) -> MockValidTx {
1415 self.validated(MockTransaction::eip4844())
1416 }
1417}
1418
1419pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1421
1422#[derive(Debug, Clone)]
1425pub struct MockTransactionRatio {
1426 pub legacy_pct: u32,
1428 pub access_list_pct: u32,
1430 pub dynamic_fee_pct: u32,
1432 pub blob_pct: u32,
1434}
1435
1436impl MockTransactionRatio {
1437 pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1443 let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1444 assert_eq!(
1445 total,
1446 100,
1447 "percentages must sum up to 100, instead got legacy: {legacy_pct}, access_list: {access_list_pct}, dynamic_fee: {dynamic_fee_pct}, blob: {blob_pct}, total: {total}",
1448 );
1449
1450 Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1451 }
1452
1453 pub fn weighted_index(&self) -> WeightedIndex<u32> {
1461 WeightedIndex::new([
1462 self.legacy_pct,
1463 self.access_list_pct,
1464 self.dynamic_fee_pct,
1465 self.blob_pct,
1466 ])
1467 .unwrap()
1468 }
1469}
1470
1471#[derive(Debug, Clone)]
1473pub struct MockFeeRange {
1474 pub gas_price: Uniform<u128>,
1476 pub priority_fee: Uniform<u128>,
1478 pub max_fee: Uniform<u128>,
1480 pub max_fee_blob: Uniform<u128>,
1482}
1483
1484impl MockFeeRange {
1485 pub fn new(
1490 gas_price: Range<u128>,
1491 priority_fee: Range<u128>,
1492 max_fee: Range<u128>,
1493 max_fee_blob: Range<u128>,
1494 ) -> Self {
1495 assert!(
1496 max_fee.start >= priority_fee.end,
1497 "max_fee_range should be strictly above the priority fee range"
1498 );
1499 Self {
1500 gas_price: gas_price.try_into().unwrap(),
1501 priority_fee: priority_fee.try_into().unwrap(),
1502 max_fee: max_fee.try_into().unwrap(),
1503 max_fee_blob: max_fee_blob.try_into().unwrap(),
1504 }
1505 }
1506
1507 pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1510 self.gas_price.sample(rng)
1511 }
1512
1513 pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1516 self.priority_fee.sample(rng)
1517 }
1518
1519 pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1522 self.max_fee.sample(rng)
1523 }
1524
1525 pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1528 self.max_fee_blob.sample(rng)
1529 }
1530}
1531
1532#[derive(Debug, Clone)]
1534pub struct MockTransactionDistribution {
1535 transaction_ratio: MockTransactionRatio,
1537 gas_limit_range: Uniform<u64>,
1539 size_range: Uniform<usize>,
1541 fee_ranges: MockFeeRange,
1543}
1544
1545impl MockTransactionDistribution {
1546 pub fn new(
1548 transaction_ratio: MockTransactionRatio,
1549 fee_ranges: MockFeeRange,
1550 gas_limit_range: Range<u64>,
1551 size_range: Range<usize>,
1552 ) -> Self {
1553 Self {
1554 transaction_ratio,
1555 gas_limit_range: gas_limit_range.try_into().unwrap(),
1556 fee_ranges,
1557 size_range: size_range.try_into().unwrap(),
1558 }
1559 }
1560
1561 pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1563 let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1564 let tx = match transaction_sample {
1565 0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1566 1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1567 2 => MockTransaction::eip1559()
1568 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1569 .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1570 3 => MockTransaction::eip4844()
1571 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1572 .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1573 .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1574 _ => unreachable!("unknown transaction type returned by the weighted index"),
1575 };
1576
1577 let size = self.size_range.sample(rng);
1578
1579 tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1580 }
1581
1582 pub fn tx_set(
1586 &self,
1587 sender: Address,
1588 nonce_range: Range<u64>,
1589 rng: &mut impl rand::Rng,
1590 ) -> MockTransactionSet {
1591 let txs =
1592 nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1593 MockTransactionSet::new(txs)
1594 }
1595
1596 pub fn tx_set_non_conflicting_types(
1602 &self,
1603 sender: Address,
1604 nonce_range: Range<u64>,
1605 rng: &mut impl rand::Rng,
1606 ) -> NonConflictingSetOutcome {
1607 let mut modified_distribution = self.clone();
1615 let first_tx = self.tx(nonce_range.start, rng);
1616
1617 if first_tx.is_eip4844() {
1620 modified_distribution.transaction_ratio = MockTransactionRatio {
1621 legacy_pct: 0,
1622 access_list_pct: 0,
1623 dynamic_fee_pct: 0,
1624 blob_pct: 100,
1625 };
1626
1627 NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1629 sender,
1630 nonce_range,
1631 rng,
1632 ))
1633 } else {
1634 let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1635 modified_distribution.transaction_ratio;
1636
1637 let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1639
1640 let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1642 .into_iter()
1643 .map(|weight| weight * 100 / total_non_blob_weight)
1644 .collect();
1645
1646 let new_ratio = MockTransactionRatio {
1647 legacy_pct: new_weights[0],
1648 access_list_pct: new_weights[1],
1649 dynamic_fee_pct: new_weights[2],
1650 blob_pct: 0,
1651 };
1652
1653 modified_distribution.transaction_ratio = new_ratio;
1656
1657 NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1659 }
1660 }
1661}
1662
1663#[derive(Debug, Clone)]
1666pub enum NonConflictingSetOutcome {
1667 BlobsOnly(MockTransactionSet),
1669 Mixed(MockTransactionSet),
1671}
1672
1673impl NonConflictingSetOutcome {
1674 pub fn into_inner(self) -> MockTransactionSet {
1676 match self {
1677 Self::BlobsOnly(set) | Self::Mixed(set) => set,
1678 }
1679 }
1680
1681 pub fn with_nonce_gaps(
1689 &mut self,
1690 gap_pct: u32,
1691 gap_range: Range<u64>,
1692 rng: &mut impl rand::Rng,
1693 ) {
1694 match self {
1695 Self::BlobsOnly(_) => {}
1696 Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1697 }
1698 }
1699}
1700
1701#[derive(Debug, Clone)]
1703pub struct MockTransactionSet {
1704 pub(crate) transactions: Vec<MockTransaction>,
1705}
1706
1707impl MockTransactionSet {
1708 const fn new(transactions: Vec<MockTransaction>) -> Self {
1710 Self { transactions }
1711 }
1712
1713 pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1720 let mut txs = Vec::with_capacity(tx_count);
1721 let mut curr_tx =
1722 MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1723 for _ in 0..tx_count {
1724 txs.push(curr_tx.clone());
1725 curr_tx = curr_tx.next();
1726 }
1727
1728 Self::new(txs)
1729 }
1730
1731 pub fn sequential_transactions_by_sender(
1736 sender: Address,
1737 tx_count: usize,
1738 tx_type: TxType,
1739 ) -> Self {
1740 Self::dependent(sender, 0, tx_count, tx_type)
1741 }
1742
1743 pub fn with_nonce_gaps(
1757 &mut self,
1758 gap_pct: u32,
1759 gap_range: Range<u64>,
1760 rng: &mut impl rand::Rng,
1761 ) {
1762 assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1763 assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1764
1765 let mut prev_nonce = 0;
1766 for tx in &mut self.transactions {
1767 if rng.random_bool(gap_pct as f64 / 100.0) {
1768 prev_nonce += gap_range.start;
1769 } else {
1770 prev_nonce += 1;
1771 }
1772 tx.set_nonce(prev_nonce);
1773 }
1774 }
1775
1776 pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1778 self.transactions.extend(txs);
1779 }
1780
1781 pub fn into_vec(self) -> Vec<MockTransaction> {
1783 self.transactions
1784 }
1785
1786 pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1788 self.transactions.iter()
1789 }
1790
1791 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1793 self.transactions.iter_mut()
1794 }
1795}
1796
1797impl IntoIterator for MockTransactionSet {
1798 type Item = MockTransaction;
1799 type IntoIter = IntoIter<MockTransaction>;
1800
1801 fn into_iter(self) -> Self::IntoIter {
1802 self.transactions.into_iter()
1803 }
1804}
1805
1806#[test]
1807fn test_mock_priority() {
1808 use crate::TransactionOrdering;
1809
1810 let o = MockOrdering::default();
1811 let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1812 let hi = lo.next().inc_price();
1813 assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1814}
1815
1816#[cfg(test)]
1817mod tests {
1818 use super::*;
1819 use alloy_consensus::Transaction;
1820 use alloy_primitives::U256;
1821
1822 #[test]
1823 fn test_mock_transaction_factory() {
1824 let mut factory = MockTransactionFactory::default();
1825
1826 let legacy = factory.create_legacy();
1828 assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1829
1830 let eip1559 = factory.create_eip1559();
1832 assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1833
1834 let eip4844 = factory.create_eip4844();
1836 assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1837 }
1838
1839 #[test]
1840 fn test_mock_transaction_set() {
1841 let sender = Address::random();
1842 let nonce_start = 0u64;
1843 let count = 3;
1844
1845 let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1847 assert_eq!(legacy_set.transactions.len(), count);
1848 for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1849 assert_eq!(tx.tx_type(), TxType::Legacy);
1850 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1851 assert_eq!(tx.sender(), sender);
1852 }
1853
1854 let eip1559_set =
1856 MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1857 assert_eq!(eip1559_set.transactions.len(), count);
1858 for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1859 assert_eq!(tx.tx_type(), TxType::Eip1559);
1860 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1861 assert_eq!(tx.sender(), sender);
1862 }
1863 }
1864
1865 #[test]
1866 fn test_mock_transaction_modifications() {
1867 let tx = MockTransaction::eip1559();
1868
1869 let original_price = tx.get_gas_price();
1871 let tx_inc = tx.inc_price();
1872 assert!(tx_inc.get_gas_price() > original_price);
1873
1874 let original_limit = tx.gas_limit();
1876 let tx_inc = tx.inc_limit();
1877 assert!(tx_inc.gas_limit() > original_limit);
1878
1879 let original_nonce = tx.nonce();
1881 let tx_inc = tx.inc_nonce();
1882 assert_eq!(tx_inc.nonce(), original_nonce + 1);
1883 }
1884
1885 #[test]
1886 fn test_mock_transaction_cost() {
1887 let tx = MockTransaction::eip1559()
1888 .with_gas_limit(7_000)
1889 .with_max_fee(100)
1890 .with_value(U256::ZERO);
1891
1892 let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1894 assert_eq!(*tx.cost(), expected_cost);
1895 }
1896}