@aztec/archiver 0.0.1-commit.f5d02921e → 0.0.1-commit.f7ea82942

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +12 -6
  2. package/dest/archiver.d.ts +5 -3
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +15 -4
  5. package/dest/config.d.ts +3 -1
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +13 -2
  8. package/dest/errors.d.ts +17 -1
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +22 -0
  11. package/dest/factory.d.ts +1 -1
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +2 -1
  14. package/dest/index.d.ts +3 -2
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +2 -1
  17. package/dest/l1/data_retrieval.d.ts +19 -10
  18. package/dest/l1/data_retrieval.d.ts.map +1 -1
  19. package/dest/l1/data_retrieval.js +25 -32
  20. package/dest/l1/validate_historical_logs.d.ts +23 -0
  21. package/dest/l1/validate_historical_logs.d.ts.map +1 -0
  22. package/dest/l1/validate_historical_logs.js +108 -0
  23. package/dest/modules/data_store_updater.d.ts +12 -5
  24. package/dest/modules/data_store_updater.d.ts.map +1 -1
  25. package/dest/modules/data_store_updater.js +13 -3
  26. package/dest/modules/instrumentation.d.ts +7 -2
  27. package/dest/modules/instrumentation.d.ts.map +1 -1
  28. package/dest/modules/instrumentation.js +22 -6
  29. package/dest/modules/l1_synchronizer.d.ts +6 -2
  30. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  31. package/dest/modules/l1_synchronizer.js +213 -124
  32. package/dest/store/block_store.d.ts +11 -3
  33. package/dest/store/block_store.d.ts.map +1 -1
  34. package/dest/store/block_store.js +88 -6
  35. package/dest/store/kv_archiver_store.d.ts +11 -9
  36. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  37. package/dest/store/kv_archiver_store.js +9 -7
  38. package/dest/store/l2_tips_cache.d.ts +1 -1
  39. package/dest/store/l2_tips_cache.d.ts.map +1 -1
  40. package/dest/store/l2_tips_cache.js +2 -2
  41. package/dest/store/log_store.d.ts +1 -1
  42. package/dest/store/log_store.d.ts.map +1 -1
  43. package/dest/store/log_store.js +2 -4
  44. package/dest/store/message_store.d.ts +3 -3
  45. package/dest/store/message_store.d.ts.map +1 -1
  46. package/dest/store/message_store.js +9 -10
  47. package/dest/test/fake_l1_state.d.ts +14 -3
  48. package/dest/test/fake_l1_state.d.ts.map +1 -1
  49. package/dest/test/fake_l1_state.js +55 -10
  50. package/dest/test/noop_l1_archiver.d.ts +1 -1
  51. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  52. package/dest/test/noop_l1_archiver.js +4 -2
  53. package/package.json +13 -13
  54. package/src/archiver.ts +28 -6
  55. package/src/config.ts +14 -1
  56. package/src/errors.ts +34 -0
  57. package/src/factory.ts +1 -0
  58. package/src/index.ts +2 -1
  59. package/src/l1/data_retrieval.ts +36 -45
  60. package/src/l1/validate_historical_logs.ts +140 -0
  61. package/src/modules/data_store_updater.ts +27 -3
  62. package/src/modules/instrumentation.ts +27 -7
  63. package/src/modules/l1_synchronizer.ts +274 -148
  64. package/src/store/block_store.ts +112 -4
  65. package/src/store/kv_archiver_store.ts +18 -10
  66. package/src/store/l2_tips_cache.ts +8 -2
  67. package/src/store/log_store.ts +2 -5
  68. package/src/store/message_store.ts +10 -12
  69. package/src/structs/inbox_message.ts +1 -1
  70. package/src/test/fake_l1_state.ts +75 -13
  71. package/src/test/noop_l1_archiver.ts +3 -1
@@ -52,7 +52,10 @@ import {
52
52
  CheckpointNotFoundError,
53
53
  CheckpointNumberNotSequentialError,
54
54
  InitialCheckpointNumberNotSequentialError,
55
+ NoProposedCheckpointToPromoteError,
56
+ ProposedCheckpointArchiveRootMismatchError,
55
57
  ProposedCheckpointNotSequentialError,
58
+ ProposedCheckpointPromotionNotSequentialError,
56
59
  ProposedCheckpointStaleError,
57
60
  } from '../errors.js';
