@aztec/archiver 0.0.1-commit.3469e52 → 0.0.1-commit.35158ae7e

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 (107) hide show
  1. package/README.md +9 -0
  2. package/dest/archiver.d.ts +12 -9
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +98 -124
  5. package/dest/config.d.ts +3 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +2 -1
  8. package/dest/errors.d.ts +26 -9
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +34 -13
  11. package/dest/factory.d.ts +4 -2
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +29 -23
  14. package/dest/index.d.ts +2 -1
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +1 -0
  17. package/dest/l1/bin/retrieve-calldata.js +35 -32
  18. package/dest/l1/calldata_retriever.d.ts +73 -50
  19. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  20. package/dest/l1/calldata_retriever.js +190 -259
  21. package/dest/l1/data_retrieval.d.ts +9 -9
  22. package/dest/l1/data_retrieval.d.ts.map +1 -1
  23. package/dest/l1/data_retrieval.js +24 -22
  24. package/dest/l1/spire_proposer.d.ts +5 -5
  25. package/dest/l1/spire_proposer.d.ts.map +1 -1
  26. package/dest/l1/spire_proposer.js +9 -17
  27. package/dest/l1/validate_trace.d.ts +6 -3
  28. package/dest/l1/validate_trace.d.ts.map +1 -1
  29. package/dest/l1/validate_trace.js +13 -9
  30. package/dest/modules/data_source_base.d.ts +27 -23
  31. package/dest/modules/data_source_base.d.ts.map +1 -1
  32. package/dest/modules/data_source_base.js +49 -124
  33. package/dest/modules/data_store_updater.d.ts +42 -26
  34. package/dest/modules/data_store_updater.d.ts.map +1 -1
  35. package/dest/modules/data_store_updater.js +149 -129
  36. package/dest/modules/instrumentation.d.ts +17 -4
  37. package/dest/modules/instrumentation.d.ts.map +1 -1
  38. package/dest/modules/instrumentation.js +36 -12
  39. package/dest/modules/l1_synchronizer.d.ts +5 -8
  40. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  41. package/dest/modules/l1_synchronizer.js +59 -25
  42. package/dest/store/block_store.d.ts +49 -32
  43. package/dest/store/block_store.d.ts.map +1 -1
  44. package/dest/store/block_store.js +188 -95
  45. package/dest/store/contract_class_store.d.ts +2 -3
  46. package/dest/store/contract_class_store.d.ts.map +1 -1
  47. package/dest/store/contract_class_store.js +16 -72
  48. package/dest/store/contract_instance_store.d.ts +1 -1
  49. package/dest/store/contract_instance_store.d.ts.map +1 -1
  50. package/dest/store/contract_instance_store.js +6 -2
  51. package/dest/store/kv_archiver_store.d.ts +64 -36
  52. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  53. package/dest/store/kv_archiver_store.js +63 -29
  54. package/dest/store/l2_tips_cache.d.ts +19 -0
  55. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  56. package/dest/store/l2_tips_cache.js +89 -0
  57. package/dest/store/log_store.d.ts +9 -6
  58. package/dest/store/log_store.d.ts.map +1 -1
  59. package/dest/store/log_store.js +150 -53
  60. package/dest/store/message_store.d.ts +5 -1
  61. package/dest/store/message_store.d.ts.map +1 -1
  62. package/dest/store/message_store.js +14 -1
  63. package/dest/test/fake_l1_state.d.ts +16 -4
  64. package/dest/test/fake_l1_state.d.ts.map +1 -1
  65. package/dest/test/fake_l1_state.js +95 -23
  66. package/dest/test/index.js +3 -1
  67. package/dest/test/mock_archiver.d.ts +1 -1
  68. package/dest/test/mock_archiver.d.ts.map +1 -1
  69. package/dest/test/mock_archiver.js +3 -2
  70. package/dest/test/mock_l2_block_source.d.ts +39 -23
  71. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  72. package/dest/test/mock_l2_block_source.js +157 -112
  73. package/dest/test/mock_structs.d.ts +6 -2
  74. package/dest/test/mock_structs.d.ts.map +1 -1
  75. package/dest/test/mock_structs.js +24 -10
  76. package/dest/test/noop_l1_archiver.d.ts +26 -0
  77. package/dest/test/noop_l1_archiver.d.ts.map +1 -0
  78. package/dest/test/noop_l1_archiver.js +72 -0
  79. package/package.json +14 -13
  80. package/src/archiver.ts +126 -151
  81. package/src/config.ts +8 -1
  82. package/src/errors.ts +52 -24
  83. package/src/factory.ts +46 -22
  84. package/src/index.ts +1 -0
  85. package/src/l1/README.md +25 -68
  86. package/src/l1/bin/retrieve-calldata.ts +45 -33
  87. package/src/l1/calldata_retriever.ts +249 -379
  88. package/src/l1/data_retrieval.ts +27 -29
  89. package/src/l1/spire_proposer.ts +7 -15
  90. package/src/l1/validate_trace.ts +24 -6
  91. package/src/modules/data_source_base.ts +84 -169
  92. package/src/modules/data_store_updater.ts +166 -161
  93. package/src/modules/instrumentation.ts +46 -14
  94. package/src/modules/l1_synchronizer.ts +72 -36
  95. package/src/store/block_store.ts +239 -140
  96. package/src/store/contract_class_store.ts +16 -110
  97. package/src/store/contract_instance_store.ts +8 -5
  98. package/src/store/kv_archiver_store.ts +104 -53
  99. package/src/store/l2_tips_cache.ts +89 -0
  100. package/src/store/log_store.ts +231 -70
  101. package/src/store/message_store.ts +20 -1
  102. package/src/test/fake_l1_state.ts +127 -28
  103. package/src/test/index.ts +3 -0
  104. package/src/test/mock_archiver.ts +3 -2
  105. package/src/test/mock_l2_block_source.ts +211 -129
  106. package/src/test/mock_structs.ts +45 -15
  107. package/src/test/noop_l1_archiver.ts +115 -0
