@aztec/archiver 4.0.0-nightly.20260126 → 4.0.0-nightly.20260128

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 (49) hide show
  1. package/dest/archiver.d.ts +4 -3
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +8 -9
  4. package/dest/errors.d.ts +6 -1
  5. package/dest/errors.d.ts.map +1 -1
  6. package/dest/errors.js +8 -0
  7. package/dest/factory.d.ts +3 -1
  8. package/dest/factory.d.ts.map +1 -1
  9. package/dest/factory.js +1 -1
  10. package/dest/modules/data_store_updater.d.ts +21 -17
  11. package/dest/modules/data_store_updater.d.ts.map +1 -1
  12. package/dest/modules/data_store_updater.js +47 -49
  13. package/dest/modules/l1_synchronizer.js +7 -7
  14. package/dest/store/block_store.d.ts +18 -14
  15. package/dest/store/block_store.d.ts.map +1 -1
  16. package/dest/store/block_store.js +48 -36
  17. package/dest/store/kv_archiver_store.d.ts +12 -11
  18. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  19. package/dest/store/kv_archiver_store.js +16 -15
  20. package/dest/store/log_store.d.ts +1 -1
  21. package/dest/store/log_store.d.ts.map +1 -1
  22. package/dest/store/log_store.js +2 -2
  23. package/dest/test/index.d.ts +2 -1
  24. package/dest/test/index.d.ts.map +1 -1
  25. package/dest/test/index.js +1 -0
  26. package/dest/test/mock_archiver.js +1 -1
  27. package/dest/test/mock_l2_block_source.d.ts +4 -4
  28. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  29. package/dest/test/mock_l2_block_source.js +5 -5
  30. package/dest/test/mock_structs.d.ts +3 -2
  31. package/dest/test/mock_structs.d.ts.map +1 -1
  32. package/dest/test/mock_structs.js +7 -5
  33. package/dest/test/noop_l1_archiver.d.ts +23 -0
  34. package/dest/test/noop_l1_archiver.d.ts.map +1 -0
  35. package/dest/test/noop_l1_archiver.js +68 -0
  36. package/package.json +13 -13
  37. package/src/archiver.ts +11 -10
  38. package/src/errors.ts +12 -0
  39. package/src/factory.ts +2 -1
  40. package/src/modules/data_store_updater.ts +57 -53
  41. package/src/modules/l1_synchronizer.ts +9 -9
  42. package/src/store/block_store.ts +50 -41
  43. package/src/store/kv_archiver_store.ts +17 -16
  44. package/src/store/log_store.ts +4 -4
  45. package/src/test/index.ts +1 -0
  46. package/src/test/mock_archiver.ts +1 -1
  47. package/src/test/mock_l2_block_source.ts +5 -5
  48. package/src/test/mock_structs.ts +22 -6
  49. package/src/test/noop_l1_archiver.ts +109 -0
@@ -258,7 +258,7 @@ export class ArchiverL1Synchronizer implements Traceable {
258
258
  `Pruning blocks after block ${lastCheckpointedBlockNumber} due to slot ${firstUncheckpointedBlockSlot} not being checkpointed`,
259
259
  { firstUncheckpointedBlockHeader: firstUncheckpointedBlockHeader.toInspect(), slotAtNextL1Block },
260
260
  );
261
- const prunedBlocks = await this.updater.removeBlocksAfter(lastCheckpointedBlockNumber);
261
+ const prunedBlocks = await this.updater.removeUncheckpointedBlocksAfter(lastCheckpointedBlockNumber);
262
262
 
