@aztec/archiver 0.0.1-commit.0c875d939 → 0.0.1-commit.10bd49492

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 (82) hide show
  1. package/dest/archiver.d.ts +7 -4
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +62 -110
  4. package/dest/errors.d.ts +7 -9
  5. package/dest/errors.d.ts.map +1 -1
  6. package/dest/errors.js +9 -14
  7. package/dest/factory.d.ts +3 -4
  8. package/dest/factory.d.ts.map +1 -1
  9. package/dest/factory.js +13 -13
  10. package/dest/index.d.ts +2 -1
  11. package/dest/index.d.ts.map +1 -1
  12. package/dest/index.js +1 -0
  13. package/dest/l1/bin/retrieve-calldata.js +32 -28
  14. package/dest/l1/calldata_retriever.d.ts +73 -52
  15. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  16. package/dest/l1/calldata_retriever.js +190 -261
  17. package/dest/l1/data_retrieval.d.ts +7 -8
  18. package/dest/l1/data_retrieval.d.ts.map +1 -1
  19. package/dest/l1/data_retrieval.js +18 -17
  20. package/dest/l1/spire_proposer.d.ts +5 -5
  21. package/dest/l1/spire_proposer.d.ts.map +1 -1
  22. package/dest/l1/spire_proposer.js +9 -17
  23. package/dest/modules/data_source_base.d.ts +10 -5
  24. package/dest/modules/data_source_base.d.ts.map +1 -1
  25. package/dest/modules/data_source_base.js +29 -73
  26. package/dest/modules/data_store_updater.d.ts +22 -7
  27. package/dest/modules/data_store_updater.d.ts.map +1 -1
  28. package/dest/modules/data_store_updater.js +69 -29
  29. package/dest/modules/instrumentation.d.ts +15 -2
  30. package/dest/modules/instrumentation.d.ts.map +1 -1
  31. package/dest/modules/instrumentation.js +19 -2
  32. package/dest/modules/l1_synchronizer.d.ts +5 -8
  33. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  34. package/dest/modules/l1_synchronizer.js +41 -10
  35. package/dest/store/block_store.d.ts +29 -26
  36. package/dest/store/block_store.d.ts.map +1 -1
  37. package/dest/store/block_store.js +130 -78
  38. package/dest/store/kv_archiver_store.d.ts +34 -11
  39. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  40. package/dest/store/kv_archiver_store.js +39 -9
  41. package/dest/store/l2_tips_cache.d.ts +19 -0
  42. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  43. package/dest/store/l2_tips_cache.js +89 -0
  44. package/dest/store/message_store.js +1 -1
  45. package/dest/test/fake_l1_state.d.ts +13 -1
  46. package/dest/test/fake_l1_state.d.ts.map +1 -1
  47. package/dest/test/fake_l1_state.js +84 -20
  48. package/dest/test/mock_archiver.d.ts +1 -1
  49. package/dest/test/mock_archiver.d.ts.map +1 -1
  50. package/dest/test/mock_archiver.js +3 -2
  51. package/dest/test/mock_l2_block_source.d.ts +21 -5
  52. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  53. package/dest/test/mock_l2_block_source.js +132 -86
  54. package/dest/test/mock_structs.d.ts +4 -1
  55. package/dest/test/mock_structs.d.ts.map +1 -1
  56. package/dest/test/mock_structs.js +13 -1
  57. package/dest/test/noop_l1_archiver.d.ts +4 -1
  58. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  59. package/dest/test/noop_l1_archiver.js +5 -1
  60. package/package.json +13 -13
  61. package/src/archiver.ts +74 -130
  62. package/src/errors.ts +10 -24
  63. package/src/factory.ts +13 -6
  64. package/src/index.ts +1 -0
  65. package/src/l1/README.md +25 -68
  66. package/src/l1/bin/retrieve-calldata.ts +40 -27
  67. package/src/l1/calldata_retriever.ts +249 -383
  68. package/src/l1/data_retrieval.ts +20 -25
  69. package/src/l1/spire_proposer.ts +7 -15
  70. package/src/modules/data_source_base.ts +56 -95
  71. package/src/modules/data_store_updater.ts +71 -30
  72. package/src/modules/instrumentation.ts +29 -2
  73. package/src/modules/l1_synchronizer.ts +46 -14
  74. package/src/store/block_store.ts +157 -105
  75. package/src/store/kv_archiver_store.ts +62 -12
  76. package/src/store/l2_tips_cache.ts +89 -0
  77. package/src/store/message_store.ts +1 -1
  78. package/src/test/fake_l1_state.ts +110 -21
  79. package/src/test/mock_archiver.ts +3 -2
  80. package/src/test/mock_l2_block_source.ts +173 -81
  81. package/src/test/mock_structs.ts +20 -6
  82. package/src/test/noop_l1_archiver.ts +7 -1
