1#![allow(dead_code)]
4
5use crate::{
6 error::PoolErrorKind,
7 pool::{state::SubPool, txpool::TxPool, AddedTransaction},
8 test_utils::{MockOrdering, MockTransactionDistribution, MockTransactionFactory},
9 TransactionOrdering,
10};
11use alloy_primitives::{map::AddressMap, Address, U256};
12use rand::Rng;
13use std::ops::{Deref, DerefMut};
14
15pub(crate) struct MockPool<T: TransactionOrdering = MockOrdering> {
17 pool: TxPool<T>,
19}
20
21impl MockPool {
22 fn total_subpool_size(&self) -> usize {
24 self.pool.pending().len() + self.pool.base_fee().len() + self.pool.queued().len()
25 }
26
27 fn enforce_invariants(&self) {
29 assert_eq!(
30 self.pool.len(),
31 self.total_subpool_size(),
32 "Tx in AllTransactions and sum(subpools) must match"
33 );
34 }
35}
36
37impl Default for MockPool {
38 fn default() -> Self {
39 Self { pool: TxPool::new(MockOrdering::default(), Default::default()) }
40 }
41}
42
43impl<T: TransactionOrdering> Deref for MockPool<T> {
44 type Target = TxPool<T>;
45
46 fn deref(&self) -> &Self::Target {
47 &self.pool
48 }
49}
50
51impl<T: TransactionOrdering> DerefMut for MockPool<T> {
52 fn deref_mut(&mut self) -> &mut Self::Target {
53 &mut self.pool
54 }
55}
56
57pub(crate) struct MockTransactionSimulator<R: Rng> {
59 base_fee: u128,
61 tx_generator: MockTransactionDistribution,
63 balances: AddressMap<U256>,
65 nonces: AddressMap<u64>,
67 senders: Vec<Address>,
69 scenarios: Vec<ScenarioType>,
71 executed: AddressMap<ExecutedScenarios>,
73 validator: MockTransactionFactory,
75 nonce_gaps: AddressMap<u64>,
77 rng: R,
79}
80
81impl<R: Rng> MockTransactionSimulator<R> {
82 pub(crate) fn new(mut rng: R, config: MockSimulatorConfig) -> Self {
84 let senders = config.addresses(&mut rng);
85 Self {
86 base_fee: config.base_fee,
87 balances: senders.iter().copied().map(|a| (a, rng.random())).collect(),
88 nonces: senders.iter().copied().map(|a| (a, 0)).collect(),
89 senders,
90 scenarios: config.scenarios,
91 tx_generator: config.tx_generator,
92 executed: Default::default(),
93 validator: Default::default(),
94 nonce_gaps: Default::default(),
95 rng,
96 }
97 }
98
99 pub(crate) fn create_pool(&self) -> MockPool {
104 let mut pool = MockPool::default();
105 let mut info = pool.block_info();
106 info.pending_basefee = self.base_fee as u64;
107 pool.set_block_info(info);
108 pool
109 }
110
111 fn rng_address(&mut self) -> Address {
113 let idx = self.rng.random_range(0..self.senders.len());
114 self.senders[idx]
115 }
116
117 fn rng_scenario(&mut self) -> ScenarioType {
119 let idx = self.rng.random_range(0..self.scenarios.len());
120 self.scenarios[idx].clone()
121 }
122
123 pub(crate) fn next(&mut self, pool: &mut MockPool) {
125 let sender = self.rng_address();
126 let scenario = self.rng_scenario();
127 let on_chain_nonce = self.nonces[&sender];
128 let on_chain_balance = self.balances[&sender];
129
130 match scenario {
131 ScenarioType::OnchainNonce => {
132 let tx = self.tx_generator.tx(on_chain_nonce, &mut self.rng).with_sender(sender);
134 let valid_tx = self.validator.validated(tx);
135
136 let res =
137 match pool.add_transaction(valid_tx, on_chain_balance, on_chain_nonce, None) {
138 Ok(res) => res,
139 Err(e) => match e.kind {
140 PoolErrorKind::SpammerExceededCapacity(_) |
142 PoolErrorKind::ReplacementUnderpriced => return,
143 _ => panic!("unexpected error: {e:?}"),
144 },
145 };
146
147 match res {
148 AddedTransaction::Pending(_) => {}
149 AddedTransaction::Parked { .. } => {
150 panic!("expected pending")
151 }
152 }
153
154 self.executed
155 .entry(sender)
156 .or_insert_with(|| ExecutedScenarios { sender, scenarios: vec![] }) .scenarios
158 .push(ExecutedScenario {
159 balance: on_chain_balance,
160 nonce: on_chain_nonce,
161 scenario: Scenario::OnchainNonce { nonce: on_chain_nonce },
162 });
163
164 self.nonces.insert(sender, on_chain_nonce + 1);
165 }
166
167 ScenarioType::HigherNonce { skip } => {
168 if self.nonce_gaps.contains_key(&sender) {
170 return;
171 }
172
173 let higher_nonce = on_chain_nonce + skip;
174
175 let tx = self.tx_generator.tx(higher_nonce, &mut self.rng).with_sender(sender);
177 let valid_tx = self.validator.validated(tx);
178
179 let res =
180 match pool.add_transaction(valid_tx, on_chain_balance, on_chain_nonce, None) {
181 Ok(res) => res,
182 Err(e) => match e.kind {
183 PoolErrorKind::SpammerExceededCapacity(_) |
185 PoolErrorKind::ReplacementUnderpriced => return,
186 _ => panic!("unexpected error: {e:?}"),
187 },
188 };
189
190 match res {
191 AddedTransaction::Pending(_) => {
192 panic!("expected parked")
193 }
194 AddedTransaction::Parked { subpool, .. } => {
195 assert_eq!(
196 subpool,
197 SubPool::Queued,
198 "expected to be moved to queued subpool"
199 );
200 }
201 }
202
203 self.executed
204 .entry(sender)
205 .or_insert_with(|| ExecutedScenarios { sender, scenarios: vec![] }) .scenarios
207 .push(ExecutedScenario {
208 balance: on_chain_balance,
209 nonce: on_chain_nonce,
210 scenario: Scenario::HigherNonce {
211 onchain: on_chain_nonce,
212 nonce: higher_nonce,
213 },
214 });
215 self.nonce_gaps.insert(sender, higher_nonce);
216 }
217
218 ScenarioType::BelowBaseFee { fee } => {
219 let tx = self
221 .tx_generator
222 .tx(on_chain_nonce, &mut self.rng)
223 .with_sender(sender)
224 .with_gas_price(fee);
225 let valid_tx = self.validator.validated(tx);
226
227 let res =
228 match pool.add_transaction(valid_tx, on_chain_balance, on_chain_nonce, None) {
229 Ok(res) => res,
230 Err(e) => match e.kind {
231 PoolErrorKind::SpammerExceededCapacity(_) |
233 PoolErrorKind::ReplacementUnderpriced => return,
234 _ => panic!("unexpected error: {e:?}"),
235 },
236 };
237
238 match res {
239 AddedTransaction::Pending(_) => panic!("expected parked"),
240 AddedTransaction::Parked { subpool, .. } => {
241 assert_eq!(
242 subpool,
243 SubPool::BaseFee,
244 "expected to be moved to base fee subpool"
245 );
246 }
247 }
248 self.executed
249 .entry(sender)
250 .or_insert_with(|| ExecutedScenarios { sender, scenarios: vec![] }) .scenarios
252 .push(ExecutedScenario {
253 balance: on_chain_balance,
254 nonce: on_chain_nonce,
255 scenario: Scenario::BelowBaseFee { fee },
256 });
257 }
258
259 ScenarioType::FillNonceGap => {
260 if self.nonce_gaps.is_empty() {
261 return;
262 }
263
264 let gap_senders: Vec<Address> = self.nonce_gaps.keys().copied().collect();
265 let idx = self.rng.random_range(0..gap_senders.len());
266 let gap_sender = gap_senders[idx];
267 let queued_nonce = self.nonce_gaps[&gap_sender];
268
269 let sender_onchain_nonce = self.nonces[&gap_sender];
270 let sender_balance = self.balances[&gap_sender];
271
272 for fill_nonce in sender_onchain_nonce..queued_nonce {
273 let tx =
274 self.tx_generator.tx(fill_nonce, &mut self.rng).with_sender(gap_sender);
275 let valid_tx = self.validator.validated(tx);
276
277 let res = match pool.add_transaction(
278 valid_tx,
279 sender_balance,
280 sender_onchain_nonce,
281 None,
282 ) {
283 Ok(res) => res,
284 Err(e) => match e.kind {
285 PoolErrorKind::SpammerExceededCapacity(_) |
287 PoolErrorKind::ReplacementUnderpriced => return,
288 _ => panic!("unexpected error: {e:?}"),
289 },
290 };
291
292 match res {
293 AddedTransaction::Pending(_) => {}
294 AddedTransaction::Parked { .. } => {
295 panic!("expected pending when filling gap")
296 }
297 }
298
299 self.executed
300 .entry(gap_sender)
301 .or_insert_with(|| ExecutedScenarios {
302 sender: gap_sender,
303 scenarios: vec![],
304 })
305 .scenarios
306 .push(ExecutedScenario {
307 balance: sender_balance,
308 nonce: fill_nonce,
309 scenario: Scenario::FillNonceGap {
310 filled_nonce: fill_nonce,
311 promoted_nonce: queued_nonce,
312 },
313 });
314 }
315 self.nonces.insert(gap_sender, queued_nonce + 1);
316 self.nonce_gaps.remove(&gap_sender);
317 }
318 }
319 pool.enforce_invariants();
321 }
322}
323
324pub(crate) struct MockSimulatorConfig {
326 pub(crate) num_senders: usize,
328 pub(crate) scenarios: Vec<ScenarioType>,
330 pub(crate) base_fee: u128,
332 pub(crate) tx_generator: MockTransactionDistribution,
334}
335
336impl MockSimulatorConfig {
337 pub(crate) fn addresses(&self, rng: &mut impl rand::Rng) -> Vec<Address> {
339 std::iter::repeat_with(|| Address::random_with(rng)).take(self.num_senders).collect()
340 }
341}
342
343#[derive(Debug, Clone)]
345#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
346pub(crate) enum ScenarioType {
347 OnchainNonce,
348 HigherNonce { skip: u64 },
349 BelowBaseFee { fee: u128 },
350 FillNonceGap,
351}
352
353#[derive(Debug, Clone)]
359#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
360pub(crate) enum Scenario {
361 OnchainNonce { nonce: u64 },
363 HigherNonce { onchain: u64, nonce: u64 },
365 BelowBaseFee { fee: u128 },
367 FillNonceGap { filled_nonce: u64, promoted_nonce: u64 },
369 Multi { scenario: Vec<Self> },
371}
372
373#[derive(Debug, Clone)]
375#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
376pub(crate) struct ExecutedScenario {
377 balance: U256,
379 nonce: u64,
381 scenario: Scenario,
383}
384
385#[derive(Debug, Clone)]
387#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
388pub(crate) struct ExecutedScenarios {
389 sender: Address,
390 scenarios: Vec<ExecutedScenario>,
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use crate::test_utils::{MockFeeRange, MockTransactionRatio};
397
398 #[test]
399 fn test_on_chain_nonce_scenario() {
400 let transaction_ratio = MockTransactionRatio {
401 legacy_pct: 30,
402 dynamic_fee_pct: 70,
403 access_list_pct: 0,
404 blob_pct: 0,
405 };
406
407 let base_fee = 10u128;
408 let fee_ranges = MockFeeRange {
409 gas_price: (base_fee..100).try_into().unwrap(),
410 priority_fee: (1u128..10).try_into().unwrap(),
411 max_fee: (base_fee..110).try_into().unwrap(),
412 max_fee_blob: (1u128..100).try_into().unwrap(),
413 };
414
415 let config = MockSimulatorConfig {
416 num_senders: 10,
417 scenarios: vec![ScenarioType::OnchainNonce],
418 base_fee,
419 tx_generator: MockTransactionDistribution::new(
420 transaction_ratio,
421 fee_ranges,
422 10..100,
423 10..100,
424 ),
425 };
426 let mut simulator = MockTransactionSimulator::new(rand::rng(), config);
427 let mut pool = simulator.create_pool();
428
429 simulator.next(&mut pool);
430 assert_eq!(pool.pending().len(), 1);
431 assert_eq!(pool.queued().len(), 0);
432 assert_eq!(pool.base_fee().len(), 0);
433 }
434
435 #[test]
436 fn test_higher_nonce_scenario() {
437 let transaction_ratio = MockTransactionRatio {
438 legacy_pct: 30,
439 dynamic_fee_pct: 70,
440 access_list_pct: 0,
441 blob_pct: 0,
442 };
443
444 let base_fee = 10u128;
445 let fee_ranges = MockFeeRange {
446 gas_price: (base_fee..100).try_into().unwrap(),
447 priority_fee: (1u128..10).try_into().unwrap(),
448 max_fee: (base_fee..110).try_into().unwrap(),
449 max_fee_blob: (1u128..100).try_into().unwrap(),
450 };
451
452 let config = MockSimulatorConfig {
453 num_senders: 10,
454 scenarios: vec![ScenarioType::HigherNonce { skip: 1 }],
455 base_fee,
456 tx_generator: MockTransactionDistribution::new(
457 transaction_ratio,
458 fee_ranges,
459 10..100,
460 10..100,
461 ),
462 };
463 let mut simulator = MockTransactionSimulator::new(rand::rng(), config);
464 let mut pool = simulator.create_pool();
465
466 simulator.next(&mut pool);
467 assert_eq!(pool.pending().len(), 0);
468 assert_eq!(pool.queued().len(), 1);
469 assert_eq!(pool.base_fee().len(), 0);
470 }
471
472 #[test]
473 fn test_below_base_fee_scenario() {
474 let transaction_ratio = MockTransactionRatio {
475 legacy_pct: 30,
476 dynamic_fee_pct: 70,
477 access_list_pct: 0,
478 blob_pct: 0,
479 };
480
481 let base_fee = 10u128;
482 let fee_ranges = MockFeeRange {
483 gas_price: (base_fee..100).try_into().unwrap(),
484 priority_fee: (1u128..10).try_into().unwrap(),
485 max_fee: (base_fee..110).try_into().unwrap(),
486 max_fee_blob: (1u128..100).try_into().unwrap(),
487 };
488
489 let config = MockSimulatorConfig {
490 num_senders: 10,
491 scenarios: vec![ScenarioType::BelowBaseFee { fee: 8 }], base_fee,
495 tx_generator: MockTransactionDistribution::new(
496 transaction_ratio,
497 fee_ranges,
498 10..100,
499 10..100,
500 ),
501 };
502 let mut simulator = MockTransactionSimulator::new(rand::rng(), config);
503 let mut pool = simulator.create_pool();
504
505 simulator.next(&mut pool);
506 assert_eq!(pool.pending().len(), 0);
507 assert_eq!(pool.queued().len(), 0);
508 assert_eq!(pool.base_fee().len(), 1);
509 }
510
511 #[test]
512 fn test_fill_nonce_gap_scenario() {
513 let transaction_ratio = MockTransactionRatio {
514 legacy_pct: 30,
515 dynamic_fee_pct: 70,
516 access_list_pct: 0,
517 blob_pct: 0,
518 };
519
520 let base_fee = 10u128;
521 let fee_ranges = MockFeeRange {
522 gas_price: (base_fee..100).try_into().unwrap(),
523 priority_fee: (1u128..10).try_into().unwrap(),
524 max_fee: (base_fee..110).try_into().unwrap(),
525 max_fee_blob: (1u128..100).try_into().unwrap(),
526 };
527
528 let config = MockSimulatorConfig {
529 num_senders: 5,
530 scenarios: vec![ScenarioType::HigherNonce { skip: 5 }],
531 base_fee,
532 tx_generator: MockTransactionDistribution::new(
533 transaction_ratio,
534 fee_ranges,
535 10..100,
536 10..100,
537 ),
538 };
539 let mut simulator = MockTransactionSimulator::new(rand::rng(), config);
540 let mut pool = simulator.create_pool();
541
542 for _ in 0..10 {
544 simulator.next(&mut pool);
545 }
546
547 let num_gaps = simulator.nonce_gaps.len();
548
549 assert_eq!(pool.pending().len(), 0);
550 assert_eq!(pool.queued().len(), num_gaps);
551 assert_eq!(pool.base_fee().len(), 0);
552
553 simulator.scenarios = vec![ScenarioType::FillNonceGap];
554 for _ in 0..num_gaps {
555 simulator.next(&mut pool);
556 }
557
558 let expected_pending = num_gaps * 6;
559 assert_eq!(pool.pending().len(), expected_pending);
560 assert_eq!(pool.queued().len(), 0);
561 assert_eq!(pool.base_fee().len(), 0);
562 }
563
564 #[test]
565 fn test_random_scenarios() {
566 let transaction_ratio = MockTransactionRatio {
567 legacy_pct: 30,
568 dynamic_fee_pct: 70,
569 access_list_pct: 0,
570 blob_pct: 0,
571 };
572
573 let base_fee = 10u128;
574 let fee_ranges = MockFeeRange {
575 gas_price: (base_fee..100).try_into().unwrap(),
576 priority_fee: (1u128..10).try_into().unwrap(),
577 max_fee: (base_fee..110).try_into().unwrap(),
578 max_fee_blob: (1u128..100).try_into().unwrap(),
579 };
580
581 let config = MockSimulatorConfig {
582 num_senders: 10,
583 scenarios: vec![
584 ScenarioType::OnchainNonce,
585 ScenarioType::HigherNonce { skip: 2 },
586 ScenarioType::BelowBaseFee { fee: 8 },
587 ScenarioType::FillNonceGap,
588 ],
589 base_fee,
590 tx_generator: MockTransactionDistribution::new(
591 transaction_ratio,
592 fee_ranges,
593 10..100,
594 10..100,
595 ),
596 };
597 let mut simulator = MockTransactionSimulator::new(rand::rng(), config);
598 let mut pool = simulator.create_pool();
599
600 for _ in 0..1000 {
601 simulator.next(&mut pool);
602 }
603 }
604}