@aztec/archiver 4.0.0-nightly.20260111 → 4.0.0-nightly.20260113

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 (41) hide show
  1. package/README.md +14 -14
  2. package/dest/archiver/archiver.d.ts +13 -10
  3. package/dest/archiver/archiver.d.ts.map +1 -1
  4. package/dest/archiver/archiver.js +97 -32
  5. package/dest/archiver/archiver_store.d.ts +11 -4
  6. package/dest/archiver/archiver_store.d.ts.map +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts +1 -1
  8. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  9. package/dest/archiver/archiver_store_test_suite.js +6 -6
  10. package/dest/archiver/config.js +2 -2
  11. package/dest/archiver/kv_archiver_store/block_store.d.ts +11 -4
  12. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  13. package/dest/archiver/kv_archiver_store/block_store.js +22 -3
  14. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +5 -4
  15. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  16. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +3 -0
  17. package/dest/archiver/l1/calldata_retriever.d.ts +2 -2
  18. package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -1
  19. package/dest/archiver/l1/calldata_retriever.js +2 -2
  20. package/dest/archiver/l1/validate_trace.js +1 -1
  21. package/dest/archiver/validation.d.ts +4 -4
  22. package/dest/archiver/validation.d.ts.map +1 -1
  23. package/dest/archiver/validation.js +1 -1
  24. package/dest/test/mock_l1_to_l2_message_source.d.ts +2 -2
  25. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  26. package/dest/test/mock_l1_to_l2_message_source.js +12 -3
  27. package/dest/test/mock_l2_block_source.d.ts +8 -4
  28. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  29. package/dest/test/mock_l2_block_source.js +65 -19
  30. package/package.json +13 -13
  31. package/src/archiver/archiver.ts +137 -40
  32. package/src/archiver/archiver_store.ts +11 -3
  33. package/src/archiver/archiver_store_test_suite.ts +12 -13
  34. package/src/archiver/config.ts +2 -2
  35. package/src/archiver/kv_archiver_store/block_store.ts +35 -7
  36. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +7 -3
  37. package/src/archiver/l1/calldata_retriever.ts +2 -2
  38. package/src/archiver/l1/validate_trace.ts +1 -1
  39. package/src/archiver/validation.ts +6 -6
  40. package/src/test/mock_l1_to_l2_message_source.ts +10 -4
  41. package/src/test/mock_l2_block_source.ts +76 -18
@@ -1,11 +1,11 @@
1
1
  import { GENESIS_ARCHIVE_ROOT } from '@aztec/constants';
2
2
  import { DefaultL1ContractsConfig } from '@aztec/ethereum/config';
3
- import { BlockNumber } from '@aztec/foundation/branded-types';
3
+ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
4
4
  import { Buffer32 } from '@aztec/foundation/buffer';
5
5
  import { Fr } from '@aztec/foundation/curves/bn254';
6
6
  import { EthAddress } from '@aztec/foundation/eth-address';
7
7
  import { createLogger } from '@aztec/foundation/log';
8
- import { L2Block, L2BlockHash, PublishedL2Block } from '@aztec/stdlib/block';
8
+ import { CheckpointedL2Block, L2Block, L2BlockHash, PublishedL2Block } from '@aztec/stdlib/block';
9
9
  import { L1PublishedData } from '@aztec/stdlib/checkpoint';
