@aztec/archiver 0.0.1-commit.f1df4d2 → 0.0.1-commit.f224bb98b

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 (86) 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 +15 -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 +36 -33
  14. package/dest/l1/calldata_retriever.d.ts +73 -50
  15. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  16. package/dest/l1/calldata_retriever.js +190 -259
  17. package/dest/l1/data_retrieval.d.ts +9 -9
  18. package/dest/l1/data_retrieval.d.ts.map +1 -1
  19. package/dest/l1/data_retrieval.js +21 -19
  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 +28 -72
  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 +27 -25
  36. package/dest/store/block_store.d.ts.map +1 -1
  37. package/dest/store/block_store.js +123 -74
  38. package/dest/store/kv_archiver_store.d.ts +33 -11
  39. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  40. package/dest/store/kv_archiver_store.js +37 -7
  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/log_store.d.ts +1 -1
  45. package/dest/store/log_store.d.ts.map +1 -1
  46. package/dest/store/log_store.js +55 -35
  47. package/dest/store/message_store.js +1 -1
  48. package/dest/test/fake_l1_state.d.ts +13 -1
  49. package/dest/test/fake_l1_state.d.ts.map +1 -1
  50. package/dest/test/fake_l1_state.js +84 -20
  51. package/dest/test/mock_archiver.d.ts +1 -1
  52. package/dest/test/mock_archiver.d.ts.map +1 -1
  53. package/dest/test/mock_archiver.js +3 -2
  54. package/dest/test/mock_l2_block_source.d.ts +21 -5
  55. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  56. package/dest/test/mock_l2_block_source.js +130 -84
  57. package/dest/test/mock_structs.d.ts +4 -1
  58. package/dest/test/mock_structs.d.ts.map +1 -1
  59. package/dest/test/mock_structs.js +13 -1
  60. package/dest/test/noop_l1_archiver.d.ts +4 -1
  61. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  62. package/dest/test/noop_l1_archiver.js +5 -1
  63. package/package.json +13 -13
  64. package/src/archiver.ts +74 -130
  65. package/src/errors.ts +10 -24
  66. package/src/factory.ts +29 -14
  67. package/src/index.ts +1 -0
  68. package/src/l1/README.md +25 -68
  69. package/src/l1/bin/retrieve-calldata.ts +46 -39
  70. package/src/l1/calldata_retriever.ts +249 -379
  71. package/src/l1/data_retrieval.ts +23 -25
  72. package/src/l1/spire_proposer.ts +7 -15
  73. package/src/modules/data_source_base.ts +55 -94
  74. package/src/modules/data_store_updater.ts +71 -30
  75. package/src/modules/instrumentation.ts +29 -2
  76. package/src/modules/l1_synchronizer.ts +46 -14
  77. package/src/store/block_store.ts +146 -103
  78. package/src/store/kv_archiver_store.ts +57 -11
  79. package/src/store/l2_tips_cache.ts +89 -0
  80. package/src/store/log_store.ts +93 -31
  81. package/src/store/message_store.ts +1 -1
  82. package/src/test/fake_l1_state.ts +110 -21
  83. package/src/test/mock_archiver.ts +3 -2
  84. package/src/test/mock_l2_block_source.ts +166 -80
  85. package/src/test/mock_structs.ts +20 -6
  86. 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,7 @@ 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
