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