263
263
  if (prunedBlocks.length > 0) {
264
264
  this.events.emit(L2BlockSourceEvents.L2PruneUncheckpointed, {
@@ -331,10 +331,10 @@ export class ArchiverL1Synchronizer implements Traceable {
331
331
  this.log.debug(
332
332
  `L2 prune from ${provenCheckpointNumber + 1} to ${localPendingCheckpointNumber} will occur on next checkpoint submission.`,
333
333
  );
334
- await this.updater.unwindCheckpoints(localPendingCheckpointNumber, checkpointsToUnwind);
334
+ await this.updater.removeCheckpointsAfter(provenCheckpointNumber);
335
335
  this.log.warn(
336
- `Unwound ${count(checkpointsToUnwind, 'checkpoint')} from checkpoint ${localPendingCheckpointNumber} ` +
337
- `to ${provenCheckpointNumber} due to predicted reorg at L1 block ${currentL1BlockNumber}. ` +
336
+ `Removed ${count(checkpointsToUnwind, 'checkpoint')} after checkpoint ${provenCheckpointNumber} ` +
337
+ `due to predicted reorg at L1 block ${currentL1BlockNumber}. ` +
338
338
  `Updated latest checkpoint is ${await this.store.getSynchedCheckpointNumber()}.`,
339
339
  );
340
340
  this.instrumentation.processPrune(timer.ms());
@@ -675,11 +675,11 @@ export class ArchiverL1Synchronizer implements Traceable {
675
675
  tipAfterUnwind--;
676
676
  }
677
677
 
678
- const checkpointsToUnwind = localPendingCheckpointNumber - tipAfterUnwind;
679
- await this.updater.unwindCheckpoints(localPendingCheckpointNumber, checkpointsToUnwind);
678
+ const checkpointsToRemove = localPendingCheckpointNumber - tipAfterUnwind;
679
+ await this.updater.removeCheckpointsAfter(CheckpointNumber(tipAfterUnwind));
680
680
 
681
681
  this.log.warn(
682
- `Unwound ${count(checkpointsToUnwind, 'checkpoint')} from checkpoint ${localPendingCheckpointNumber} ` +
682
+ `Removed ${count(checkpointsToRemove, 'checkpoint')} after checkpoint ${tipAfterUnwind} ` +
683
683
  `due to mismatched checkpoint hashes at L1 block ${currentL1BlockNumber}. ` +
684
684
  `Updated L2 latest checkpoint is ${await this.store.getSynchedCheckpointNumber()}.`,
685
685
  );
@@ -806,8 +806,8 @@ export class ArchiverL1Synchronizer implements Traceable {
806
806
  const updatedValidationResult =
807
807
  rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
808
808
  const [processDuration, result] = await elapsed(() =>
809
- execInSpan(this.tracer, 'Archiver.setCheckpointData', () =>
810
- this.updater.setNewCheckpointData(validCheckpoints, updatedValidationResult),
809
+ execInSpan(this.tracer, 'Archiver.addCheckpoints', () =>
810
+ this.updater.addCheckpoints(validCheckpoints, updatedValidationResult),
811
811
  ),
812
812
  );
813
813
  this.instrumentation.processNewBlocks(
@@ -9,11 +9,11 @@ import { isDefined } from '@aztec/foundation/types';
9
9
  import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } from '@aztec/kv-store';
10
10
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
11
  import {
12
+ BlockHash,
12
13
  Body,
13
14
  CheckpointedL2Block,
14
15
  CommitteeAttestation,
15
16
  L2Block,
16
- L2BlockHash,
17
17
  type ValidateCheckpointResult,
18
18
  deserializeValidateCheckpointResult,
19
19
  serializeValidateCheckpointResult,
@@ -38,6 +38,7 @@ import {
38
38
  BlockIndexNotSequentialError,
39
39
  BlockNotFoundError,
40
40
  BlockNumberNotSequentialError,
41
+ CannotOverwriteCheckpointedBlockError,
41
42
  CheckpointNotFoundError,
42
43
  CheckpointNumberNotConsistentError,
43
44
  CheckpointNumberNotSequentialError,
@@ -77,6 +78,8 @@ export type CheckpointData = {
77
78
  attestations: Buffer[];
78
79
  };
79
80
 
81
+ export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
82
+
80
83
  /**
81
84
  * LMDB-based block storage for the archiver.
82
85
  */
@@ -142,11 +145,13 @@ export class BlockStore {
142
145
  }
143
146
 
144
147
  /**
145
- * Append new blocks to the store's list. All blocks must be for the 'current' checkpoint
146
- * @param blocks - The L2 blocks to be added to the store.
148
+ * Append new proposed blocks to the store's list. All blocks must be for the 'current' checkpoint.
149
+ * These are uncheckpointed blocks that have been proposed by the sequencer but not yet included in a checkpoint on L1.
150
+ * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
151
+ * @param blocks - The proposed L2 blocks to be added to the store.
147
152
  * @returns True if the operation is successful.
148
153
  */
149
- async addBlocks(blocks: L2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
154
+ async addProposedBlocks(blocks: L2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
150
155
  if (blocks.length === 0) {
151
156
  return true;
152
157
  }
@@ -162,6 +167,12 @@ export class BlockStore {
162
167
  const previousBlockNumber = await this.getLatestBlockNumber();
163
168
  const previousCheckpointNumber = await this.getLatestCheckpointNumber();
164
169
 
170
+ // Verify we're not overwriting checkpointed blocks
171
+ const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
172
+ if (!opts.force && firstBlockNumber <= lastCheckpointedBlockNumber) {
173
+ throw new CannotOverwriteCheckpointedBlockError(firstBlockNumber, lastCheckpointedBlockNumber);
174
+ }
175
+
165
176
  // Check that the first block number is the expected one
166
177
  if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
167
178
  throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
@@ -340,7 +351,7 @@ export class BlockStore {
340
351
  }
341
352
 
342
353
  private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
343
- const blockHash = L2BlockHash.fromField(await block.hash());
354
+ const blockHash = BlockHash.fromField(await block.hash());
344
355
 
345
356
  await this.#blocks.set(block.number, {
346
357
  header: block.header.toBuffer(),
@@ -385,51 +396,48 @@ export class BlockStore {
385
396
  }
386
397
 
387
398
  /**
388
- * Unwinds checkpoints from the database
389
- * @param from - The tip of the chain, passed for verification purposes,
390
- * ensuring that we don't end up deleting something we did not intend
391
- * @param checkpointsToUnwind - The number of checkpoints we are to unwind
392
- * @returns True if the operation is successful
399
+ * Removes all checkpoints with checkpoint number > checkpointNumber.
400
+ * Also removes ALL blocks (both checkpointed and uncheckpointed) after the last block of the given checkpoint.
401
+ * @param checkpointNumber - Remove all checkpoints strictly after this one.
393
402
  */
394
- async unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number) {
403
+ async removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<RemoveCheckpointsResult> {
395
404
  return await this.db.transactionAsync(async () => {
396
- const last = await this.getLatestCheckpointNumber();
397
- if (from !== last) {
398
- throw new Error(`Can only unwind checkpoints from the tip (requested ${from} but current tip is ${last})`);
405
+ const latestCheckpointNumber = await this.getLatestCheckpointNumber();
406
+
407
+ if (checkpointNumber >= latestCheckpointNumber) {
408
+ this.#log.warn(`No checkpoints to remove after ${checkpointNumber} (latest is ${latestCheckpointNumber})`);
409
+ return { blocksRemoved: undefined };
399
410
  }
400
411
 
412
+ // If the proven checkpoint is beyond the target, update it
401
413
  const proven = await this.getProvenCheckpointNumber();
402
- if (from - checkpointsToUnwind < proven) {
403
- await this.setProvenCheckpointNumber(CheckpointNumber(from - checkpointsToUnwind));
414
+ if (proven > checkpointNumber) {
415
+ this.#log.warn(`Updating proven checkpoint ${proven} to last valid checkpoint ${checkpointNumber}`);
416
+ await this.setProvenCheckpointNumber(checkpointNumber);
404
417
  }
405
418
 
406
- for (let i = 0; i < checkpointsToUnwind; i++) {
407
- const checkpointNumber = from - i;
408
- const checkpoint = await this.#checkpoints.getAsync(checkpointNumber);
409
-
410
- if (checkpoint === undefined) {
411
- this.#log.warn(`Cannot remove checkpoint ${checkpointNumber} from the store since we don't have it`);
412
- continue;
419
+ // Find the last block number to keep (last block of the given checkpoint, or 0 if no checkpoint)
420
+ let lastBlockToKeep: BlockNumber;
421
+ if (checkpointNumber <= 0) {
422
+ lastBlockToKeep = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
423
+ } else {
424
+ const targetCheckpoint = await this.#checkpoints.getAsync(checkpointNumber);
425
+ if (!targetCheckpoint) {
426
+ throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
413
427
  }
414
- await this.#checkpoints.delete(checkpointNumber);
415
- const maxBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
416
-
417
- for (let blockNumber = checkpoint.startBlock; blockNumber <= maxBlock; blockNumber++) {
418
- const block = await this.getBlock(BlockNumber(blockNumber));
428
+ lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.numBlocks - 1);
429
+ }
419
430
 
420
- if (block === undefined) {
421
- this.#log.warn(`Cannot remove block ${blockNumber} from the store since we don't have it`);
422
- continue;
423
- }
431
+ // Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
432
+ const blocksRemoved = await this.removeBlocksAfter(lastBlockToKeep);
424
433
 
425
- await this.deleteBlock(block);
426
- this.#log.debug(
427
- `Unwound block ${blockNumber} ${(await block.hash()).toString()} for checkpoint ${checkpointNumber}`,
428
- );
429
- }
434
+ // Remove all checkpoints after the target
435
+ for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
436
+ await this.#checkpoints.delete(c);
437
+ this.#log.debug(`Removed checkpoint ${c}`);
430
438
  }
431
439
 
432
- return true;
440
+ return { blocksRemoved };
433
441
  });
434
442
  }
435
443
 
@@ -510,10 +518,11 @@ export class BlockStore {
510
518
 
511
519
  /**
512
520
  * Removes all blocks with block number > blockNumber.
521
+ * Does not remove any associated checkpoints.
513
522
  * @param blockNumber - The block number to remove after.
514
523
  * @returns The removed blocks (for event emission).
515
524
  */
516
- async unwindBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
525
+ async removeBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
517
526
  return await this.db.transactionAsync(async () => {
518
527
  const removedBlocks: L2Block[] = [];
519
528
 
@@ -664,7 +673,7 @@ export class BlockStore {
664
673
  * @param blockHash - The hash of the block to return.
665
674
  * @returns The requested L2 block.
666
675
  */
667
- async getBlockByHash(blockHash: L2BlockHash): Promise<L2Block | undefined> {
676
+ async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
668
677
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
669
678
  if (blockNumber === undefined) {
670
679
  return undefined;
@@ -690,7 +699,7 @@ export class BlockStore {
690
699
  * @param blockHash - The hash of the block to return.
691
700
  * @returns The requested block header.
692
701
  */
693
- async getBlockHeaderByHash(blockHash: L2BlockHash): Promise<BlockHeader | undefined> {
702
+ async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
694
703
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
695
704
  if (blockNumber === undefined) {
696
705
  return undefined;
@@ -6,7 +6,7 @@ import { createLogger } from '@aztec/foundation/log';
6
6
  import type { AztecAsyncKVStore, CustomRange, StoreSize } from '@aztec/kv-store';
7
7
  import { FunctionSelector } from '@aztec/stdlib/abi';
8
8
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
9
- import { CheckpointedL2Block, L2Block, L2BlockHash, type ValidateCheckpointResult } from '@aztec/stdlib/block';
9
+ import { BlockHash, CheckpointedL2Block, L2Block, type ValidateCheckpointResult } from '@aztec/stdlib/block';
10
10
  import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
11
11
  import type {
12
12
  ContractClassPublic,
@@ -25,7 +25,7 @@ import type { UInt64 } from '@aztec/stdlib/types';
25
25
  import { join } from 'path';
26
26
 
27
27
  import type { InboxMessage } from '../structs/inbox_message.js';
28
- import { BlockStore, type CheckpointData } from './block_store.js';
28
+ import { BlockStore, type CheckpointData, type RemoveCheckpointsResult } from './block_store.js';
29
29
  import { ContractClassStore } from './contract_class_store.js';
30
30
  import { ContractInstanceStore } from './contract_instance_store.js';
31
31
  import { LogStore } from './log_store.js';
@@ -235,12 +235,14 @@ export class KVArchiverDataStore implements ContractDataSource {
235
235
  }
236
236
 
237
237
  /**
238
- * Append new blocks to the store's list.
239
- * @param blocks - The L2 blocks to be added to the store and the last processed L1 block.
238
+ * Append new proposed blocks to the store's list.
239
+ * These are uncheckpointed blocks that have been proposed by the sequencer but not yet included in a checkpoint on L1.
240
+ * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
241
+ * @param blocks - The proposed L2 blocks to be added to the store.
240
242
  * @returns True if the operation is successful.
241
243
  */
242
- addBlocks(blocks: L2Block[], opts: { force?: boolean; checkpointNumber?: number } = {}): Promise<boolean> {
243
- return this.#blockStore.addBlocks(blocks, opts);
244
+ addProposedBlocks(blocks: L2Block[], opts: { force?: boolean; checkpointNumber?: number } = {}): Promise<boolean> {
245
+ return this.#blockStore.addProposedBlocks(blocks, opts);
244
246
  }
245
247
 
246
248
  /**
@@ -261,14 +263,12 @@ export class KVArchiverDataStore implements ContractDataSource {
261
263
  }
262
264
 
263
265
  /**
264
- * Unwinds checkpoints from the database
265
- * @param from - The tip of the chain, passed for verification purposes,
266
- * ensuring that we don't end up deleting something we did not intend
267
- * @param checkpointsToUnwind - The number of checkpoints we are to unwind
268
- * @returns True if the operation is successful
266
+ * Removes all checkpoints with checkpoint number > checkpointNumber.
267
+ * Also removes ALL blocks (both checkpointed and uncheckpointed) after the last block of the given checkpoint.
268
+ * @param checkpointNumber - Remove all checkpoints strictly after this one.
269
269
  */
270
- unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
271
- return this.#blockStore.unwindCheckpoints(from, checkpointsToUnwind);
270
+ removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<RemoveCheckpointsResult> {
271
+ return this.#blockStore.removeCheckpointsAfter(checkpointNumber);
272
272
  }
273
273
 
274
274
  /**
@@ -313,7 +313,7 @@ export class KVArchiverDataStore implements ContractDataSource {
313
313
  * @param blockHash - The block hash to return.
314
314
  */
315
315
  getBlockByHash(blockHash: Fr): Promise<L2Block | undefined> {
316
- return this.#blockStore.getBlockByHash(L2BlockHash.fromField(blockHash));
316
+ return this.#blockStore.getBlockByHash(BlockHash.fromField(blockHash));
317
317
  }
318
318
  /**
319
319
  * Returns the block for the given archive root, or undefined if not exists.
@@ -358,7 +358,7 @@ export class KVArchiverDataStore implements ContractDataSource {
358
358
  * @param blockHash - The block hash to return.
359
359
  */
360
360
  getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
361
- return this.#blockStore.getBlockHeaderByHash(L2BlockHash.fromField(blockHash));
361
+ return this.#blockStore.getBlockHeaderByHash(BlockHash.fromField(blockHash));
362
362
  }
363
363
 
364
364
  /**
@@ -629,10 +629,11 @@ export class KVArchiverDataStore implements ContractDataSource {
629
629
 
630
630
  /**
631
631
  * Removes all blocks with block number > blockNumber.
632
+ * Does not remove any associated checkpoints.
632
633
  * @param blockNumber - The block number to remove after.
633
634
  * @returns The removed blocks (for event emission).
634
635
  */
635
636
  removeBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
636
- return this.#blockStore.unwindBlocksAfter(blockNumber);
637
+ return this.#blockStore.removeBlocksAfter(blockNumber);
637
638
  }
638
639
  }
@@ -6,7 +6,7 @@ import { createLogger } from '@aztec/foundation/log';
6
6
  import { BufferReader, numToUInt32BE } from '@aztec/foundation/serialize';
7
7
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
8
8
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
9
- import { L2Block, L2BlockHash } from '@aztec/stdlib/block';
9
+ import { BlockHash, L2Block } from '@aztec/stdlib/block';
10
10
  import { MAX_LOGS_PER_TAG } from '@aztec/stdlib/interfaces/api-limit';
11
11
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
12
12
  import {
@@ -275,14 +275,14 @@ export class LogStore {
275
275
  return Buffer.concat([blockHash.toBuffer(), ...data]);
276
276
  }
277
277
 
278
- #unpackBlockHash(reader: BufferReader): L2BlockHash {
278
+ #unpackBlockHash(reader: BufferReader): BlockHash {
279
279
  const blockHash = reader.remainingBytes() > 0 ? reader.readObject(Fr) : undefined;
280
280
 
281
281
  if (!blockHash) {
282
282
  throw new Error('Failed to read block hash from log entry buffer');
283
283
  }
284
284
 
285
- return L2BlockHash.fromField(blockHash);
285
+ return BlockHash.fromField(blockHash);
286
286
  }
287
287
 
288
288
  deleteLogs(blocks: L2Block[]): Promise<boolean> {
@@ -543,7 +543,7 @@ export class LogStore {
543
543
  #accumulateLogs(
544
544
  results: (ExtendedContractClassLog | ExtendedPublicLog)[],
545
545
  blockNumber: number,
546
- blockHash: L2BlockHash,
546
+ blockHash: BlockHash,
547
547
  txIndex: number,
548
548
  txLogs: (ContractClassLog | PublicLog)[],
549
549
  filter: LogFilter = {},
package/src/test/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './mock_structs.js';
2
2
  export * from './mock_l2_block_source.js';
3
3
  export * from './mock_l1_to_l2_message_source.js';
4
4
  export * from './mock_archiver.js';
5
+ export * from './noop_l1_archiver.js';
@@ -57,7 +57,7 @@ export class MockPrefilledArchiver extends MockArchiver {
57
57
 
58
58
  const fromBlock = this.l2Blocks.length;
59
59
  // TODO: Add L2 blocks and checkpoints separately once archiver has the apis for that.
60
- this.addBlocks(this.prefilled.slice(fromBlock, fromBlock + numBlocks).flatMap(c => c.blocks));
60
+ this.addProposedBlocks(this.prefilled.slice(fromBlock, fromBlock + numBlocks).flatMap(c => c.blocks));
61
61
  return Promise.resolve();
62
62
  }
63
63
  }
@@ -8,9 +8,9 @@ import { createLogger } from '@aztec/foundation/log';
8
8
  import type { FunctionSelector } from '@aztec/stdlib/abi';
9
9
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
10
10
  import {
11
+ BlockHash,
11
12
  CheckpointedL2Block,
12
13
  L2Block,
13
- L2BlockHash,
14
14
  type L2BlockSource,
15
15
  type L2Tips,
16
16
  type ValidateCheckpointResult,
@@ -43,9 +43,9 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
43
43
  this.log.verbose(`Created ${numBlocks} blocks in the mock L2 block source`);
44
44
  }
45
45
 
46
- public addBlocks(blocks: L2Block[]) {
46
+ public addProposedBlocks(blocks: L2Block[]) {
47
47
  this.l2Blocks.push(...blocks);
48
- this.log.verbose(`Added ${blocks.length} blocks to the mock L2 block source`);
48
+ this.log.verbose(`Added ${blocks.length} proposed blocks to the mock L2 block source`);
49
49
  }
50
50
 
51
51
  public removeBlocks(numBlocks: number) {
@@ -322,7 +322,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
322
322
  return {
323
323
  data: txEffect,
324
324
  l2BlockNumber: block.number,
325
- l2BlockHash: L2BlockHash.fromField(await block.hash()),
325
+ l2BlockHash: BlockHash.fromField(await block.hash()),
326
326
  txIndexInBlock: block.body.txEffects.indexOf(txEffect),
327
327
  };
328
328
  }
@@ -343,7 +343,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
343
343
  TxExecutionResult.SUCCESS,
344
344
  undefined,
345
345
  txEffect.transactionFee.toBigInt(),
346
- L2BlockHash.fromField(await block.hash()),
346
+ BlockHash.fromField(await block.hash()),
347
347
  block.number,
348
348
  );
349
349
  }
@@ -46,24 +46,40 @@ export function makeInboxMessage(
46
46
  }
47
47
 
48
48
  export function makeInboxMessages(
49
- count: number,
49
+ totalCount: number,
50
50
  opts: {
51
51
  initialHash?: Buffer16;
52
52
  initialCheckpointNumber?: CheckpointNumber;
53
+ messagesPerCheckpoint?: number;
53
54
  overrideFn?: (msg: InboxMessage, index: number) => InboxMessage;
54
55
  } = {},
55
56
  ): InboxMessage[] {
56
- const { initialHash = Buffer16.ZERO, overrideFn = msg => msg, initialCheckpointNumber = 1 } = opts;
57
+ const {
58
+ initialHash = Buffer16.ZERO,
59
+ overrideFn = msg => msg,
60
+ initialCheckpointNumber = CheckpointNumber(1),
61
+ messagesPerCheckpoint = 1,
62
+ } = opts;
63
+
57
64
  const messages: InboxMessage[] = [];
58
65
  let rollingHash = initialHash;
59
- for (let i = 0; i < count; i++) {
66
+ for (let i = 0; i < totalCount; i++) {
67
+ const msgIndex = i % messagesPerCheckpoint;
68
+ const checkpointNumber = CheckpointNumber.fromBigInt(
69
+ BigInt(initialCheckpointNumber) + BigInt(i) / BigInt(messagesPerCheckpoint),
70
+ );
60
71
  const leaf = Fr.random();
61
- const checkpointNumber = CheckpointNumber(i + initialCheckpointNumber);
62
- const message = overrideFn(makeInboxMessage(rollingHash, { leaf, checkpointNumber }), i);
72
+ const message = overrideFn(
73
+ makeInboxMessage(rollingHash, {
74
+ leaf,
75
+ checkpointNumber,
76
+ index: InboxLeaf.smallestIndexForCheckpoint(checkpointNumber) + BigInt(msgIndex),
77
+ }),
78
+ i,
79
+ );
63
80
  rollingHash = message.rollingHash;
64
81
  messages.push(message);
65
82
  }
66
-
67
83
  return messages;
68
84
  }
69
85
 
@@ -0,0 +1,109 @@
1
+ import type { BlobClientInterface } from '@aztec/blob-client/client';
2
+ import type { RollupContract } from '@aztec/ethereum/contracts';
3
+ import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
4
+ import { Buffer32 } from '@aztec/foundation/buffer';
5
+ import { Fr } from '@aztec/foundation/curves/bn254';
6
+ import { EthAddress } from '@aztec/foundation/eth-address';
7
+ import type { FunctionsOf } from '@aztec/foundation/types';
8
+ import type { ArchiverEmitter } from '@aztec/stdlib/block';
9
+ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
10
+ import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client';
11
+
12
+ import { mock } from 'jest-mock-extended';
13
+ import { EventEmitter } from 'node:events';
14
+
15
+ import { Archiver } from '../archiver.js';
16
+ import { ArchiverInstrumentation } from '../modules/instrumentation.js';
17
+ import type { ArchiverL1Synchronizer } from '../modules/l1_synchronizer.js';
18
+ import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
19
+
20
+ /** Noop L1 synchronizer for testing without L1 connectivity. */
21
+ class NoopL1Synchronizer implements FunctionsOf<ArchiverL1Synchronizer> {
22
+ public readonly tracer: Tracer;
23
+
24
+ constructor(tracer: Tracer) {
25
+ this.tracer = tracer;
26
+ }
27
+
28
+ setConfig(_config: unknown) {}
29
+ getL1BlockNumber(): bigint | undefined {
30
+ return 0n;
31
+ }
32
+ getL1Timestamp(): bigint | undefined {
33
+ return 0n;
34
+ }
35
+ testEthereumNodeSynced(): Promise<void> {
36
+ return Promise.resolve();
37
+ }
38
+ syncFromL1(_initialSyncComplete: boolean): Promise<void> {
39
+ return Promise.resolve();
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Archiver with mocked L1 connectivity for testing.
45
+ * Uses mock L1 clients and a noop synchronizer, enabling tests that
46
+ * don't require real Ethereum connectivity.
47
+ */
48
+ export class NoopL1Archiver extends Archiver {
49
+ constructor(
50
+ dataStore: KVArchiverDataStore,
51
+ l1Constants: L1RollupConstants & { genesisArchiveRoot: Fr },
52
+ instrumentation: ArchiverInstrumentation,
53
+ ) {
54
+ // Create mocks for L1 clients
55
+ const publicClient = mock<ViemPublicClient>();
56
+ const debugClient = mock<ViemPublicDebugClient>();
57
+ const rollup = mock<RollupContract>();
58
+ const blobClient = mock<BlobClientInterface>();
59
+
60
+ // Mock methods called during start()
61
+ blobClient.testSources.mockResolvedValue();
62
+ publicClient.getBlockNumber.mockResolvedValue(1n);
63
+
64
+ const events = new EventEmitter() as ArchiverEmitter;
65
+ const synchronizer = new NoopL1Synchronizer(instrumentation.tracer);
66
+
67
+ super(
68
+ publicClient,
69
+ debugClient,
70
+ rollup,
71
+ {
72
+ registryAddress: EthAddress.ZERO,
73
+ governanceProposerAddress: EthAddress.ZERO,
74
+ slashFactoryAddress: EthAddress.ZERO,
75
+ slashingProposerAddress: EthAddress.ZERO,
76
+ },
77
+ dataStore,
78
+ {
79
+ pollingIntervalMs: 1000,
80
+ batchSize: 100,
81
+ skipValidateCheckpointAttestations: true,
82
+ maxAllowedEthClientDriftSeconds: 300,
83
+ ethereumAllowNoDebugHosts: true, // Skip trace validation
84
+ },
85
+ blobClient,
86
+ instrumentation,
87
+ { ...l1Constants, l1StartBlockHash: Buffer32.random() },
88
+ synchronizer as ArchiverL1Synchronizer,
89
+ events,
90
+ );
91
+ }
92
+
93
+ /** Override start to skip L1 validation checks. */
94
+ public override start(_blockUntilSynced?: boolean): Promise<void> {
95
+ // Just start the running promise without L1 checks
96
+ this.runningPromise.start();
97
+ return Promise.resolve();
98
+ }
99
+ }
100
+
101
+ /** Creates an archiver with mocked L1 connectivity for testing. */
102
+ export async function createNoopL1Archiver(
103
+ dataStore: KVArchiverDataStore,
104
+ l1Constants: L1RollupConstants & { genesisArchiveRoot: Fr },
105
+ telemetry: TelemetryClient = getTelemetryClient(),
106
+ ): Promise<NoopL1Archiver> {
107
+ const instrumentation = await ArchiverInstrumentation.new(telemetry, () => dataStore.estimateSize());
108
+ return new NoopL1Archiver(dataStore, l1Constants, instrumentation);
109
+ }