@@ -1,7 +1,6 @@
1
1
  import type { BlobClientInterface } from '@aztec/blob-client/client';
2
2
  import { EpochCache } from '@aztec/epoch-cache';
3
3
  import { InboxContract, RollupContract } from '@aztec/ethereum/contracts';
4
- import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
5
4
  import type { L1BlockId } from '@aztec/ethereum/l1-types';
6
5
  import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
7
6
  import { maxBigint } from '@aztec/foundation/bigint';
@@ -9,7 +8,6 @@ import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/br
9
8
  import { Buffer32 } from '@aztec/foundation/buffer';
10
9
  import { pick } from '@aztec/foundation/collection';
11
10
  import { Fr } from '@aztec/foundation/curves/bn254';
12
- import { EthAddress } from '@aztec/foundation/eth-address';
13
11
  import { type Logger, createLogger } from '@aztec/foundation/log';
14
12
  import { count } from '@aztec/foundation/string';
15
13
  import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
@@ -28,6 +26,7 @@ import {
28
26
  retrievedToPublishedCheckpoint,
29
27
  } from '../l1/data_retrieval.js';
30
28
  import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
29
+ import type { L2TipsCache } from '../store/l2_tips_cache.js';
31
30
  import type { InboxMessage } from '../structs/inbox_message.js';
32
31
  import { ArchiverDataStoreUpdater } from './data_store_updater.js';
33
32
  import type { ArchiverInstrumentation } from './instrumentation.js';
@@ -60,10 +59,6 @@ export class ArchiverL1Synchronizer implements Traceable {
60
59
  private readonly debugClient: ViemPublicDebugClient,
61
60
  private readonly rollup: RollupContract,
62
61
  private readonly inbox: InboxContract,
63
- private readonly l1Addresses: Pick<
64
- L1ContractAddresses,
65
- 'registryAddress' | 'governanceProposerAddress' | 'slashFactoryAddress'
66
- > & { slashingProposerAddress: EthAddress },
67
62
  private readonly store: KVArchiverDataStore,
68
63
  private config: {
69
64
  batchSize: number;
@@ -74,12 +69,18 @@ export class ArchiverL1Synchronizer implements Traceable {
74
69
  private readonly epochCache: EpochCache,
75
70
  private readonly dateProvider: DateProvider,
76
71
  private readonly instrumentation: ArchiverInstrumentation,
77
- private readonly l1Constants: L1RollupConstants & { l1StartBlockHash: Buffer32; genesisArchiveRoot: Fr },
72
+ private readonly l1Constants: L1RollupConstants & {
73
+ l1StartBlockHash: Buffer32;
74
+ genesisArchiveRoot: Fr;
75
+ },
78
76
  private readonly events: ArchiverEmitter,
79
77
  tracer: Tracer,
78
+ l2TipsCache?: L2TipsCache,
80
79
  private readonly log: Logger = createLogger('archiver:l1-sync'),
81
80
  ) {
82
- this.updater = new ArchiverDataStoreUpdater(this.store);
81
+ this.updater = new ArchiverDataStoreUpdater(this.store, l2TipsCache, {
82
+ rollupManaLimit: l1Constants.rollupManaLimit,
83
+ });
83
84
  this.tracer = tracer;
84
85
  }
85
86
 
@@ -215,6 +216,9 @@ export class ArchiverL1Synchronizer implements Traceable {
215
216
  this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
216
217
  }
217
218
 
219
+ // Update the finalized L2 checkpoint based on L1 finality.
220
+ await this.updateFinalizedCheckpoint();
221
+
218
222
  // After syncing has completed, update the current l1 block number and timestamp,
219
223
  // otherwise we risk announcing to the world that we've synced to a given point,
220
224
  // but the corresponding blocks have not been processed (see #12631).
@@ -230,6 +234,27 @@ export class ArchiverL1Synchronizer implements Traceable {
230
234
  });
231
235
  }