58
61
 
@@ -262,16 +265,28 @@ export class BlockStore {
262
265
  }
263
266
 
264
267
  return await this.db.transactionAsync(async () => {
265
- // Check that the checkpoint immediately before the first block to be added is present in the store.
266
268
  const firstCheckpointNumber = checkpoints[0].checkpoint.number;
267
269
  const previousCheckpointNumber = await this.getLatestCheckpointNumber();
268
270
 
269
- if (previousCheckpointNumber !== firstCheckpointNumber - 1 && !opts.force) {
271
+ // Handle already-stored checkpoints at the start of the batch.
272
+ // This can happen after an L1 reorg re-includes a checkpoint in a different L1 block.
273
+ // We accept them if archives match (same content) and update their L1 metadata.
274
+ if (!opts.force && firstCheckpointNumber <= previousCheckpointNumber) {
275
+ checkpoints = await this.skipOrUpdateAlreadyStoredCheckpoints(checkpoints, previousCheckpointNumber);
276
+ if (checkpoints.length === 0) {
277
+ return true;
278
+ }
279
+ // Re-check sequentiality after skipping
280
+ const newFirstNumber = checkpoints[0].checkpoint.number;
281
+ if (previousCheckpointNumber !== newFirstNumber - 1) {
282
+ throw new InitialCheckpointNumberNotSequentialError(newFirstNumber, previousCheckpointNumber);
283
+ }
284
+ } else if (previousCheckpointNumber !== firstCheckpointNumber - 1 && !opts.force) {
270
285
  throw new InitialCheckpointNumberNotSequentialError(firstCheckpointNumber, previousCheckpointNumber);
271
286
  }
272
287
 
273
288
  // Get the last block of the previous checkpoint for archive chaining
274
- let previousBlock = await this.getPreviousCheckpointBlock(firstCheckpointNumber);
289
+ let previousBlock = await this.getPreviousCheckpointBlock(checkpoints[0].checkpoint.number);
275
290
 
276
291
  // Iterate over checkpoints array and insert them, checking that the block numbers are sequential.
277
292
  let previousCheckpoint: PublishedCheckpoint | undefined = undefined;
@@ -322,6 +337,50 @@ export class BlockStore {
322
337
  });
323
338
  }
324
339
 
340
+ /**
341
+ * Handles checkpoints at the start of a batch that are already stored (e.g. due to L1 reorg).
342
+ * Verifies the archive root matches, updates L1 metadata, and returns only the new checkpoints.
343
+ */
344
+ private async skipOrUpdateAlreadyStoredCheckpoints(
345
+ checkpoints: PublishedCheckpoint[],
346
+ latestStored: CheckpointNumber,
347
+ ): Promise<PublishedCheckpoint[]> {
348
+ let i = 0;
349
+ for (; i < checkpoints.length && checkpoints[i].checkpoint.number <= latestStored; i++) {
350
+ const incoming = checkpoints[i];
351
+ const stored = await this.getCheckpointData(incoming.checkpoint.number);
352
+ if (!stored) {
353
+ // Should not happen if latestStored is correct, but be safe
354
+ break;
355
+ }
356
+ // Verify the checkpoint content matches (archive root)
357
+ if (!stored.archive.root.equals(incoming.checkpoint.archive.root)) {
358
+ throw new Error(
359
+ `Checkpoint ${incoming.checkpoint.number} already exists in store but with a different archive root. ` +
360
+ `Stored: ${stored.archive.root}, incoming: ${incoming.checkpoint.archive.root}`,
361
+ );
362
+ }
363
+ // Update L1 metadata and attestations for the already-stored checkpoint
364
+ this.#log.warn(
365
+ `Checkpoint ${incoming.checkpoint.number} already stored, updating L1 info ` +
366
+ `(L1 block ${stored.l1.blockNumber} -> ${incoming.l1.blockNumber})`,
367
+ );
368
+ await this.#checkpoints.set(incoming.checkpoint.number, {
369
+ header: incoming.checkpoint.header.toBuffer(),
370
+ archive: incoming.checkpoint.archive.toBuffer(),
371
+ checkpointOutHash: incoming.checkpoint.getCheckpointOutHash().toBuffer(),
372
+ l1: incoming.l1.toBuffer(),
373
+ attestations: incoming.attestations.map(a => a.toBuffer()),
374
+ checkpointNumber: incoming.checkpoint.number,
375
+ startBlock: incoming.checkpoint.blocks[0].number,
376
+ blockCount: incoming.checkpoint.blocks.length,
377
+ });
378
+ // Update the sync point to reflect the new L1 block
379
+ await this.#lastSynchedL1Block.set(incoming.l1.blockNumber);
380
+ }
381
+ return checkpoints.slice(i);
382
+ }
383
+
325
384
  /**
326
385
  * Gets the last block of the checkpoint before the given one.
327
386
  * Returns undefined if there is no previous checkpoint (i.e. genesis).
@@ -638,6 +697,55 @@ export class BlockStore {
638
697
  await this.#proposedCheckpoint.delete();
639
698
  }
640
699
 
700
+ /**
701
+ * Promotes the proposed checkpoint singleton to a confirmed checkpoint entry.
702
+ * This persists the checkpoint to the store, clears the proposed singleton, and updates the L1 sync point.
703
+ * Should only be called after the checkpoint has been validated.
704
+ * @param expectedArchiveRoot - The archive root to match against the proposed checkpoint, to guard against races.
705
+ */
706
+ async promoteProposedToCheckpointed(
707
+ l1: L1PublishedData,
708
+ attestations: CommitteeAttestation[],
709
+ expectedArchiveRoot: Fr,
710
+ ): Promise<void> {
711
+ return await this.db.transactionAsync(async () => {
712
+ const proposed = await this.getProposedCheckpointOnly();
713
+ if (!proposed) {
714
+ throw new NoProposedCheckpointToPromoteError();
715
+ }
716
+ if (!proposed.archive.root.equals(expectedArchiveRoot)) {
717
+ throw new ProposedCheckpointArchiveRootMismatchError(expectedArchiveRoot, proposed.archive.root);
718
+ }
719
+
720
+ // Verify sequentiality: promoted checkpoint must follow the latest confirmed one
721
+ const latestCheckpointNumber = await this.getLatestCheckpointNumber();
722
+ if (latestCheckpointNumber !== proposed.checkpointNumber - 1) {
723
+ throw new ProposedCheckpointPromotionNotSequentialError(proposed.checkpointNumber, latestCheckpointNumber);
724
+ }
725
+
726
+ // Write the checkpoint entry
727
+ await this.#checkpoints.set(proposed.checkpointNumber, {
728
+ header: proposed.header.toBuffer(),
729
+ archive: proposed.archive.toBuffer(),
730
+ checkpointOutHash: proposed.checkpointOutHash.toBuffer(),
731
+ l1: l1.toBuffer(),
732
+ attestations: attestations.map(attestation => attestation.toBuffer()),
733
+ checkpointNumber: proposed.checkpointNumber,
734
+ startBlock: proposed.startBlock,
735
+ blockCount: proposed.blockCount,
736
+ });
737
+
738
+ // Update the slot-to-checkpoint index
739
+ await this.#slotToCheckpoint.set(proposed.header.slotNumber, proposed.checkpointNumber);
740
+
741
+ // Clear the proposed checkpoint singleton
742
+ await this.#proposedCheckpoint.delete();
743
+
744
+ // Update the last synced L1 block
745
+ await this.#lastSynchedL1Block.set(l1.blockNumber);
746
+ });
747
+ }
748
+
641
749
  /** Clears the proposed checkpoint if the given confirmed checkpoint number supersedes it. */
