Skip to content

Commit c19beb8

Browse files
committed
refactor test_batch_commit_gap with test fixture
1 parent 54bb0c3 commit c19beb8

File tree

3 files changed

+107
-100
lines changed

3 files changed

+107
-100
lines changed

crates/node/src/test_utils/event_utils.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,53 @@ impl<'a> EventWaiter<'a> {
143143
Ok(())
144144
}
145145

146+
/// Wait for batch commit indexed event on all specified nodes.
147+
pub async fn batch_commit_indexed(self, target_batch_index: u64, target_l1_block_number: u64) -> eyre::Result<()> {
148+
self.wait_for_event_on_all(|e| {
149+
if let ChainOrchestratorEvent::BatchCommitIndexed {batch_info, l1_block_number} = e {
150+
(batch_info.index == target_batch_index && *l1_block_number == target_l1_block_number).then_some(())
151+
} else {
152+
None
153+
}
154+
})
155+
.await?;
156+
Ok(())
157+
}
158+
159+
/// Wait for batch commit duplicate event on all specified nodes.
160+
pub async fn batch_commit_duplicates(self, target_batch_index: u64) -> eyre::Result<()> {
161+
self.wait_for_event_on_all(|e| {
162+
if let ChainOrchestratorEvent::BatchCommitDuplicate(batch_index) = e {
163+
(*batch_index == target_batch_index).then_some(())
164+
} else {
165+
None
166+
}
167+
})
168+
.await?;
169+
Ok(())
170+
}
171+
172+
/// Wait for batch commit gap event on all specified nodes.
173+
pub async fn batch_commit_gap(
174+
self,
175+
expected_missing_index: u64,
176+
expected_l1_block_number_reset: u64,
177+
) -> eyre::Result<()> {
178+
self.wait_for_event_on_all(|e| {
179+
if let ChainOrchestratorEvent::BatchCommitGap { missing_index, l1_block_number_reset } =
180+
e
181+
{
182+
(*missing_index == expected_missing_index
183+
&& *l1_block_number_reset == expected_l1_block_number_reset)
184+
.then_some(())
185+
} else {
186+
None
187+
}
188+
})
189+
.await?;
190+
Ok(())
191+
}
192+
146193
/// Wait for batch reverted event on all specified nodes.
147194
pub async fn batch_reverted(self) -> eyre::Result<()> {
148195
self.wait_for_event_on_all(|e| {

crates/node/src/test_utils/fixture.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use reth_tokio_util::EventStream;
2727
use rollup_node_chain_orchestrator::{ChainOrchestratorEvent, ChainOrchestratorHandle};
2828
use rollup_node_primitives::BlockInfo;
2929
use rollup_node_sequencer::L1MessageInclusionMode;
30-
use rollup_node_watcher::L1Notification;
30+
use rollup_node_watcher::{L1Notification, L1WatcherCommand};
3131
use scroll_alloy_consensus::ScrollPooledTransaction;
3232
use scroll_alloy_provider::{ScrollAuthApiEngineClient, ScrollEngineApi};
3333
use scroll_alloy_rpc_types::Transaction;
@@ -36,8 +36,9 @@ use std::{
3636
fmt::{Debug, Formatter},
3737
path::PathBuf,
3838
sync::Arc,
39+
time::Duration,
3940
};
40-
use tokio::sync::{mpsc, Mutex};
41+
use tokio::sync::{mpsc, mpsc::UnboundedReceiver, Mutex};
4142

4243
/// Main test fixture providing a high-level interface for testing rollup nodes.
4344
#[derive(Debug)]
@@ -77,6 +78,8 @@ pub struct NodeHandle {
7778
pub engine: Engine<Arc<dyn ScrollEngineApi + Send + Sync + 'static>>,
7879
/// L1 watcher notification channel.
7980
pub l1_watcher_tx: Option<mpsc::Sender<Arc<L1Notification>>>,
81+
/// L1 watcher command receiver.
82+
pub l1_watcher_command_rx: Arc<Mutex<UnboundedReceiver<L1WatcherCommand>>>,
8083
/// Chain orchestrator listener.
8184
pub chain_orchestrator_rx: EventStream<ChainOrchestratorEvent>,
8285
/// Chain orchestrator handle.
@@ -201,6 +204,27 @@ impl TestFixture {
201204
) -> eyre::Result<rollup_node_chain_orchestrator::ChainOrchestratorStatus> {
202205
self.get_status(0).await
203206
}
207+
208+
/// Wait for an L1 watcher command on the sequencer node.
209+
///
210+
/// Returns the received command or an error if timeout is reached.
211+
pub async fn expect_l1_watcher_command(&self) -> eyre::Result<L1WatcherCommand> {
212+
self.expect_l1_watcher_command_on(0).await
213+
}
214+
215+
/// Wait for an L1 watcher command on a specific node.
216+
///
217+
/// Returns the received command or an error if timeout is reached.
218+
pub async fn expect_l1_watcher_command_on(
219+
&self,
220+
node_index: usize,
221+
) -> eyre::Result<L1WatcherCommand> {
222+
let mut command_rx = self.nodes[node_index].l1_watcher_command_rx.lock().await;
223+
tokio::time::timeout(Duration::from_secs(5), command_rx.recv())
224+
.await
225+
.map_err(|_| eyre::eyre!("Timeout waiting for L1 watcher command"))?
226+
.ok_or_else(|| eyre::eyre!("L1 watcher command channel closed"))
227+
}
204228
}
205229

206230
/// Builder for creating test fixtures with a fluent API.
@@ -452,6 +476,7 @@ impl TestFixtureBuilder {
452476

453477
// Get handles if available
454478
let l1_watcher_tx = node.inner.add_ons_handle.l1_watcher_tx.clone();
479+
let l1_watcher_command_rx = node.inner.add_ons_handle.l1_watcher_command_rx.clone();
455480
let rollup_manager_handle = node.inner.add_ons_handle.rollup_manager_handle.clone();
456481
let chain_orchestrator_rx =
457482
node.inner.add_ons_handle.rollup_manager_handle.get_event_listener().await?;
@@ -461,6 +486,7 @@ impl TestFixtureBuilder {
461486
engine,
462487
chain_orchestrator_rx,
463488
l1_watcher_tx,
489+
l1_watcher_command_rx,
464490
rollup_manager_handle,
465491
typ: if config.sequencer_args.sequencer_enabled && index == 0 {
466492
NodeType::Sequencer

crates/node/tests/e2e.rs

Lines changed: 32 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,104 +1814,38 @@ async fn signer_rotation() -> eyre::Result<()> {
18141814

18151815
// Test that the chain orchestrator detects gaps in batch commits, triggers a reset command to the
18161816
// L1 watcher for self-healing and skips duplicate batch commits.
1817-
// #[tokio::test]
1818-
// async fn test_batch_commit_gap() -> eyre::Result<()> {
1819-
// reth_tracing::init_test_tracing();
1820-
//
1821-
// let (mut nodes, _tasks, _wallet) = setup_engine(
1822-
// default_test_scroll_rollup_node_config(),
1823-
// 1,
1824-
// (*SCROLL_DEV).clone(),
1825-
// false,
1826-
// false,
1827-
// )
1828-
// .await?;
1829-
// let node = nodes.pop().unwrap();
1830-
//
1831-
// // Get handles for sending L1 notifications and receiving commands
1832-
// let l1_watcher_tx = node.inner.add_ons_handle.l1_watcher_tx.clone().unwrap();
1833-
// let l1_watcher_command_rx = node.inner.add_ons_handle.l1_watcher_command_rx.clone();
1834-
// let chain_orchestrator = node.inner.add_ons_handle.rollup_manager_handle.clone();
1835-
//
1836-
// // Get event listener to monitor chain orchestrator events
1837-
// let mut events = chain_orchestrator.get_event_listener().await?;
1838-
//
1839-
// // Node is unsynced initially -> does not derive batches (which is what we want)
1840-
//
1841-
// // Send batch commit 1 to populate the database
1842-
// let batch_commit_1 =
1843-
// BatchCommitData { hash: B256::random(), index: 1, block_number: 1, ..Default::default()
1844-
// };
1845-
//
1846-
// l1_watcher_tx
1847-
// .send(Arc::new(L1Notification::BatchCommit {
1848-
// block_info: BlockInfo { number: batch_commit_1.block_number, hash: B256::random() },
1849-
// data: batch_commit_1.clone(),
1850-
// }))
1851-
// .await?;
1852-
// wait_for_event_5s(
1853-
// &mut events,
1854-
// ChainOrchestratorEvent::BatchCommitIndexed {
1855-
// batch_info: BatchInfo { index: batch_commit_1.index, hash: batch_commit_1.hash },
1856-
// l1_block_number: batch_commit_1.block_number,
1857-
// },
1858-
// )
1859-
// .await?;
1860-
//
1861-
// // Send duplicate batch commit 1 - should be skipped and duplicate detected
1862-
// l1_watcher_tx
1863-
// .send(Arc::new(L1Notification::BatchCommit {
1864-
// block_info: BlockInfo { number: batch_commit_1.block_number, hash: B256::random() },
1865-
// data: batch_commit_1.clone(),
1866-
// }))
1867-
// .await?;
1868-
// wait_for_event_5s(
1869-
// &mut events,
1870-
// ChainOrchestratorEvent::BatchCommitDuplicate(batch_commit_1.index),
1871-
// )
1872-
// .await?;
1873-
//
1874-
// // Send batch commit 3 - should trigger reset due to gap (missing batch 2)
1875-
// let batch_commit_3 = BatchCommitData {
1876-
// hash: B256::random(),
1877-
// index: 3, // Gap! Missing index 2
1878-
// block_number: 3,
1879-
// ..Default::default()
1880-
// };
1881-
//
1882-
// l1_watcher_tx
1883-
// .send(Arc::new(L1Notification::BatchCommit {
1884-
// block_info: BlockInfo { number: batch_commit_3.block_number, hash: B256::random() },
1885-
// data: batch_commit_3.clone(),
1886-
// }))
1887-
// .await?;
1888-
// wait_for_event_5s(
1889-
// &mut events,
1890-
// ChainOrchestratorEvent::BatchCommitGap {
1891-
// missing_index: batch_commit_3.index,
1892-
// l1_block_number_reset: batch_commit_1.block_number,
1893-
// },
1894-
// )
1895-
// .await?;
1896-
//
1897-
// let mut command_rx = l1_watcher_command_rx.lock().await;
1898-
// let command = tokio::time::timeout(Duration::from_secs(5), command_rx.recv())
1899-
// .await
1900-
// .expect("should receive command within timeout")
1901-
// .expect("should receive Some(command)");
1902-
//
1903-
// // Verify it's a ResetToBlock command with the correct block number
1904-
// match command {
1905-
// L1WatcherCommand::ResetToBlock { block, .. } => {
1906-
// assert_eq!(
1907-
// block, batch_commit_1.block_number,
1908-
// "Reset block should be the L1 block of the last known batch"
1909-
// );
1910-
// }
1911-
// }
1912-
//
1913-
// Ok(())
1914-
// }
1817+
#[tokio::test]
1818+
async fn test_batch_commit_gap() -> eyre::Result<()> {
1819+
reth_tracing::init_test_tracing();
1820+
1821+
let mut fixture = TestFixture::builder().sequencer().build().await?;
1822+
1823+
// Node is unsynced initially -> does not derive batches (which is what we want)
1824+
1825+
let batch_1_hash = B256::random();
1826+
// Send batch commit 1 to populate the database
1827+
fixture.l1().commit_batch().hash(batch_1_hash).index(1).block_number(1).send().await?;
1828+
fixture.expect_event().batch_commit_indexed(1, 1).await?;
1829+
1830+
// Send duplicate batch commit 1 - should be skipped and duplicate detected
1831+
fixture.l1().commit_batch().hash(batch_1_hash).index(1).block_number(1).send().await?;
1832+
fixture.expect_event().batch_commit_duplicates(1).await?;
1833+
1834+
// Send batch commit 3 - should trigger reset due to gap (missing batch 2)
1835+
fixture.l1().commit_batch().index(3).block_number(3).send().await?;
1836+
// Expect gap event: missing index 3, reset to L1 block 1 (where batch 1 was committed)
1837+
fixture.expect_event().batch_commit_gap(3, 1).await?;
1838+
1839+
// Verify that a ResetToBlock command was sent to the L1 watcher
1840+
let command = fixture.expect_l1_watcher_command().await?;
1841+
match command {
1842+
rollup_node_watcher::L1WatcherCommand::ResetToBlock { block, .. } => {
1843+
assert_eq!(block, 1, "Reset block should be the L1 block of the last known batch");
1844+
}
1845+
}
1846+
1847+
Ok(())
1848+
}
19151849

19161850
// Test that the chain orchestrator detects gaps in L1 messages, triggers a reset command to the
19171851
// L1 watcher for self-healing and skips duplicate L1 messages received.

0 commit comments

Comments
 (0)