@@ -15,7 +15,7 @@ use rollup_node_primitives::{
15
15
} ;
16
16
use rollup_node_watcher:: L1Notification ;
17
17
use scroll_alloy_consensus:: TxL1Message ;
18
- use scroll_alloy_hardforks:: { ScrollHardfork , ScrollHardforks } ;
18
+ use scroll_alloy_hardforks:: ScrollHardforks ;
19
19
use scroll_alloy_network:: Scroll ;
20
20
use scroll_db:: { Database , DatabaseError , DatabaseOperations , L1MessageStart , UnwindResult } ;
21
21
use scroll_network:: NewBlockWithPeer ;
@@ -79,6 +79,8 @@ pub struct ChainOrchestrator<ChainSpec, BC, P> {
79
79
chain_buffer_size : usize ,
80
80
/// A boolean to represent if the L1 has been synced.
81
81
l1_synced : bool ,
82
+ /// The L1 message queue index at which the V2 L1 message queue was enabled.
83
+ l1_v2_message_queue_start_index : u64 ,
82
84
/// The waker to notify when the engine driver should be polled.
83
85
waker : AtomicWaker ,
84
86
}
97
99
l2_client : P ,
98
100
optimistic_sync_threshold : u64 ,
99
101
chain_buffer_size : usize ,
102
+ l1_v2_message_queue_start_index : u64 ,
100
103
) -> Result < Self , ChainOrchestratorError > {
101
104
let chain = init_chain_from_db ( & database, & l2_client, chain_buffer_size) . await ?;
102
105
Ok ( Self {
@@ -117,6 +120,7 @@ impl<
117
120
optimistic_sync_threshold,
118
121
chain_buffer_size,
119
122
l1_synced : false ,
123
+ l1_v2_message_queue_start_index,
120
124
waker : AtomicWaker :: new ( ) ,
121
125
} )
122
126
}
@@ -534,15 +538,14 @@ impl<
534
538
Box :: pin ( Self :: handle_batch_commit ( self . database . clone ( ) , batch) ) ,
535
539
) )
536
540
}
537
- L1Notification :: L1Message { message, block_number, block_timestamp } => {
541
+ L1Notification :: L1Message { message, block_number, block_timestamp : _ } => {
538
542
ChainOrchestratorFuture :: HandleL1Message ( self . handle_metered (
539
543
ChainOrchestratorItem :: L1Message ,
540
544
Box :: pin ( Self :: handle_l1_message (
545
+ self . l1_v2_message_queue_start_index ,
541
546
self . database . clone ( ) ,
542
- self . chain_spec . clone ( ) ,
543
547
message,
544
548
block_number,
545
- block_timestamp,
546
549
) ) ,
547
550
) )
548
551
}
@@ -623,33 +626,15 @@ impl<
623
626
624
627
/// Handles an L1 message by inserting it into the database.
625
628
async fn handle_l1_message (
629
+ l1_v2_message_queue_start_index : u64 ,
626
630
database : Arc < Database > ,
627
- chain_spec : Arc < ChainSpec > ,
628
631
l1_message : TxL1Message ,
629
632
l1_block_number : u64 ,
630
- block_timestamp : u64 ,
631
633
) -> Result < Option < ChainOrchestratorEvent > , ChainOrchestratorError > {
632
634
let event = ChainOrchestratorEvent :: L1MessageCommitted ( l1_message. queue_index ) ;
633
-
634
- let queue_hash = if chain_spec
635
- . scroll_fork_activation ( ScrollHardfork :: EuclidV2 )
636
- . active_at_timestamp_or_number ( block_timestamp, l1_block_number) &&
637
- l1_message. queue_index > 0
638
- {
639
- let index = l1_message. queue_index - 1 ;
640
- let prev_queue_hash = database
641
- . get_l1_message_by_index ( index)
642
- . await ?
643
- . map ( |m| m. queue_hash )
644
- . ok_or ( DatabaseError :: L1MessageNotFound ( L1MessageStart :: Index ( index) ) ) ?;
645
-
646
- let mut input = prev_queue_hash. unwrap_or_default ( ) . to_vec ( ) ;
647
- input. append ( & mut l1_message. tx_hash ( ) . to_vec ( ) ) ;
648
- Some ( keccak256 ( input) & L1_MESSAGE_QUEUE_HASH_MASK )
649
- } else {
650
- None
651
- } ;
652
-
635
+ let queue_hash =
636
+ compute_l1_message_queue_hash ( & database, & l1_message, l1_v2_message_queue_start_index)
637
+ . await ?;
653
638
let l1_message = L1MessageEnvelope :: new ( l1_message, l1_block_number, None , queue_hash) ;
654
639
database. insert_l1_message ( l1_message) . await ?;
655
640
Ok ( Some ( event) )
@@ -700,6 +685,39 @@ impl<
700
685
}
701
686
}
702
687
688
+ /// Computes the queue hash by taking the previous queue hash and performing a 2-to-1 hash with the
689
+ /// current transaction hash using keccak. It then applies a mask to the last 32 bits as these bits
690
+ /// are used to store the timestamp at which the message was enqueued in the contract. For the first
691
+ /// message in the queue, the previous queue hash is zero. If the L1 message queue index is before
692
+ /// migration to `L1MessageQueueV2`, the queue hash will be None.
693
+ ///
694
+ /// The solidity contract (`L1MessageQueueV2.sol`) implementation is defined here: <https://github.com/scroll-tech/scroll-contracts/blob/67c1bde19c1d3462abf8c175916a2bb3c89530e4/src/L1/rollup/L1MessageQueueV2.sol#L379-L403>
695
+ async fn compute_l1_message_queue_hash (
696
+ database : & Arc < Database > ,
697
+ l1_message : & TxL1Message ,
698
+ l1_v2_message_queue_start_index : u64 ,
699
+ ) -> Result < Option < alloy_primitives:: FixedBytes < 32 > > , ChainOrchestratorError > {
700
+ let queue_hash = if l1_message. queue_index == l1_v2_message_queue_start_index {
701
+ let mut input = B256 :: default ( ) . to_vec ( ) ;
702
+ input. append ( & mut l1_message. tx_hash ( ) . to_vec ( ) ) ;
703
+ Some ( keccak256 ( input) & L1_MESSAGE_QUEUE_HASH_MASK )
704
+ } else if l1_message. queue_index > l1_v2_message_queue_start_index {
705
+ let index = l1_message. queue_index - 1 ;
706
+ let mut input = database
707
+ . get_l1_message_by_index ( index)
708
+ . await ?
709
+ . map ( |m| m. queue_hash )
710
+ . ok_or ( DatabaseError :: L1MessageNotFound ( L1MessageStart :: Index ( index) ) ) ?
711
+ . unwrap_or_default ( )
712
+ . to_vec ( ) ;
713
+ input. append ( & mut l1_message. tx_hash ( ) . to_vec ( ) ) ;
714
+ Some ( keccak256 ( input) & L1_MESSAGE_QUEUE_HASH_MASK )
715
+ } else {
716
+ None
717
+ } ;
718
+ Ok ( queue_hash)
719
+ }
720
+
703
721
async fn init_chain_from_db < P : Provider < Scroll > + ' static > (
704
722
database : & Arc < Database > ,
705
723
l2_client : & P ,
@@ -954,6 +972,7 @@ mod test {
954
972
955
973
const TEST_OPTIMISTIC_SYNC_THRESHOLD : u64 = 100 ;
956
974
const TEST_CHAIN_BUFFER_SIZE : usize = 2000 ;
975
+ const TEST_L1_MESSAGE_QUEUE_INDEX_BOUNDARY : u64 = 953885 ;
957
976
958
977
/// A headers+bodies client that stores the headers and bodies in memory, with an artificial
959
978
/// soft bodies response limit that is set to 20 by default.
@@ -1105,6 +1124,7 @@ mod test {
1105
1124
. expect ( "Failed to parse mainnet genesis block" ) ;
1106
1125
assertor. push_success ( & mainnet_genesis) ;
1107
1126
let provider = ProviderBuilder :: < _ , _ , Scroll > :: default ( ) . connect_mocked_client ( assertor) ;
1127
+
1108
1128
let db = Arc :: new ( setup_test_db ( ) . await ) ;
1109
1129
(
1110
1130
ChainOrchestrator :: new (
@@ -1114,6 +1134,7 @@ mod test {
1114
1134
provider,
1115
1135
TEST_OPTIMISTIC_SYNC_THRESHOLD ,
1116
1136
TEST_CHAIN_BUFFER_SIZE ,
1137
+ TEST_L1_MESSAGE_QUEUE_INDEX_BOUNDARY ,
1117
1138
)
1118
1139
. await
1119
1140
. unwrap ( ) ,
@@ -1274,6 +1295,8 @@ mod test {
1274
1295
1275
1296
#[ tokio:: test]
1276
1297
async fn test_handle_l1_message ( ) {
1298
+ reth_tracing:: init_test_tracing ( ) ;
1299
+
1277
1300
// Instantiate chain orchestrator and db
1278
1301
let ( mut chain_orchestrator, db) = setup_test_chain_orchestrator ( ) . await ;
1279
1302
@@ -1283,7 +1306,7 @@ mod test {
1283
1306
let mut u = Unstructured :: new ( & bytes) ;
1284
1307
1285
1308
let message = TxL1Message {
1286
- queue_index : i64 :: arbitrary ( & mut u ) . unwrap ( ) . unsigned_abs ( ) ,
1309
+ queue_index : TEST_L1_MESSAGE_QUEUE_INDEX_BOUNDARY - 1 ,
1287
1310
..Arbitrary :: arbitrary ( & mut u) . unwrap ( )
1288
1311
} ;
1289
1312
let block_number = u64:: arbitrary ( & mut u) . unwrap ( ) ;
@@ -1309,15 +1332,18 @@ mod test {
1309
1332
1310
1333
// insert the previous L1 message in database.
1311
1334
chain_orchestrator. handle_l1_notification ( L1Notification :: L1Message {
1312
- message : TxL1Message { queue_index : 1062109 , ..Default :: default ( ) } ,
1335
+ message : TxL1Message {
1336
+ queue_index : TEST_L1_MESSAGE_QUEUE_INDEX_BOUNDARY ,
1337
+ ..Default :: default ( )
1338
+ } ,
1313
1339
block_number : 1475588 ,
1314
1340
block_timestamp : 1745305199 ,
1315
1341
} ) ;
1316
1342
let _ = chain_orchestrator. next ( ) . await . unwrap ( ) . unwrap ( ) ;
1317
1343
1318
1344
// <https://sepolia.scrollscan.com/tx/0xd80cd61ac5d8665919da19128cc8c16d3647e1e2e278b931769e986d01c6b910>
1319
1345
let message = TxL1Message {
1320
- queue_index : 1062110 ,
1346
+ queue_index : TEST_L1_MESSAGE_QUEUE_INDEX_BOUNDARY + 1 ,
1321
1347
gas_limit : 168000 ,
1322
1348
to : address ! ( "Ba50f5340FB9F3Bd074bD638c9BE13eCB36E603d" ) ,
1323
1349
value : U256 :: ZERO ,
@@ -1336,7 +1362,7 @@ mod test {
1336
1362
db. get_l1_message_by_index ( message. queue_index ) . await . unwrap ( ) . unwrap ( ) ;
1337
1363
1338
1364
assert_eq ! (
1339
- b256!( "5e48ae1092c7f912849b9935f4e66870d2034b24fb2016f506e6754900000000 " ) ,
1365
+ b256!( "b2331b9010aac89f012d648fccc1f0a9aa5ef7b7b2afe21be297dd1a00000000 " ) ,
1340
1366
l1_message_result. queue_hash. unwrap( )
1341
1367
) ;
1342
1368
}
@@ -1380,19 +1406,19 @@ mod test {
1380
1406
queue_hash : None ,
1381
1407
l1_block_number : 1 ,
1382
1408
l2_block_number : None ,
1383
- ..Arbitrary :: arbitrary ( & mut u) . unwrap ( )
1409
+ transaction : TxL1Message { queue_index : 1 , ..Arbitrary :: arbitrary ( & mut u) . unwrap ( ) } ,
1384
1410
} ;
1385
1411
let l1_message_block_20 = L1MessageEnvelope {
1386
1412
queue_hash : None ,
1387
1413
l1_block_number : 20 ,
1388
1414
l2_block_number : None ,
1389
- ..Arbitrary :: arbitrary ( & mut u) . unwrap ( )
1415
+ transaction : TxL1Message { queue_index : 2 , ..Arbitrary :: arbitrary ( & mut u) . unwrap ( ) } ,
1390
1416
} ;
1391
1417
let l1_message_block_30 = L1MessageEnvelope {
1392
1418
queue_hash : None ,
1393
1419
l1_block_number : 30 ,
1394
1420
l2_block_number : None ,
1395
- ..Arbitrary :: arbitrary ( & mut u) . unwrap ( )
1421
+ transaction : TxL1Message { queue_index : 3 , ..Arbitrary :: arbitrary ( & mut u) . unwrap ( ) } ,
1396
1422
} ;
1397
1423
1398
1424
// Index L1 messages
0 commit comments