642
750
  async clearProposedCheckpointIfSuperseded(confirmedCheckpointNumber: CheckpointNumber): Promise<void> {
643
751
  const proposedCheckpointNumber = await this.getProposedCheckpointNumber();
@@ -920,7 +1028,7 @@ export class BlockStore {
920
1028
  return {
921
1029
  header: BlockHeader.fromBuffer(blockStorage.header),
922
1030
  archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
923
- blockHash: Fr.fromBuffer(blockStorage.blockHash),
1031
+ blockHash: BlockHash.fromBuffer(blockStorage.blockHash),
924
1032
  checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
925
1033
  indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
926
1034
  };
@@ -10,12 +10,14 @@ import {
10
10
  type BlockData,
11
11
  BlockHash,
12
12
  CheckpointedL2Block,
13
+ type CommitteeAttestation,
13
14
  L2Block,
14
15
  type ValidateCheckpointResult,
15
16
  } from '@aztec/stdlib/block';
16
17
  import type {
17
18
  CheckpointData,
18
19
  CommonCheckpointData,
20
+ L1PublishedData,
19
21
  ProposedCheckpointData,
20
22
  ProposedCheckpointInput,
21
23
  PublishedCheckpoint,
@@ -558,13 +560,6 @@ export class KVArchiverDataStore implements ContractDataSource {
558
560
  await this.#blockStore.setSynchedL1BlockNumber(l1BlockNumber);
559
561
  }
560
562
 
561
- /**
562
- * Stores the l1 block that messages have been synched until
563
- */
564
- async setMessageSynchedL1Block(l1Block: L1BlockId) {
565
- await this.#messageStore.setSynchedL1Block(l1Block);
566
- }
567
-
568
563
  /**
569
564
  * Returns the number of the most recent proven block
570
565
  * @returns The number of the most recent proven block
@@ -597,9 +592,9 @@ export class KVArchiverDataStore implements ContractDataSource {
597
592
  return this.#messageStore.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
598
593
  }
599
594
 
600
- /** Persists the inbox tree-in-progress checkpoint number from L1 state. */
601
- public setInboxTreeInProgress(value: bigint): Promise<void> {
602
- return this.#messageStore.setInboxTreeInProgress(value);
595
+ /** Atomically updates the message sync state: the L1 sync point and the inbox tree-in-progress marker. */
596
+ public setMessageSyncState(l1Block: L1BlockId, treeInProgress: bigint | undefined): Promise<void> {
597
+ return this.#messageStore.setMessageSyncState(l1Block, treeInProgress);
603
598
  }
604
599
 
605
600
  /** Returns an async iterator to all L1 to L2 messages on the range. */
@@ -654,6 +649,19 @@ export class KVArchiverDataStore implements ContractDataSource {
654
649
  return this.#blockStore.deleteProposedCheckpoint();
655
650
  }
656
651
 
652
+ /**
653
+ * Promotes the proposed checkpoint to a confirmed checkpoint entry.
654
+ * Should only be called after the checkpoint has been validated.
655
+ * @param expectedArchiveRoot - The archive root to match against the proposed checkpoint, to guard against races.
656
+ */
657
+ public promoteProposedToCheckpointed(
658
+ l1: L1PublishedData,
659
+ attestations: CommitteeAttestation[],
660
+ expectedArchiveRoot: Fr,
661
+ ): Promise<void> {
662
+ return this.#blockStore.promoteProposedToCheckpointed(l1, attestations, expectedArchiveRoot);
663
+ }
664
+
657
665
  /**
658
666
  * Gets the number of the latest L2 block processed.
659
667
  * @returns The number of the latest L2 block processed.
@@ -1,6 +1,12 @@
1
- import { GENESIS_BLOCK_HEADER_HASH, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
1
+ import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
2
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
3
- import { type BlockData, type CheckpointId, GENESIS_CHECKPOINT_HEADER_HASH, type L2Tips } from '@aztec/stdlib/block';
3
+ import {
4
+ type BlockData,
5
+ type CheckpointId,
6
+ GENESIS_BLOCK_HEADER_HASH,
7
+ GENESIS_CHECKPOINT_HEADER_HASH,
8
+ type L2Tips,
9
+ } from '@aztec/stdlib/block';
4
10
 
5
11
  import type { BlockStore } from './block_store.js';
6
12
 
@@ -1,7 +1,6 @@
1
1
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
2
  import { BlockNumber } from '@aztec/foundation/branded-types';
3
3
  import { compactArray, filterAsync } from '@aztec/foundation/collection';
4
- import { Fr } from '@aztec/foundation/curves/bn254';
5
4
  import { createLogger } from '@aztec/foundation/log';
6
5
  import { BufferReader, numToUInt32BE } from '@aztec/foundation/serialize';
7
6
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
@@ -302,13 +301,11 @@ export class LogStore {
302
301
  }
303
302
 
304
303
  #unpackBlockHash(reader: BufferReader): BlockHash {
305
- const blockHash = reader.remainingBytes() > 0 ? reader.readObject(Fr) : undefined;
306
-
307
- if (!blockHash) {
304
+ if (reader.remainingBytes() === 0) {
308
305
  throw new Error('Failed to read block hash from log entry buffer');
309
306
  }
310
307
 
311
- return new BlockHash(blockHash);
308
+ return BlockHash.fromBuffer(reader);
312
309
  }
313
310
 
314
311
  deleteLogs(blocks: L2Block[]): Promise<boolean> {
@@ -161,15 +161,6 @@ export class MessageStore {
161
161
  lastMessage = message;
162
162
  }
163
163
 
164
- // Update the L1 sync point to that of the last message added.
165
- const currentSyncPoint = await this.getSynchedL1Block();
166
- if (!currentSyncPoint || currentSyncPoint.l1BlockNumber < lastMessage!.l1BlockNumber) {
167
- await this.setSynchedL1Block({
168
- l1BlockNumber: lastMessage!.l1BlockNumber,
169
- l1BlockHash: lastMessage!.l1BlockHash,
170
- });
171
- }
172
-
173
164
  // Update total message count with the number of inserted messages.
174
165
  await this.increaseTotalMessageCount(messageCount);
175
166
  });
@@ -194,9 +185,16 @@ export class MessageStore {
194
185
  return this.#inboxTreeInProgress.getAsync();
195
186
  }
196
187
 
197
- /** Persists the inbox tree-in-progress checkpoint number from L1 state. */
198
- public async setInboxTreeInProgress(value: bigint): Promise<void> {
199
- await this.#inboxTreeInProgress.set(value);
188
+ /** Atomically updates the message sync state: the L1 sync point and the inbox tree-in-progress marker. */
189
+ public setMessageSyncState(l1Block: L1BlockId, treeInProgress: bigint | undefined): Promise<void> {
190
+ return this.db.transactionAsync(async () => {
191
+ await this.setSynchedL1Block(l1Block);
192
+ if (treeInProgress !== undefined) {
193
+ await this.#inboxTreeInProgress.set(treeInProgress);
194
+ } else {
195
+ await this.#inboxTreeInProgress.delete();
196
+ }
197
+ });
200
198
  }
201
199
 
202
200
  public async getL1ToL2Messages(checkpointNumber: CheckpointNumber): Promise<Fr[]> {
@@ -8,7 +8,7 @@ export type InboxMessage = {
8
8
  index: bigint;
9
9
  leaf: Fr;
10
10
  checkpointNumber: CheckpointNumber;
11
- l1BlockNumber: bigint; // L1 block number - NOT Aztec L2
11
+ l1BlockNumber: bigint;
12
12
  l1BlockHash: Buffer32;
13
13
  rollingHash: Buffer16;
14
14
  };
@@ -151,8 +151,10 @@ export class FakeL1State {
151
151
  // Computed from checkpoints based on L1 block visibility
152
152
  private pendingCheckpointNumber: CheckpointNumber = CheckpointNumber(0);
153
153
 
154
- // The L1 block number reported as "finalized" (defaults to the start block)
155
- private finalizedL1BlockNumber: bigint;
154
+ // The L1 block number reported as "finalized" (defaults to the start block).
155
+ // `undefined` simulates the startup window on a fresh devnet where
156
+ // `getBlock({ blockTag: 'finalized' })` fails with "finalized block not found".
157
+ private finalizedL1BlockNumber: bigint | undefined;
156
158
 
157
159
  constructor(private readonly config: FakeL1StateConfig) {
158
160
  this.l1BlockNumber = config.l1StartBlock;
@@ -288,8 +290,11 @@ export class FakeL1State {
288
290
  this.updatePendingCheckpointNumber();
289
291
  }
290
292
 
291
- /** Sets the L1 block number that will be reported as "finalized". */
292
- setFinalizedL1BlockNumber(blockNumber: bigint): void {
293
+ /**
294
+ * Sets the L1 block number that will be reported as "finalized". Pass `undefined` to
295
+ * simulate a chain that does not yet have a finalized block (devnet startup).
296
+ */
297
+ setFinalizedL1BlockNumber(blockNumber: bigint | undefined): void {
293
298
  this.finalizedL1BlockNumber = blockNumber;
294
299
  }
295
300
 
@@ -332,6 +337,21 @@ export class FakeL1State {
332
337
  this.updatePendingCheckpointNumber();
333
338
  }
334
339
 
340
+ /**
341
+ * Moves a checkpoint to a different L1 block number (simulates L1 reorg that
342
+ * re-includes the same checkpoint transaction in a different block).
343
+ * The checkpoint content stays the same — only the L1 metadata changes.
344
+ * Auto-updates pending status.
345
+ */
346
+ moveCheckpointToL1Block(checkpointNumber: CheckpointNumber, newL1BlockNumber: bigint): void {
347
+ for (const cpData of this.checkpoints) {
348
+ if (cpData.checkpointNumber === checkpointNumber) {
349
+ cpData.l1BlockNumber = newL1BlockNumber;
350
+ }
351
+ }
352
+ this.updatePendingCheckpointNumber();
353
+ }
354
+
335
355
  /**
336
356
  * Removes messages after a given total index (simulates L1 reorg).
337
357
  * Auto-updates rolling hash.
@@ -451,19 +471,33 @@ export class FakeL1State {
451
471
  createMockInboxContract(_publicClient: MockProxy<ViemPublicClient>): MockProxy<InboxContract> {
452
472
  const mockInbox = mock<InboxContract>();
453
473
 
454
- mockInbox.getState.mockImplementation(() => {
474
+ mockInbox.getState.mockImplementation((opts: { blockTag?: string; blockNumber?: bigint } = {}) => {
475
+ // Filter messages visible at the given block number (or all if not specified)
476
+ const blockNumber = opts.blockNumber ?? this.l1BlockNumber;
477
+ const visibleMessages = this.messages.filter(m => m.l1BlockNumber <= blockNumber);
478
+
455
479
  // treeInProgress must be > any sealed checkpoint. On L1, a checkpoint can only be proposed
456
480
  // after its messages are sealed, so treeInProgress > checkpointNumber for all published checkpoints.
457
481
  const maxFromMessages =
458
- this.messages.length > 0 ? Math.max(...this.messages.map(m => Number(m.checkpointNumber))) + 1 : 0;
482
+ visibleMessages.length > 0 ? Math.max(...visibleMessages.map(m => Number(m.checkpointNumber))) + 1 : 0;
459
483
  const maxFromCheckpoints =
460
484
  this.checkpoints.length > 0
461
- ? Math.max(...this.checkpoints.filter(cp => !cp.pruned).map(cp => Number(cp.checkpointNumber))) + 1
485
+ ? Math.max(
486
+ ...this.checkpoints
487
+ .filter(cp => !cp.pruned && cp.l1BlockNumber <= blockNumber)
488
+ .map(cp => Number(cp.checkpointNumber)),
489
+ 0,
490
+ ) + 1
462
491
  : 0;
463
492
  const treeInProgress = Math.max(maxFromMessages, maxFromCheckpoints, INITIAL_CHECKPOINT_NUMBER);
493
+
494
+ // Compute rolling hash only for visible messages
495
+ const rollingHash =
496
+ visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1].rollingHash : Buffer16.ZERO;
497
+
464
498
  return Promise.resolve({
465
- messagesRollingHash: this.messagesRollingHash,
466
- totalMessagesInserted: BigInt(this.messages.length),
499
+ messagesRollingHash: rollingHash,
500
+ totalMessagesInserted: BigInt(visibleMessages.length),
467
501
  treeInProgress: BigInt(treeInProgress),
468
502
  });
469
503
  });
@@ -473,8 +507,8 @@ export class FakeL1State {
473
507
  Promise.resolve(this.getMessageSentLogs(fromBlock, toBlock)),
474
508
  );
475
509
 
476
- mockInbox.getMessageSentEventByHash.mockImplementation((hash: string, fromBlock: bigint, toBlock: bigint) =>
477
- Promise.resolve(this.getMessageSentLogs(fromBlock, toBlock, hash)),
510
+ mockInbox.getMessageSentEventByHash.mockImplementation((msgHash: string, aroundL1BlockNumber: bigint) =>
511
+ Promise.resolve(this.getMessageSentLogByHash(msgHash, aroundL1BlockNumber) as MessageSentLog),
478
512
  );
479
513
 
480
514
  return mockInbox;
@@ -490,6 +524,11 @@ export class FakeL1State {
490
524
  publicClient.getBlock.mockImplementation((async (args: { blockNumber?: bigint; blockTag?: string } = {}) => {
491
525
  let blockNum: bigint;
492
526
  if (args.blockTag === 'finalized') {
527
+ if (this.finalizedL1BlockNumber === undefined) {
528
+ throw Object.assign(new Error('finalized block not found'), {
529
+ details: 'finalized block not found',
530
+ });
531
+ }
493
532
  blockNum = this.finalizedL1BlockNumber;
494
533
  } else {
495
534
  blockNum = args.blockNumber ?? (await publicClient.getBlockNumber());
@@ -515,10 +554,10 @@ export class FakeL1State {
515
554
  createMockBlobClient(): MockProxy<BlobClientInterface> {
516
555
  const blobClient = mock<BlobClientInterface>();
517
556
 
518
- // The blockId is the transaction's blockHash, which we set to the checkpoint's archive root
557
+ // The blockId is the L1 block hash, which we derive from the L1 block number
519
558
  blobClient.getBlobSidecar.mockImplementation((blockId: `0x${string}`) =>
520
559
  Promise.resolve(
521
- this.checkpoints.find(cpData => cpData.checkpoint.archive.root.toString() === blockId)?.blobs ?? [],
560
+ this.checkpoints.find(cpData => Buffer32.fromBigInt(cpData.l1BlockNumber).toString() === blockId)?.blobs ?? [],
522
561
  ),
523
562
  );
524
563
 
@@ -594,6 +633,29 @@ export class FakeL1State {
594
633
  }));
595
634
  }
596
635
 
636
+ private getMessageSentLogByHash(msgHash: string, aroundL1BlockNumber: bigint): MessageSentLog | undefined {
637
+ const msg = this.messages.find(
638
+ msg =>
639
+ msg.leaf.toString() === msgHash &&
640
+ msg.l1BlockNumber >= aroundL1BlockNumber - 5n &&
641
+ msg.l1BlockNumber <= aroundL1BlockNumber + 5n,
642
+ );
643
+ if (!msg) {
644
+ return undefined;
645
+ }
646
+ return {
647
+ l1BlockNumber: msg.l1BlockNumber,
648
+ l1BlockHash: Buffer32.fromBigInt(msg.l1BlockNumber),
649
+ l1TransactionHash: `0x${msg.l1BlockNumber.toString(16)}` as `0x${string}`,
650
+ args: {
651
+ checkpointNumber: msg.checkpointNumber,
652
+ index: msg.index,
653
+ leaf: msg.leaf,
654
+ rollingHash: msg.rollingHash,
655
+ },
656
+ };
657
+ }
658
+
597
659
  private async makeRollupTx(
598
660
  checkpoint: Checkpoint,
599
661
  signers: Secp256k1Signer[],
@@ -70,9 +70,10 @@ export class NoopL1Archiver extends Archiver {
70
70
  debugClient,
71
71
  rollup,
72
72
  {
73
+ rollupAddress: EthAddress.ZERO,
73
74
  registryAddress: EthAddress.ZERO,
75
+ inboxAddress: EthAddress.ZERO,
74
76
  governanceProposerAddress: EthAddress.ZERO,
75
- slashFactoryAddress: EthAddress.ZERO,
76
77
  slashingProposerAddress: EthAddress.ZERO,
77
78
  },
78
79
  dataStore,
@@ -82,6 +83,7 @@ export class NoopL1Archiver extends Archiver {
82
83
  skipValidateCheckpointAttestations: true,
83
84
  maxAllowedEthClientDriftSeconds: 300,
84
85
  ethereumAllowNoDebugHosts: true, // Skip trace validation
86
+ skipHistoricalLogsCheck: true, // Skip historical logs validation
85
87
  },
86
88
  blobClient,
87
89
  instrumentation,