Skip to content

Commit d80987f

Browse files
committed
tests: add test fixture for easier abstraction
1 parent 8d487db commit d80987f

File tree

11 files changed

+1448
-178
lines changed

11 files changed

+1448
-178
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
@@ -169,6 +169,7 @@ reth-provider = { git = "https://github.com/scroll-tech/reth.git", default-featu
169169
reth-rpc-api = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
170170
reth-rpc-eth-api = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
171171
reth-rpc-eth-types = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
172+
reth-rpc-layer = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
172173
reth-rpc-server-types = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
173174
reth-storage-api = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
174175
reth-tasks = { git = "https://github.com/scroll-tech/reth.git", default-features = false }

crates/node/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ alloy-rpc-types-engine = { workspace = true, optional = true }
8080
reth-e2e-test-utils = { workspace = true, optional = true }
8181
reth-engine-local = { workspace = true, optional = true }
8282
reth-provider = { workspace = true, optional = true }
83+
reth-rpc-layer = { workspace = true, optional = true }
8384
reth-rpc-server-types = { workspace = true, optional = true }
85+
reth-tokio-util = { workspace = true, optional = true }
8486
scroll-alloy-rpc-types-engine = { workspace = true, optional = true }
8587
scroll-alloy-rpc-types.workspace = true
8688

@@ -107,6 +109,7 @@ reth-e2e-test-utils.workspace = true
107109
reth-node-core.workspace = true
108110
reth-provider.workspace = true
109111
reth-primitives-traits.workspace = true
112+
reth-rpc-layer.workspace = true
110113
reth-rpc-server-types.workspace = true
111114
reth-scroll-node = { workspace = true, features = ["test-utils"] }
112115
reth-storage-api.workspace = true
@@ -139,6 +142,8 @@ test-utils = [
139142
"rollup-node/test-utils",
140143
"reth-e2e-test-utils",
141144
"reth-rpc-server-types",
145+
"reth-rpc-layer",
146+
"reth-tokio-util",
142147
"scroll-alloy-rpc-types-engine",
143148
"alloy-rpc-types-engine",
144149
"reth-primitives-traits/test-utils",
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//! Block building helpers for test fixtures.
2+
3+
use super::fixture::TestFixture;
4+
use alloy_primitives::B256;
5+
use futures::StreamExt;
6+
use reth_primitives_traits::transaction::TxHashRef;
7+
use reth_rpc_api::EngineApiClient;
8+
use reth_scroll_chainspec::ScrollChainSpec;
9+
use reth_scroll_engine_primitives::ScrollEngineTypes;
10+
use reth_scroll_primitives::ScrollBlock;
11+
use rollup_node_sequencer::SequencerEvent;
12+
use scroll_alloy_consensus::ScrollTransaction;
13+
use scroll_alloy_provider::ScrollAuthApiEngineClient;
14+
use scroll_db::Database;
15+
16+
/// Builder for constructing and validating blocks in tests.
17+
#[derive(Debug)]
18+
pub struct BlockBuilder<'a, EC> {
19+
fixture: &'a mut TestFixture<EC>,
20+
expected_tx_hashes: Vec<B256>,
21+
expected_tx_count: Option<usize>,
22+
expected_base_fee: Option<u64>,
23+
expect_l1_message: bool,
24+
expected_l1_message_count: Option<usize>,
25+
}
26+
27+
impl<'a, EC: EngineApiClient<ScrollEngineTypes> + Sync + Send + 'static> BlockBuilder<'a, EC> {
28+
/// Create a new block builder.
29+
pub(crate) fn new(fixture: &'a mut TestFixture<EC>) -> Self {
30+
Self {
31+
fixture,
32+
expected_tx_hashes: Vec::new(),
33+
expected_tx_count: None,
34+
expected_base_fee: None,
35+
expect_l1_message: false,
36+
expected_l1_message_count: None,
37+
}
38+
}
39+
40+
/// Expect a specific transaction to be included in the block.
41+
pub fn expect_tx(mut self, tx_hash: B256) -> Self {
42+
self.expected_tx_hashes.push(tx_hash);
43+
self
44+
}
45+
46+
/// Expect a specific number of transactions in the block.
47+
pub fn expect_tx_count(mut self, count: usize) -> Self {
48+
self.expected_tx_count = Some(count);
49+
self
50+
}
51+
52+
/// Expect a specific base fee per gas.
53+
pub fn expect_base_fee(mut self, base_fee: u64) -> Self {
54+
self.expected_base_fee = Some(base_fee);
55+
self
56+
}
57+
58+
/// Expect at least one L1 message in the block.
59+
pub fn expect_l1_message(mut self) -> Self {
60+
self.expect_l1_message = true;
61+
self
62+
}
63+
64+
/// Expect a specific number of L1 messages in the block.
65+
pub fn expect_l1_message_count(mut self, count: usize) -> Self {
66+
self.expected_l1_message_count = Some(count);
67+
self
68+
}
69+
70+
/// Build the block and validate against expectations.
71+
pub async fn await_block(self) -> eyre::Result<ScrollBlock> {
72+
let sequencer_node = &mut self.fixture.nodes[0];
73+
74+
// Get the sequencer from the rollup manager handle
75+
let handle = &mut sequencer_node.rollup_manager_handle;
76+
77+
// Trigger block building
78+
handle.build_block();
79+
80+
// Wait for the block to be built by listening to the event stream
81+
let events = &mut sequencer_node.chain_orchestrator_rx;
82+
83+
loop {
84+
if let Some(event) = events.next().await {
85+
if let rollup_node_chain_orchestrator::ChainOrchestratorEvent::BlockSequenced(
86+
block,
87+
) = event
88+
{
89+
return self.validate_block(&block);
90+
}
91+
} else {
92+
return Err(eyre::eyre!("Event stream ended without block sequenced event"));
93+
}
94+
}
95+
}
96+
97+
/// Build a block using the low-level sequencer API (for direct sequencer tests).
98+
pub async fn build_with_sequencer(
99+
self,
100+
sequencer: &mut rollup_node_sequencer::Sequencer<Database, ScrollChainSpec>,
101+
engine: &mut scroll_engine::Engine<ScrollAuthApiEngineClient<EC>>,
102+
) -> eyre::Result<Option<ScrollBlock>> {
103+
// Start payload building
104+
sequencer.start_payload_building(engine).await?;
105+
106+
// Wait for the payload to be ready
107+
let payload_id = loop {
108+
if let Some(SequencerEvent::PayloadReady(id)) = sequencer.next().await {
109+
break id;
110+
}
111+
};
112+
113+
// Finalize the payload building
114+
let block_opt = sequencer.finalize_payload_building(payload_id, engine).await?;
115+
116+
if let Some(ref block) = block_opt {
117+
self.validate_block(block)?;
118+
}
119+
120+
Ok(block_opt)
121+
}
122+
123+
/// Validate the block against expectations.
124+
fn validate_block(self, block: &ScrollBlock) -> eyre::Result<ScrollBlock> {
125+
// Check transaction count
126+
if let Some(expected_count) = self.expected_tx_count {
127+
if block.body.transactions.len() != expected_count {
128+
return Err(eyre::eyre!(
129+
"Expected {} transactions, but block has {}",
130+
expected_count,
131+
block.body.transactions.len()
132+
));
133+
}
134+
}
135+
136+
// Check specific transaction hashes
137+
for expected_hash in &self.expected_tx_hashes {
138+
if !block.body.transactions.iter().any(|tx| tx.tx_hash() == expected_hash) {
139+
return Err(eyre::eyre!(
140+
"Expected transaction {:?} not found in block",
141+
expected_hash
142+
));
143+
}
144+
}
145+
146+
// Check base fee
147+
if let Some(expected_base_fee) = self.expected_base_fee {
148+
let actual_base_fee = block
149+
.header
150+
.base_fee_per_gas
151+
.ok_or_else(|| eyre::eyre!("Block has no base fee"))?;
152+
if actual_base_fee != expected_base_fee {
153+
return Err(eyre::eyre!(
154+
"Expected base fee {}, but block has {}",
155+
expected_base_fee,
156+
actual_base_fee
157+
));
158+
}
159+
}
160+
161+
// Check L1 messages
162+
if self.expect_l1_message {
163+
let l1_message_count =
164+
block.body.transactions.iter().filter(|tx| tx.queue_index().is_some()).count();
165+
if l1_message_count == 0 {
166+
return Err(eyre::eyre!("Expected at least one L1 message, but block has none"));
167+
}
168+
}
169+
170+
if let Some(expected_count) = self.expected_l1_message_count {
171+
let l1_message_count =
172+
block.body.transactions.iter().filter(|tx| tx.queue_index().is_some()).count();
173+
if l1_message_count != expected_count {
174+
return Err(eyre::eyre!(
175+
"Expected {} L1 messages, but block has {}",
176+
expected_count,
177+
l1_message_count
178+
));
179+
}
180+
}
181+
182+
Ok(block.clone())
183+
}
184+
}

0 commit comments

Comments
 (0)