10
10
  import { EmptyL1RollupConstants, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
11
11
  import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
@@ -15,6 +15,7 @@ import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
15
15
  l2Blocks = [];
16
16
  provenBlockNumber = 0;
17
17
  finalizedBlockNumber = 0;
18
+ checkpointedBlockNumber = 0;
18
19
  log = createLogger('archiver:mock_l2_block_source');
19
20
  async createBlocks(numBlocks) {
20
21
  for(let i = 0; i < numBlocks; i++){
@@ -41,6 +42,9 @@ import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
41
42
  }
42
43
  this.finalizedBlockNumber = finalizedBlockNumber;
43
44
  }
45
+ setCheckpointedBlockNumber(checkpointedBlockNumber) {
46
+ this.checkpointedBlockNumber = checkpointedBlockNumber;
47
+ }
44
48
  /**
45
49
  * Method to fetch the rollup contract address at the base-layer.
46
50
  * @returns The rollup address.
@@ -62,9 +66,30 @@ import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
62
66
  getProvenBlockNumber() {
63
67
  return Promise.resolve(BlockNumber(this.provenBlockNumber));
64
68
  }
65
- getCheckpointedBlock(_number) {
66
- // In this mock, we don't track checkpointed blocks separately
67
- return Promise.resolve(undefined);
69
+ getCheckpointedBlock(number) {
70
+ if (number > this.checkpointedBlockNumber) {
71
+ return Promise.resolve(undefined);
72
+ }
73
+ const block = this.l2Blocks[number - 1];
74
+ if (!block) {
75
+ return Promise.resolve(undefined);
76
+ }
77
+ const checkpointedBlock = new CheckpointedL2Block(CheckpointNumber(number), block.toL2Block(), new L1PublishedData(BigInt(number), BigInt(number), `0x${number.toString(16).padStart(64, '0')}`), []);
78
+ return Promise.resolve(checkpointedBlock);
79
+ }
80
+ async getCheckpointedBlocks(from, limit, _proven) {
81
+ const result = [];
82
+ for(let i = 0; i < limit; i++){
83
+ const blockNum = from + i;
84
+ if (blockNum > this.checkpointedBlockNumber) {
85
+ break;
86
+ }
87
+ const block = await this.getCheckpointedBlock(BlockNumber(blockNum));
88
+ if (block) {
89
+ result.push(block);
90
+ }
91
+ }
92
+ return result;
68
93
  }
69
94
  /**
70
95
  * Gets an l2 block.
@@ -105,6 +130,10 @@ import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
105
130
  attestations: []
106
131
  }));
107
132
  }
133
+ async getL2BlocksNew(from, limit, proven) {
134
+ const blocks = await this.getBlocks(from, limit, proven);
135
+ return blocks.map((x)=>x.toL2Block());
136
+ }
108
137
  async getPublishedBlockByHash(blockHash) {
109
138
  for (const block of this.l2Blocks){
110
139
  const hash = await block.hash();
@@ -199,27 +228,44 @@ import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
199
228
  return undefined;
200
229
  }
201
230
  async getL2Tips() {
202
- const [latest, proven, finalized] = [
231
+ const [latest, proven, finalized, checkpointed] = [
203
232
  await this.getBlockNumber(),
204
233
  await this.getProvenBlockNumber(),
205
- this.finalizedBlockNumber
234
+ this.finalizedBlockNumber,
235
+ this.checkpointedBlockNumber
206
236
  ];
207
237
  const latestBlock = this.l2Blocks[latest - 1];
208
238
  const provenBlock = this.l2Blocks[proven - 1];
209
239
  const finalizedBlock = this.l2Blocks[finalized - 1];
240
+ const checkpointedBlock = this.l2Blocks[checkpointed - 1];
241
+ const latestBlockId = {
242
+ number: BlockNumber(latest),
243
+ hash: (await latestBlock?.hash())?.toString()
244
+ };
245
+ const provenBlockId = {
246
+ number: BlockNumber(proven),
247
+ hash: (await provenBlock?.hash())?.toString()
248
+ };
249
+ const finalizedBlockId = {
250
+ number: BlockNumber(finalized),
251
+ hash: (await finalizedBlock?.hash())?.toString()
252
+ };
253
+ const checkpointedBlockId = {
254
+ number: BlockNumber(checkpointed),
255
+ hash: (await checkpointedBlock?.hash())?.toString()
256
+ };
257
+ const makeTipId = (blockId)=>({
258
+ block: blockId,
259
+ checkpoint: {
260
+ number: CheckpointNumber(blockId.number),
261
+ hash: blockId.hash
262
+ }
263
+ });
210
264
  return {
211
- latest: {
212
- number: BlockNumber(latest),
213
- hash: (await latestBlock?.hash())?.toString()
214
- },
215
- proven: {
216
- number: BlockNumber(proven),
217
- hash: (await provenBlock?.hash())?.toString()
218
- },
219
- finalized: {
220
- number: BlockNumber(finalized),
221
- hash: (await finalizedBlock?.hash())?.toString()
222
- }
265
+ proposed: latestBlockId,
266
+ checkpointed: makeTipId(checkpointedBlockId),
267
+ proven: makeTipId(provenBlockId),
268
+ finalized: makeTipId(finalizedBlockId)
223
269
  };
224
270
  }
225
271
  getL2EpochNumber() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/archiver",
3
- "version": "4.0.0-nightly.20260111",
3
+ "version": "4.0.0-nightly.20260113",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -66,18 +66,18 @@
66
66
  ]
67
67
  },
68
68
  "dependencies": {
69
- "@aztec/blob-client": "4.0.0-nightly.20260111",
70
- "@aztec/blob-lib": "4.0.0-nightly.20260111",
71
- "@aztec/constants": "4.0.0-nightly.20260111",
72
- "@aztec/epoch-cache": "4.0.0-nightly.20260111",
73
- "@aztec/ethereum": "4.0.0-nightly.20260111",
74
- "@aztec/foundation": "4.0.0-nightly.20260111",
75
- "@aztec/kv-store": "4.0.0-nightly.20260111",
76
- "@aztec/l1-artifacts": "4.0.0-nightly.20260111",
77
- "@aztec/noir-protocol-circuits-types": "4.0.0-nightly.20260111",
78
- "@aztec/protocol-contracts": "4.0.0-nightly.20260111",
79
- "@aztec/stdlib": "4.0.0-nightly.20260111",
80
- "@aztec/telemetry-client": "4.0.0-nightly.20260111",
69
+ "@aztec/blob-client": "4.0.0-nightly.20260113",
70
+ "@aztec/blob-lib": "4.0.0-nightly.20260113",
71
+ "@aztec/constants": "4.0.0-nightly.20260113",
72
+ "@aztec/epoch-cache": "4.0.0-nightly.20260113",
73
+ "@aztec/ethereum": "4.0.0-nightly.20260113",
74
+ "@aztec/foundation": "4.0.0-nightly.20260113",
75
+ "@aztec/kv-store": "4.0.0-nightly.20260113",
76
+ "@aztec/l1-artifacts": "4.0.0-nightly.20260113",
77
+ "@aztec/noir-protocol-circuits-types": "4.0.0-nightly.20260113",
78
+ "@aztec/protocol-contracts": "4.0.0-nightly.20260113",
79
+ "@aztec/stdlib": "4.0.0-nightly.20260113",
80
+ "@aztec/telemetry-client": "4.0.0-nightly.20260113",
81
81
  "lodash.groupby": "^4.6.0",
82
82
  "lodash.omit": "^4.5.0",
83
83
  "tslib": "^2.5.0",
@@ -1,5 +1,5 @@
1
1
  import type { BlobClientInterface } from '@aztec/blob-client/client';
2
- import { GENESIS_BLOCK_HEADER_HASH } from '@aztec/constants';
2
+ import { GENESIS_BLOCK_HEADER_HASH, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
3
3
  import { EpochCache } from '@aztec/epoch-cache';
4
4
  import { createEthereumChain } from '@aztec/ethereum/chain';
5
5
  import { BlockTagTooOldError, InboxContract, RollupContract } from '@aztec/ethereum/contracts';
@@ -33,8 +33,10 @@ import type { FunctionSelector } from '@aztec/stdlib/abi';
33
33
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
34
34
  import {
35
35
  type ArchiverEmitter,
36
+ type CheckpointId,
36
37
  CheckpointedL2Block,
37
38
  CommitteeAttestation,
39
+ GENESIS_CHECKPOINT_HEADER_HASH,
38
40
  L2Block,
39
41
  L2BlockNew,
40
42
  type L2BlockSink,
@@ -103,7 +105,7 @@ import {
103
105
  } from './l1/data_retrieval.js';
104
106
  import { validateAndLogTraceAvailability } from './l1/validate_trace.js';
105
107
  import type { InboxMessage } from './structs/inbox_message.js';
106
- import { type ValidateBlockResult, validateCheckpointAttestations } from './validation.js';
108
+ import { type ValidateCheckpointResult, validateCheckpointAttestations } from './validation.js';
107
109
 
108
110
  /**
109
111
  * Helper interface to combine all sources this archiver implementation provides.
@@ -128,7 +130,7 @@ function mapArchiverConfig(config: Partial<ArchiverConfig>) {
128
130
  return {
129
131
  pollingIntervalMs: config.archiverPollingIntervalMS,
130
132
  batchSize: config.archiverBatchSize,
131
- skipValidateBlockAttestations: config.skipValidateBlockAttestations,
133
+ skipValidateCheckpointAttestations: config.skipValidateCheckpointAttestations,
132
134
  maxAllowedEthClientDriftSeconds: config.maxAllowedEthClientDriftSeconds,
133
135
  ethereumAllowNoDebugHosts: config.ethereumAllowNoDebugHosts,
134
136
  };
@@ -139,7 +141,7 @@ type RollupStatus = {
139
141
  provenArchive: Hex;
140
142
  pendingCheckpointNumber: CheckpointNumber;
141
143
  pendingArchive: Hex;
142
- validationResult: ValidateBlockResult | undefined;
144
+ validationResult: ValidateCheckpointResult | undefined;
143
145
  lastRetrievedCheckpoint?: PublishedCheckpoint;
144
146
  lastL1BlockWithCheckpoint?: bigint;
145
147
  };
@@ -193,7 +195,7 @@ export class Archiver
193
195
  private config: {
194
196
  pollingIntervalMs: number;
195
197
  batchSize: number;
196
- skipValidateBlockAttestations?: boolean;
198
+ skipValidateCheckpointAttestations?: boolean;
197
199
  maxAllowedEthClientDriftSeconds: number;
198
200
  ethereumAllowNoDebugHosts?: boolean;
199
201
  },
@@ -593,14 +595,11 @@ export class Archiver
593
595
  );
594
596
  const newBlocks = blockPromises.filter(isDefined).flat();
595
597
 
596
- // TODO(pw/mbps): Don't convert to legacy blocks here
597
- const blocks: L2Block[] = (await Promise.all(newBlocks.map(x => this.getBlock(x.number)))).filter(isDefined);
598
-
599
598
  // Emit an event for listening services to react to the chain prune
600
599
  this.emit(L2BlockSourceEvents.L2PruneDetected, {
601
600
  type: L2BlockSourceEvents.L2PruneDetected,
602
601
  epochNumber: pruneFromEpochNumber,
603
- blocks,
602
+ blocks: newBlocks,
604
603
  });
605
604
 
606
605
  this.log.debug(
@@ -793,7 +792,8 @@ export class Archiver
793
792
  @trackSpan('Archiver.handleCheckpoints')
794
793
  private async handleCheckpoints(blocksSynchedTo: bigint, currentL1BlockNumber: bigint): Promise<RollupStatus> {
795
794
  const localPendingCheckpointNumber = await this.getSynchedCheckpointNumber();
796
- const initialValidationResult: ValidateBlockResult | undefined = await this.store.getPendingChainValidationStatus();
795
+ const initialValidationResult: ValidateCheckpointResult | undefined =
796
+ await this.store.getPendingChainValidationStatus();
797
797
  const {
798
798
  provenCheckpointNumber,
799
799
  provenArchive,
@@ -1008,7 +1008,7 @@ export class Archiver
1008
1008
  const validCheckpoints: PublishedCheckpoint[] = [];
1009
1009
 
1010
1010
  for (const published of publishedCheckpoints) {
1011
- const validationResult = this.config.skipValidateBlockAttestations
1011
+ const validationResult = this.config.skipValidateCheckpointAttestations
1012
1012
  ? { valid: true as const }
1013
1013
  : await validateCheckpointAttestations(published, this.epochCache, this.l1constants, this.log);
1014
1014
 
@@ -1021,7 +1021,7 @@ export class Archiver
1021
1021
  rollupStatus.validationResult?.valid !== validationResult.valid ||
1022
1022
  (!rollupStatus.validationResult.valid &&
1023
1023
  !validationResult.valid &&
1024
- rollupStatus.validationResult.block.blockNumber === validationResult.block.blockNumber)
1024
+ rollupStatus.validationResult.checkpoint.checkpointNumber === validationResult.checkpoint.checkpointNumber)
1025
1025
  ) {
1026
1026
  rollupStatus.validationResult = validationResult;
1027
1027
  }
@@ -1033,9 +1033,9 @@ export class Archiver
1033
1033
  ...pick(validationResult, 'reason'),
1034
1034
  });
1035
1035
 
1036
- // Emit event for invalid block detection
1037
- this.emit(L2BlockSourceEvents.InvalidAttestationsBlockDetected, {
1038
- type: L2BlockSourceEvents.InvalidAttestationsBlockDetected,
1036
+ // Emit event for invalid checkpoint detection
1037
+ this.emit(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, {
1038
+ type: L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
1039
1039
  validationResult,
1040
1040
  });
1041
1041
 
@@ -1362,7 +1362,7 @@ export class Archiver
1362
1362
 
1363
1363
  public addCheckpoints(
1364
1364
  checkpoints: PublishedCheckpoint[],
1365
- pendingChainValidationStatus?: ValidateBlockResult,
1365
+ pendingChainValidationStatus?: ValidateCheckpointResult,
1366
1366
  ): Promise<boolean> {
1367
1367
  return this.store.addCheckpoints(checkpoints, pendingChainValidationStatus);
1368
1368
  }
@@ -1392,6 +1392,16 @@ export class Archiver
1392
1392
  return publishedBlock;
1393
1393
  }
1394
1394
 
1395
+ public async getL2BlocksNew(from: BlockNumber, limit: number, proven?: boolean): Promise<L2BlockNew[]> {
1396
+ const blocks = await this.store.store.getBlocks(from, limit);
1397
+
1398
+ if (proven === true) {
1399
+ const provenBlockNumber = await this.store.getProvenBlockNumber();
1400
+ return blocks.filter(b => b.number <= provenBlockNumber);
1401
+ }
1402
+ return blocks;
1403
+ }
1404
+
1395
1405
  public async getBlockHeader(number: BlockNumber | 'latest'): Promise<BlockHeader | undefined> {
1396
1406
  if (number === 'latest') {
1397
1407
  number = await this.store.getSynchedL2BlockNumber();
@@ -1407,6 +1417,20 @@ export class Archiver
1407
1417
  return this.store.getCheckpointedBlock(number);
1408
1418
  }
1409
1419
 
1420
+ public async getCheckpointedBlocks(
1421
+ from: BlockNumber,
1422
+ limit: number,
1423
+ proven?: boolean,
1424
+ ): Promise<CheckpointedL2Block[]> {
1425
+ const blocks = await this.store.store.getCheckpointedBlocks(from, limit);
1426
+
1427
+ if (proven === true) {
1428
+ const provenBlockNumber = await this.store.getProvenBlockNumber();
1429
+ return blocks.filter(b => b.block.number <= provenBlockNumber);
1430
+ }
1431
+ return blocks;
1432
+ }
1433
+
1410
1434
  getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
1411
1435
  return this.store.getCheckpointedBlockByHash(blockHash);
1412
1436
  }
@@ -1414,6 +1438,9 @@ export class Archiver
1414
1438
  getProvenBlockNumber(): Promise<BlockNumber> {
1415
1439
  return this.store.getProvenBlockNumber();
1416
1440
  }
1441
+ getCheckpointedBlockNumber(): Promise<BlockNumber> {
1442
+ return this.store.getCheckpointedL2BlockNumber();
1443
+ }
1417
1444
  getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
1418
1445
  return this.store.getCheckpointedBlockByArchive(archive);
1419
1446
  }
@@ -1515,7 +1542,7 @@ export class Archiver
1515
1542
  return this.store.getDebugFunctionName(address, selector);
1516
1543
  }
1517
1544
 
1518
- async getPendingChainValidationStatus(): Promise<ValidateBlockResult> {
1545
+ async getPendingChainValidationStatus(): Promise<ValidateCheckpointResult> {
1519
1546
  return (await this.store.getPendingChainValidationStatus()) ?? { valid: true };
1520
1547
  }
1521
1548
 
@@ -1524,9 +1551,10 @@ export class Archiver
1524
1551
  }
1525
1552
 
1526
1553
  async getL2Tips(): Promise<L2Tips> {
1527
- const [latestBlockNumber, provenBlockNumber] = await Promise.all([
1554
+ const [latestBlockNumber, provenBlockNumber, checkpointedBlockNumber] = await Promise.all([
1528
1555
  this.getBlockNumber(),
1529
1556
  this.getProvenBlockNumber(),
1557
+ this.getCheckpointedBlockNumber(),
1530
1558
  ] as const);
1531
1559
 
1532
1560
  // TODO(#13569): Compute proper finalized block number based on L1 finalized block.
@@ -1534,44 +1562,112 @@ export class Archiver
1534
1562
  // NOTE: update end-to-end/src/e2e_epochs/epochs_empty_blocks.test.ts as that uses finalized blocks in computations
1535
1563
  const finalizedBlockNumber = BlockNumber(Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0));
1536
1564
 
1537
- const [latestBlockHeader, provenBlockHeader, finalizedBlockHeader] = await Promise.all([
1538
- latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
1539
- provenBlockNumber > 0 ? this.getBlockHeader(provenBlockNumber) : undefined,
1540
- finalizedBlockNumber > 0 ? this.getBlockHeader(finalizedBlockNumber) : undefined,
1541
- ] as const);
1565
+ const beforeInitialblockNumber = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
1542
1566
 
1543
- if (latestBlockNumber > 0 && !latestBlockHeader) {
1567
+ // Get the latest block header and checkpointed blocks for proven, finalised and checkpointed blocks
1568
+ const [latestBlockHeader, provenCheckpointedBlock, finalizedCheckpointedBlock, checkpointedBlock] =
1569
+ await Promise.all([
1570
+ latestBlockNumber > beforeInitialblockNumber ? this.getBlockHeader(latestBlockNumber) : undefined,
1571
+ provenBlockNumber > beforeInitialblockNumber ? this.getCheckpointedBlock(provenBlockNumber) : undefined,
1572
+ finalizedBlockNumber > beforeInitialblockNumber ? this.getCheckpointedBlock(finalizedBlockNumber) : undefined,
1573
+ checkpointedBlockNumber > beforeInitialblockNumber
1574
+ ? this.getCheckpointedBlock(checkpointedBlockNumber)
1575
+ : undefined,
1576
+ ] as const);
1577
+
1578
+ if (latestBlockNumber > beforeInitialblockNumber && !latestBlockHeader) {
1544
1579
  throw new Error(`Failed to retrieve latest block header for block ${latestBlockNumber}`);
1545
1580
  }
1546
1581
 
1547
- if (provenBlockNumber > 0 && !provenBlockHeader) {
1582
+ // Checkpointed blocks must exist for proven, finalized and checkpointed tips if they are beyond the initial block number.
1583
+ if (checkpointedBlockNumber > beforeInitialblockNumber && !checkpointedBlock?.block.header) {
1584
+ throw new Error(
1585
+ `Failed to retrieve checkpointed block header for block ${checkpointedBlockNumber} (latest block is ${latestBlockNumber})`,
1586
+ );
1587
+ }
1588
+
1589
+ if (provenBlockNumber > beforeInitialblockNumber && !provenCheckpointedBlock?.block.header) {
1548
1590
  throw new Error(
1549
- `Failed to retrieve proven block header for block ${provenBlockNumber} (latest block is ${latestBlockNumber})`,
1591
+ `Failed to retrieve proven checkpointed for block ${provenBlockNumber} (latest block is ${latestBlockNumber})`,
1550
1592
  );
1551
1593
  }
1552
1594
 
1553
- if (finalizedBlockNumber > 0 && !finalizedBlockHeader) {
1595
+ if (finalizedBlockNumber > beforeInitialblockNumber && !finalizedCheckpointedBlock?.block.header) {
1554
1596
  throw new Error(
1555
1597
  `Failed to retrieve finalized block header for block ${finalizedBlockNumber} (latest block is ${latestBlockNumber})`,
1556
1598
  );
1557
1599
  }
1558
1600
 
1559
1601
  const latestBlockHeaderHash = (await latestBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1560
- const provenBlockHeaderHash = (await provenBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1561
- const finalizedBlockHeaderHash = (await finalizedBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1602
+ const provenBlockHeaderHash = (await provenCheckpointedBlock?.block.header?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1603
+ const finalizedBlockHeaderHash =
1604
+ (await finalizedCheckpointedBlock?.block.header?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1605
+ const checkpointedBlockHeaderHash = (await checkpointedBlock?.block.header?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1606
+
1607
+ // Now attempt to retrieve checkpoints for proven, finalised and checkpointed blocks
1608
+ const [[provenBlockCheckpoint], [finalizedBlockCheckpoint], [checkpointedBlockCheckpoint]] = await Promise.all([
1609
+ provenCheckpointedBlock !== undefined
1610
+ ? await this.getPublishedCheckpoints(provenCheckpointedBlock?.checkpointNumber, 1)
1611
+ : [undefined],
1612
+ finalizedCheckpointedBlock !== undefined
1613
+ ? await this.getPublishedCheckpoints(finalizedCheckpointedBlock?.checkpointNumber, 1)
1614
+ : [undefined],
1615
+ checkpointedBlock !== undefined
1616
+ ? await this.getPublishedCheckpoints(checkpointedBlock?.checkpointNumber, 1)
1617
+ : [undefined],
1618
+ ]);
1619
+
1620
+ const initialcheckpointId: CheckpointId = {
1621
+ number: CheckpointNumber.ZERO,
1622
+ hash: GENESIS_CHECKPOINT_HEADER_HASH.toString(),
1623
+ };
1562
1624
 
1563
- return {
1564
- latest: { number: latestBlockNumber, hash: latestBlockHeaderHash.toString() },
1565
- proven: { number: provenBlockNumber, hash: provenBlockHeaderHash.toString() },
1566
- finalized: { number: finalizedBlockNumber, hash: finalizedBlockHeaderHash.toString() },
1625
+ const makeCheckpointId = (checkpoint: PublishedCheckpoint | undefined) => {
1626
+ if (checkpoint === undefined) {
1627
+ return initialcheckpointId;
1628
+ }
1629
+ return {
1630
+ number: checkpoint.checkpoint.number,
1631
+ hash: checkpoint.checkpoint.hash().toString(),
1632
+ };
1633
+ };
1634
+
1635
+ const l2Tips: L2Tips = {
1636
+ proposed: {
1637
+ number: latestBlockNumber,
1638
+ hash: latestBlockHeaderHash.toString(),
1639
+ },
1640
+ proven: {
1641
+ block: {
1642
+ number: provenBlockNumber,
1643
+ hash: provenBlockHeaderHash.toString(),
1644
+ },
1645
+ checkpoint: makeCheckpointId(provenBlockCheckpoint),
1646
+ },
1647
+ finalized: {
1648
+ block: {
1649
+ number: finalizedBlockNumber,
1650
+ hash: finalizedBlockHeaderHash.toString(),
1651
+ },
1652
+ checkpoint: makeCheckpointId(finalizedBlockCheckpoint),
1653
+ },
1654
+ checkpointed: {
1655
+ block: {
1656
+ number: checkpointedBlockNumber,
1657
+ hash: checkpointedBlockHeaderHash.toString(),
1658
+ },
1659
+ checkpoint: makeCheckpointId(checkpointedBlockCheckpoint),
1660
+ },
1567
1661
  };
1662
+
1663
+ return l2Tips;
1568
1664
  }
1569
1665
 
1570
1666
  public async rollbackTo(targetL2BlockNumber: BlockNumber): Promise<void> {
1571
1667
  // TODO(pw/mbps): This still assumes 1 block per checkpoint
1572
1668
  const currentBlocks = await this.getL2Tips();
1573
- const currentL2Block = currentBlocks.latest.number;
1574
- const currentProvenBlock = currentBlocks.proven.number;
1669
+ const currentL2Block = currentBlocks.proposed.number;
1670
+ const currentProvenBlock = currentBlocks.proven.block.number;
1575
1671
 
1576
1672
  if (targetL2BlockNumber >= currentL2Block) {
1577
1673
  throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
@@ -1777,6 +1873,7 @@ export class ArchiverStoreHelper
1777
1873
  | 'addBlocks'
1778
1874
  | 'getBlock'
1779
1875
  | 'getBlocks'
1876
+ | 'getCheckpointedBlocks'
1780
1877
  >
1781
1878
  {
1782
1879
  #log = createLogger('archiver:block-helper');
@@ -1935,7 +2032,7 @@ export class ArchiverStoreHelper
1935
2032
  ).every(Boolean);
1936
2033
  }
1937
2034
 
1938
- public addBlocks(blocks: L2BlockNew[], pendingChainValidationStatus?: ValidateBlockResult): Promise<boolean> {
2035
+ public addBlocks(blocks: L2BlockNew[], pendingChainValidationStatus?: ValidateCheckpointResult): Promise<boolean> {
1939
2036
  // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
1940
2037
  // or if the previous block is not in the store.
1941
2038
  return this.store.transactionAsync(async () => {
@@ -1958,7 +2055,7 @@ export class ArchiverStoreHelper
1958
2055
 
1959
2056
  public addCheckpoints(
1960
2057
  checkpoints: PublishedCheckpoint[],
1961
- pendingChainValidationStatus?: ValidateBlockResult,
2058
+ pendingChainValidationStatus?: ValidateCheckpointResult,
1962
2059
  ): Promise<boolean> {
1963
2060
  // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
1964
2061
  // or if the previous block is not in the store.
@@ -2102,7 +2199,7 @@ export class ArchiverStoreHelper
2102
2199
  return this.store.getContractClassLogs(filter);
2103
2200
  }
2104
2201
  getSynchedL2BlockNumber(): Promise<BlockNumber> {
2105
- return this.store.getCheckpointedL2BlockNumber();
2202
+ return this.store.getLatestBlockNumber();
2106
2203
  }
2107
2204
  getProvenCheckpointNumber(): Promise<CheckpointNumber> {
2108
2205
  return this.store.getProvenCheckpointNumber();
@@ -2158,10 +2255,10 @@ export class ArchiverStoreHelper
2158
2255
  getLastL1ToL2Message(): Promise<InboxMessage | undefined> {
2159
2256
  return this.store.getLastL1ToL2Message();
2160
2257
  }
2161
- getPendingChainValidationStatus(): Promise<ValidateBlockResult | undefined> {
2258
+ getPendingChainValidationStatus(): Promise<ValidateCheckpointResult | undefined> {
2162
2259
  return this.store.getPendingChainValidationStatus();
2163
2260
  }
2164
- setPendingChainValidationStatus(status: ValidateBlockResult | undefined): Promise<void> {
2261
+ setPendingChainValidationStatus(status: ValidateCheckpointResult | undefined): Promise<void> {
2165
2262
  this.#log.debug(`Setting pending chain validation status to valid ${status?.valid}`, status);
2166
2263
  return this.store.setPendingChainValidationStatus(status);
2167
2264
  }
@@ -4,7 +4,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import type { CustomRange } from '@aztec/kv-store';
5
5
  import type { FunctionSelector } from '@aztec/stdlib/abi';
6
6
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
7
- import type { CheckpointedL2Block, L2BlockNew, ValidateBlockResult } from '@aztec/stdlib/block';
7
+ import type { CheckpointedL2Block, L2BlockNew, ValidateCheckpointResult } from '@aztec/stdlib/block';
8
8
  import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
9
9
  import type {
10
10
  ContractClassPublic,
@@ -85,6 +85,14 @@ export interface ArchiverDataStore {
85
85
  */
86
86
  getCheckpointedBlock(number: number): Promise<CheckpointedL2Block | undefined>;
87
87
 
88
+ /**
89
+ * Gets up to `limit` amount of checkpointed L2 blocks starting from `from`.
90
+ * @param from - Number of the first block to return (inclusive).
91
+ * @param limit - The number of blocks to return.
92
+ * @returns The requested checkpointed L2 blocks.
93
+ */
94
+ getCheckpointedBlocks(from: number, limit: number): Promise<CheckpointedL2Block[]>;
95
+
88
96
  /**
89
97
  * Returns the block for the given hash, or undefined if not exists.
90
98
  * @param blockHash - The block hash to return.
@@ -365,8 +373,8 @@ export interface ArchiverDataStore {
365
373
  getLastL1ToL2Message(): Promise<InboxMessage | undefined>;
366
374
 
367
375
  /** Returns the last synced validation status of the pending chain. */
368
- getPendingChainValidationStatus(): Promise<ValidateBlockResult | undefined>;
376
+ getPendingChainValidationStatus(): Promise<ValidateCheckpointResult | undefined>;
369
377
 
370
378
  /** Sets the last synced validation status of the pending chain. */
371
- setPendingChainValidationStatus(status: ValidateBlockResult | undefined): Promise<void>;
379
+ setPendingChainValidationStatus(status: ValidateCheckpointResult | undefined): Promise<void>;
372
380
  }
