@@ -36,6 +36,7 @@ use rollup_node_sequencer::L1MessageInclusionMode;
36
36
use rollup_node_watcher:: L1Notification ;
37
37
use scroll_alloy_consensus:: TxL1Message ;
38
38
use scroll_alloy_rpc_types:: Transaction as ScrollAlloyTransaction ;
39
+ use scroll_db:: L1MessageStart ;
39
40
use scroll_network:: { NewBlockWithPeer , SCROLL_MAINNET } ;
40
41
use scroll_wire:: { ScrollWireConfig , ScrollWireProtocolHandler } ;
41
42
use std:: { path:: PathBuf , sync:: Arc , time:: Duration } ;
@@ -1180,7 +1181,7 @@ async fn can_handle_l1_message_reorg() -> eyre::Result<()> {
1180
1181
for i in 1 ..=10 {
1181
1182
node0_rnm_handle. build_block ( ) . await ;
1182
1183
let b = wait_for_block_sequenced_5s ( & mut node0_rnm_events, i) . await ?;
1183
- println ! ( "Sequenced block {} {:?} ", b. header. number, b. header. hash_slow( ) ) ;
1184
+ tracing :: info! ( target : "scroll::test ", block_number = ? b. header. number, block_hash = ? b. header. hash_slow( ) , "Sequenced block" ) ;
1184
1185
}
1185
1186
1186
1187
// Assert that the follower node has received all 10 blocks from the sequencer node.
@@ -1281,6 +1282,147 @@ async fn can_handle_l1_message_reorg() -> eyre::Result<()> {
1281
1282
Ok ( ( ) )
1282
1283
}
1283
1284
1285
+ /// Tests that a follower node correctly rejects L2 blocks containing L1 messages it hasn't received
1286
+ /// yet.
1287
+ ///
1288
+ /// This test verifies the security mechanism that prevents nodes from processing blocks with
1289
+ /// unknown L1 messages, ensuring L2 chain consistency.
1290
+ ///
1291
+ /// # Test scenario
1292
+ /// 1. Sets up two nodes: a sequencer and a follower
1293
+ /// 2. The sequencer builds 10 initial blocks that are successfully imported by the follower
1294
+ /// 3. An L1 message is sent only to the sequencer (not to the follower)
1295
+ /// 4. The sequencer includes this L1 message in block 11 and continues building blocks up to block
1296
+ /// 15
1297
+ /// 5. The follower detects the unknown L1 message and stops processing at block 10
1298
+ /// 6. Once the L1 message is finally sent to the follower, it can process the previously rejected
1299
+ /// blocks
1300
+ /// 7. The test confirms both nodes are synchronized at block 16 after the follower catches up
1301
+ ///
1302
+ /// # Key verification points
1303
+ /// - The follower correctly identifies missing L1 messages with a `L1MessageMissingInDatabase`
1304
+ /// event
1305
+ /// - Block processing halts at the last valid block when an unknown L1 message is encountered
1306
+ /// - The follower can resume processing and catch up once it receives the missing L1 message
1307
+ /// - This prevents nodes from accepting blocks with L1 messages they cannot validate
1308
+ #[ tokio:: test]
1309
+ async fn can_reject_l2_block_with_unknown_l1_message ( ) -> eyre:: Result < ( ) > {
1310
+ reth_tracing:: init_test_tracing ( ) ;
1311
+ color_eyre:: install ( ) ?;
1312
+ let chain_spec = ( * SCROLL_DEV ) . clone ( ) ;
1313
+
1314
+ // Launch 2 nodes: node0=sequencer and node1=follower.
1315
+ let config = default_sequencer_test_scroll_rollup_node_config ( ) ;
1316
+ let ( mut nodes, _tasks, _) = setup_engine ( config, 2 , chain_spec. clone ( ) , false , false ) . await ?;
1317
+ let node0 = nodes. remove ( 0 ) ;
1318
+ let node1 = nodes. remove ( 0 ) ;
1319
+
1320
+ // Get handles
1321
+ let node0_rnm_handle = node0. inner . add_ons_handle . rollup_manager_handle . clone ( ) ;
1322
+ let mut node0_rnm_events = node0_rnm_handle. get_event_listener ( ) . await ?;
1323
+ let node0_l1_watcher_tx = node0. inner . add_ons_handle . l1_watcher_tx . as_ref ( ) . unwrap ( ) ;
1324
+
1325
+ let node1_rnm_handle = node1. inner . add_ons_handle . rollup_manager_handle . clone ( ) ;
1326
+ let mut node1_rnm_events = node1_rnm_handle. get_event_listener ( ) . await ?;
1327
+ let node1_l1_watcher_tx = node1. inner . add_ons_handle . l1_watcher_tx . as_ref ( ) . unwrap ( ) ;
1328
+
1329
+ // Let the sequencer build 10 blocks before performing the reorg process.
1330
+ for i in 1 ..=10 {
1331
+ node0_rnm_handle. build_block ( ) . await ;
1332
+ let b = wait_for_block_sequenced_5s ( & mut node0_rnm_events, i) . await ?;
1333
+ tracing:: info!( target: "scroll::test" , block_number = ?b. header. number, block_hash = ?b. header. hash_slow( ) , "Sequenced block" )
1334
+ }
1335
+
1336
+ // Assert that the follower node has received all 10 blocks from the sequencer node.
1337
+ wait_for_block_imported_5s ( & mut node1_rnm_events, 10 ) . await ?;
1338
+
1339
+ // Send a L1 message and wait for it to be indexed.
1340
+ let l1_message_notification = L1Notification :: L1Message {
1341
+ message : TxL1Message {
1342
+ queue_index : 0 ,
1343
+ gas_limit : 21000 ,
1344
+ to : Default :: default ( ) ,
1345
+ value : Default :: default ( ) ,
1346
+ sender : address ! ( "f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" ) ,
1347
+ input : Default :: default ( ) ,
1348
+ } ,
1349
+ block_number : 10 ,
1350
+ block_timestamp : 0 ,
1351
+ } ;
1352
+
1353
+ // Send the L1 message to the sequencer node but not to follower node.
1354
+ node0_l1_watcher_tx. send ( Arc :: new ( l1_message_notification. clone ( ) ) ) . await ?;
1355
+ node0_l1_watcher_tx. send ( Arc :: new ( L1Notification :: NewBlock ( 10 ) ) ) . await ?;
1356
+ wait_for_event_5s (
1357
+ & mut node0_rnm_events,
1358
+ RollupManagerEvent :: ChainOrchestratorEvent ( ChainOrchestratorEvent :: L1MessageCommitted ( 0 ) ) ,
1359
+ )
1360
+ . await ?;
1361
+
1362
+ // Build block that contains the L1 message.
1363
+ node0_rnm_handle. build_block ( ) . await ;
1364
+ wait_for_event_predicate_5s ( & mut node0_rnm_events, |e| {
1365
+ if let RollupManagerEvent :: BlockSequenced ( block) = e {
1366
+ if block. header . number == 11 &&
1367
+ block. body . transactions . len ( ) == 1 &&
1368
+ block. body . transactions . iter ( ) . any ( |tx| tx. is_l1_message ( ) )
1369
+ {
1370
+ return true ;
1371
+ }
1372
+ }
1373
+
1374
+ false
1375
+ } )
1376
+ . await ?;
1377
+
1378
+ for i in 12 ..=15 {
1379
+ node0_rnm_handle. build_block ( ) . await ;
1380
+ wait_for_block_sequenced_5s ( & mut node0_rnm_events, i) . await ?;
1381
+ }
1382
+
1383
+ wait_for_event_5s (
1384
+ & mut node1_rnm_events,
1385
+ RollupManagerEvent :: L1MessageMissingInDatabase {
1386
+ start : L1MessageStart :: Hash ( b256 ! (
1387
+ "0x0a2f8e75392ab51a26a2af835042c614eb141cd934fe1bdd4934c10f2fe17e98"
1388
+ ) ) ,
1389
+ } ,
1390
+ )
1391
+ . await ?;
1392
+
1393
+ // follower node should not import block 15
1394
+ // follower node doesn't know about the L1 message so stops processing the chain at block 10
1395
+ assert_eq ! ( latest_block( & node1) . await ?. header. number, 10 ) ;
1396
+
1397
+ // Finally send L1 the L1 message to follower node.
1398
+ node1_l1_watcher_tx. send ( Arc :: new ( l1_message_notification) ) . await ?;
1399
+ node1_l1_watcher_tx. send ( Arc :: new ( L1Notification :: NewBlock ( 10 ) ) ) . await ?;
1400
+ wait_for_event_5s (
1401
+ & mut node1_rnm_events,
1402
+ RollupManagerEvent :: ChainOrchestratorEvent ( ChainOrchestratorEvent :: L1MessageCommitted ( 0 ) ) ,
1403
+ )
1404
+ . await ?;
1405
+
1406
+ // Produce another block and send to follower node.
1407
+ node0_rnm_handle. build_block ( ) . await ;
1408
+ wait_for_block_sequenced_5s ( & mut node0_rnm_events, 16 ) . await ?;
1409
+
1410
+ // Assert that the follower node has received the latest block from the sequencer node and
1411
+ // processed the missing chain before.
1412
+ // This is possible now because it has received the L1 message.
1413
+ wait_for_block_imported_5s ( & mut node1_rnm_events, 16 ) . await ?;
1414
+
1415
+ // Assert both nodes are at block 16.
1416
+ let node0_latest_block = latest_block ( & node0) . await ?;
1417
+ assert_eq ! ( node0_latest_block. header. number, 16 ) ;
1418
+ assert_eq ! (
1419
+ node0_latest_block. header. hash_slow( ) ,
1420
+ latest_block( & node1) . await ?. header. hash_slow( )
1421
+ ) ;
1422
+
1423
+ Ok ( ( ) )
1424
+ }
1425
+
1284
1426
#[ tokio:: test]
1285
1427
async fn can_gossip_over_eth_wire ( ) -> eyre:: Result < ( ) > {
1286
1428
reth_tracing:: init_test_tracing ( ) ;
@@ -1570,10 +1712,11 @@ async fn wait_for_event_predicate(
1570
1712
maybe_event = event_stream. next( ) => {
1571
1713
match maybe_event {
1572
1714
Some ( e) if predicate( e. clone( ) ) => {
1715
+ tracing:: debug!( target: "scroll::test" , event = ?e, "Received event" ) ;
1573
1716
return Ok ( ( ) ) ;
1574
1717
}
1575
1718
Some ( e) => {
1576
- tracing:: debug!( target: "TODO:nodeX " , "ignoring event {:?}" , e ) ;
1719
+ tracing:: debug!( target: "scroll::test " , event = ?e , "Ignoring event" ) ;
1577
1720
} , // Ignore other events
1578
1721
None => return Err ( eyre:: eyre!( "Event stream ended unexpectedly" ) ) ,
1579
1722
}
0 commit comments