232
236
 
237
+ /** Query L1 for its finalized block and update the finalized checkpoint accordingly. */
238
+ private async updateFinalizedCheckpoint(): Promise<void> {
239
+ try {
240
+ const finalizedL1Block = await this.publicClient.getBlock({ blockTag: 'finalized', includeTransactions: false });
241
+ const finalizedL1BlockNumber = finalizedL1Block.number;
242
+ const finalizedCheckpointNumber = await this.rollup.getProvenCheckpointNumber({
243
+ blockNumber: finalizedL1BlockNumber,
244
+ });
245
+ const localFinalizedCheckpointNumber = await this.store.getFinalizedCheckpointNumber();
246
+ if (localFinalizedCheckpointNumber !== finalizedCheckpointNumber) {
247
+ await this.updater.setFinalizedCheckpointNumber(finalizedCheckpointNumber);
248
+ this.log.info(`Updated finalized chain to checkpoint ${finalizedCheckpointNumber}`, {
249
+ finalizedCheckpointNumber,
250
+ finalizedL1BlockNumber,
251
+ });
252
+ }
253
+ } catch (err) {
254
+ this.log.warn(`Failed to update finalized checkpoint: ${err}`);
255
+ }
256
+ }
257
+
233
258
  /** Prune all proposed local blocks that should have been checkpointed by now. */