@@ -9,16 +9,18 @@ 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
+ type BlockData,
13
+ BlockHash,
12
14
  Body,
13
15
  CheckpointedL2Block,
14
16
  CommitteeAttestation,
15
- L2BlockHash,
16
- L2BlockNew,
17
+ L2Block,
17
18
  type ValidateCheckpointResult,
18
19
  deserializeValidateCheckpointResult,
19
20
  serializeValidateCheckpointResult,
20
21
  } from '@aztec/stdlib/block';
21
- import { L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
22
+ import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
23
+ import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
22
24
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
23
25
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
24
26
  import {
@@ -27,19 +29,20 @@ import {
27
29
  TxEffect,
28
30
  TxHash,
29
31
  TxReceipt,
32
+ TxStatus,
30
33
  deserializeIndexedTxEffect,
31
34
  serializeIndexedTxEffect,
32
35
  } from '@aztec/stdlib/tx';
33
36
 
34
37
  import {
38
+ BlockAlreadyCheckpointedError,
35
39
  BlockArchiveNotConsistentError,
36
40
  BlockIndexNotSequentialError,
37
41
  BlockNotFoundError,
38
42
  BlockNumberNotSequentialError,
43
+ CannotOverwriteCheckpointedBlockError,
39
44
  CheckpointNotFoundError,
40
- CheckpointNumberNotConsistentError,
41
45
  CheckpointNumberNotSequentialError,
42
- InitialBlockNumberNotSequentialError,
43
46
  InitialCheckpointNumberNotSequentialError,
44
47
  } from '../errors.js';
45
48
 
@@ -58,22 +61,15 @@ type BlockStorage = {
58
61
  type CheckpointStorage = {
59
62
  header: Buffer;
60
63
  archive: Buffer;
64
+ checkpointOutHash: Buffer;
61
65
  checkpointNumber: number;
62
66
  startBlock: number;
63
- numBlocks: number;
67
+ blockCount: number;
64
68
  l1: Buffer;
65
69
  attestations: Buffer[];
66
70
  };
67
71
 
68
- export type CheckpointData = {
69
- checkpointNumber: CheckpointNumber;
70
- header: CheckpointHeader;
71
- archive: AppendOnlyTreeSnapshot;
72
- startBlock: number;
73
- numBlocks: number;
74
- l1: L1PublishedData;
75
- attestations: Buffer[];
76
- };
72
+ export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
77
73
 
78
74
  /**
79
75
  * LMDB-based block storage for the archiver.
@@ -85,6 +81,9 @@ export class BlockStore {
85
81
  /** Map checkpoint number to checkpoint data */
86
82
  #checkpoints: AztecAsyncMap<number, CheckpointStorage>;
87
83
 
84
+ /** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
85
+ #slotToCheckpoint: AztecAsyncMap<number, number>;
86
+
88
87
  /** Map block hash to list of tx hashes */
89
88
  #blockTxs: AztecAsyncMap<string, Buffer>;
90
89
 
@@ -97,6 +96,9 @@ export class BlockStore {
97
96
  /** Stores last proven checkpoint */
98
97
  #lastProvenCheckpoint: AztecAsyncSingleton<number>;
99
98
 
99
+ /** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
100
+ #lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
101
+
100
102
  /** Stores the pending chain validation status */
101
103
  #pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
102
104
 
@@ -120,92 +122,95 @@ export class BlockStore {
120
122
  this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
121
123
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
122
124
  this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
125
+ this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
123
126
  this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
124
127
  this.#checkpoints = db.openMap('archiver_checkpoints');
128
+ this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
125
129
  }
126
130
 
127
131
  /**
128
- * Append new blocks to the store's list. All blocks must be for the 'current' checkpoint
129
- * @param blocks - The L2 blocks to be added to the store.
130
- * @returns True if the operation is successful.
132
+ * Returns the finalized L2 block number. An L2 block is finalized when it was proven
133
+ * in an L1 block that has itself been finalized on Ethereum.
134
+ * @returns The finalized block number.
131
135
  */
132
- async addBlocks(blocks: L2BlockNew[], opts: { force?: boolean } = {}): Promise<boolean> {
133
- if (blocks.length === 0) {
134
- return true;
136
+ async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
137
+ const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
138
+ if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
139
+ return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
140
+ }
141
+ const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
142
+ if (!checkpointStorage) {
143
+ throw new CheckpointNotFoundError(finalizedCheckpointNumber);
135
144
  }
145
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
146
+ }
136
147
 
148
+ /**
149
+ * Append a new proposed block to the store.
150
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
151
+ * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
152
+ * @param block - The proposed L2 block to be added to the store.
153
+ * @returns True if the operation is successful.
154
+ */
155
+ async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
137
156
  return await this.db.transactionAsync(async () => {
138
- // Check that the block immediately before the first block to be added is present in the store.
139
- const firstBlockNumber = blocks[0].number;
140
- const firstBlockCheckpointNumber = blocks[0].checkpointNumber;
141
- const firstBlockIndex = blocks[0].indexWithinCheckpoint;
142
- const firstBlockLastArchive = blocks[0].header.lastArchive.root;
157
+ const blockNumber = block.number;
158
+ const blockCheckpointNumber = block.checkpointNumber;
159
+ const blockIndex = block.indexWithinCheckpoint;
160
+ const blockLastArchive = block.header.lastArchive.root;
143
161
 
144
162
  // Extract the latest block and checkpoint numbers
145
163
  const previousBlockNumber = await this.getLatestBlockNumber();
146
164
  const previousCheckpointNumber = await this.getLatestCheckpointNumber();
147
165
 
148
- // Check that the first block number is the expected one
149
- if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
150
- throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
166
+ // Verify we're not overwriting checkpointed blocks
167
+ const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
168
+ if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
169
+ // Check if the proposed block matches the already-checkpointed one
170
+ const existingBlock = await this.getBlock(BlockNumber(blockNumber));
171
+ if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
172
+ throw new BlockAlreadyCheckpointedError(blockNumber);
173
+ }
174
+ throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
175
+ }
176
+
177
+ // Check that the block number is the expected one
178
+ if (!opts.force && previousBlockNumber !== blockNumber - 1) {
179
+ throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
151
180
  }
152
181
 
153
182
  // The same check as above but for checkpoints
154
- if (!opts.force && previousCheckpointNumber !== firstBlockCheckpointNumber - 1) {
155
- throw new InitialCheckpointNumberNotSequentialError(firstBlockCheckpointNumber, previousCheckpointNumber);
183
+ if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
184
+ throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
156
185
  }
157
186
 
158
187
  // Extract the previous block if there is one and see if it is for the same checkpoint or not
159
188
  const previousBlockResult = await this.getBlock(previousBlockNumber);
160
189
 
161
- let expectedFirstblockIndex = 0;
190
+ let expectedBlockIndex = 0;
162
191
  let previousBlockIndex: number | undefined = undefined;
163
192
  if (previousBlockResult !== undefined) {
164
- if (previousBlockResult.checkpointNumber === firstBlockCheckpointNumber) {
193
+ if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
165
194
  // The previous block is for the same checkpoint, therefore our index should follow it
166
195
  previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
167
- expectedFirstblockIndex = previousBlockIndex + 1;
196
+ expectedBlockIndex = previousBlockIndex + 1;
168
197
  }
169
- if (!previousBlockResult.archive.root.equals(firstBlockLastArchive)) {
198
+ if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
170
199
  throw new BlockArchiveNotConsistentError(
171
- firstBlockNumber,
200
+ blockNumber,
172
201
  previousBlockResult.number,
173
- firstBlockLastArchive,
202
+ blockLastArchive,
174
203
  previousBlockResult.archive.root,
175
204
  );
176
205
  }
177
206
  }
178
207
 
179
- // Now check that the first block has the expected index value
180
- if (!opts.force && expectedFirstblockIndex !== firstBlockIndex) {
181
- throw new BlockIndexNotSequentialError(firstBlockIndex, previousBlockIndex);
208
+ // Now check that the block has the expected index value
209
+ if (!opts.force && expectedBlockIndex !== blockIndex) {
210
+ throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
182
211
  }
183
212
 
184
- // Iterate over blocks array and insert them, checking that the block numbers and indexes are sequential. Also check they are for the correct checkpoint.
185
- let previousBlock: L2BlockNew | undefined = undefined;
186
- for (const block of blocks) {
187
- if (!opts.force && previousBlock) {
188
- if (previousBlock.number + 1 !== block.number) {
189
- throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
190
- }
191
- if (previousBlock.indexWithinCheckpoint + 1 !== block.indexWithinCheckpoint) {
192
- throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
193
- }
194
- if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
195
- throw new BlockArchiveNotConsistentError(
196
- block.number,
197
- previousBlock.number,
198
- block.header.lastArchive.root,
199
- previousBlock.archive.root,
200
- );
201
- }
202
- }
203
- if (!opts.force && firstBlockCheckpointNumber !== block.checkpointNumber) {
204
- throw new CheckpointNumberNotConsistentError(block.checkpointNumber, firstBlockCheckpointNumber);
205
- }
206
- previousBlock = block;
207
- await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
208
- }
213
+ await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
209
214
 
210
215
  return true;
211
216
  });
@@ -241,11 +246,11 @@ export class BlockStore {
241
246
  }
242
247
 
243
248
  let previousBlockNumber: BlockNumber | undefined = undefined;
244
- let previousBlock: L2BlockNew | undefined = undefined;
249
+ let previousBlock: L2Block | undefined = undefined;
245
250
 
246
251
  // If we have a previous checkpoint then we need to get the previous block number
247
252
  if (previousCheckpointData !== undefined) {
248
- previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.numBlocks - 1);
253
+ previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
249
254
  previousBlock = await this.getBlock(previousBlockNumber);
250
255
  if (previousBlock === undefined) {
251
256
  // We should be able to get the required previous block
@@ -309,12 +314,16 @@ export class BlockStore {
309
314
  await this.#checkpoints.set(checkpoint.checkpoint.number, {
310
315
  header: checkpoint.checkpoint.header.toBuffer(),
311
316
  archive: checkpoint.checkpoint.archive.toBuffer(),
317
+ checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
312
318
  l1: checkpoint.l1.toBuffer(),
313
319
  attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
314
320
  checkpointNumber: checkpoint.checkpoint.number,
315
321
  startBlock: checkpoint.checkpoint.blocks[0].number,
316
- numBlocks: checkpoint.checkpoint.blocks.length,
322
+ blockCount: checkpoint.checkpoint.blocks.length,
317
323
  });
324
+
325
+ // Update slot-to-checkpoint index
326
+ await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
318
327
  }
319
328
 
320
329
  await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
@@ -322,8 +331,8 @@ export class BlockStore {
322
331
  });
