reth_transaction_pool/test_utils/
mock.rs

1//! Mock types.
2
3use crate::{
4    identifier::{SenderIdentifiers, TransactionId},
5    pool::txpool::TxPool,
6    traits::TransactionOrigin,
7    CoinbaseTipOrdering, EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction,
8    ValidPoolTransaction,
9};
10use alloy_consensus::{
11    constants::{EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID},
12    transaction::TxSeismicElements,
13    TxEip1559, TxEip2930, TxEip4844, TxLegacy,
14};
15use alloy_eips::{
16    eip1559::MIN_PROTOCOL_BASE_FEE,
17    eip2930::AccessList,
18    eip4844::{BlobTransactionSidecar, BlobTransactionValidationError, DATA_GAS_PER_BLOB},
19};
20use alloy_primitives::{
21    Address, Bytes, ChainId, PrimitiveSignature as Signature, TxHash, TxKind, B256, U256,
22};
23use paste::paste;
24use rand::{
25    distributions::{Uniform, WeightedIndex},
26    prelude::Distribution,
27};
28use reth_primitives::{
29    transaction::{SignedTransactionIntoRecoveredExt, TryFromRecoveredTransactionError},
30    PooledTransactionsElement, PooledTransactionsElementEcRecovered, RecoveredTx, Transaction,
31    TransactionSigned, TxType,
32};
33use reth_primitives_traits::InMemorySize;
34use std::{ops::Range, sync::Arc, time::Instant, vec::IntoIter};
35
36/// A transaction pool implementation using [`MockOrdering`] for transaction ordering.
37///
38/// This type is an alias for [`TxPool<MockOrdering>`].
39pub type MockTxPool = TxPool<MockOrdering>;
40
41/// A validated transaction in the transaction pool, using [`MockTransaction`] as the transaction
42/// type.
43///
44/// This type is an alias for [`ValidPoolTransaction<MockTransaction>`].
45pub type MockValidTx = ValidPoolTransaction<MockTransaction>;
46
47/// Create an empty `TxPool`
48pub fn mock_tx_pool() -> MockTxPool {
49    MockTxPool::new(Default::default(), Default::default())
50}
51
52/// Sets the value for the field
53macro_rules! set_value {
54    ($this:ident => $field:ident) => {
55        let new_value = $field;
56        match $this {
57            MockTransaction::Legacy { ref mut $field, .. } |
58            MockTransaction::Eip1559 { ref mut $field, .. } |
59            MockTransaction::Eip4844 { ref mut $field, .. } |
60            MockTransaction::Eip2930 { ref mut $field, .. } => {
61                *$field = new_value;
62            }
63        }
64        // Ensure the tx cost is always correct after each mutation.
65        $this.update_cost();
66    };
67}
68
69/// Gets the value for the field
70macro_rules! get_value {
71    ($this:tt => $field:ident) => {
72        match $this {
73            MockTransaction::Legacy { $field, .. } |
74            MockTransaction::Eip1559 { $field, .. } |
75            MockTransaction::Eip4844 { $field, .. } |
76            MockTransaction::Eip2930 { $field, .. } => $field,
77        }
78    };
79}
80
81// Generates all setters and getters
82macro_rules! make_setters_getters {
83    ($($name:ident => $t:ty);*) => {
84        paste! {$(
85            /// Sets the value of the specified field.
86            pub fn [<set_ $name>](&mut self, $name: $t) -> &mut Self {
87                set_value!(self => $name);
88                self
89            }
90
91            /// Sets the value of the specified field using a fluent interface.
92            pub fn [<with_ $name>](mut self, $name: $t) -> Self {
93                set_value!(self => $name);
94                self
95            }
96
97            /// Gets the value of the specified field.
98            pub const fn [<get_ $name>](&self) -> &$t {
99                get_value!(self => $name)
100            }
101        )*}
102    };
103}
104
105/// A Bare transaction type used for testing.
106#[derive(Debug, Clone, Eq, PartialEq)]
107pub enum MockTransaction {
108    /// Legacy transaction type.
109    Legacy {
110        /// The chain id of the transaction.
111        chain_id: Option<ChainId>,
112        /// The hash of the transaction.
113        hash: B256,
114        /// The sender's address.
115        sender: Address,
116        /// The transaction nonce.
117        nonce: u64,
118        /// The gas price for the transaction.
119        gas_price: u128,
120        /// The gas limit for the transaction.
121        gas_limit: u64,
122        /// The transaction's destination.
123        to: TxKind,
124        /// The value of the transaction.
125        value: U256,
126        /// The transaction input data.
127        input: Bytes,
128        /// The size of the transaction, returned in the implementation of [`PoolTransaction`].
129        size: usize,
130        /// The cost of the transaction, returned in the implementation of [`PoolTransaction`].
131        cost: U256,
132    },
133    /// EIP-2930 transaction type.
134    Eip2930 {
135        /// The chain id of the transaction.
136        chain_id: ChainId,
137        /// The hash of the transaction.
138        hash: B256,
139        /// The sender's address.
140        sender: Address,
141        /// The transaction nonce.
142        nonce: u64,
143        /// The transaction's destination.
144        to: TxKind,
145        /// The gas limit for the transaction.
146        gas_limit: u64,
147        /// The transaction input data.
148        input: Bytes,
149        /// The value of the transaction.
150        value: U256,
151        /// The gas price for the transaction.
152        gas_price: u128,
153        /// The access list associated with the transaction.
154        access_list: AccessList,
155        /// The size of the transaction, returned in the implementation of [`PoolTransaction`].
156        size: usize,
157        /// The cost of the transaction, returned in the implementation of [`PoolTransaction`].
158        cost: U256,
159    },
160    /// EIP-1559 transaction type.
161    Eip1559 {
162        /// The chain id of the transaction.
163        chain_id: ChainId,
164        /// The hash of the transaction.
165        hash: B256,
166        /// The sender's address.
167        sender: Address,
168        /// The transaction nonce.
169        nonce: u64,
170        /// The maximum fee per gas for the transaction.
171        max_fee_per_gas: u128,
172        /// The maximum priority fee per gas for the transaction.
173        max_priority_fee_per_gas: u128,
174        /// The gas limit for the transaction.
175        gas_limit: u64,
176        /// The transaction's destination.
177        to: TxKind,
178        /// The value of the transaction.
179        value: U256,
180        /// The access list associated with the transaction.
181        access_list: AccessList,
182        /// The transaction input data.
183        input: Bytes,
184        /// The size of the transaction, returned in the implementation of [`PoolTransaction`].
185        size: usize,
186        /// The cost of the transaction, returned in the implementation of [`PoolTransaction`].
187        cost: U256,
188    },
189    /// EIP-4844 transaction type.
190    Eip4844 {
191        /// The chain id of the transaction.
192        chain_id: ChainId,
193        /// The hash of the transaction.
194        hash: B256,
195        /// The sender's address.
196        sender: Address,
197        /// The transaction nonce.
198        nonce: u64,
199        /// The maximum fee per gas for the transaction.
200        max_fee_per_gas: u128,
201        /// The maximum priority fee per gas for the transaction.
202        max_priority_fee_per_gas: u128,
203        /// The maximum fee per blob gas for the transaction.
204        max_fee_per_blob_gas: u128,
205        /// The gas limit for the transaction.
206        gas_limit: u64,
207        /// The transaction's destination.
208        to: Address,
209        /// The value of the transaction.
210        value: U256,
211        /// The access list associated with the transaction.
212        access_list: AccessList,
213        /// The transaction input data.
214        input: Bytes,
215        /// The sidecar information for the transaction.
216        sidecar: BlobTransactionSidecar,
217        /// The size of the transaction, returned in the implementation of [`PoolTransaction`].
218        size: usize,
219        /// The cost of the transaction, returned in the implementation of [`PoolTransaction`].
220        cost: U256,
221    },
222}
223
224// === impl MockTransaction ===
225
226impl MockTransaction {
227    make_setters_getters! {
228        nonce => u64;
229        hash => B256;
230        sender => Address;
231        gas_limit => u64;
232        value => U256;
233        input => Bytes;
234        size => usize
235    }
236
237    /// Returns a new legacy transaction with random address and hash and empty values
238    pub fn legacy() -> Self {
239        Self::Legacy {
240            chain_id: Some(1),
241            hash: B256::random(),
242            sender: Address::random(),
243            nonce: 0,
244            gas_price: 0,
245            gas_limit: 0,
246            to: Address::random().into(),
247            value: Default::default(),
248            input: Default::default(),
249            size: Default::default(),
250            cost: U256::ZERO,
251        }
252    }
253
254    /// Returns a new EIP2930 transaction with random address and hash and empty values
255    pub fn eip2930() -> Self {
256        Self::Eip2930 {
257            chain_id: 1,
258            hash: B256::random(),
259            sender: Address::random(),
260            nonce: 0,
261            to: Address::random().into(),
262            gas_limit: 0,
263            input: Bytes::new(),
264            value: Default::default(),
265            gas_price: 0,
266            access_list: Default::default(),
267            size: Default::default(),
268            cost: U256::ZERO,
269        }
270    }
271
272    /// Returns a new EIP1559 transaction with random address and hash and empty values
273    pub fn eip1559() -> Self {
274        Self::Eip1559 {
275            chain_id: 1,
276            hash: B256::random(),
277            sender: Address::random(),
278            nonce: 0,
279            max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
280            max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
281            gas_limit: 0,
282            to: Address::random().into(),
283            value: Default::default(),
284            input: Bytes::new(),
285            access_list: Default::default(),
286            size: Default::default(),
287            cost: U256::ZERO,
288        }
289    }
290
291    /// Returns a new EIP4844 transaction with random address and hash and empty values
292    pub fn eip4844() -> Self {
293        Self::Eip4844 {
294            chain_id: 1,
295            hash: B256::random(),
296            sender: Address::random(),
297            nonce: 0,
298            max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
299            max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
300            max_fee_per_blob_gas: DATA_GAS_PER_BLOB as u128,
301            gas_limit: 0,
302            to: Address::random(),
303            value: Default::default(),
304            input: Bytes::new(),
305            access_list: Default::default(),
306            sidecar: Default::default(),
307            size: Default::default(),
308            cost: U256::ZERO,
309        }
310    }
311
312    /// Returns a new EIP4844 transaction with a provided sidecar
313    pub fn eip4844_with_sidecar(sidecar: BlobTransactionSidecar) -> Self {
314        let mut transaction = Self::eip4844();
315        if let Self::Eip4844 { sidecar: ref mut existing_sidecar, .. } = &mut transaction {
316            *existing_sidecar = sidecar;
317        }
318        transaction
319    }
320
321    /// Creates a new transaction with the given [`TxType`].
322    ///
323    /// See the default constructors for each of the transaction types:
324    ///
325    /// * [`MockTransaction::legacy`]
326    /// * [`MockTransaction::eip2930`]
327    /// * [`MockTransaction::eip1559`]
328    /// * [`MockTransaction::eip4844`]
329    pub fn new_from_type(tx_type: TxType) -> Self {
330        #[allow(unreachable_patterns)]
331        match tx_type {
332            TxType::Legacy => Self::legacy(),
333            TxType::Eip2930 => Self::eip2930(),
334            TxType::Eip1559 => Self::eip1559(),
335            TxType::Eip4844 => Self::eip4844(),
336
337            _ => unreachable!("Invalid transaction type"),
338        }
339    }
340
341    /// Sets the max fee per blob gas for EIP-4844 transactions,
342    pub fn with_blob_fee(mut self, val: u128) -> Self {
343        self.set_blob_fee(val);
344        self
345    }
346
347    /// Sets the max fee per blob gas for EIP-4844 transactions,
348    pub fn set_blob_fee(&mut self, val: u128) -> &mut Self {
349        if let Self::Eip4844 { max_fee_per_blob_gas, .. } = self {
350            *max_fee_per_blob_gas = val;
351        }
352        self
353    }
354
355    /// Sets the priority fee for dynamic fee transactions (EIP-1559 and EIP-4844)
356    pub fn set_priority_fee(&mut self, val: u128) -> &mut Self {
357        if let Self::Eip1559 { max_priority_fee_per_gas, .. } |
358        Self::Eip4844 { max_priority_fee_per_gas, .. } = self
359        {
360            *max_priority_fee_per_gas = val;
361        }
362        self
363    }
364
365    /// Sets the priority fee for dynamic fee transactions (EIP-1559 and EIP-4844)
366    pub fn with_priority_fee(mut self, val: u128) -> Self {
367        self.set_priority_fee(val);
368        self
369    }
370
371    /// Gets the priority fee for dynamic fee transactions (EIP-1559 and EIP-4844)
372    pub const fn get_priority_fee(&self) -> Option<u128> {
373        match self {
374            Self::Eip1559 { max_priority_fee_per_gas, .. } |
375            Self::Eip4844 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
376            _ => None,
377        }
378    }
379
380    /// Sets the max fee for dynamic fee transactions (EIP-1559 and EIP-4844)
381    pub fn set_max_fee(&mut self, val: u128) -> &mut Self {
382        if let Self::Eip1559 { max_fee_per_gas, .. } | Self::Eip4844 { max_fee_per_gas, .. } = self
383        {
384            *max_fee_per_gas = val;
385        }
386        self
387    }
388
389    /// Sets the max fee for dynamic fee transactions (EIP-1559 and EIP-4844)
390    pub fn with_max_fee(mut self, val: u128) -> Self {
391        self.set_max_fee(val);
392        self
393    }
394
395    /// Gets the max fee for dynamic fee transactions (EIP-1559 and EIP-4844)
396    pub const fn get_max_fee(&self) -> Option<u128> {
397        match self {
398            Self::Eip1559 { max_fee_per_gas, .. } | Self::Eip4844 { max_fee_per_gas, .. } => {
399                Some(*max_fee_per_gas)
400            }
401            _ => None,
402        }
403    }
404
405    /// Sets the access list for transactions supporting EIP-1559, EIP-4844, and EIP-2930.
406    pub fn set_accesslist(&mut self, list: AccessList) -> &mut Self {
407        match self {
408            Self::Legacy { .. } => {}
409            Self::Eip1559 { access_list: accesslist, .. } |
410            Self::Eip4844 { access_list: accesslist, .. } |
411            Self::Eip2930 { access_list: accesslist, .. } => {
412                *accesslist = list;
413            }
414        }
415        self
416    }
417
418    /// Sets the gas price for the transaction.
419    pub fn set_gas_price(&mut self, val: u128) -> &mut Self {
420        match self {
421            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
422                *gas_price = val;
423            }
424            Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
425            Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
426                *max_fee_per_gas = val;
427                *max_priority_fee_per_gas = val;
428            }
429        }
430        self
431    }
432
433    /// Sets the gas price for the transaction.
434    pub fn with_gas_price(mut self, val: u128) -> Self {
435        match self {
436            Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
437                *gas_price = val;
438            }
439            Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
440            Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
441                *max_fee_per_gas = val;
442                *max_priority_fee_per_gas = val;
443            }
444        }
445        self
446    }
447
448    /// Gets the gas price for the transaction.
449    pub const fn get_gas_price(&self) -> u128 {
450        match self {
451            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
452            Self::Eip1559 { max_fee_per_gas, .. } | Self::Eip4844 { max_fee_per_gas, .. } => {
453                *max_fee_per_gas
454            }
455        }
456    }
457
458    /// Returns a clone with a decreased nonce
459    pub fn prev(&self) -> Self {
460        self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
461    }
462
463    /// Returns a clone with an increased nonce
464    pub fn next(&self) -> Self {
465        self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
466    }
467
468    /// Returns a clone with an increased nonce
469    pub fn skip(&self, skip: u64) -> Self {
470        self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
471    }
472
473    /// Returns a clone with incremented nonce
474    pub fn inc_nonce(self) -> Self {
475        let nonce = self.get_nonce() + 1;
476        self.with_nonce(nonce)
477    }
478
479    /// Sets a new random hash
480    pub fn rng_hash(self) -> Self {
481        self.with_hash(B256::random())
482    }
483
484    /// Returns a new transaction with a higher gas price +1
485    pub fn inc_price(&self) -> Self {
486        self.inc_price_by(1)
487    }
488
489    /// Returns a new transaction with a higher gas price
490    pub fn inc_price_by(&self, value: u128) -> Self {
491        self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
492    }
493
494    /// Returns a new transaction with a lower gas price -1
495    pub fn decr_price(&self) -> Self {
496        self.decr_price_by(1)
497    }
498
499    /// Returns a new transaction with a lower gas price
500    pub fn decr_price_by(&self, value: u128) -> Self {
501        self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
502    }
503
504    /// Returns a new transaction with a higher value
505    pub fn inc_value(&self) -> Self {
506        self.clone().with_value(self.get_value().checked_add(U256::from(1)).unwrap())
507    }
508
509    /// Returns a new transaction with a higher gas limit
510    pub fn inc_limit(&self) -> Self {
511        self.clone().with_gas_limit(self.get_gas_limit() + 1)
512    }
513
514    /// Returns a new transaction with a higher blob fee +1
515    ///
516    /// If it's an EIP-4844 transaction.
517    pub fn inc_blob_fee(&self) -> Self {
518        self.inc_blob_fee_by(1)
519    }
520
521    /// Returns a new transaction with a higher blob fee
522    ///
523    /// If it's an EIP-4844 transaction.
524    pub fn inc_blob_fee_by(&self, value: u128) -> Self {
525        let mut this = self.clone();
526        if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
527            *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
528        }
529        this
530    }
531
532    /// Returns a new transaction with a lower blob fee -1
533    ///
534    /// If it's an EIP-4844 transaction.
535    pub fn decr_blob_fee(&self) -> Self {
536        self.decr_price_by(1)
537    }
538
539    /// Returns a new transaction with a lower blob fee
540    ///
541    /// If it's an EIP-4844 transaction.
542    pub fn decr_blob_fee_by(&self, value: u128) -> Self {
543        let mut this = self.clone();
544        if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
545            *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
546        }
547        this
548    }
549
550    /// Returns the transaction type identifier associated with the current [`MockTransaction`].
551    pub const fn tx_type(&self) -> u8 {
552        match self {
553            Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
554            Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
555            Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
556            Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
557        }
558    }
559
560    /// Checks if the transaction is of the legacy type.
561    pub const fn is_legacy(&self) -> bool {
562        matches!(self, Self::Legacy { .. })
563    }
564
565    /// Checks if the transaction is of the EIP-1559 type.
566    pub const fn is_eip1559(&self) -> bool {
567        matches!(self, Self::Eip1559 { .. })
568    }
569
570    /// Checks if the transaction is of the EIP-4844 type.
571    pub const fn is_eip4844(&self) -> bool {
572        matches!(self, Self::Eip4844 { .. })
573    }
574
575    /// Checks if the transaction is of the EIP-2930 type.
576    pub const fn is_eip2930(&self) -> bool {
577        matches!(self, Self::Eip2930 { .. })
578    }
579
580    fn update_cost(&mut self) {
581        match self {
582            Self::Legacy { cost, gas_limit, gas_price, value, .. } |
583            Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
584                *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
585            }
586            Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
587            Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } => {
588                *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
589            }
590        };
591    }
592}
593
594impl PoolTransaction for MockTransaction {
595    type TryFromConsensusError = TryFromRecoveredTransactionError;
596
597    type Consensus = TransactionSigned;
598
599    type Pooled = PooledTransactionsElement;
600
601    fn try_from_consensus(
602        tx: RecoveredTx<Self::Consensus>,
603    ) -> Result<Self, Self::TryFromConsensusError> {
604        tx.try_into()
605    }
606
607    fn into_consensus(self) -> RecoveredTx<Self::Consensus> {
608        self.into()
609    }
610
611    fn from_pooled(pooled: RecoveredTx<Self::Pooled>) -> Self {
612        pooled.into()
613    }
614
615    fn try_consensus_into_pooled(
616        tx: RecoveredTx<Self::Consensus>,
617    ) -> Result<RecoveredTx<Self::Pooled>, Self::TryFromConsensusError> {
618        let (tx, signer) = tx.to_components();
619        Self::Pooled::try_from(tx)
620            .map(|tx| tx.with_signer(signer))
621            .map_err(|_| TryFromRecoveredTransactionError::BlobSidecarMissing)
622    }
623
624    fn hash(&self) -> &TxHash {
625        self.get_hash()
626    }
627
628    fn sender(&self) -> Address {
629        *self.get_sender()
630    }
631
632    fn sender_ref(&self) -> &Address {
633        self.get_sender()
634    }
635
636    fn nonce(&self) -> u64 {
637        *self.get_nonce()
638    }
639
640    // Having `get_cost` from `make_setters_getters` would be cleaner but we didn't
641    // want to also generate the error-prone cost setters. For now cost should be
642    // correct at construction and auto-updated per field update via `update_cost`,
643    // not to be manually set.
644    fn cost(&self) -> &U256 {
645        match self {
646            Self::Legacy { cost, .. } |
647            Self::Eip2930 { cost, .. } |
648            Self::Eip1559 { cost, .. } |
649            Self::Eip4844 { cost, .. } => cost,
650        }
651    }
652
653    fn gas_limit(&self) -> u64 {
654        *self.get_gas_limit()
655    }
656
657    fn max_fee_per_gas(&self) -> u128 {
658        match self {
659            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
660            Self::Eip1559 { max_fee_per_gas, .. } | Self::Eip4844 { max_fee_per_gas, .. } => {
661                *max_fee_per_gas
662            }
663        }
664    }
665
666    fn access_list(&self) -> Option<&AccessList> {
667        match self {
668            Self::Legacy { .. } => None,
669            Self::Eip1559 { access_list: accesslist, .. } |
670            Self::Eip4844 { access_list: accesslist, .. } |
671            Self::Eip2930 { access_list: accesslist, .. } => Some(accesslist),
672        }
673    }
674
675    fn max_priority_fee_per_gas(&self) -> Option<u128> {
676        match self {
677            Self::Legacy { .. } | Self::Eip2930 { .. } => None,
678            Self::Eip1559 { max_priority_fee_per_gas, .. } |
679            Self::Eip4844 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
680        }
681    }
682
683    fn max_fee_per_blob_gas(&self) -> Option<u128> {
684        match self {
685            Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
686            _ => None,
687        }
688    }
689
690    /// Calculates the effective tip per gas given a base fee.
691    fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
692        // Convert base_fee to u128 for precision in calculations
693        let base_fee = base_fee as u128;
694
695        // Retrieve the maximum fee per gas
696        let max_fee_per_gas = self.max_fee_per_gas();
697
698        // If the maximum fee per gas is less than the base fee, return None
699        if max_fee_per_gas < base_fee {
700            return None
701        }
702
703        // Calculate the fee by subtracting the base fee from the maximum fee per gas
704        let fee = max_fee_per_gas - base_fee;
705
706        // If the maximum priority fee per gas is available, return the minimum of fee and priority
707        // fee
708        if let Some(priority_fee) = self.max_priority_fee_per_gas() {
709            return Some(fee.min(priority_fee))
710        }
711
712        // Otherwise, return the calculated fee
713        Some(fee)
714    }
715
716    /// Returns the priority fee or gas price based on the transaction type.
717    fn priority_fee_or_price(&self) -> u128 {
718        match self {
719            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
720            Self::Eip1559 { max_priority_fee_per_gas, .. } |
721            Self::Eip4844 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
722        }
723    }
724
725    /// Returns the transaction kind associated with the transaction.
726    fn kind(&self) -> TxKind {
727        match self {
728            Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
729            Self::Eip4844 { to, .. } => TxKind::Call(*to),
730        }
731    }
732
733    /// Returns true if the transaction is a contract creation.
734    fn is_create(&self) -> bool {
735        match self {
736            Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
737                to.is_create()
738            }
739            Self::Eip4844 { .. } => false,
740        }
741    }
742
743    /// Returns the input data associated with the transaction.
744    fn input(&self) -> &[u8] {
745        self.get_input()
746    }
747
748    /// Returns the size of the transaction.
749    fn size(&self) -> usize {
750        *self.get_size()
751    }
752
753    /// Returns the transaction type as a byte identifier.
754    fn tx_type(&self) -> u8 {
755        match self {
756            Self::Legacy { .. } => TxType::Legacy.into(),
757            Self::Eip1559 { .. } => TxType::Eip1559.into(),
758            Self::Eip4844 { .. } => TxType::Eip4844.into(),
759            Self::Eip2930 { .. } => TxType::Eip2930.into(),
760        }
761    }
762
763    /// Returns the encoded length of the transaction.
764    fn encoded_length(&self) -> usize {
765        self.size()
766    }
767
768    /// Returns the chain ID associated with the transaction.
769    fn chain_id(&self) -> Option<u64> {
770        match self {
771            Self::Legacy { chain_id, .. } => *chain_id,
772            Self::Eip1559 { chain_id, .. } |
773            Self::Eip4844 { chain_id, .. } |
774            Self::Eip2930 { chain_id, .. } => Some(*chain_id),
775        }
776    }
777
778    fn seismic_elements(&self) -> Option<&TxSeismicElements> {
779        None
780    }
781}
782
783impl EthPoolTransaction for MockTransaction {
784    fn take_blob(&mut self) -> EthBlobTransactionSidecar {
785        match self {
786            Self::Eip4844 { sidecar, .. } => EthBlobTransactionSidecar::Present(sidecar.clone()),
787            _ => EthBlobTransactionSidecar::None,
788        }
789    }
790
791    fn blob_count(&self) -> usize {
792        match self {
793            Self::Eip4844 { sidecar, .. } => sidecar.blobs.len(),
794            _ => 0,
795        }
796    }
797
798    fn try_into_pooled_eip4844(
799        self,
800        sidecar: Arc<BlobTransactionSidecar>,
801    ) -> Option<RecoveredTx<Self::Pooled>> {
802        let (tx, signer) = self.into_consensus().to_components();
803        Self::Pooled::try_from_blob_transaction(tx, Arc::unwrap_or_clone(sidecar))
804            .map(|tx| tx.with_signer(signer))
805            .ok()
806    }
807
808    fn try_from_eip4844(
809        tx: RecoveredTx<Self::Consensus>,
810        sidecar: BlobTransactionSidecar,
811    ) -> Option<Self> {
812        let (tx, signer) = tx.to_components();
813        Self::Pooled::try_from_blob_transaction(tx, sidecar)
814            .map(|tx| tx.with_signer(signer))
815            .ok()
816            .map(Self::from_pooled)
817    }
818
819    fn validate_blob(
820        &self,
821        _blob: &BlobTransactionSidecar,
822        _settings: &reth_primitives::kzg::KzgSettings,
823    ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
824        match &self {
825            Self::Eip4844 { .. } => Ok(()),
826            _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
827        }
828    }
829
830    fn authorization_count(&self) -> usize {
831        0
832    }
833}
834
835impl TryFrom<RecoveredTx> for MockTransaction {
836    type Error = TryFromRecoveredTransactionError;
837
838    fn try_from(tx: RecoveredTx) -> Result<Self, Self::Error> {
839        let sender = tx.signer();
840        let transaction = tx.into_signed();
841        let hash = transaction.hash();
842        let size = transaction.size();
843
844        #[allow(unreachable_patterns)]
845        match transaction.transaction {
846            Transaction::Legacy(TxLegacy {
847                chain_id,
848                nonce,
849                gas_price,
850                gas_limit,
851                to,
852                value,
853                input,
854            }) => Ok(Self::Legacy {
855                chain_id,
856                hash,
857                sender,
858                nonce,
859                gas_price,
860                gas_limit,
861                to,
862                value,
863                input,
864                size,
865                cost: U256::from(gas_limit) * U256::from(gas_price) + value,
866            }),
867            Transaction::Eip2930(TxEip2930 {
868                chain_id,
869                nonce,
870                gas_price,
871                gas_limit,
872                to,
873                value,
874                input,
875                access_list,
876            }) => Ok(Self::Eip2930 {
877                chain_id,
878                hash,
879                sender,
880                nonce,
881                gas_price,
882                gas_limit,
883                to,
884                value,
885                input,
886                access_list,
887                size,
888                cost: U256::from(gas_limit) * U256::from(gas_price) + value,
889            }),
890            Transaction::Eip1559(TxEip1559 {
891                chain_id,
892                nonce,
893                gas_limit,
894                max_fee_per_gas,
895                max_priority_fee_per_gas,
896                to,
897                value,
898                input,
899                access_list,
900            }) => Ok(Self::Eip1559 {
901                chain_id,
902                hash,
903                sender,
904                nonce,
905                max_fee_per_gas,
906                max_priority_fee_per_gas,
907                gas_limit,
908                to,
909                value,
910                input,
911                access_list,
912                size,
913                cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
914            }),
915            Transaction::Eip4844(TxEip4844 {
916                chain_id,
917                nonce,
918                gas_limit,
919                max_fee_per_gas,
920                max_priority_fee_per_gas,
921                to,
922                value,
923                input,
924                access_list,
925                blob_versioned_hashes: _,
926                max_fee_per_blob_gas,
927            }) => Ok(Self::Eip4844 {
928                chain_id,
929                hash,
930                sender,
931                nonce,
932                max_fee_per_gas,
933                max_priority_fee_per_gas,
934                max_fee_per_blob_gas,
935                gas_limit,
936                to,
937                value,
938                input,
939                access_list,
940                sidecar: BlobTransactionSidecar::default(),
941                size,
942                cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
943            }),
944            _ => unreachable!("Invalid transaction type"),
945        }
946    }
947}
948
949impl From<PooledTransactionsElementEcRecovered> for MockTransaction {
950    fn from(tx: PooledTransactionsElementEcRecovered) -> Self {
951        tx.into_ecrecovered_transaction().try_into().expect(
952            "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
953        )
954    }
955}
956
957impl From<MockTransaction> for RecoveredTx {
958    fn from(tx: MockTransaction) -> Self {
959        let signed_tx =
960            TransactionSigned::new(tx.clone().into(), Signature::test_signature(), *tx.hash());
961
962        Self::from_signed_transaction(signed_tx, tx.sender())
963    }
964}
965
966impl From<MockTransaction> for Transaction {
967    fn from(mock: MockTransaction) -> Self {
968        match mock {
969            MockTransaction::Legacy {
970                chain_id,
971                nonce,
972                gas_price,
973                gas_limit,
974                to,
975                value,
976                input,
977                ..
978            } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
979            MockTransaction::Eip2930 {
980                chain_id,
981                nonce,
982                gas_price,
983                gas_limit,
984                to,
985                value,
986                access_list,
987                input,
988                ..
989            } => Self::Eip2930(TxEip2930 {
990                chain_id,
991                nonce,
992                gas_price,
993                gas_limit,
994                to,
995                value,
996                access_list,
997                input,
998            }),
999            MockTransaction::Eip1559 {
1000                chain_id,
1001                nonce,
1002                gas_limit,
1003                max_fee_per_gas,
1004                max_priority_fee_per_gas,
1005                to,
1006                value,
1007                access_list,
1008                input,
1009                ..
1010            } => Self::Eip1559(TxEip1559 {
1011                chain_id,
1012                nonce,
1013                gas_limit,
1014                max_fee_per_gas,
1015                max_priority_fee_per_gas,
1016                to,
1017                value,
1018                access_list,
1019                input,
1020            }),
1021            MockTransaction::Eip4844 {
1022                chain_id,
1023                nonce,
1024                gas_limit,
1025                max_fee_per_gas,
1026                max_priority_fee_per_gas,
1027                to,
1028                value,
1029                access_list,
1030                sidecar,
1031                max_fee_per_blob_gas,
1032                input,
1033                ..
1034            } => Self::Eip4844(TxEip4844 {
1035                chain_id,
1036                nonce,
1037                gas_limit,
1038                max_fee_per_gas,
1039                max_priority_fee_per_gas,
1040                to,
1041                value,
1042                access_list,
1043                blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1044                max_fee_per_blob_gas,
1045                input,
1046            }),
1047        }
1048    }
1049}
1050
1051#[cfg(any(test, feature = "arbitrary"))]
1052impl proptest::arbitrary::Arbitrary for MockTransaction {
1053    type Parameters = ();
1054    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1055        use proptest::prelude::Strategy;
1056        use proptest_arbitrary_interop::arb;
1057
1058        arb::<(TransactionSigned, Address)>()
1059            .prop_map(|(signed_transaction, signer)| {
1060                RecoveredTx::from_signed_transaction(signed_transaction, signer)
1061                    .try_into()
1062                    .expect("Failed to create an Arbitrary MockTransaction via RecoveredTx")
1063            })
1064            .boxed()
1065    }
1066
1067    type Strategy = proptest::strategy::BoxedStrategy<Self>;
1068}
1069
1070/// A factory for creating and managing various types of mock transactions.
1071#[derive(Debug, Default)]
1072pub struct MockTransactionFactory {
1073    pub(crate) ids: SenderIdentifiers,
1074}
1075
1076// === impl MockTransactionFactory ===
1077
1078impl MockTransactionFactory {
1079    /// Generates a transaction ID for the given [`MockTransaction`].
1080    pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1081        let sender = self.ids.sender_id_or_create(tx.sender());
1082        TransactionId::new(sender, tx.nonce())
1083    }
1084
1085    /// Validates a [`MockTransaction`] and returns a [`MockValidTx`].
1086    pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1087        self.validated_with_origin(TransactionOrigin::External, transaction)
1088    }
1089
1090    /// Validates a [`MockTransaction`] and returns a shared [`Arc<MockValidTx>`].
1091    pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1092        Arc::new(self.validated(transaction))
1093    }
1094
1095    /// Converts the transaction into a validated transaction with a specified origin.
1096    pub fn validated_with_origin(
1097        &mut self,
1098        origin: TransactionOrigin,
1099        transaction: MockTransaction,
1100    ) -> MockValidTx {
1101        MockValidTx {
1102            propagate: false,
1103            transaction_id: self.tx_id(&transaction),
1104            transaction,
1105            timestamp: Instant::now(),
1106            origin,
1107        }
1108    }
1109
1110    /// Creates a validated legacy [`MockTransaction`].
1111    pub fn create_legacy(&mut self) -> MockValidTx {
1112        self.validated(MockTransaction::legacy())
1113    }
1114
1115    /// Creates a validated EIP-1559 [`MockTransaction`].
1116    pub fn create_eip1559(&mut self) -> MockValidTx {
1117        self.validated(MockTransaction::eip1559())
1118    }
1119
1120    /// Creates a validated EIP-4844 [`MockTransaction`].
1121    pub fn create_eip4844(&mut self) -> MockValidTx {
1122        self.validated(MockTransaction::eip4844())
1123    }
1124}
1125
1126/// `MockOrdering` is just a `CoinbaseTipOrdering` with `MockTransaction`
1127pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1128
1129/// A ratio of each of the configured transaction types. The percentages sum up to 100, this is
1130/// enforced in [`MockTransactionRatio::new`] by an assert.
1131#[derive(Debug, Clone)]
1132pub struct MockTransactionRatio {
1133    /// Percent of transactions that are legacy transactions
1134    pub legacy_pct: u32,
1135    /// Percent of transactions that are access list transactions
1136    pub access_list_pct: u32,
1137    /// Percent of transactions that are EIP-1559 transactions
1138    pub dynamic_fee_pct: u32,
1139    /// Percent of transactions that are EIP-4844 transactions
1140    pub blob_pct: u32,
1141}
1142
1143impl MockTransactionRatio {
1144    /// Creates a new [`MockTransactionRatio`] with the given percentages.
1145    ///
1146    /// Each argument is treated as a full percent, for example `30u32` is `30%`.
1147    ///
1148    /// The percentages must sum up to 100 exactly, or this method will panic.
1149    pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1150        let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1151        assert_eq!(
1152            total,
1153            100,
1154            "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}",
1155        );
1156
1157        Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1158    }
1159
1160    /// Create a [`WeightedIndex`] from this transaction ratio.
1161    ///
1162    /// This index will sample in the following order:
1163    /// * Legacy transaction => 0
1164    /// * EIP-2930 transaction => 1
1165    /// * EIP-1559 transaction => 2
1166    /// * EIP-4844 transaction => 3
1167    pub fn weighted_index(&self) -> WeightedIndex<u32> {
1168        WeightedIndex::new([
1169            self.legacy_pct,
1170            self.access_list_pct,
1171            self.dynamic_fee_pct,
1172            self.blob_pct,
1173        ])
1174        .unwrap()
1175    }
1176}
1177
1178/// The range of each type of fee, for the different transaction types
1179#[derive(Debug, Clone)]
1180pub struct MockFeeRange {
1181    /// The range of `gas_price` or legacy and access list transactions
1182    pub gas_price: Uniform<u128>,
1183    /// The range of priority fees for EIP-1559 and EIP-4844 transactions
1184    pub priority_fee: Uniform<u128>,
1185    /// The range of max fees for EIP-1559 and EIP-4844 transactions
1186    pub max_fee: Uniform<u128>,
1187    /// The range of max fees per blob gas for EIP-4844 transactions
1188    pub max_fee_blob: Uniform<u128>,
1189}
1190
1191impl MockFeeRange {
1192    /// Creates a new [`MockFeeRange`] with the given ranges.
1193    ///
1194    /// Expects the bottom of the `priority_fee_range` to be greater than the top of the
1195    /// `max_fee_range`.
1196    pub fn new(
1197        gas_price: Range<u128>,
1198        priority_fee: Range<u128>,
1199        max_fee: Range<u128>,
1200        max_fee_blob: Range<u128>,
1201    ) -> Self {
1202        assert!(
1203            max_fee.start <= priority_fee.end,
1204            "max_fee_range should be strictly below the priority fee range"
1205        );
1206        Self {
1207            gas_price: gas_price.into(),
1208            priority_fee: priority_fee.into(),
1209            max_fee: max_fee.into(),
1210            max_fee_blob: max_fee_blob.into(),
1211        }
1212    }
1213
1214    /// Returns a sample of `gas_price` for legacy and access list transactions with the given
1215    /// [Rng](rand::Rng).
1216    pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1217        self.gas_price.sample(rng)
1218    }
1219
1220    /// Returns a sample of `max_priority_fee_per_gas` for EIP-1559 and EIP-4844 transactions with
1221    /// the given [Rng](rand::Rng).
1222    pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1223        self.priority_fee.sample(rng)
1224    }
1225
1226    /// Returns a sample of `max_fee_per_gas` for EIP-1559 and EIP-4844 transactions with the given
1227    /// [Rng](rand::Rng).
1228    pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1229        self.max_fee.sample(rng)
1230    }
1231
1232    /// Returns a sample of `max_fee_per_blob_gas` for EIP-4844 transactions with the given
1233    /// [Rng](rand::Rng).
1234    pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1235        self.max_fee_blob.sample(rng)
1236    }
1237}
1238
1239/// A configured distribution that can generate transactions
1240#[derive(Debug, Clone)]
1241pub struct MockTransactionDistribution {
1242    /// ratio of each transaction type to generate
1243    transaction_ratio: MockTransactionRatio,
1244    /// generates the gas limit
1245    gas_limit_range: Uniform<u64>,
1246    /// generates the transaction's fake size
1247    size_range: Uniform<usize>,
1248    /// generates fees for the given transaction types
1249    fee_ranges: MockFeeRange,
1250}
1251
1252impl MockTransactionDistribution {
1253    /// Creates a new generator distribution.
1254    pub fn new(
1255        transaction_ratio: MockTransactionRatio,
1256        fee_ranges: MockFeeRange,
1257        gas_limit_range: Range<u64>,
1258        size_range: Range<usize>,
1259    ) -> Self {
1260        Self {
1261            transaction_ratio,
1262            gas_limit_range: gas_limit_range.into(),
1263            fee_ranges,
1264            size_range: size_range.into(),
1265        }
1266    }
1267
1268    /// Generates a new transaction
1269    pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1270        let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1271        let tx = match transaction_sample {
1272            0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1273            1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1274            2 => MockTransaction::eip1559()
1275                .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1276                .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1277            3 => MockTransaction::eip4844()
1278                .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1279                .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1280                .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1281            _ => unreachable!("unknown transaction type returned by the weighted index"),
1282        };
1283
1284        let size = self.size_range.sample(rng);
1285
1286        tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1287    }
1288
1289    /// Generates a new transaction set for the given sender.
1290    ///
1291    /// The nonce range defines which nonces to set, and how many transactions to generate.
1292    pub fn tx_set(
1293        &self,
1294        sender: Address,
1295        nonce_range: Range<u64>,
1296        rng: &mut impl rand::Rng,
1297    ) -> MockTransactionSet {
1298        let txs =
1299            nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1300        MockTransactionSet::new(txs)
1301    }
1302
1303    /// Generates a transaction set that ensures that blob txs are not mixed with other transaction
1304    /// types.
1305    ///
1306    /// This is done by taking the existing distribution, and using the first transaction to
1307    /// determine whether or not the sender should generate entirely blob transactions.
1308    pub fn tx_set_non_conflicting_types(
1309        &self,
1310        sender: Address,
1311        nonce_range: Range<u64>,
1312        rng: &mut impl rand::Rng,
1313    ) -> NonConflictingSetOutcome {
1314        // This will create a modified distribution that will only generate blob transactions
1315        // for the given sender, if the blob transaction is the first transaction in the set.
1316        //
1317        // Otherwise, it will modify the transaction distribution to only generate legacy, eip2930,
1318        // and eip1559 transactions.
1319        //
1320        // The new distribution should still have the same relative amount of transaction types.
1321        let mut modified_distribution = self.clone();
1322        let first_tx = self.tx(nonce_range.start, rng);
1323
1324        // now we can check and modify the distribution, preserving potentially uneven ratios
1325        // between transaction types
1326        if first_tx.is_eip4844() {
1327            modified_distribution.transaction_ratio = MockTransactionRatio {
1328                legacy_pct: 0,
1329                access_list_pct: 0,
1330                dynamic_fee_pct: 0,
1331                blob_pct: 100,
1332            };
1333
1334            // finally generate the transaction set
1335            NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1336                sender,
1337                nonce_range,
1338                rng,
1339            ))
1340        } else {
1341            let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1342                modified_distribution.transaction_ratio;
1343
1344            // Calculate the total weight of non-blob transactions
1345            let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1346
1347            // Calculate new weights, preserving the ratio between non-blob transaction types
1348            let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1349                .into_iter()
1350                .map(|weight| weight * 100 / total_non_blob_weight)
1351                .collect();
1352
1353            let new_ratio = MockTransactionRatio {
1354                legacy_pct: new_weights[0],
1355                access_list_pct: new_weights[1],
1356                dynamic_fee_pct: new_weights[2],
1357                blob_pct: 0,
1358            };
1359
1360            // Set the new transaction ratio excluding blob transactions and preserving the relative
1361            // ratios
1362            modified_distribution.transaction_ratio = new_ratio;
1363
1364            // finally generate the transaction set
1365            NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1366        }
1367    }
1368}
1369
1370/// Indicates whether or not the non-conflicting transaction set generated includes only blobs, or
1371/// a mix of transaction types.
1372#[derive(Debug, Clone)]
1373pub enum NonConflictingSetOutcome {
1374    /// The transaction set includes only blob transactions
1375    BlobsOnly(MockTransactionSet),
1376    /// The transaction set includes a mix of transaction types
1377    Mixed(MockTransactionSet),
1378}
1379
1380impl NonConflictingSetOutcome {
1381    /// Returns the inner [`MockTransactionSet`]
1382    pub fn into_inner(self) -> MockTransactionSet {
1383        match self {
1384            Self::BlobsOnly(set) | Self::Mixed(set) => set,
1385        }
1386    }
1387
1388    /// Introduces artificial nonce gaps into the transaction set, at random, with a range of gap
1389    /// sizes.
1390    ///
1391    /// If this is a [`NonConflictingSetOutcome::BlobsOnly`], then nonce gaps will not be
1392    /// introduced. Otherwise, the nonce gaps will be introduced to the mixed transaction set.
1393    ///
1394    /// See [`MockTransactionSet::with_nonce_gaps`] for more information on the generation process.
1395    pub fn with_nonce_gaps(
1396        &mut self,
1397        gap_pct: u32,
1398        gap_range: Range<u64>,
1399        rng: &mut impl rand::Rng,
1400    ) {
1401        match self {
1402            Self::BlobsOnly(_) => {}
1403            Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1404        }
1405    }
1406}
1407
1408/// A set of [`MockTransaction`]s that can be modified at once
1409#[derive(Debug, Clone)]
1410pub struct MockTransactionSet {
1411    pub(crate) transactions: Vec<MockTransaction>,
1412}
1413
1414impl MockTransactionSet {
1415    /// Create a new [`MockTransactionSet`] from a list of transactions
1416    const fn new(transactions: Vec<MockTransaction>) -> Self {
1417        Self { transactions }
1418    }
1419
1420    /// Creates a series of dependent transactions for a given sender and nonce.
1421    ///
1422    /// This method generates a sequence of transactions starting from the provided nonce
1423    /// for the given sender.
1424    ///
1425    /// The number of transactions created is determined by `tx_count`.
1426    pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1427        let mut txs = Vec::with_capacity(tx_count);
1428        let mut curr_tx =
1429            MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1430        for _ in 0..tx_count {
1431            txs.push(curr_tx.clone());
1432            curr_tx = curr_tx.next();
1433        }
1434
1435        Self::new(txs)
1436    }
1437
1438    /// Creates a chain of transactions for a given sender with a specified count.
1439    ///
1440    /// This method generates a sequence of transactions starting from the specified sender
1441    /// and creates a chain of transactions based on the `tx_count`.
1442    pub fn sequential_transactions_by_sender(
1443        sender: Address,
1444        tx_count: usize,
1445        tx_type: TxType,
1446    ) -> Self {
1447        Self::dependent(sender, 0, tx_count, tx_type)
1448    }
1449
1450    /// Introduces artificial nonce gaps into the transaction set, at random, with a range of gap
1451    /// sizes.
1452    ///
1453    /// This assumes that the `gap_pct` is between 0 and 100, and the `gap_range` has a lower bound
1454    /// of at least one. This is enforced with assertions.
1455    ///
1456    /// The `gap_pct` is the percent chance that the next transaction in the set will introduce a
1457    /// nonce gap.
1458    ///
1459    /// Let an example transaction set be `[(tx1, 1), (tx2, 2)]`, where the first element of the
1460    /// tuple is a transaction, and the second element is the nonce. If the `gap_pct` is 50, and
1461    /// the `gap_range` is `1..=1`, then the resulting transaction set could would be either
1462    /// `[(tx1, 1), (tx2, 2)]` or `[(tx1, 1), (tx2, 3)]`, with a 50% chance of either.
1463    pub fn with_nonce_gaps(
1464        &mut self,
1465        gap_pct: u32,
1466        gap_range: Range<u64>,
1467        rng: &mut impl rand::Rng,
1468    ) {
1469        assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1470        assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1471
1472        let mut prev_nonce = 0;
1473        for tx in &mut self.transactions {
1474            if rng.gen_bool(gap_pct as f64 / 100.0) {
1475                prev_nonce += gap_range.start;
1476            } else {
1477                prev_nonce += 1;
1478            }
1479            tx.set_nonce(prev_nonce);
1480        }
1481    }
1482
1483    /// Add transactions to the [`MockTransactionSet`]
1484    pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1485        self.transactions.extend(txs);
1486    }
1487
1488    /// Extract the inner [Vec] of [`MockTransaction`]s
1489    pub fn into_vec(self) -> Vec<MockTransaction> {
1490        self.transactions
1491    }
1492
1493    /// Returns an iterator over the contained transactions in the set
1494    pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1495        self.transactions.iter()
1496    }
1497
1498    /// Returns a mutable iterator over the contained transactions in the set.
1499    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1500        self.transactions.iter_mut()
1501    }
1502}
1503
1504impl IntoIterator for MockTransactionSet {
1505    type Item = MockTransaction;
1506    type IntoIter = IntoIter<MockTransaction>;
1507
1508    fn into_iter(self) -> Self::IntoIter {
1509        self.transactions.into_iter()
1510    }
1511}
1512
1513#[test]
1514fn test_mock_priority() {
1515    use crate::TransactionOrdering;
1516
1517    let o = MockOrdering::default();
1518    let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1519    let hi = lo.next().inc_price();
1520    assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1521}