@@ -20,10 +20,9 @@ import {
20
20
  EthAddress,
21
21
  L2BlockHash,
22
22
  L2BlockNew,
23
- type ValidateBlockResult,
24
- randomBlockInfo,
23
+ type ValidateCheckpointResult,
25
24
  } from '@aztec/stdlib/block';
26
- import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
25
+ import { Checkpoint, L1PublishedData, PublishedCheckpoint, randomCheckpointInfo } from '@aztec/stdlib/checkpoint';
27
26
  import {
28
27
  type ContractClassPublic,
29
28
  type ContractInstanceWithAddress,
@@ -2756,7 +2755,7 @@ export function describeArchiverDataStore(
2756
2755
  });
2757
2756
 
2758
2757
  it('should store and retrieve a valid validation status', async () => {
2759
- const validStatus: ValidateBlockResult = { valid: true };
2758
+ const validStatus: ValidateCheckpointResult = { valid: true };
2760
2759
 
2761
2760
  await store.setPendingChainValidationStatus(validStatus);
2762
2761
  const retrievedStatus = await store.getPendingChainValidationStatus();
@@ -2765,9 +2764,9 @@ export function describeArchiverDataStore(
2765
2764
  });
2766
2765
 
2767
2766
  it('should store and retrieve an invalid validation status with insufficient attestations', async () => {
2768
- const invalidStatus: ValidateBlockResult = {
2767
+ const invalidStatus: ValidateCheckpointResult = {
2769
2768
  valid: false,
2770
- block: randomBlockInfo(1),
2769
+ checkpoint: randomCheckpointInfo(1),
2771
2770
  committee: [EthAddress.random(), EthAddress.random()],
2772
2771
  epoch: EpochNumber(123),
2773
2772
  seed: 456n,
@@ -2783,9 +2782,9 @@ export function describeArchiverDataStore(
2783
2782
  });
2784
2783
 
2785
2784
  it('should store and retrieve an invalid validation status with invalid attestation', async () => {
2786
- const invalidStatus: ValidateBlockResult = {
2785
+ const invalidStatus: ValidateCheckpointResult = {
2787
2786
  valid: false,
2788
- block: randomBlockInfo(2),
2787
+ checkpoint: randomCheckpointInfo(2),
2789
2788
  committee: [EthAddress.random()],
2790
2789
  attestors: [EthAddress.random()],
2791
2790
  epoch: EpochNumber(789),
@@ -2802,10 +2801,10 @@ export function describeArchiverDataStore(
2802
2801
  });
2803
2802
 
2804
2803
  it('should overwrite existing status when setting a new one', async () => {
2805
- const firstStatus: ValidateBlockResult = { valid: true };
2806
- const secondStatus: ValidateBlockResult = {
2804
+ const firstStatus: ValidateCheckpointResult = { valid: true };
2805
+ const secondStatus: ValidateCheckpointResult = {
2807
2806
  valid: false,
2808
- block: randomBlockInfo(3),
2807
+ checkpoint: randomCheckpointInfo(3),
2809
2808
  committee: [EthAddress.random()],
2810
2809
  epoch: EpochNumber(999),
2811
2810
  seed: 888n,
@@ -2822,9 +2821,9 @@ export function describeArchiverDataStore(
2822
2821
  });
2823
2822
 
2824
2823
  it('should handle empty committee and attestations arrays', async () => {
2825
- const statusWithEmptyArrays: ValidateBlockResult = {
2824
+ const statusWithEmptyArrays: ValidateCheckpointResult = {
2826
2825
  valid: false,
2827
- block: randomBlockInfo(4),
2826
+ checkpoint: randomCheckpointInfo(4),
2828
2827
  committee: [],
2829
2828
  epoch: EpochNumber(0),
2830
2829
  seed: 0n,
@@ -46,8 +46,8 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
46
46
  parseEnv: (val: string | undefined) => (val ? +val : undefined),
47
47
  description: 'The maximum possible size of the archiver DB in KB. Overwrites the general dataStoreMapSizeKb.',
48
48
  },
49
- skipValidateBlockAttestations: {
50
- description: 'Whether to skip validating block attestations (use only for testing).',
49
+ skipValidateCheckpointAttestations: {
50
+ description: 'Skip validating checkpoint attestations (for testing purposes only)',
51
51
  ...booleanConfigHelper(false),
52
52
  },
53
53
  maxAllowedEthClientDriftSeconds: {