Skip to content

Commit 7697e42

Browse files
committed
tests: add load tests to the cache ingestion and simulation loop
1 parent 9ad68c9 commit 7697e42

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ url = "2.5.4"
6262
[dev-dependencies]
6363
alloy-hardforks = "0.4.0"
6464
alloy-chains = "0.2"
65+
signet-bundle = "0.16.0-rc.11"
6566

6667
# comment / uncomment for local dev
6768
# [patch.crates-io]

tests/bundle_load_test.rs

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
//! Load tests for bundle simulation.
2+
//!
3+
//! These tests exercise the block building loop with high volumes of bundles
4+
//! and transactions to verify correctness and deadline compliance under stress.
5+
6+
use alloy::{
7+
primitives::{Address, U256},
8+
serde::OtherFields,
9+
signers::local::PrivateKeySigner,
10+
};
11+
use builder::test_utils::{
12+
DEFAULT_BALANCE, DEFAULT_BASEFEE, TestBlockBuildBuilder, TestDbBuilder, TestSimEnvBuilder,
13+
create_transfer_tx, scenarios_test_block_env,
14+
};
15+
use signet_bundle::RecoveredBundle;
16+
use signet_sim::{BuiltBlock, SimCache};
17+
use std::time::Duration;
18+
19+
/// Block number used for all test environments and bundles.
20+
const BLOCK_NUMBER: u64 = 100;
21+
22+
/// Block timestamp used for all test environments and bundles.
23+
const BLOCK_TIMESTAMP: u64 = 1_700_000_000;
24+
25+
/// Parmigiana rollup chain ID.
26+
const RU_CHAIN_ID: u64 = 88888;
27+
28+
/// Generate N random funded signers and a database builder with all of them funded.
29+
fn generate_funded_accounts(n: usize) -> (Vec<PrivateKeySigner>, TestDbBuilder) {
30+
let signers: Vec<PrivateKeySigner> = (0..n).map(|_| PrivateKeySigner::random()).collect();
31+
let balance = U256::from(DEFAULT_BALANCE);
32+
33+
let mut db_builder = TestDbBuilder::new();
34+
for signer in &signers {
35+
db_builder = db_builder.with_account(signer.address(), balance, 0);
36+
}
37+
38+
(signers, db_builder)
39+
}
40+
41+
/// Create a `RecoveredBundle` with one transfer transaction.
42+
fn make_bundle(signer: &PrivateKeySigner, to: Address, uuid: String) -> RecoveredBundle {
43+
let tx = create_transfer_tx(
44+
signer,
45+
to,
46+
U256::from(1_000u64),
47+
0,
48+
RU_CHAIN_ID,
49+
10_000_000_000, // 10 gwei priority fee
50+
)
51+
.unwrap();
52+
53+
RecoveredBundle::new_unchecked(
54+
vec![tx],
55+
vec![],
56+
BLOCK_NUMBER,
57+
Some(BLOCK_TIMESTAMP - 100),
58+
Some(BLOCK_TIMESTAMP + 100),
59+
vec![],
60+
Some(uuid),
61+
vec![],
62+
None,
63+
None,
64+
vec![],
65+
OtherFields::default(),
66+
)
67+
}
68+
69+
/// Build a `TestBlockBuildBuilder` from a pre-funded db builder.
70+
fn build_env(db_builder: TestDbBuilder) -> TestBlockBuildBuilder {
71+
let db = db_builder.build();
72+
let block_env =
73+
scenarios_test_block_env(BLOCK_NUMBER, DEFAULT_BASEFEE, BLOCK_TIMESTAMP, 3_000_000_000);
74+
let sim_env = TestSimEnvBuilder::new()
75+
.with_rollup_db(db.clone())
76+
.with_host_db(db)
77+
.with_block_env(block_env);
78+
TestBlockBuildBuilder::new().with_sim_env_builder(sim_env)
79+
}
80+
81+
/// 50 bundles each containing 1 transfer tx. Verify block builds and includes txs.
82+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
83+
async fn test_load_many_bundles() {
84+
let count = 50;
85+
let (signers, db_builder) = generate_funded_accounts(count);
86+
let recipient = Address::repeat_byte(0xAA);
87+
88+
let cache = SimCache::with_capacity(count);
89+
let bundles: Vec<RecoveredBundle> = signers
90+
.iter()
91+
.enumerate()
92+
.map(|(i, signer)| make_bundle(signer, recipient, format!("bundle-{i}")))
93+
.collect();
94+
95+
cache.add_bundles(bundles, DEFAULT_BASEFEE);
96+
assert_eq!(cache.len(), count);
97+
98+
let builder = build_env(db_builder).with_cache(cache).with_deadline(Duration::from_secs(5));
99+
let built: BuiltBlock = builder.build().build().await;
100+
101+
assert!(built.tx_count() > 0, "expected transactions in built block, got 0");
102+
assert_eq!(
103+
built.tx_count(),
104+
count,
105+
"expected all {count} bundle txs to be included, got {}",
106+
built.tx_count()
107+
);
108+
}
109+
110+
111+
/// 50 bundles each containing 1 transfer tx. Verify block builds and includes txs.
112+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
113+
async fn test_load_50k_bundles() {
114+
let count = 50_000;
115+
let (signers, db_builder) = generate_funded_accounts(count);
116+
let recipient = Address::repeat_byte(0xAA);
117+
118+
let cache = SimCache::with_capacity(count);
119+
let bundles: Vec<RecoveredBundle> = signers
120+
.iter()
121+
.enumerate()
122+
.map(|(i, signer)| make_bundle(signer, recipient, format!("bundle-{i}")))
123+
.collect();
124+
125+
cache.add_bundles(bundles, DEFAULT_BASEFEE);
126+
assert_eq!(cache.len(), count);
127+
128+
let builder = build_env(db_builder).with_cache(cache).with_deadline(Duration::from_secs(12));
129+
let built: BuiltBlock = builder.build().build().await;
130+
131+
assert!(built.tx_count() > 0, "expected transactions in built block, got 0");
132+
}
133+
134+
/// 30 bundles + 30 standalone txs. Verify both types land in the built block.
135+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
136+
async fn test_load_bundles_and_txs_mixed() {
137+
let bundle_count = 30;
138+
let tx_count = 30;
139+
let total = bundle_count + tx_count;
140+
141+
let (signers, db_builder) = generate_funded_accounts(total);
142+
let recipient = Address::repeat_byte(0xBB);
143+
144+
let cache = SimCache::with_capacity(total);
145+
146+
let bundles: Vec<RecoveredBundle> = signers[..bundle_count]
147+
.iter()
148+
.enumerate()
149+
.map(|(i, signer)| make_bundle(signer, recipient, format!("mix-bundle-{i}")))
150+
.collect();
151+
cache.add_bundles(bundles, DEFAULT_BASEFEE);
152+
153+
for signer in &signers[bundle_count..] {
154+
let tx = create_transfer_tx(
155+
signer,
156+
recipient,
157+
U256::from(1_000u64),
158+
0,
159+
RU_CHAIN_ID,
160+
10_000_000_000,
161+
)
162+
.unwrap();
163+
cache.add_tx(tx, DEFAULT_BASEFEE);
164+
}
165+
166+
assert_eq!(cache.len(), total);
167+
168+
let builder = build_env(db_builder).with_cache(cache).with_deadline(Duration::from_secs(5));
169+
let built: BuiltBlock = builder.build().build().await;
170+
171+
assert!(built.tx_count() > 0, "expected transactions in built block");
172+
assert_eq!(
173+
built.tx_count(),
174+
total,
175+
"expected all {total} items included, got {}",
176+
built.tx_count()
177+
);
178+
}
179+
180+
/// Many bundles with a constrained gas limit. Verify gas cap is respected.
181+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
182+
async fn test_load_saturate_gas_limit() {
183+
let count = 50;
184+
let (signers, db_builder) = generate_funded_accounts(count);
185+
let recipient = Address::repeat_byte(0xCC);
186+
187+
let cache = SimCache::with_capacity(count);
188+
let bundles: Vec<RecoveredBundle> = signers
189+
.iter()
190+
.enumerate()
191+
.map(|(i, signer)| make_bundle(signer, recipient, format!("gas-bundle-{i}")))
192+
.collect();
193+
cache.add_bundles(bundles, DEFAULT_BASEFEE);
194+
195+
// Each transfer costs 21,000 gas. Allow room for ~10 transfers.
196+
let max_gas: u64 = 21_000 * 10;
197+
198+
let builder = build_env(db_builder)
199+
.with_cache(cache)
200+
.with_deadline(Duration::from_secs(5))
201+
.with_max_gas(max_gas);
202+
let built: BuiltBlock = builder.build().build().await;
203+
204+
assert!(
205+
built.tx_count() <= 10,
206+
"expected at most 10 txs within gas limit, got {}",
207+
built.tx_count()
208+
);
209+
assert!(built.tx_count() > 0, "expected at least some txs to be included");
210+
}
211+
212+
/// Many bundles with a tight deadline. Verify block completes within time.
213+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
214+
async fn test_load_deadline_pressure() {
215+
let count = 100;
216+
let (signers, db_builder) = generate_funded_accounts(count);
217+
let recipient = Address::repeat_byte(0xDD);
218+
219+
let cache = SimCache::with_capacity(count);
220+
let bundles: Vec<RecoveredBundle> = signers
221+
.iter()
222+
.enumerate()
223+
.map(|(i, signer)| make_bundle(signer, recipient, format!("deadline-bundle-{i}")))
224+
.collect();
225+
cache.add_bundles(bundles, DEFAULT_BASEFEE);
226+
227+
let deadline = Duration::from_millis(500);
228+
let start = std::time::Instant::now();
229+
230+
let builder = build_env(db_builder).with_cache(cache).with_deadline(deadline);
231+
let built: BuiltBlock = builder.build().build().await;
232+
233+
let elapsed = start.elapsed();
234+
235+
assert!(built.tx_count() > 0, "expected at least some txs under deadline pressure");
236+
237+
// Should complete within a reasonable margin of the deadline.
238+
assert!(elapsed < deadline * 3, "block build took {elapsed:?}, expected within ~{deadline:?}");
239+
}

0 commit comments

Comments
 (0)