234
259
  private async pruneUncheckpointedBlocks(currentL1Timestamp: bigint) {
235
260
  const [lastCheckpointedBlockNumber, lastProposedBlockNumber] = await Promise.all([
@@ -550,7 +575,7 @@ export class ArchiverL1Synchronizer implements Traceable {
550
575
  if (provenCheckpointNumber === 0) {
551
576
  const localProvenCheckpointNumber = await this.store.getProvenCheckpointNumber();
552
577
  if (localProvenCheckpointNumber !== provenCheckpointNumber) {
553
- await this.store.setProvenCheckpointNumber(provenCheckpointNumber);
578
+ await this.updater.setProvenCheckpointNumber(provenCheckpointNumber);
554
579
  this.log.info(`Rolled back proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
555
580
  }
556
581
  }
@@ -582,13 +607,13 @@ export class ArchiverL1Synchronizer implements Traceable {
582
607
  ) {
583
608
  const localProvenCheckpointNumber = await this.store.getProvenCheckpointNumber();
584
609
  if (localProvenCheckpointNumber !== provenCheckpointNumber) {
585
- await this.store.setProvenCheckpointNumber(provenCheckpointNumber);
610
+ await this.updater.setProvenCheckpointNumber(provenCheckpointNumber);
586
611
  this.log.info(`Updated proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
587
612
  const provenSlotNumber = localCheckpointForDestinationProvenCheckpointNumber.header.slotNumber;
588
613
  const provenEpochNumber: EpochNumber = getEpochAtSlot(provenSlotNumber, this.l1Constants);
589
614
  const lastBlockNumberInCheckpoint =
590
615
  localCheckpointForDestinationProvenCheckpointNumber.startBlock +
591
- localCheckpointForDestinationProvenCheckpointNumber.numBlocks -
616
+ localCheckpointForDestinationProvenCheckpointNumber.blockCount -
592
617
  1;
593
618
 
594
619
  this.events.emit(L2BlockSourceEvents.L2BlockProven, {
@@ -597,7 +622,7 @@ export class ArchiverL1Synchronizer implements Traceable {
597
622
  slotNumber: provenSlotNumber,
598
623
  epochNumber: provenEpochNumber,
599
624
  });
600
- this.instrumentation.updateLastProvenBlock(lastBlockNumberInCheckpoint);
625
+ this.instrumentation.updateLastProvenCheckpoint(localCheckpointForDestinationProvenCheckpointNumber);
601
626
  } else {
602
627
  this.log.trace(`Proven checkpoint ${provenCheckpointNumber} already stored.`);
603
628
  }
@@ -706,7 +731,6 @@ export class ArchiverL1Synchronizer implements Traceable {
706
731
  this.blobClient,
707
732
  searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
708
733
  searchEndBlock,
709
- this.l1Addresses,
710
734
  this.instrumentation,
711
735
  this.log,
712
736
  !initialSyncComplete, // isHistoricalSync
@@ -801,6 +825,14 @@ export class ArchiverL1Synchronizer implements Traceable {
801
825
  );
802
826
  }
803
827
 
828
+ for (const published of validCheckpoints) {
829
+ this.instrumentation.processCheckpointL1Timing({
830
+ slotNumber: published.checkpoint.header.slotNumber,
831
+ l1Timestamp: published.l1.timestamp,
832
+ l1Constants: this.l1Constants,
833
+ });
834
+ }
835
+
804
836
  try {
805
837
  const updatedValidationResult =
806
838
  rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
@@ -819,7 +851,7 @@ export class ArchiverL1Synchronizer implements Traceable {
819
851
  const prunedCheckpointNumber = result.prunedBlocks[0].checkpointNumber;
820
852
  const prunedSlotNumber = result.prunedBlocks[0].header.globalVariables.slotNumber;
821
853
 
822
- this.log.warn(
854
+ this.log.info(
823
855
  `Pruned ${result.prunedBlocks.length} mismatching blocks for checkpoint ${prunedCheckpointNumber}`,
824
856
  { prunedBlocks: result.prunedBlocks.map(b => b.toBlockInfo()), prunedSlotNumber, prunedCheckpointNumber },
825
857
  );
@@ -9,6 +9,7 @@ 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,
12
13
  BlockHash,
13
14
  Body,
14
15
  CheckpointedL2Block,
@@ -18,8 +19,8 @@ import {
18
19
  deserializeValidateCheckpointResult,
19
20
  serializeValidateCheckpointResult,
20
21
  } from '@aztec/stdlib/block';
21
- import { L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
22
- import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
22
+ import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
23
+ import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
23
24
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
24
25
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
25
26
  import {
@@ -34,15 +35,14 @@ import {
34
35
  } from '@aztec/stdlib/tx';
35
36
 
36
37
  import {
38
+ BlockAlreadyCheckpointedError,
37
39
  BlockArchiveNotConsistentError,
38
40
  BlockIndexNotSequentialError,
39
41
  BlockNotFoundError,
40
42
  BlockNumberNotSequentialError,
41
43
  CannotOverwriteCheckpointedBlockError,
42
44
  CheckpointNotFoundError,
43
- CheckpointNumberNotConsistentError,
44
45
  CheckpointNumberNotSequentialError,
45
- InitialBlockNumberNotSequentialError,
46
46
  InitialCheckpointNumberNotSequentialError,
47
47
  } from '../errors.js';
48
48
 
@@ -61,23 +61,14 @@ type BlockStorage = {
61
61
  type CheckpointStorage = {
62
62
  header: Buffer;
63
63
  archive: Buffer;
64
+ checkpointOutHash: Buffer;
64
65
  checkpointNumber: number;
65
66
  startBlock: number;
66
- numBlocks: number;
67
+ blockCount: number;
67
68
  l1: Buffer;
68
69
  attestations: Buffer[];
69
70
  };
70
71
 
71
- export type CheckpointData = {
72
- checkpointNumber: CheckpointNumber;
73
- header: CheckpointHeader;
74
- archive: AppendOnlyTreeSnapshot;
75
- startBlock: number;
76
- numBlocks: number;
77
- l1: L1PublishedData;
78
- attestations: Buffer[];
79
- };
80
-
81
72
  export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
82
73
 
83
74
  /**
@@ -90,6 +81,9 @@ export class BlockStore {
90
81
  /** Map checkpoint number to checkpoint data */
91
82
  #checkpoints: AztecAsyncMap<number, CheckpointStorage>;
92
83
 
84
+ /** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
85
+ #slotToCheckpoint: AztecAsyncMap<number, number>;
86
+
93
87
  /** Map block hash to list of tx hashes */
94
88
  #blockTxs: AztecAsyncMap<string, Buffer>;
95
89
 
@@ -102,6 +96,9 @@ export class BlockStore {
102
96
  /** Stores last proven checkpoint */
103
97
  #lastProvenCheckpoint: AztecAsyncSingleton<number>;
104
98
 
99
+ /** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
100
+ #lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
101
+
105
102
  /** Stores the pending chain validation status */
106
103
  #pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
107
104
 
@@ -116,10 +113,7 @@ export class BlockStore {
116
113
 
117
114
  #log = createLogger('archiver:block_store');
118
115
 
119
- constructor(
120
- private db: AztecAsyncKVStore,
121
- private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
122
- ) {
116
+ constructor(private db: AztecAsyncKVStore) {
123
117
  this.#blocks = db.openMap('archiver_blocks');
124
118
  this.#blockTxs = db.openMap('archiver_block_txs');
125
119
  this.#txEffects = db.openMap('archiver_tx_effects');
@@ -128,40 +122,42 @@ export class BlockStore {
128
122
  this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
129
123
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
130
124
  this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
125
+ this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
131
126
  this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
132
127
  this.#checkpoints = db.openMap('archiver_checkpoints');
128
+ this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
133
129
  }
134
130
 
135
131
  /**
136
- * Computes the finalized block number based on the proven block number.
137
- * A block is considered finalized when it's 2 epochs behind the proven block.
138
- * TODO(#13569): Compute proper finalized block number based on L1 finalized block.
139
- * TODO(palla/mbps): Even the provisional computation is wrong, since it should subtract checkpoints, not blocks
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.
140
134
  * @returns The finalized block number.
141
135
  */
142
136
  async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
143
- const provenBlockNumber = await this.getProvenBlockNumber();
144
- return BlockNumber(Math.max(provenBlockNumber - this.l1Constants.epochDuration * 2, 0));
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);
144
+ }
145
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
145
146
  }
146
147
 
147
148
  /**
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.
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.
150
151
  * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
151
- * @param blocks - The proposed L2 blocks to be added to the store.
152
+ * @param block - The proposed L2 block to be added to the store.
152
153
  * @returns True if the operation is successful.
153
154
  */
154
- async addProposedBlocks(blocks: L2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
155
- if (blocks.length === 0) {
156
- return true;
157
- }
158
-
155
+ async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
159
156
  return await this.db.transactionAsync(async () => {
160
- // Check that the block immediately before the first block to be added is present in the store.
161
- const firstBlockNumber = blocks[0].number;
162
- const firstBlockCheckpointNumber = blocks[0].checkpointNumber;
163
- const firstBlockIndex = blocks[0].indexWithinCheckpoint;
164
- 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;
165
161
 
166
162
  // Extract the latest block and checkpoint numbers
167
163
  const previousBlockNumber = await this.getLatestBlockNumber();
@@ -169,71 +165,52 @@ export class BlockStore {
169
165
 
170
166
  // Verify we're not overwriting checkpointed blocks
171
167
  const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
172
- if (!opts.force && firstBlockNumber <= lastCheckpointedBlockNumber) {
173
- throw new CannotOverwriteCheckpointedBlockError(firstBlockNumber, lastCheckpointedBlockNumber);
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);
174
175
  }
175
176
 
176
- // Check that the first block number is the expected one
177
- if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
178
- throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
177
+ // Check that the block number is the expected one
178
+ if (!opts.force && previousBlockNumber !== blockNumber - 1) {
179
+ throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
179
180
  }
180
181
 
181
182
  // The same check as above but for checkpoints
182
- if (!opts.force && previousCheckpointNumber !== firstBlockCheckpointNumber - 1) {
183
- throw new InitialCheckpointNumberNotSequentialError(firstBlockCheckpointNumber, previousCheckpointNumber);
183
+ if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
184
+ throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
184
185
  }
185
186
 
186
187
  // Extract the previous block if there is one and see if it is for the same checkpoint or not
187
188
  const previousBlockResult = await this.getBlock(previousBlockNumber);
188
189
 
189
- let expectedFirstblockIndex = 0;
190
+ let expectedBlockIndex = 0;
190
191
  let previousBlockIndex: number | undefined = undefined;
191
192
  if (previousBlockResult !== undefined) {
192
- if (previousBlockResult.checkpointNumber === firstBlockCheckpointNumber) {
193
+ if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
193
194
  // The previous block is for the same checkpoint, therefore our index should follow it
194
195
  previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
195
- expectedFirstblockIndex = previousBlockIndex + 1;
196
+ expectedBlockIndex = previousBlockIndex + 1;
196
197
  }
197
- if (!previousBlockResult.archive.root.equals(firstBlockLastArchive)) {
198
+ if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
198
199
  throw new BlockArchiveNotConsistentError(
199
- firstBlockNumber,
200
+ blockNumber,
200
201
  previousBlockResult.number,
201
- firstBlockLastArchive,
202
+ blockLastArchive,
202
203
  previousBlockResult.archive.root,
203
204
  );
204
205
  }
205
206
  }
206
207
 
207
- // Now check that the first block has the expected index value
208
- if (!opts.force && expectedFirstblockIndex !== firstBlockIndex) {
209
- 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);
210
211
  }
211
212
 
212
- // Iterate over blocks array and insert them, checking that the block numbers and indexes are sequential. Also check they are for the correct checkpoint.
213
- let previousBlock: L2Block | undefined = undefined;
214
- for (const block of blocks) {
215
- if (!opts.force && previousBlock) {
216
- if (previousBlock.number + 1 !== block.number) {
217
- throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
218
- }
219
- if (previousBlock.indexWithinCheckpoint + 1 !== block.indexWithinCheckpoint) {
220
- throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
221
- }
222
- if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
223
- throw new BlockArchiveNotConsistentError(
224
- block.number,
225
- previousBlock.number,
226
- block.header.lastArchive.root,
227
- previousBlock.archive.root,
228
- );
229
- }
230
- }
231
- if (!opts.force && firstBlockCheckpointNumber !== block.checkpointNumber) {
232
- throw new CheckpointNumberNotConsistentError(block.checkpointNumber, firstBlockCheckpointNumber);
233
- }
234
- previousBlock = block;
235
- await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
236
- }
213
+ await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
237
214
 
238
215
  return true;
239
216
  });
@@ -273,7 +250,7 @@ export class BlockStore {
273
250
 
274
251
  // If we have a previous checkpoint then we need to get the previous block number
275
252
  if (previousCheckpointData !== undefined) {
276
- previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.numBlocks - 1);
253
+ previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
277
254
  previousBlock = await this.getBlock(previousBlockNumber);
278
255
  if (previousBlock === undefined) {
279
256
  // We should be able to get the required previous block
@@ -337,12 +314,16 @@ export class BlockStore {
337
314
  await this.#checkpoints.set(checkpoint.checkpoint.number, {
338
315
  header: checkpoint.checkpoint.header.toBuffer(),
339
316
  archive: checkpoint.checkpoint.archive.toBuffer(),
317
+ checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
340
318
  l1: checkpoint.l1.toBuffer(),
341
319
  attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
342
320
  checkpointNumber: checkpoint.checkpoint.number,
343
321
  startBlock: checkpoint.checkpoint.blocks[0].number,
344
- numBlocks: checkpoint.checkpoint.blocks.length,
322
+ blockCount: checkpoint.checkpoint.blocks.length,
345
323
  });
324
+
325
+ // Update slot-to-checkpoint index
326
+ await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
346
327
  }
347
328
 
348
329
  await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
@@ -425,7 +406,7 @@ export class BlockStore {
425
406
  if (!targetCheckpoint) {
426
407
  throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
427
408
  }
428
- lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.numBlocks - 1);
409
+ lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
429
410
  }
430
411
 
431
412
  // Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
@@ -433,6 +414,11 @@ export class BlockStore {
433
414
 
434
415
  // Remove all checkpoints after the target
435
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);
421
+ }
436
422
  await this.#checkpoints.delete(c);
437
423
  this.#log.debug(`Removed checkpoint ${c}`);
438
424
  }
@@ -461,17 +447,32 @@ export class BlockStore {
461
447
  return checkpoints;
462
448
  }
463
449
 
464
- private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage) {
465
- 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 {
466
467
  header: CheckpointHeader.fromBuffer(checkpointStorage.header),
467
468
  archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
469
+ checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
468
470
  checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
469
- startBlock: checkpointStorage.startBlock,
470
- numBlocks: checkpointStorage.numBlocks,
471
+ startBlock: BlockNumber(checkpointStorage.startBlock),
472
+ blockCount: checkpointStorage.blockCount,
471
473
  l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
472
- attestations: checkpointStorage.attestations,
474
+ attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
473
475
  };
474
- return data;
475
476
  }
476
477
 
477
478
  async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
@@ -483,7 +484,7 @@ export class BlockStore {
483
484
  const blocksForCheckpoint = await toArray(
484
485
  this.#blocks.entriesAsync({
485
486
  start: checkpoint.startBlock,
486
- end: checkpoint.startBlock + checkpoint.numBlocks,
487
+ end: checkpoint.startBlock + checkpoint.blockCount,
487
488
  }),
488
489
  );
489
490
 
@@ -556,7 +557,7 @@ export class BlockStore {
556
557
  if (!checkpointStorage) {
557
558
  throw new CheckpointNotFoundError(provenCheckpointNumber);
558
559
  } else {
559
- return BlockNumber(checkpointStorage.startBlock + checkpointStorage.numBlocks - 1);
560
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
560
561
  }
561
562
  }
562
563
 
@@ -655,6 +656,32 @@ export class BlockStore {
655
656
  }
656
657
  }
657
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
+
658
685
  /**
659
686
  * Gets an L2 block.
660
687
  * @param blockNumber - The number of the block to return.
@@ -759,15 +786,24 @@ export class BlockStore {
759
786
  }
760
787
  }
761
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
+
762
799
  private async getBlockFromBlockStorage(
763
800
  blockNumber: number,
764
801
  blockStorage: BlockStorage,
765
802
  ): Promise<L2Block | undefined> {
766
- const header = BlockHeader.fromBuffer(blockStorage.header);
767
- const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
768
- const blockHash = blockStorage.blockHash;
769
- header.setHash(Fr.fromBuffer(blockHash));
770
- const blockHashString = bufferToHex(blockHash);
803
+ const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
804
+ this.getBlockDataFromBlockStorage(blockStorage);
805
+ header.setHash(blockHash);
806
+ const blockHashString = bufferToHex(blockStorage.blockHash);
771
807
  const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
772
808
  if (blockTxsBuffer === undefined) {
773
809
  this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
@@ -786,13 +822,7 @@ export class BlockStore {
786
822
  txEffects.push(deserializeIndexedTxEffect(txEffect).data);
787
823
  }
788
824
  const body = new Body(txEffects);
789
- const block = new L2Block(
790
- archive,
791
- header,
792
- body,
793
- CheckpointNumber(blockStorage.checkpointNumber!),
794
- IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
795
- );
825
+ const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
796
826
 
797
827
  if (block.number !== blockNumber) {
798
828
  throw new Error(
@@ -822,7 +852,10 @@ export class BlockStore {
822
852
  * @param txHash - The hash of a tx we try to get the receipt for.
823
853
  * @returns The requested tx receipt (or undefined if not found).
824
854
  */
825
- async getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
855
+ async getSettledTxReceipt(
856
+ txHash: TxHash,
857
+ l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
858
+ ): Promise<TxReceipt | undefined> {
826
859
  const txEffect = await this.getTxEffect(txHash);
827
860
  if (!txEffect) {
828
861
  return undefined;
@@ -831,10 +864,11 @@ export class BlockStore {
831
864
  const blockNumber = BlockNumber(txEffect.l2BlockNumber);
832
865
 
833
866
  // Use existing archiver methods to determine finalization level
834
- const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
867
+ const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
835
868
  this.getProvenBlockNumber(),
836
869
  this.getCheckpointedL2BlockNumber(),
837
870
  this.getFinalizedL2BlockNumber(),
871
+ this.getBlockData(blockNumber),
838
872
  ]);
839
873
 
840
874
  let status: TxStatus;
@@ -848,6 +882,9 @@ export class BlockStore {
848
882
  status = TxStatus.PROPOSED;
849
883
  }
850
884
 
885
+ const epochNumber =
886
+ blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;
887
+
851
888
  return new TxReceipt(
852
889
  txHash,
853
890
  status,
@@ -856,6 +893,7 @@ export class BlockStore {
856
893
  txEffect.data.transactionFee.toBigInt(),
857
894
  txEffect.l2BlockHash,
858
895
  blockNumber,
896
+ epochNumber,
859
897
  );
860
898
  }
861
899
 
@@ -892,7 +930,7 @@ export class BlockStore {
892
930
  if (!checkpoint) {
893
931
  return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
894
932
  }
895
- return BlockNumber(checkpoint.startBlock + checkpoint.numBlocks - 1);
933
+ return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
896
934
  }
897
935
 
898
936
  async getLatestL2BlockNumber(): Promise<BlockNumber> {
@@ -927,6 +965,20 @@ export class BlockStore {
927
965
  return result;
928
966
  }
929
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
+
930
982
  #computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
931
983
  if (limit < 1) {
932
984
  throw new Error(`Invalid limit: ${limit}`);