23
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
24
24
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
25
25
  import {
@@ -34,15 +34,14 @@ import {
34
34
  } from '@aztec/stdlib/tx';
35
35
 
36
36
  import {
37
+ BlockAlreadyCheckpointedError,
37
38
  BlockArchiveNotConsistentError,
38
39
  BlockIndexNotSequentialError,
39
40
  BlockNotFoundError,
40
41
  BlockNumberNotSequentialError,
41
42
  CannotOverwriteCheckpointedBlockError,
42
43
  CheckpointNotFoundError,
43
- CheckpointNumberNotConsistentError,
44
44
  CheckpointNumberNotSequentialError,
45
- InitialBlockNumberNotSequentialError,
46
45
  InitialCheckpointNumberNotSequentialError,
47
46
  } from '../errors.js';
48
47
 
@@ -61,23 +60,14 @@ type BlockStorage = {
61
60
  type CheckpointStorage = {
62
61
  header: Buffer;
63
62
  archive: Buffer;
63
+ checkpointOutHash: Buffer;
64
64
  checkpointNumber: number;
65
65
  startBlock: number;
66
- numBlocks: number;
66
+ blockCount: number;
67
67
  l1: Buffer;
68
68
  attestations: Buffer[];
69
69
  };
70
70
 
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
71
  export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
82
72
 
83
73
  /**
@@ -90,6 +80,9 @@ export class BlockStore {
90
80
  /** Map checkpoint number to checkpoint data */
91
81
  #checkpoints: AztecAsyncMap<number, CheckpointStorage>;
92
82
 
83
+ /** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
84
+ #slotToCheckpoint: AztecAsyncMap<number, number>;
85
+
93
86
  /** Map block hash to list of tx hashes */
94
87
  #blockTxs: AztecAsyncMap<string, Buffer>;
95
88
 
@@ -102,6 +95,9 @@ export class BlockStore {
102
95
  /** Stores last proven checkpoint */
103
96
  #lastProvenCheckpoint: AztecAsyncSingleton<number>;
104
97
 
98
+ /** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
99
+ #lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
100
+
105
101
  /** Stores the pending chain validation status */
106
102
  #pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
107
103
 
@@ -116,10 +112,7 @@ export class BlockStore {
116
112
 
117
113
  #log = createLogger('archiver:block_store');
118
114
 
119
- constructor(
120
- private db: AztecAsyncKVStore,
121
- private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
122
- ) {
115
+ constructor(private db: AztecAsyncKVStore) {
123
116
  this.#blocks = db.openMap('archiver_blocks');
124
117
  this.#blockTxs = db.openMap('archiver_block_txs');
125
118
  this.#txEffects = db.openMap('archiver_tx_effects');
@@ -128,40 +121,42 @@ export class BlockStore {
128
121
  this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
129
122
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
130
123
  this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
124
+ this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
131
125
  this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
132
126
  this.#checkpoints = db.openMap('archiver_checkpoints');
127
+ this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
133
128
  }
134
129
 
135
130
  /**
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
131
+ * Returns the finalized L2 block number. An L2 block is finalized when it was proven
132
+ * in an L1 block that has itself been finalized on Ethereum.
140
133
  * @returns The finalized block number.
141
134
  */
142
135
  async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
143
- const provenBlockNumber = await this.getProvenBlockNumber();
144
- return BlockNumber(Math.max(provenBlockNumber - this.l1Constants.epochDuration * 2, 0));
136
+ const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
137
+ if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
138
+ return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
139
+ }
140
+ const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
141
+ if (!checkpointStorage) {
142
+ throw new CheckpointNotFoundError(finalizedCheckpointNumber);
143
+ }
144
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
145
145
  }
146
146
 
147
147
  /**
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.
148
+ * Append a new proposed block to the store.
149
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
150
150
  * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
151
- * @param blocks - The proposed L2 blocks to be added to the store.
151
+ * @param block - The proposed L2 block to be added to the store.
152
152
  * @returns True if the operation is successful.
153
153
  */
154
- async addProposedBlocks(blocks: L2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
155
- if (blocks.length === 0) {
156
- return true;
157
- }
158
-
154
+ async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
159
155
  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;
156
+ const blockNumber = block.number;
157
+ const blockCheckpointNumber = block.checkpointNumber;
158
+ const blockIndex = block.indexWithinCheckpoint;
159
+ const blockLastArchive = block.header.lastArchive.root;
165
160
 
166
161
  // Extract the latest block and checkpoint numbers
167
162
  const previousBlockNumber = await this.getLatestBlockNumber();
@@ -169,71 +164,52 @@ export class BlockStore {
169
164
 
170
165
  // Verify we're not overwriting checkpointed blocks
171
166
  const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
172
- if (!opts.force && firstBlockNumber <= lastCheckpointedBlockNumber) {
173
- throw new CannotOverwriteCheckpointedBlockError(firstBlockNumber, lastCheckpointedBlockNumber);
167
+ if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
168
+ // Check if the proposed block matches the already-checkpointed one
169
+ const existingBlock = await this.getBlock(BlockNumber(blockNumber));
170
+ if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
171
+ throw new BlockAlreadyCheckpointedError(blockNumber);
172
+ }
173
+ throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
174
174
  }
175
175
 
176
- // Check that the first block number is the expected one
177
- if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
178
- throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
176
+ // Check that the block number is the expected one
177
+ if (!opts.force && previousBlockNumber !== blockNumber - 1) {
178
+ throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
179
179
  }
180
180
 
181
181
  // The same check as above but for checkpoints
182
- if (!opts.force && previousCheckpointNumber !== firstBlockCheckpointNumber - 1) {
183
- throw new InitialCheckpointNumberNotSequentialError(firstBlockCheckpointNumber, previousCheckpointNumber);
182
+ if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
183
+ throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
184
184
  }
185
185
 
186
186
  // Extract the previous block if there is one and see if it is for the same checkpoint or not
187
187
  const previousBlockResult = await this.getBlock(previousBlockNumber);
188
188
 
189
- let expectedFirstblockIndex = 0;
189
+ let expectedBlockIndex = 0;
190
190
  let previousBlockIndex: number | undefined = undefined;
191
191
  if (previousBlockResult !== undefined) {
192
- if (previousBlockResult.checkpointNumber === firstBlockCheckpointNumber) {
192
+ if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
193
193
  // The previous block is for the same checkpoint, therefore our index should follow it
194
194
  previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
195
- expectedFirstblockIndex = previousBlockIndex + 1;
195
+ expectedBlockIndex = previousBlockIndex + 1;
196
196
  }
197
- if (!previousBlockResult.archive.root.equals(firstBlockLastArchive)) {
197
+ if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
198
198
  throw new BlockArchiveNotConsistentError(
199
- firstBlockNumber,
199
+ blockNumber,
200
200
  previousBlockResult.number,
201
- firstBlockLastArchive,
201
+ blockLastArchive,
202
202
  previousBlockResult.archive.root,
203
203
  );
204
204
  }
205
205
  }
206
206
 
207
- // Now check that the first block has the expected index value
208
- if (!opts.force && expectedFirstblockIndex !== firstBlockIndex) {
209
- throw new BlockIndexNotSequentialError(firstBlockIndex, previousBlockIndex);
207
+ // Now check that the block has the expected index value
208
+ if (!opts.force && expectedBlockIndex !== blockIndex) {
209
+ throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
210
210
  }
211
211
 
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
- }
212
+ await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
237
213
 
238
214
  return true;
239
215
  });
@@ -273,7 +249,7 @@ export class BlockStore {
273
249
 
274
250
  // If we have a previous checkpoint then we need to get the previous block number
275
251
  if (previousCheckpointData !== undefined) {
276
- previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.numBlocks - 1);
252
+ previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
277
253
  previousBlock = await this.getBlock(previousBlockNumber);
278
254
  if (previousBlock === undefined) {
279
255
  // We should be able to get the required previous block
@@ -337,12 +313,16 @@ export class BlockStore {
337
313
  await this.#checkpoints.set(checkpoint.checkpoint.number, {
338
314
  header: checkpoint.checkpoint.header.toBuffer(),
339
315
  archive: checkpoint.checkpoint.archive.toBuffer(),
316
+ checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
340
317
  l1: checkpoint.l1.toBuffer(),
341
318
  attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
342
319
  checkpointNumber: checkpoint.checkpoint.number,
343
320
  startBlock: checkpoint.checkpoint.blocks[0].number,
344
- numBlocks: checkpoint.checkpoint.blocks.length,
321
+ blockCount: checkpoint.checkpoint.blocks.length,
345
322
  });
323
+
324
+ // Update slot-to-checkpoint index
325
+ await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
346
326
  }
347
327
 
348
328
  await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
@@ -425,7 +405,7 @@ export class BlockStore {
425
405
  if (!targetCheckpoint) {
426
406
  throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
427
407
  }
428
- lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.numBlocks - 1);
408
+ lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
429
409
  }
430
410
 
431
411
  // Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
@@ -433,6 +413,11 @@ export class BlockStore {
433
413
 
434
414
  // Remove all checkpoints after the target
435
415
  for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
416
+ const checkpointStorage = await this.#checkpoints.getAsync(c);
417
+ if (checkpointStorage) {
418
+ const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
419
+ await this.#slotToCheckpoint.delete(slotNumber);
420
+ }
436
421
  await this.#checkpoints.delete(c);
437
422
  this.#log.debug(`Removed checkpoint ${c}`);
438
423
  }
@@ -461,17 +446,32 @@ export class BlockStore {
461
446
  return checkpoints;
462
447
  }
463
448
 
464
- private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage) {
465
- const data: CheckpointData = {
449
+ /** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
450
+ async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
451
+ const result: CheckpointData[] = [];
452
+ for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
453
+ start: startSlot,
454
+ end: endSlot + 1,
455
+ })) {
456
+ const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
457
+ if (checkpointStorage) {
458
+ result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
459
+ }
460
+ }
461
+ return result;
462
+ }
463
+
464
+ private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
465
+ return {
466
466
  header: CheckpointHeader.fromBuffer(checkpointStorage.header),
467
467
  archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
468
+ checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
468
469
  checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
469
- startBlock: checkpointStorage.startBlock,
470
- numBlocks: checkpointStorage.numBlocks,
470
+ startBlock: BlockNumber(checkpointStorage.startBlock),
471
+ blockCount: checkpointStorage.blockCount,
471
472
  l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
472
- attestations: checkpointStorage.attestations,
473
+ attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
473
474
  };
474
- return data;
475
475
  }
476
476
 
477
477
  async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
@@ -483,7 +483,7 @@ export class BlockStore {
483
483
  const blocksForCheckpoint = await toArray(
484
484
  this.#blocks.entriesAsync({
485
485
  start: checkpoint.startBlock,
486
- end: checkpoint.startBlock + checkpoint.numBlocks,
486
+ end: checkpoint.startBlock + checkpoint.blockCount,
487
487
  }),
488
488
  );
489
489
 
@@ -556,7 +556,7 @@ export class BlockStore {
556
556
  if (!checkpointStorage) {
557
557
  throw new CheckpointNotFoundError(provenCheckpointNumber);
558
558
  } else {
559
- return BlockNumber(checkpointStorage.startBlock + checkpointStorage.numBlocks - 1);
559
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
560
560
  }
561
561
  }
562
562
 
@@ -655,6 +655,32 @@ export class BlockStore {
655
655
  }
656
656
  }
657
657
 
658
+ /**
659
+ * Gets block metadata (without tx data) by block number.
660
+ * @param blockNumber - The number of the block to return.
661
+ * @returns The requested block data.
662
+ */
663
+ async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
664
+ const blockStorage = await this.#blocks.getAsync(blockNumber);
665
+ if (!blockStorage || !blockStorage.header) {
666
+ return undefined;
667
+ }
668
+ return this.getBlockDataFromBlockStorage(blockStorage);
669
+ }
670
+
671
+ /**
672
+ * Gets block metadata (without tx data) by archive root.
673
+ * @param archive - The archive root of the block to return.
674
+ * @returns The requested block data.
675
+ */
676
+ async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
677
+ const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
678
+ if (blockNumber === undefined) {
679
+ return undefined;
680
+ }
681
+ return this.getBlockData(BlockNumber(blockNumber));
682
+ }
683
+
658
684
  /**
659
685
  * Gets an L2 block.
660
686
  * @param blockNumber - The number of the block to return.
@@ -759,15 +785,24 @@ export class BlockStore {
759
785
  }
760
786
  }
761
787
 
788
+ private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
789
+ return {
790
+ header: BlockHeader.fromBuffer(blockStorage.header),
791
+ archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
792
+ blockHash: Fr.fromBuffer(blockStorage.blockHash),
793
+ checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
794
+ indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
795
+ };
796
+ }
797
+
762
798
  private async getBlockFromBlockStorage(
763
799
  blockNumber: number,
764
800
  blockStorage: BlockStorage,
765
801
  ): 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);
802
+ const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
803
+ this.getBlockDataFromBlockStorage(blockStorage);
804
+ header.setHash(blockHash);
805
+ const blockHashString = bufferToHex(blockStorage.blockHash);
771
806
  const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
772
807
  if (blockTxsBuffer === undefined) {
773
808
  this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
@@ -786,13 +821,7 @@ export class BlockStore {
786
821
  txEffects.push(deserializeIndexedTxEffect(txEffect).data);
787
822
  }
788
823
  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
- );
824
+ const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
796
825
 
797
826
  if (block.number !== blockNumber) {
798
827
  throw new Error(
@@ -892,7 +921,7 @@ export class BlockStore {
892
921
  if (!checkpoint) {
893
922
  return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
894
923
  }
895
- return BlockNumber(checkpoint.startBlock + checkpoint.numBlocks - 1);
924
+ return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
896
925
  }
897
926
 
898
927
  async getLatestL2BlockNumber(): Promise<BlockNumber> {
@@ -927,6 +956,20 @@ export class BlockStore {
927
956
  return result;
928
957
  }
929
958
 
959
+ async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
960
+ const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
961
+ this.getLatestCheckpointNumber(),
962
+ this.#lastFinalizedCheckpoint.getAsync(),
963
+ ]);
964
+ return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
965
+ ? latestCheckpointNumber
966
+ : CheckpointNumber(finalizedCheckpointNumber ?? 0);
967
+ }
968
+
969
+ setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
970
+ return this.#lastFinalizedCheckpoint.set(checkpointNumber);
971
+ }
972
+
930
973
  #computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
931
974
  if (limit < 1) {
932
975
  throw new Error(`Invalid limit: ${limit}`);