323
332
  }
324
333
 
325
- private async addBlockToDatabase(block: L2BlockNew, checkpointNumber: number, indexWithinCheckpoint: number) {
326
- const blockHash = L2BlockHash.fromField(await block.hash());
334
+ private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
335
+ const blockHash = await block.hash();
327
336
 
328
337
  await this.#blocks.set(block.number, {
329
338
  header: block.header.toBuffer(),
@@ -351,7 +360,7 @@ export class BlockStore {
351
360
  }
352
361
 
353
362
  /** Deletes a block and all associated data (tx effects, indices). */
354
- private async deleteBlock(block: L2BlockNew): Promise<void> {
363
+ private async deleteBlock(block: L2Block): Promise<void> {
355
364
  // Delete the block from the main blocks map
356
365
  await this.#blocks.delete(block.number);
357
366
 
@@ -368,51 +377,53 @@ export class BlockStore {
368
377
  }
369
378
 
370
379
  /**
371
- * Unwinds checkpoints from the database
372
- * @param from - The tip of the chain, passed for verification purposes,
373
- * ensuring that we don't end up deleting something we did not intend
374
- * @param checkpointsToUnwind - The number of checkpoints we are to unwind
375
- * @returns True if the operation is successful
380
+ * Removes all checkpoints with checkpoint number > checkpointNumber.
381
+ * Also removes ALL blocks (both checkpointed and uncheckpointed) after the last block of the given checkpoint.
382
+ * @param checkpointNumber - Remove all checkpoints strictly after this one.
376
383
  */
377
- async unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number) {
384
+ async removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<RemoveCheckpointsResult> {
378
385
  return await this.db.transactionAsync(async () => {
379
- const last = await this.getLatestCheckpointNumber();
380
- if (from !== last) {
381
- throw new Error(`Can only unwind checkpoints from the tip (requested ${from} but current tip is ${last})`);
386
+ const latestCheckpointNumber = await this.getLatestCheckpointNumber();
387
+
388
+ if (checkpointNumber >= latestCheckpointNumber) {
389
+ this.#log.warn(`No checkpoints to remove after ${checkpointNumber} (latest is ${latestCheckpointNumber})`);
390
+ return { blocksRemoved: undefined };
382
391
  }
383
392
 
393
+ // If the proven checkpoint is beyond the target, update it
384
394
  const proven = await this.getProvenCheckpointNumber();
385
- if (from - checkpointsToUnwind < proven) {
386
- await this.setProvenCheckpointNumber(CheckpointNumber(from - checkpointsToUnwind));
395
+ if (proven > checkpointNumber) {
396
+ this.#log.warn(`Updating proven checkpoint ${proven} to last valid checkpoint ${checkpointNumber}`);
397
+ await this.setProvenCheckpointNumber(checkpointNumber);
387
398
  }
388
399
 
389
- for (let i = 0; i < checkpointsToUnwind; i++) {
390
- const checkpointNumber = from - i;
391
- const checkpoint = await this.#checkpoints.getAsync(checkpointNumber);
392
-
393
- if (checkpoint === undefined) {
394
- this.#log.warn(`Cannot remove checkpoint ${checkpointNumber} from the store since we don't have it`);
395
- continue;
400
+ // Find the last block number to keep (last block of the given checkpoint, or 0 if no checkpoint)
401
+ let lastBlockToKeep: BlockNumber;
402
+ if (checkpointNumber <= 0) {
403
+ lastBlockToKeep = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
404
+ } else {
405
+ const targetCheckpoint = await this.#checkpoints.getAsync(checkpointNumber);
406
+ if (!targetCheckpoint) {
407
+ throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
396
408
  }
397
- await this.#checkpoints.delete(checkpointNumber);
398
- const maxBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
399
-
400
- for (let blockNumber = checkpoint.startBlock; blockNumber <= maxBlock; blockNumber++) {
401
- const block = await this.getBlock(BlockNumber(blockNumber));
409
+ lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
410
+ }
402
411
 
403
- if (block === undefined) {
404
- this.#log.warn(`Cannot remove block ${blockNumber} from the store since we don't have it`);
405
- continue;
406
- }
412
+ // Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
413
+ const blocksRemoved = await this.removeBlocksAfter(lastBlockToKeep);
407
414
 
408
- await this.deleteBlock(block);
409
- this.#log.debug(
410
- `Unwound block ${blockNumber} ${(await block.hash()).toString()} for checkpoint ${checkpointNumber}`,
411
- );
415
+ // Remove all checkpoints after the target
416
+ for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
417
+ const checkpointStorage = await this.#checkpoints.getAsync(c);
418
+ if (checkpointStorage) {
419
+ const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
420
+ await this.#slotToCheckpoint.delete(slotNumber);
412
421
  }
422
+ await this.#checkpoints.delete(c);
423
+ this.#log.debug(`Removed checkpoint ${c}`);
413
424
  }
414
425
 
415
- return true;
426
+ return { blocksRemoved };
416
427
  });
417
428
  }
418
429
 
@@ -436,20 +447,35 @@ export class BlockStore {
436
447
  return checkpoints;
437
448
  }
438
449
 
439
- private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage) {
440
- const data: CheckpointData = {
450
+ /** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
451
+ async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
452
+ const result: CheckpointData[] = [];
453
+ for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
454
+ start: startSlot,
455
+ end: endSlot + 1,
456
+ })) {
457
+ const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
458
+ if (checkpointStorage) {
459
+ result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
460
+ }
461
+ }
462
+ return result;
463
+ }
464
+
465
+ private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
466
+ return {
441
467
  header: CheckpointHeader.fromBuffer(checkpointStorage.header),
442
468
  archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
469
+ checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
443
470
  checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
444
- startBlock: checkpointStorage.startBlock,
445
- numBlocks: checkpointStorage.numBlocks,
471
+ startBlock: BlockNumber(checkpointStorage.startBlock),
472
+ blockCount: checkpointStorage.blockCount,
446
473
  l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
447
- attestations: checkpointStorage.attestations,
474
+ attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
448
475
  };
449
- return data;
450
476
  }
451
477
 
452
- async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2BlockNew[] | undefined> {
478
+ async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
453
479
  const checkpoint = await this.#checkpoints.getAsync(checkpointNumber);
454
480
  if (!checkpoint) {
455
481
  return undefined;
@@ -458,7 +484,7 @@ export class BlockStore {
458
484
  const blocksForCheckpoint = await toArray(
459
485
  this.#blocks.entriesAsync({
460
486
  start: checkpoint.startBlock,
461
- end: checkpoint.startBlock + checkpoint.numBlocks,
487
+ end: checkpoint.startBlock + checkpoint.blockCount,
462
488
  }),
463
489
  );
464
490
 
@@ -472,8 +498,8 @@ export class BlockStore {
472
498
  * @param slotNumber - The slot number to search for.
473
499
  * @returns All blocks with the given slot number, in ascending block number order.
474
500
  */
475
- async getBlocksForSlot(slotNumber: SlotNumber): Promise<L2BlockNew[]> {
476
- const blocks: L2BlockNew[] = [];
501
+ async getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
502
+ const blocks: L2Block[] = [];
477
503
 
478
504
  // Iterate backwards through all blocks and filter by slot number
479
505
  // This is more efficient since we usually query for the most recent slot
@@ -493,12 +519,13 @@ export class BlockStore {
493
519
 
494
520
  /**
495
521
  * Removes all blocks with block number > blockNumber.
522
+ * Does not remove any associated checkpoints.
496
523
  * @param blockNumber - The block number to remove after.
497
524
  * @returns The removed blocks (for event emission).
498
525
  */
499
- async unwindBlocksAfter(blockNumber: BlockNumber): Promise<L2BlockNew[]> {
526
+ async removeBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
500
527
  return await this.db.transactionAsync(async () => {
501
- const removedBlocks: L2BlockNew[] = [];
528
+ const removedBlocks: L2Block[] = [];
502
529
 
503
530
  // Get the latest block number to determine the range
504
531
  const latestBlockNumber = await this.getLatestBlockNumber();
@@ -530,7 +557,7 @@ export class BlockStore {
530
557
  if (!checkpointStorage) {
531
558
  throw new CheckpointNotFoundError(provenCheckpointNumber);
532
559
  } else {
533
- return BlockNumber(checkpointStorage.startBlock + checkpointStorage.numBlocks - 1);
560
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
534
561
  }
535
562
  }
536
563
 
@@ -598,7 +625,7 @@ export class BlockStore {
598
625
  }
599
626
  }
600
627
 
601
- async getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
628
+ async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
602
629
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
603
630
  if (blockNumber === undefined) {
604
631
  return undefined;
@@ -620,7 +647,7 @@ export class BlockStore {
620
647
  * @param limit - The number of blocks to return.
621
648
  * @returns The requested L2 blocks
622
649
  */
623
- async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2BlockNew> {
650
+ async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2Block> {
624
651
  for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
625
652
  const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
626
653
  if (block) {
@@ -629,12 +656,38 @@ export class BlockStore {
629
656
  }
630
657
  }
631
658
 
659
+ /**
660
+ * Gets block metadata (without tx data) by block number.
661
+ * @param blockNumber - The number of the block to return.
662
+ * @returns The requested block data.
663
+ */
664
+ async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
665
+ const blockStorage = await this.#blocks.getAsync(blockNumber);
666
+ if (!blockStorage || !blockStorage.header) {
667
+ return undefined;
668
+ }
669
+ return this.getBlockDataFromBlockStorage(blockStorage);
670
+ }
671
+
672
+ /**
673
+ * Gets block metadata (without tx data) by archive root.
674
+ * @param archive - The archive root of the block to return.
675
+ * @returns The requested block data.
676
+ */
677
+ async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
678
+ const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
679
+ if (blockNumber === undefined) {
680
+ return undefined;
681
+ }
682
+ return this.getBlockData(BlockNumber(blockNumber));
683
+ }
684
+
632
685
  /**
633
686
  * Gets an L2 block.
634
687
  * @param blockNumber - The number of the block to return.
635
688
  * @returns The requested L2 block.
636
689
  */
637
- async getBlock(blockNumber: BlockNumber): Promise<L2BlockNew | undefined> {
690
+ async getBlock(blockNumber: BlockNumber): Promise<L2Block | undefined> {
638
691
  const blockStorage = await this.#blocks.getAsync(blockNumber);
639
692
  if (!blockStorage || !blockStorage.header) {
640
693
  return Promise.resolve(undefined);
@@ -647,7 +700,7 @@ export class BlockStore {
647
700
  * @param blockHash - The hash of the block to return.
648
701
  * @returns The requested L2 block.
649
702
  */
650
- async getBlockByHash(blockHash: L2BlockHash): Promise<L2BlockNew | undefined> {
703
+ async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
651
704
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
652
705
  if (blockNumber === undefined) {
653
706
  return undefined;
@@ -660,7 +713,7 @@ export class BlockStore {
660
713
  * @param archive - The archive root of the block to return.
661
714
  * @returns The requested L2 block.
662
715
  */
663
- async getBlockByArchive(archive: Fr): Promise<L2BlockNew | undefined> {
716
+ async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
664
717
  const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
665
718
  if (blockNumber === undefined) {
666
719
  return undefined;
@@ -673,7 +726,7 @@ export class BlockStore {
673
726
  * @param blockHash - The hash of the block to return.
674
727
  * @returns The requested block header.
675
728
  */
676
- async getBlockHeaderByHash(blockHash: L2BlockHash): Promise<BlockHeader | undefined> {
729
+ async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
677
730
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
678
731
  if (blockNumber === undefined) {
679
732
  return undefined;
@@ -733,15 +786,24 @@ export class BlockStore {
733
786
  }
734
787
  }
735
788
 
789
+ private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
790
+ return {
791
+ header: BlockHeader.fromBuffer(blockStorage.header),
792
+ archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
793
+ blockHash: Fr.fromBuffer(blockStorage.blockHash),
794
+ checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
795
+ indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
796
+ };
797
+ }
798
+
736
799
  private async getBlockFromBlockStorage(
737
800
  blockNumber: number,
738
801
  blockStorage: BlockStorage,
739
- ): Promise<L2BlockNew | undefined> {
740
- const header = BlockHeader.fromBuffer(blockStorage.header);
741
- const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
742
- const blockHash = blockStorage.blockHash;
743
- header.setHash(Fr.fromBuffer(blockHash));
744
- const blockHashString = bufferToHex(blockHash);
802
+ ): Promise<L2Block | undefined> {
803
+ const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
804
+ this.getBlockDataFromBlockStorage(blockStorage);
805
+ header.setHash(blockHash);
806
+ const blockHashString = bufferToHex(blockStorage.blockHash);
745
807
  const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
746
808
  if (blockTxsBuffer === undefined) {
747
809
  this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
@@ -760,13 +822,7 @@ export class BlockStore {
760
822
  txEffects.push(deserializeIndexedTxEffect(txEffect).data);
761
823
  }
762
824
  const body = new Body(txEffects);
763
- const block = new L2BlockNew(
764
- archive,
765
- header,
766
- body,
767
- CheckpointNumber(blockStorage.checkpointNumber!),
768
- IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
769
- );
825
+ const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
770
826
 
771
827
  if (block.number !== blockNumber) {
772
828
  throw new Error(
@@ -796,19 +852,48 @@ export class BlockStore {
796
852
  * @param txHash - The hash of a tx we try to get the receipt for.
797
853
  * @returns The requested tx receipt (or undefined if not found).
798
854
  */
799
- async getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
855
+ async getSettledTxReceipt(
856
+ txHash: TxHash,
857
+ l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
858
+ ): Promise<TxReceipt | undefined> {
800
859
  const txEffect = await this.getTxEffect(txHash);
801
860
  if (!txEffect) {
802
861
  return undefined;
803
862
  }
804
863
 
864
+ const blockNumber = BlockNumber(txEffect.l2BlockNumber);
865
+
866
+ // Use existing archiver methods to determine finalization level
867
+ const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
868
+ this.getProvenBlockNumber(),
869
+ this.getCheckpointedL2BlockNumber(),
870
+ this.getFinalizedL2BlockNumber(),
871
+ this.getBlockData(blockNumber),
872
+ ]);
873
+
874
+ let status: TxStatus;
875
+ if (blockNumber <= finalizedBlockNumber) {
876
+ status = TxStatus.FINALIZED;
877
+ } else if (blockNumber <= provenBlockNumber) {
878
+ status = TxStatus.PROVEN;
879
+ } else if (blockNumber <= checkpointedBlockNumber) {
880
+ status = TxStatus.CHECKPOINTED;
881
+ } else {
882
+ status = TxStatus.PROPOSED;
883
+ }
884
+
885
+ const epochNumber =
886
+ blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;
887
+
805
888
  return new TxReceipt(
806
889
  txHash,
807
- TxReceipt.statusFromRevertCode(txEffect.data.revertCode),
808
- '',
890
+ status,
891
+ TxReceipt.executionResultFromRevertCode(txEffect.data.revertCode),
892
+ undefined,
809
893
  txEffect.data.transactionFee.toBigInt(),
810
894
  txEffect.l2BlockHash,
811
- BlockNumber(txEffect.l2BlockNumber),
895
+ blockNumber,
896
+ epochNumber,
812
897
  );
813
898
  }
814
899
 
@@ -845,7 +930,7 @@ export class BlockStore {
845
930
  if (!checkpoint) {
846
931
  return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
847
932
  }
848
- return BlockNumber(checkpoint.startBlock + checkpoint.numBlocks - 1);
933
+ return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
849
934
  }
850
935
 
851
936
  async getLatestL2BlockNumber(): Promise<BlockNumber> {
@@ -880,6 +965,20 @@ export class BlockStore {
880
965
  return result;
881
966
  }
882
967
 
968
+ async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
969
+ const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
970
+ this.getLatestCheckpointNumber(),
971
+ this.#lastFinalizedCheckpoint.getAsync(),
972
+ ]);
973
+ return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
974
+ ? latestCheckpointNumber
975
+ : CheckpointNumber(finalizedCheckpointNumber ?? 0);
976
+ }
977
+
978
+ setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
979
+ return this.#lastFinalizedCheckpoint.set(checkpointNumber);
980
+ }
981
+
883
982
  #computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
884
983
  if (limit < 1) {
885
984
  throw new Error(`Invalid limit: ${limit}`);