@@ -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 ] ) {
@@ -338,53 +340,6 @@ describe('Archiver Sync', () => {
338340 ) ;
339341 } ) ;
340342
341- it ( 'stop processing if one of the checkpoints has a mismatch inHash' , async ( ) => {
342- expect ( await archiver . getCheckpointNumber ( ) ) . toEqual ( CheckpointNumber ( 0 ) ) ;
343-
344- // Add checkpoint 1 and 2 with all messages visible
345- await fake . addCheckpoint ( CheckpointNumber ( 1 ) , {
346- l1BlockNumber : 70n ,
347- messagesL1BlockNumber : 50n ,
348- numL1ToL2Messages : 3 ,
349- } ) ;
350-
351- await fake . addCheckpoint ( CheckpointNumber ( 2 ) , {
352- l1BlockNumber : 80n ,
353- messagesL1BlockNumber : 60n ,
354- numL1ToL2Messages : 3 ,
355- } ) ;
356-
357- // Add checkpoint 3 with 3 messages at L1 block 100n
358- const { checkpoint : cp3 , messages : msgs3 } = await fake . addCheckpoint ( CheckpointNumber ( 3 ) , {
359- l1BlockNumber : 90n ,
360- messagesL1BlockNumber : 100n ,
361- numL1ToL2Messages : 3 ,
362- } ) ;
363-
364- // Move last 2 messages of checkpoint 3 to L1 block 103n (beyond current L1 block)
365- // This simulates partial message visibility
366- const totalMessages = 3 + 3 + 3 ; // 9 messages total
367- fake . moveMessageAtIndexToL1Block ( totalMessages - 1 , 103n ) ; // Move last message
368- fake . moveMessageAtIndexToL1Block ( totalMessages - 2 , 103n ) ; // Move second to last
369-
370- // Set current L1 block to 102n - only 1 message from checkpoint 3 will be visible
371- fake . setL1BlockNumber ( 102n ) ;
372-
373- // The archiver will compute inHash from only the first message,
374- // which won't match the checkpoint's inHash (computed from all 3 messages)
375- const visibleMessages = msgs3 . slice ( 0 , 1 ) ;
376- const computedInHash = computeInHashFromL1ToL2Messages ( visibleMessages ) ;
377-
378- // Run archiver (expect failure)
379- await expect ( ( ) => archiver . syncImmediate ( ) ) . rejects . toThrow (
380- new RegExp ( `mismatch inHash for checkpoint 3.*${ computedInHash } .*${ cp3 . header . inHash } ` , 'i' ) ,
381- ) ;
382-
383- // Should still be at checkpoint 0 since the error prevents checkpoint processing
384- // (checkpoints 1 and 2 also fail because they're in the same batch)
385- expect ( await archiver . getCheckpointNumber ( ) ) . toEqual ( CheckpointNumber ( 0 ) ) ;
386- } , 10_000 ) ;
387-
388343 it ( 'skip event search if no changes found' , async ( ) => {
389344 const loggerSpy = jest . spyOn ( syncLogger , 'debug' ) ;
390345
@@ -508,9 +463,9 @@ describe('Archiver Sync', () => {
508463 } ) ;
509464
510465 it ( 'does not fetch messages when local and remote state both have zero messages' , async ( ) => {
511- // When there are no messages on L1, the remote inbox state has messagesRollingHash = Buffer16 .ZERO
512- // and totalMessagesInserted = 0 . The local store also returns 0 messages and undefined lastMessage.
513- // The fallback for the local rolling hash must use Buffer16.ZERO (not Buffer32.ZERO) to match .
466+ // When there are no messages on L1, the remote Inbox current bucket is genesis (rolling hash Fr .ZERO,
467+ // total 0) . The local store also returns 0 messages and undefined lastMessage, whose rolling-hash fallback
468+ // is Fr.ZERO — so local and remote state match and no message fetch is attempted .
514469 fake . setL1BlockNumber ( 100n ) ;
515470
516471 // Add a checkpoint with zero messages so the sync has something to process
@@ -1142,10 +1097,7 @@ describe('Archiver Sync', () => {
11421097 // Sync
11431098 await archiver . syncImmediate ( ) ;
11441099
1145- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1146- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 2 ) ) ) . toHaveLength ( 0 ) ;
1147- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 3 ) ) ) . toHaveLength ( 4 ) ;
1148- await expect ( archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . rejects . toThrow ( L1ToL2MessagesNotReadyError ) ;
1100+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ ...msgs1 , ...msgs3 ] ) ) ;
11491101
11501102 // Simulate L1 reorg: remove last 2 messages from checkpoint 3, add new messages for checkpoints 4 and 5
11511103 logger . warn ( 'Reorging L1 to L2 messages' ) ;
@@ -1162,18 +1114,8 @@ describe('Archiver Sync', () => {
11621114 fake . setL1BlockNumber ( 111n ) ;
11631115 await archiver . syncImmediate ( ) ;
11641116
1165- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1166- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 2 ) ) ) . toHaveLength ( 0 ) ;
1167- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 3 ) ) ) . toHaveLength ( 2 ) ; // Reduced from 4 to 2
1168- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . toHaveLength ( 1 ) ;
1169- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 5 ) ) ) . toHaveLength ( 2 ) ;
1170-
1171- expect ( ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . map ( leaf => leaf . toString ( ) ) ) . toEqual (
1172- [ msg40 ] . map ( leaf => leaf . toString ( ) ) ,
1173- ) ;
1174- expect ( ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 5 ) ) ) . map ( leaf => leaf . toString ( ) ) ) . toEqual (
1175- [ msg50 , msg51 ] . map ( leaf => leaf . toString ( ) ) ,
1176- ) ;
1117+ // The reorg kept the first 4 messages (2 from CP1, 2 from CP3) and appended the new ones.
1118+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ msgs1 [ 0 ] , msgs1 [ 1 ] , msgs3 [ 0 ] , msgs3 [ 1 ] , msg40 , msg50 , msg51 ] ) ) ;
11771119 } ) ;
11781120
11791121 it ( 'short-circuits rollback at the finalized L1 block' , async ( ) => {
@@ -1189,8 +1131,7 @@ describe('Archiver Sync', () => {
11891131 fake . setL1BlockNumber ( 110n ) ;
11901132 await archiver . syncImmediate ( ) ;
11911133
1192- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1193- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 3 ) ) ) . toHaveLength ( 4 ) ;
1134+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ ...msgs1 , ...msgs3 ] ) ) ;
11941135
11951136 // Simulate L1 reorg: remove the last 2 messages from checkpoint 3 and add new ones.
11961137 fake . removeMessagesAfter ( 4 ) ;
@@ -1211,8 +1152,7 @@ describe('Archiver Sync', () => {
12111152 ) ;
12121153 expect ( callsAtFinalizedOrBelow ) . toHaveLength ( 0 ) ;
12131154
1214- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1215- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . toHaveLength ( 1 ) ;
1155+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ msgs1 [ 0 ] , msgs1 [ 1 ] , msgs3 [ 0 ] , msgs3 [ 1 ] , msg40 ] ) ) ;
12161156 } ) ;
12171157
12181158 it ( 'falls back to per-message log queries when finalized block is undefined' , async ( ) => {
@@ -1241,8 +1181,7 @@ describe('Archiver Sync', () => {
12411181 // 2 messages mismatch on remote (msgs3[2], msgs3[3]) and one matches (msgs3[1]) before we break.
12421182 expect ( eventByHashSpy ) . toHaveBeenCalledTimes ( 3 ) ;
12431183
1244- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 1 ) ) ) . toHaveLength ( 2 ) ;
1245- expect ( await archiver . getL1ToL2Messages ( CheckpointNumber ( 4 ) ) ) . toHaveLength ( 1 ) ;
1184+ expect ( await getStoredLeaves ( ) ) . toEqual ( asHex ( [ msgs1 [ 0 ] , msgs1 [ 1 ] , msgs3 [ 0 ] , msgs3 [ 1 ] , msg40 ] ) ) ;
12461185 } ) ;
12471186
12481187 it ( 'persists the finalized L1 block monotonically after message sync' , async ( ) => {
0 commit comments