Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 182 additions & 21 deletions crates/transaction-pool/src/test_utils/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#![allow(dead_code)]

use crate::{
pool::{txpool::TxPool, AddedTransaction},
pool::{state::SubPool, txpool::TxPool, AddedTransaction},
test_utils::{MockOrdering, MockTransactionDistribution, MockTransactionFactory},
TransactionOrdering,
};
Expand Down Expand Up @@ -95,6 +95,18 @@ impl<R: Rng> MockTransactionSimulator<R> {
}
}

/// Creates a pool configured for this simulator
///
/// This is needed because `MockPool::default()` sets `pending_basefee` to 7, but we might want
/// to use different values
pub(crate) fn create_pool(&self) -> MockPool {
let mut pool = MockPool::default();
let mut info = pool.block_info();
info.pending_basefee = self.base_fee as u64;
pool.set_block_info(info);
pool
}

/// Returns a random address from the senders set
fn rng_address(&mut self) -> Address {
let idx = self.rng.random_range(0..self.senders.len());
Expand All @@ -116,32 +128,95 @@ impl<R: Rng> MockTransactionSimulator<R> {

match scenario {
ScenarioType::OnchainNonce => {
let tx = self
.tx_generator
.tx(on_chain_nonce, &mut self.rng)
.with_gas_price(self.base_fee);
// uses fee from fee_ranges
let tx = self.tx_generator.tx(on_chain_nonce, &mut self.rng);
let valid_tx = self.validator.validated(tx);

let res =
pool.add_transaction(valid_tx, on_chain_balance, on_chain_nonce, None).unwrap();

// TODO(mattsse): need a way expect based on the current state of the pool and tx
// settings

match res {
AddedTransaction::Pending(_) => {}
AddedTransaction::Parked { .. } => {
panic!("expected pending")
}
}
self.executed
.entry(sender)
.or_insert_with(|| ExecutedScenarios { sender, scenarios: vec![] }) // in the case of a new sender
.scenarios
.push(ExecutedScenario {
balance: on_chain_balance,
nonce: on_chain_nonce,
scenario: Scenario::OnchainNonce { nonce: on_chain_nonce },
});
}

ScenarioType::HigherNonce { skip } => {
let higher_nonce = on_chain_nonce + skip;

// uses fee from fee_ranges
let tx = self.tx_generator.tx(higher_nonce, &mut self.rng);
let valid_tx = self.validator.validated(tx);

let res =
pool.add_transaction(valid_tx, on_chain_balance, on_chain_nonce, None).unwrap();

// TODO(mattsse): check subpools
match res {
AddedTransaction::Pending(_) => {
panic!("expected parked")
}
AddedTransaction::Parked { subpool, .. } => {
assert_eq!(
subpool,
SubPool::Queued,
"expected to be moved to queued subpool"
);
}
}
self.executed
.entry(sender)
.or_insert_with(|| ExecutedScenarios { sender, scenarios: vec![] }) // in the case of a new sender
.scenarios
.push(ExecutedScenario {
balance: on_chain_balance,
nonce: on_chain_nonce,
scenario: Scenario::HigherNonce {
onchain: on_chain_nonce,
nonce: higher_nonce,
},
});
}
ScenarioType::HigherNonce { .. } => {
unimplemented!()

ScenarioType::BelowBaseFee { fee } => {
// fee should be in [MIN_PROTOCOL_BASE_FEE, base_fee)
let tx = self.tx_generator.tx(on_chain_nonce, &mut self.rng).with_gas_price(fee);
let valid_tx = self.validator.validated(tx);

let res =
pool.add_transaction(valid_tx, on_chain_balance, on_chain_nonce, None).unwrap();

match res {
AddedTransaction::Pending(_) => panic!("expected parked"),
AddedTransaction::Parked { subpool, .. } => {
assert_eq!(
subpool,
SubPool::BaseFee,
"expected to be moved to base fee subpool"
);
}
}
self.executed
.entry(sender)
.or_insert_with(|| ExecutedScenarios { sender, scenarios: vec![] }) // in the case of a new sender
.scenarios
.push(ExecutedScenario {
balance: on_chain_balance,
nonce: on_chain_nonce,
scenario: Scenario::BelowBaseFee { fee },
});
}
}

// make sure everything is set
pool.enforce_invariants()
}
Expand Down Expand Up @@ -172,6 +247,7 @@ impl MockSimulatorConfig {
pub(crate) enum ScenarioType {
OnchainNonce,
HigherNonce { skip: u64 },
BelowBaseFee { fee: u128 },
}

/// The actual scenario, ready to be executed
Expand All @@ -186,10 +262,10 @@ pub(crate) enum Scenario {
OnchainNonce { nonce: u64 },
/// Send a tx with a higher nonce that what the sender has on chain
HigherNonce { onchain: u64, nonce: u64 },
Multi {
// Execute multiple test scenarios
scenario: Vec<Self>,
},
/// Send a tx with a base fee below the base fee of the pool
BelowBaseFee { fee: u128 },
/// Execute multiple test scenarios
Multi { scenario: Vec<Self> },
}

/// Represents an executed scenario
Expand Down Expand Up @@ -226,17 +302,18 @@ mod tests {
blob_pct: 0,
};

let base_fee = 10u128;
let fee_ranges = MockFeeRange {
gas_price: (10u128..100).try_into().unwrap(),
priority_fee: (10u128..100).try_into().unwrap(),
max_fee: (100u128..110).try_into().unwrap(),
gas_price: (base_fee..100).try_into().unwrap(),
priority_fee: (1u128..10).try_into().unwrap(),
max_fee: (base_fee..110).try_into().unwrap(),
max_fee_blob: (1u128..100).try_into().unwrap(),
};

let config = MockSimulatorConfig {
num_senders: 10,
scenarios: vec![ScenarioType::OnchainNonce],
base_fee: 10,
base_fee,
tx_generator: MockTransactionDistribution::new(
transaction_ratio,
fee_ranges,
Expand All @@ -245,8 +322,92 @@ mod tests {
),
};
let mut simulator = MockTransactionSimulator::new(rand::rng(), config);
let mut pool = MockPool::default();
let mut pool = simulator.create_pool();

simulator.next(&mut pool);
assert_eq!(pool.pending().len(), 1);
assert_eq!(pool.queued().len(), 0);
assert_eq!(pool.base_fee().len(), 0);
}

#[test]
fn test_higher_nonce_scenario() {
let transaction_ratio = MockTransactionRatio {
legacy_pct: 30,
dynamic_fee_pct: 70,
access_list_pct: 0,
blob_pct: 0,
};

let base_fee = 10u128;
let fee_ranges = MockFeeRange {
gas_price: (base_fee..100).try_into().unwrap(),
priority_fee: (1u128..10).try_into().unwrap(),
max_fee: (base_fee..110).try_into().unwrap(),
max_fee_blob: (1u128..100).try_into().unwrap(),
};

let config = MockSimulatorConfig {
num_senders: 10,
scenarios: vec![ScenarioType::HigherNonce { skip: 1 }],
base_fee,
tx_generator: MockTransactionDistribution::new(
transaction_ratio,
fee_ranges,
10..100,
10..100,
),
};
let mut simulator = MockTransactionSimulator::new(rand::rng(), config);
let mut pool = simulator.create_pool();

simulator.next(&mut pool);
assert_eq!(pool.pending().len(), 0);
assert_eq!(pool.queued().len(), 1);
assert_eq!(pool.base_fee().len(), 0);
}

#[test]
fn test_below_base_fee_scenario() {
let transaction_ratio = MockTransactionRatio {
legacy_pct: 30,
dynamic_fee_pct: 70,
access_list_pct: 0,
blob_pct: 0,
};

let base_fee = 10u128;
let fee_ranges = MockFeeRange {
gas_price: (base_fee..100).try_into().unwrap(),
priority_fee: (1u128..10).try_into().unwrap(),
max_fee: (base_fee..110).try_into().unwrap(),
max_fee_blob: (1u128..100).try_into().unwrap(),
};

let config = MockSimulatorConfig {
num_senders: 10,
scenarios: vec![ScenarioType::BelowBaseFee { fee: 8 }], /* fee should be in
* [MIN_PROTOCOL_BASE_FEE,
* base_fee) */
base_fee,
tx_generator: MockTransactionDistribution::new(
transaction_ratio,
fee_ranges,
10..100,
10..100,
),
};
let mut simulator = MockTransactionSimulator::new(rand::rng(), config);
let mut pool = simulator.create_pool();

simulator.next(&mut pool);
assert_eq!(pool.pending().len(), 0);
assert_eq!(pool.queued().len(), 0);
assert_eq!(pool.base_fee().len(), 1);
}

#[test]
fn test_many_random_scenarios() {
// todo: we should use a more deterministic approach to test this
}
}
Loading