@@ -16,14 +16,14 @@ import { sum, times } from '@aztec/foundation/collection';
1616import { Secp256k1Signer } from '@aztec/foundation/crypto/secp256k1-signer' ;
1717import { Fr } from '@aztec/foundation/curves/bn254' ;
1818import { EthAddress } from '@aztec/foundation/eth-address' ;
19+ import { toArray } from '@aztec/foundation/iterable' ;
1920import { type Logger , createLogger } from '@aztec/foundation/log' ;
2021import { retryFastUntil } from '@aztec/foundation/retry' ;
2122import { TestDateProvider } from '@aztec/foundation/timer' ;
2223import { openTmpStore } from '@aztec/kv-store/lmdb-v2' ;
2324import { GENESIS_BLOCK_HEADER_HASH , L2BlockSourceEvents , type L2BlockSourceUpdatedEvent } from '@aztec/stdlib/block' ;
2425import type { ProposedCheckpointInput } from '@aztec/stdlib/checkpoint' ;
2526import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers' ;
26- import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging' ;
2727import { CheckpointHeader } from '@aztec/stdlib/rollup' ;
2828import { mockCheckpointAndMessages } from '@aztec/stdlib/testing' ;
2929import { ConsensusTimetable } from '@aztec/stdlib/timetable' ;
@@ -37,7 +37,7 @@ import { type MockProxy, mock } from 'jest-mock-extended';
3737import type { GetBlockReturnType } from 'viem' ;
3838
3939import { Archiver , type ArchiverEmitter } from './archiver.js' ;
40- import { BlockOrCheckpointSlotExpiredError , L1ToL2MessagesNotReadyError } from './errors.js' ;
40+ import { BlockOrCheckpointSlotExpiredError } from './errors.js' ;
4141import type { ArchiverInstrumentation } from './modules/instrumentation.js' ;
4242import { ArchiverL1Synchronizer } from './modules/l1_synchronizer.js' ;
4343import { type ArchiverDataStores , createArchiverDataStores } from './store/data_stores.js' ;
@@ -187,6 +187,11 @@ describe('Archiver Sync', () => {
187187 await archiver ?. stop ( ) ;
188188 } ) ;
189189
190+ // Returns every stored L1-to-L2 message leaf (as hex), in insertion order (compact indexing, AZIP-22 Fast Inbox).
191+ const getStoredLeaves = async ( ) =>
192+ ( await toArray ( archiverStore . messages . iterateL1ToL2Messages ( ) ) ) . map ( m => m . leaf . toString ( ) ) ;
193+ const asHex = ( leaves : Fr [ ] ) => leaves . map ( l => l . toString ( ) ) ;
194+
190195 describe ( 'basic sync' , ( ) => {
191196 it ( 'syncs l1 to l2 messages and checkpoints' , async ( ) => {
192197 expect ( await archiver . getCheckpointNumber ( ) ) . toEqual ( CheckpointNumber ( 0 ) ) ;
@@ -221,7 +226,7 @@ describe('Archiver Sync', () => {
221226 expect ( await archiver . getCheckpointNumber ( ) ) . toEqual ( CheckpointNumber ( 1 ) ) ;
222227
223228 // Verify messages for checkpoint 1
224- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toEqual ( msgs1 ) ;
229+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( msgs1 ) ) ;
225230
226231 // Mark checkpoint 1 as proven
227232 fake . markCheckpointAsProven ( CheckpointNumber ( 1 ) ) ;
@@ -234,11 +239,8 @@ describe('Archiver Sync', () => {
234239 await archiver . syncImmediate ( ) ;
235240 expect ( await archiver . getCheckpointNumber ( ) ) . toEqual ( CheckpointNumber ( 3 ) ) ;
236241
237- // Verify messages for all checkpoints
238- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toEqual ( msgs1 ) ;
239- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 2 ) ) ) . toEqual ( msgs2 ) ;
240- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 3 ) ) ) . toEqual ( msgs3 ) ;
241- await expect ( archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . rejects . toThrow ( L1ToL2MessagesNotReadyError ) ;
242+ // Verify messages for all checkpoints, stored contiguously in insertion order.
243+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ ...msgs1 , ...msgs2 , ...msgs3 ] ) ) ;
242244
243245 // Verify private logs are surfaced through the block body.
244246 for ( const checkpoint of [ cp1 , cp2 , cp3 ] ) {
@@ -336,53 +338,6 @@ describe('Archiver Sync', () => {
336338 ) ;
337339 } ) ;
338340
339- it ( 'stop processing if one of the checkpoints has a mismatch inHash' , async ( ) => {
340- expect ( await archiver . getCheckpointNumber ( ) ) . toEqual ( CheckpointNumber ( 0 ) ) ;
341-
342- // Add checkpoint 1 and 2 with all messages visible
343- await fake . addCheckpoint ( CheckpointNumber ( 1 ) , {
344- l1BlockNumber : 70n ,
345- messagesL1BlockNumber : 50n ,
346- numL1ToL2Messages : 3 ,
347- } ) ;
348-
349- await fake . addCheckpoint ( CheckpointNumber ( 2 ) , {
350- l1BlockNumber : 80n ,
351- messagesL1BlockNumber : 60n ,
352- numL1ToL2Messages : 3 ,
353- } ) ;
354-
355- // Add checkpoint 3 with 3 messages at L1 block 100n
356- const { checkpoint : cp3 , messages : msgs3 } = await fake . addCheckpoint ( CheckpointNumber ( 3 ) , {
357- l1BlockNumber : 90n ,
358- messagesL1BlockNumber : 100n ,
359- numL1ToL2Messages : 3 ,
360- } ) ;
361-
362- // Move last 2 messages of checkpoint 3 to L1 block 103n (beyond current L1 block)
363- // This simulates partial message visibility
364- const totalMessages = 3 + 3 + 3 ; // 9 messages total
365- fake . moveMessageAtIndexToL1Block ( totalMessages - 1 , 103n ) ; // Move last message
366- fake . moveMessageAtIndexToL1Block ( totalMessages - 2 , 103n ) ; // Move second to last
367-
368- // Set current L1 block to 102n - only 1 message from checkpoint 3 will be visible
369- fake . setL1BlockNumber ( 102n ) ;
370-
371- // The archiver will compute inHash from only the first message,
372- // which won't match the checkpoint's inHash (computed from all 3 messages)
373- const visibleMessages = msgs3 . slice ( 0 , 1 ) ;
374- const computedInHash = computeInHashFromL1ToL2Messages ( visibleMessages ) ;
375-
376- // Run archiver (expect failure)
377- await expect ( ( ) => archiver . syncImmediate ( ) ) . rejects . toThrow (
378- new RegExp ( `mismatch inHash for checkpoint 3.*${ computedInHash } .*${ cp3 . header . inHash } ` , 'i' ) ,
379- ) ;
380-
381- // Should still be at checkpoint 0 since the error prevents checkpoint processing
382- // (checkpoints 1 and 2 also fail because they're in the same batch)
383- expect ( await archiver . getCheckpointNumber ( ) ) . toEqual ( CheckpointNumber ( 0 ) ) ;
384- } , 10_000 ) ;
385-
386341 it ( 'skip event search if no changes found' , async ( ) => {
387342 const loggerSpy = jest . spyOn ( syncLogger , 'debug' ) ;
388343
@@ -506,9 +461,9 @@ describe('Archiver Sync', () => {
506461 } ) ;
507462
508463 it ( 'does not fetch messages when local and remote state both have zero messages' , async ( ) => {
509- // When there are no messages on L1, the remote inbox state has messagesRollingHash = Buffer16 .ZERO
510- // and totalMessagesInserted = 0 . The local store also returns 0 messages and undefined lastMessage.
511- // The fallback for the local rolling hash must use Buffer16.ZERO (not Buffer32.ZERO) to match .
464+ // When there are no messages on L1, the remote Inbox current bucket is genesis (rolling hash Fr .ZERO,
465+ // total 0) . The local store also returns 0 messages and undefined lastMessage, whose rolling-hash fallback
466+ // is Fr.ZERO — so local and remote state match and no message fetch is attempted .
512467 fake . setL1BlockNumber ( 100n ) ;
513468
514469 // Add a checkpoint with zero messages so the sync has something to process
@@ -1140,10 +1095,7 @@ describe('Archiver Sync', () => {
11401095 // Sync
11411096 await archiver . syncImmediate ( ) ;
11421097
1143- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1144- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 2 ) ) ) . toHaveLength ( 0 ) ;
1145- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 3 ) ) ) . toHaveLength ( 4 ) ;
1146- await expect ( archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . rejects . toThrow ( L1ToL2MessagesNotReadyError ) ;
1098+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ ...msgs1 , ...msgs3 ] ) ) ;
11471099
11481100 // Simulate L1 reorg: remove last 2 messages from checkpoint 3, add new messages for checkpoints 4 and 5
11491101 logger . warn ( 'Reorging L1 to L2 messages' ) ;
@@ -1160,18 +1112,8 @@ describe('Archiver Sync', () => {
11601112 fake . setL1BlockNumber ( 111n ) ;
11611113 await archiver . syncImmediate ( ) ;
11621114
1163- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1164- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 2 ) ) ) . toHaveLength ( 0 ) ;
1165- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 3 ) ) ) . toHaveLength ( 2 ) ; // Reduced from 4 to 2
1166- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . toHaveLength ( 1 ) ;
1167- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 5 ) ) ) . toHaveLength ( 2 ) ;
1168-
1169- expect ( ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . map ( leaf => leaf . toString ( ) ) ) . toEqual (
1170- [ msg40 ] . map ( leaf => leaf . toString ( ) ) ,
1171- ) ;
1172- expect ( ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 5 ) ) ) . map ( leaf => leaf . toString ( ) ) ) . toEqual (
1173- [ msg50 , msg51 ] . map ( leaf => leaf . toString ( ) ) ,
1174- ) ;
1115+ // The reorg kept the first 4 messages (2 from CP1, 2 from CP3) and appended the new ones.
1116+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ msgs1 [ 0 ] , msgs1 [ 1 ] , msgs3 [ 0 ] , msgs3 [ 1 ] , msg40 , msg50 , msg51 ] ) ) ;
11751117 } ) ;
11761118
11771119 it ( 'short-circuits rollback at the finalized L1 block' , async ( ) => {
@@ -1187,8 +1129,7 @@ describe('Archiver Sync', () => {
11871129 fake . setL1BlockNumber ( 110n ) ;
11881130 await archiver . syncImmediate ( ) ;
11891131
1190- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1191- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 3 ) ) ) . toHaveLength ( 4 ) ;
1132+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ ...msgs1 , ...msgs3 ] ) ) ;
11921133
11931134 // Simulate L1 reorg: remove the last 2 messages from checkpoint 3 and add new ones.
11941135 fake . removeMessagesAfter ( 4 ) ;
@@ -1209,8 +1150,7 @@ describe('Archiver Sync', () => {
12091150 ) ;
12101151 expect ( callsAtFinalizedOrBelow ) . toHaveLength ( 0 ) ;
12111152
1212- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1213- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . toHaveLength ( 1 ) ;
1153+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ msgs1 [ 0 ] , msgs1 [ 1 ] , msgs3 [ 0 ] , msgs3 [ 1 ] , msg40 ] ) ) ;
12141154 } ) ;
12151155
12161156 it ( 'falls back to per-message log queries when finalized block is undefined' , async ( ) => {
@@ -1239,8 +1179,7 @@ describe('Archiver Sync', () => {
12391179 // 2 messages mismatch on remote (msgs3[2], msgs3[3]) and one matches (msgs3[1]) before we break.
12401180 expect ( eventByHashSpy ) . toHaveBeenCalledTimes ( 3 ) ;
12411181
1242- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1243- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . toHaveLength ( 1 ) ;
1182+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ msgs1 [ 0 ] , msgs1 [ 1 ] , msgs3 [ 0 ] , msgs3 [ 1 ] , msg40 ] ) ) ;
12441183 } ) ;
12451184
12461185 it ( 'persists the finalized L1 block monotonically after message sync' , async ( ) => {
0 commit comments