1use 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
42pub type MockTxPool = TxPool<MockOrdering>;
46
47pub type MockValidTx = ValidPoolTransaction<MockTransaction>;
52
53pub fn mock_tx_pool() -> MockTxPool {
55 MockTxPool::new(Default::default(), Default::default())
56}
57
58macro_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 $this.update_cost();
73 };
74}
75
76macro_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
89macro_rules! make_setters_getters {
91 ($($name:ident => $t:ty);*) => {
92 paste! {$(
93 pub fn [<set_ $name>](&mut self, $name: $t) -> &mut Self {
95 set_value!(self => $name);
96 self
97 }
98
99 pub fn [<with_ $name>](mut self, $name: $t) -> Self {
101 set_value!(self => $name);
102 self
103 }
104
105 pub const fn [<get_ $name>](&self) -> &$t {
107 get_value!(self => $name)
108 }
109 )*}
110 };
111}
112
113#[derive(Debug, Clone, Eq, PartialEq)]
115pub enum MockTransaction {
116 Legacy {
118 chain_id: Option<ChainId>,
120 hash: B256,
122 sender: Address,
124 nonce: u64,
126 gas_price: u128,
128 gas_limit: u64,
130 to: TxKind,
132 value: U256,
134 input: Bytes,
136 size: usize,
138 cost: U256,
140 },
141 Eip2930 {
143 chain_id: ChainId,
145 hash: B256,
147 sender: Address,
149 nonce: u64,
151 to: TxKind,
153 gas_limit: u64,
155 input: Bytes,
157 value: U256,
159 gas_price: u128,
161 access_list: AccessList,
163 size: usize,
165 cost: U256,
167 },
168 Eip1559 {
170 chain_id: ChainId,
172 hash: B256,
174 sender: Address,
176 nonce: u64,
178 max_fee_per_gas: u128,
180 max_priority_fee_per_gas: u128,
182 gas_limit: u64,
184 to: TxKind,
186 value: U256,
188 access_list: AccessList,
190 input: Bytes,
192 size: usize,
194 cost: U256,
196 },
197 Eip4844 {
199 chain_id: ChainId,
201 hash: B256,
203 sender: Address,
205 nonce: u64,
207 max_fee_per_gas: u128,
209 max_priority_fee_per_gas: u128,
211 max_fee_per_blob_gas: u128,
213 gas_limit: u64,
215 to: Address,
217 value: U256,
219 access_list: AccessList,
221 input: Bytes,
223 sidecar: BlobTransactionSidecar,
225 blob_versioned_hashes: Vec<B256>,
227 size: usize,
229 cost: U256,
231 },
232 Eip7702 {
234 chain_id: ChainId,
236 hash: B256,
238 sender: Address,
240 nonce: u64,
242 max_fee_per_gas: u128,
244 max_priority_fee_per_gas: u128,
246 gas_limit: u64,
248 to: Address,
250 value: U256,
252 access_list: AccessList,
254 authorization_list: Vec<SignedAuthorization>,
256 input: Bytes,
258 size: usize,
260 cost: U256,
262 },
263}
264
265impl 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 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 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 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 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 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 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 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 pub fn with_blob_fee(mut self, val: u128) -> Self {
409 self.set_blob_fee(val);
410 self
411 }
412
413 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 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 pub fn with_priority_fee(mut self, val: u128) -> Self {
433 self.set_priority_fee(val);
434 self
435 }
436
437 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 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 pub fn with_max_fee(mut self, val: u128) -> Self {
460 self.set_max_fee(val);
461 self
462 }
463
464 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 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 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 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 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 pub fn prev(&self) -> Self {
532 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
533 }
534
535 pub fn next(&self) -> Self {
537 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
538 }
539
540 pub fn skip(&self, skip: u64) -> Self {
542 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
543 }
544
545 pub fn inc_nonce(self) -> Self {
547 let nonce = self.get_nonce() + 1;
548 self.with_nonce(nonce)
549 }
550
551 pub fn rng_hash(self) -> Self {
553 self.with_hash(B256::random())
554 }
555
556 pub fn inc_price(&self) -> Self {
558 self.inc_price_by(1)
559 }
560
561 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 pub fn decr_price(&self) -> Self {
568 self.decr_price_by(1)
569 }
570
571 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 pub fn inc_value(&self) -> Self {
578 self.clone().with_value(self.get_value().checked_add(U256::from(1)).unwrap())
579 }
580
581 pub fn inc_limit(&self) -> Self {
583 self.clone().with_gas_limit(self.get_gas_limit() + 1)
584 }
585
586 pub fn inc_blob_fee(&self) -> Self {
590 self.inc_blob_fee_by(1)
591 }
592
593 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 pub fn decr_blob_fee(&self) -> Self {
608 self.decr_price_by(1)
609 }
610
611 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 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 pub const fn is_legacy(&self) -> bool {
635 matches!(self, Self::Legacy { .. })
636 }
637
638 pub const fn is_eip1559(&self) -> bool {
640 matches!(self, Self::Eip1559 { .. })
641 }
642
643 pub const fn is_eip4844(&self) -> bool {
645 matches!(self, Self::Eip4844 { .. })
646 }
647
648 pub const fn is_eip2930(&self) -> bool {
650 matches!(self, Self::Eip2930 { .. })
651 }
652
653 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 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 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 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 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#[derive(Debug, Default)]
1311pub struct MockTransactionFactory {
1312 pub(crate) ids: SenderIdentifiers,
1313}
1314
1315impl MockTransactionFactory {
1318 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 pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1326 self.validated_with_origin(TransactionOrigin::External, transaction)
1327 }
1328
1329 pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1331 Arc::new(self.validated(transaction))
1332 }
1333
1334 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 pub fn create_legacy(&mut self) -> MockValidTx {
1351 self.validated(MockTransaction::legacy())
1352 }
1353
1354 pub fn create_eip1559(&mut self) -> MockValidTx {
1356 self.validated(MockTransaction::eip1559())
1357 }
1358
1359 pub fn create_eip4844(&mut self) -> MockValidTx {
1361 self.validated(MockTransaction::eip4844())
1362 }
1363}
1364
1365pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1367
1368#[derive(Debug, Clone)]
1371pub struct MockTransactionRatio {
1372 pub legacy_pct: u32,
1374 pub access_list_pct: u32,
1376 pub dynamic_fee_pct: u32,
1378 pub blob_pct: u32,
1380}
1381
1382impl MockTransactionRatio {
1383 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 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#[derive(Debug, Clone)]
1419pub struct MockFeeRange {
1420 pub gas_price: Uniform<u128>,
1422 pub priority_fee: Uniform<u128>,
1424 pub max_fee: Uniform<u128>,
1426 pub max_fee_blob: Uniform<u128>,
1428}
1429
1430impl MockFeeRange {
1431 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 pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1456 self.gas_price.sample(rng)
1457 }
1458
1459 pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1462 self.priority_fee.sample(rng)
1463 }
1464
1465 pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1468 self.max_fee.sample(rng)
1469 }
1470
1471 pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1474 self.max_fee_blob.sample(rng)
1475 }
1476}
1477
1478#[derive(Debug, Clone)]
1480pub struct MockTransactionDistribution {
1481 transaction_ratio: MockTransactionRatio,
1483 gas_limit_range: Uniform<u64>,
1485 size_range: Uniform<usize>,
1487 fee_ranges: MockFeeRange,
1489}
1490
1491impl MockTransactionDistribution {
1492 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 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 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 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 let mut modified_distribution = self.clone();
1561 let first_tx = self.tx(nonce_range.start, rng);
1562
1563 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 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 let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1585
1586 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 modified_distribution.transaction_ratio = new_ratio;
1602
1603 NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1605 }
1606 }
1607}
1608
1609#[derive(Debug, Clone)]
1612pub enum NonConflictingSetOutcome {
1613 BlobsOnly(MockTransactionSet),
1615 Mixed(MockTransactionSet),
1617}
1618
1619impl NonConflictingSetOutcome {
1620 pub fn into_inner(self) -> MockTransactionSet {
1622 match self {
1623 Self::BlobsOnly(set) | Self::Mixed(set) => set,
1624 }
1625 }
1626
1627 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#[derive(Debug, Clone)]
1649pub struct MockTransactionSet {
1650 pub(crate) transactions: Vec<MockTransaction>,
1651}
1652
1653impl MockTransactionSet {
1654 const fn new(transactions: Vec<MockTransaction>) -> Self {
1656 Self { transactions }
1657 }
1658
1659 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 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 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 pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1724 self.transactions.extend(txs);
1725 }
1726
1727 pub fn into_vec(self) -> Vec<MockTransaction> {
1729 self.transactions
1730 }
1731
1732 pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1734 self.transactions.iter()
1735 }
1736
1737 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}