@aztec/archiver 3.0.0-nightly.20251214 → 3.0.0-nightly.20251217

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 (48) hide show
  1. package/dest/archiver/archiver.d.ts +60 -36
  2. package/dest/archiver/archiver.d.ts.map +1 -1
  3. package/dest/archiver/archiver.js +366 -180
  4. package/dest/archiver/archiver_store.d.ts +79 -23
  5. package/dest/archiver/archiver_store.d.ts.map +1 -1
  6. package/dest/archiver/archiver_store_test_suite.d.ts +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  8. package/dest/archiver/archiver_store_test_suite.js +1666 -244
  9. package/dest/archiver/errors.d.ts +25 -1
  10. package/dest/archiver/errors.d.ts.map +1 -1
  11. package/dest/archiver/errors.js +37 -0
  12. package/dest/archiver/index.d.ts +2 -2
  13. package/dest/archiver/index.d.ts.map +1 -1
  14. package/dest/archiver/kv_archiver_store/block_store.d.ts +49 -17
  15. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  16. package/dest/archiver/kv_archiver_store/block_store.js +320 -83
  17. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +30 -28
  18. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  19. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +51 -27
  20. package/dest/archiver/kv_archiver_store/log_store.d.ts +5 -5
  21. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  22. package/dest/archiver/kv_archiver_store/log_store.js +39 -15
  23. package/dest/archiver/l1/bin/retrieve-calldata.js +2 -2
  24. package/dest/archiver/l1/calldata_retriever.d.ts +17 -3
  25. package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -1
  26. package/dest/archiver/l1/calldata_retriever.js +75 -7
  27. package/dest/archiver/l1/data_retrieval.d.ts +11 -8
  28. package/dest/archiver/l1/data_retrieval.d.ts.map +1 -1
  29. package/dest/archiver/l1/data_retrieval.js +30 -17
  30. package/dest/archiver/structs/published.d.ts +1 -2
  31. package/dest/archiver/structs/published.d.ts.map +1 -1
  32. package/dest/test/mock_l2_block_source.d.ts +3 -2
  33. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  34. package/dest/test/mock_l2_block_source.js +8 -15
  35. package/package.json +13 -13
  36. package/src/archiver/archiver.ts +464 -222
  37. package/src/archiver/archiver_store.ts +88 -22
  38. package/src/archiver/archiver_store_test_suite.ts +1689 -226
  39. package/src/archiver/errors.ts +64 -0
  40. package/src/archiver/index.ts +1 -1
  41. package/src/archiver/kv_archiver_store/block_store.ts +435 -94
  42. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +63 -39
  43. package/src/archiver/kv_archiver_store/log_store.ts +62 -25
  44. package/src/archiver/l1/bin/retrieve-calldata.ts +2 -2
  45. package/src/archiver/l1/calldata_retriever.ts +116 -6
  46. package/src/archiver/l1/data_retrieval.ts +34 -13
  47. package/src/archiver/structs/published.ts +0 -1
  48. package/src/test/mock_l2_block_source.ts +9 -16
@@ -17,6 +17,7 @@ import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundati
17
17
  import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
18
18
  import { count } from '@aztec/foundation/string';
19
19
  import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
20
+ import { isDefined } from '@aztec/foundation/types';
20
21
  import type { CustomRange } from '@aztec/kv-store';
21
22
  import { RollupAbi } from '@aztec/l1-artifacts';
22
23
  import {
@@ -32,13 +33,17 @@ import type { FunctionSelector } from '@aztec/stdlib/abi';
32
33
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
33
34
  import {
34
35
  type ArchiverEmitter,
36
+ CheckpointedL2Block,
37
+ CommitteeAttestation,
35
38
  L2Block,
39
+ L2BlockNew,
40
+ type L2BlockSink,
36
41
  type L2BlockSource,
37
42
  L2BlockSourceEvents,
38
43
  type L2Tips,
39
44
  PublishedL2Block,
40
45
  } from '@aztec/stdlib/block';
41
- import type { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
46
+ import { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
42
47
  import {
43
48
  type ContractClassPublic,
44
49
  type ContractDataSource,
@@ -78,8 +83,9 @@ import { type GetContractReturnType, type Hex, createPublicClient, fallback, htt
78
83
 
79
84
  import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
80
85
  import type { ArchiverConfig } from './config.js';
81
- import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
86
+ import { InitialCheckpointNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
82
87
  import { ArchiverInstrumentation } from './instrumentation.js';
88
+ import type { CheckpointData } from './kv_archiver_store/block_store.js';
83
89
  import {
84
90
  retrieveCheckpointsFromRollup,
85
91
  retrieveL1ToL2Message,
@@ -95,6 +101,13 @@ import { type ValidateBlockResult, validateCheckpointAttestations } from './vali
95
101
  */
96
102
  export type ArchiveSource = L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource;
97
103
 
104
+ /** Request to add a block to the archiver, queued for processing by the sync loop. */
105
+ type AddBlockRequest = {
106
+ block: L2BlockNew;
107
+ resolve: () => void;
108
+ reject: (err: Error) => void;
109
+ };
110
+
98
111
  export type ArchiverDeps = {
99
112
  telemetry?: TelemetryClient;
100
113
  blobSinkClient: BlobSinkClientInterface;
@@ -127,7 +140,10 @@ type RollupStatus = {
127
140
  * Responsible for handling robust L1 polling so that other components do not need to
128
141
  * concern themselves with it.
129
142
  */
130
- export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implements ArchiveSource, Traceable {
143
+ export class Archiver
144
+ extends (EventEmitter as new () => ArchiverEmitter)
145
+ implements ArchiveSource, L2BlockSink, Traceable
146
+ {
131
147
  /** A loop in which we will be continually fetching new checkpoints. */
132
148
  private runningPromise: RunningPromise;
133
149
 
@@ -141,6 +157,9 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
141
157
  private initialSyncComplete: boolean = false;
142
158
  private initialSyncPromise: PromiseWithResolvers<void>;
143
159
 
160
+ /** Queue of blocks to be added to the store, processed by the sync loop. */
161
+ private blockQueue: AddBlockRequest[] = [];
162
+
144
163
  public readonly tracer: Tracer;
145
164
 
146
165
  /**
@@ -301,10 +320,10 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
301
320
  // Log initial state for the archiver
302
321
  const { l1StartBlock } = this.l1constants;
303
322
  const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
304
- const currentL2Block = await this.getBlockNumber();
323
+ const currentL2Checkpoint = await this.getSynchedCheckpointNumber();
305
324
  this.log.info(
306
- `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo} and L2 block ${currentL2Block}`,
307
- { blocksSynchedTo, messagesSynchedTo, currentL2Block },
325
+ `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo} and L2 checkpoint ${currentL2Checkpoint}`,
326
+ { blocksSynchedTo, messagesSynchedTo, currentL2Checkpoint },
308
327
  );
309
328
 
310
329
  // Start sync loop, and return the wait for initial sync if we are asked to block until synced
@@ -318,6 +337,51 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
318
337
  return this.runningPromise.trigger();
319
338
  }
320
339
 
340
+ /**
341
+ * Queues a block to be added to the archiver store and triggers processing.
342
+ * The block will be processed by the sync loop.
343
+ * Implements the L2BlockSink interface.
344
+ * @param block - The L2 block to add.
345
+ * @returns A promise that resolves when the block has been added to the store, or rejects on error.
346
+ */
347
+ public addBlock(block: L2BlockNew): Promise<void> {
348
+ return new Promise<void>((resolve, reject) => {
349
+ this.blockQueue.push({ block, resolve, reject });
350
+ this.log.debug(`Queued block ${block.number} for processing`);
351
+ // Trigger an immediate sync, but don't wait for it - the promise resolves when the block is processed
352
+ this.syncImmediate().catch(err => {
353
+ this.log.error(`Sync immediate call failed: ${err}`);
354
+ });
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Processes all queued blocks, adding them to the store.
360
+ * Called at the beginning of each sync iteration.
361
+ * Blocks are processed in the order they were queued.
362
+ */
363
+ private async processQueuedBlocks(): Promise<void> {
364
+ if (this.blockQueue.length === 0) {
365
+ return;
366
+ }
367
+
368
+ // Take all blocks from the queue
369
+ const queuedItems = this.blockQueue.splice(0, this.blockQueue.length);
370
+ this.log.debug(`Processing ${queuedItems.length} queued block(s)`);
371
+
372
+ // Process each block individually to properly resolve/reject each promise
373
+ for (const { block, resolve, reject } of queuedItems) {
374
+ try {
375
+ await this.store.addBlocks([block]);
376
+ this.log.debug(`Added block ${block.number} to store`);
377
+ resolve();
378
+ } catch (err: any) {
379
+ this.log.error(`Failed to add block ${block.number} to store: ${err.message}`);
380
+ reject(err);
381
+ }
382
+ }
383
+ }
384
+
321
385
  public waitForInitialSync() {
322
386
  return this.initialSyncPromise.promise;
323
387
  }
@@ -337,11 +401,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
337
401
  }
338
402
  }
339
403
 
340
- /**
341
- * Fetches logs from L1 contracts and processes them.
342
- */
343
- @trackSpan('Archiver.sync')
344
- private async sync() {
404
+ private async syncFromL1() {
345
405
  /**
346
406
  * We keep track of three "pointers" to L1 blocks:
347
407
  * 1. the last L1 block that published an L2 block
@@ -427,7 +487,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
427
487
  // past it, since otherwise we'll keep downloading it and reprocessing it on every iteration until
428
488
  // we get a valid checkpoint to advance the syncpoint.
429
489
  if (!rollupStatus.validationResult?.valid && rollupStatus.lastL1BlockWithCheckpoint !== undefined) {
430
- await this.store.setBlockSynchedL1BlockNumber(rollupStatus.lastL1BlockWithCheckpoint);
490
+ await this.store.setCheckpointSynchedL1BlockNumber(rollupStatus.lastL1BlockWithCheckpoint);
431
491
  }
432
492
 
433
493
  // And lastly we check if we are missing any checkpoints behind us due to a possible L1 reorg.
@@ -461,6 +521,17 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
461
521
  }
462
522
  }
463
523
 
524
+ /**
525
+ * Fetches logs from L1 contracts and processes them.
526
+ */
527
+ @trackSpan('Archiver.sync')
528
+ private async sync() {
529
+ // Process any queued blocks first, before doing L1 sync
530
+ await this.processQueuedBlocks();
531
+ // Now perform L1 sync
532
+ await this.syncFromL1();
533
+ }
534
+
464
535
  /** Queries the rollup contract on whether a prune can be executed on the immediate next L1 block. */
465
536
  private async canPrune(currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
466
537
  const time = (currentL1Timestamp ?? 0n) + BigInt(this.l1constants.ethereumSlotDuration);
@@ -499,13 +570,26 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
499
570
 
500
571
  const checkpointsToUnwind = localPendingCheckpointNumber - provenCheckpointNumber;
501
572
 
502
- const checkpoints = await this.getCheckpoints(pruneFrom, checkpointsToUnwind);
573
+ const checkpointPromises = Array.from({ length: checkpointsToUnwind })
574
+ .fill(0)
575
+ .map((_, i) => this.store.getCheckpointData(CheckpointNumber(i + pruneFrom)));
576
+ const checkpoints = await Promise.all(checkpointPromises);
577
+
578
+ const blockPromises = await Promise.all(
579
+ checkpoints
580
+ .filter(isDefined)
581
+ .map(cp => this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber))),
582
+ );
583
+ const newBlocks = blockPromises.filter(isDefined).flat();
584
+
585
+ // TODO(pw/mbps): Don't convert to legacy blocks here
586
+ const blocks: L2Block[] = (await Promise.all(newBlocks.map(x => this.getBlock(x.number)))).filter(isDefined);
503
587
 
504
588
  // Emit an event for listening services to react to the chain prune
505
589
  this.emit(L2BlockSourceEvents.L2PruneDetected, {
506
590
  type: L2BlockSourceEvents.L2PruneDetected,
507
591
  epochNumber: pruneFromEpochNumber,
508
- blocks: checkpoints.flatMap(c => L2Block.fromCheckpoint(c)),
592
+ blocks,
509
593
  });
510
594
 
511
595
  this.log.debug(
@@ -733,17 +817,18 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
733
817
  }
734
818
  }
735
819
 
736
- const localCheckpointForDestinationProvenCheckpointNumber = await this.getCheckpoint(provenCheckpointNumber);
820
+ const localCheckpointForDestinationProvenCheckpointNumber =
821
+ await this.store.getCheckpointData(provenCheckpointNumber);
737
822
 
738
823
  // Sanity check. I've hit what seems to be a state where the proven checkpoint is set to a value greater than the latest
739
824
  // synched checkpoint when requesting L2Tips from the archiver. This is the only place where the proven checkpoint is set.
740
825
  const synched = await this.getSynchedCheckpointNumber();
741
826
  if (
742
827
  localCheckpointForDestinationProvenCheckpointNumber &&
743
- synched < localCheckpointForDestinationProvenCheckpointNumber.number
828
+ synched < localCheckpointForDestinationProvenCheckpointNumber.checkpointNumber
744
829
  ) {
745
830
  this.log.error(
746
- `Hit local checkpoint greater than last synched checkpoint: ${localCheckpointForDestinationProvenCheckpointNumber.number} > ${synched}`,
831
+ `Hit local checkpoint greater than last synched checkpoint: ${localCheckpointForDestinationProvenCheckpointNumber.checkpointNumber} > ${synched}`,
747
832
  );
748
833
  }
749
834
 
@@ -753,7 +838,6 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
753
838
  }`,
754
839
  );
755
840
 
756
- const lastProvenBlockNumber = await this.getLastBlockNumberInCheckpoint(provenCheckpointNumber);
757
841
  if (
758
842
  localCheckpointForDestinationProvenCheckpointNumber &&
759
843
  provenArchive === localCheckpointForDestinationProvenCheckpointNumber.archive.root.toString()
@@ -766,25 +850,29 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
766
850
  });
767
851
  const provenSlotNumber = localCheckpointForDestinationProvenCheckpointNumber.header.slotNumber;
768
852
  const provenEpochNumber: EpochNumber = getEpochAtSlot(provenSlotNumber, this.l1constants);
853
+ const lastBlockNumberInCheckpoint =
854
+ localCheckpointForDestinationProvenCheckpointNumber.startBlock +
855
+ localCheckpointForDestinationProvenCheckpointNumber.numBlocks -
856
+ 1;
769
857
 
770
858
  this.emit(L2BlockSourceEvents.L2BlockProven, {
771
859
  type: L2BlockSourceEvents.L2BlockProven,
772
- blockNumber: lastProvenBlockNumber,
860
+ blockNumber: BlockNumber(lastBlockNumberInCheckpoint),
773
861
  slotNumber: provenSlotNumber,
774
862
  epochNumber: provenEpochNumber,
775
863
  });
864
+ this.instrumentation.updateLastProvenBlock(lastBlockNumberInCheckpoint);
776
865
  } else {
777
866
  this.log.trace(`Proven checkpoint ${provenCheckpointNumber} already stored.`);
778
867
  }
779
868
  }
780
- this.instrumentation.updateLastProvenBlock(lastProvenBlockNumber);
781
869
  };
782
870
 
783
871
  // This is an edge case that we only hit if there are no proposed checkpoints.
784
872
  // If we have 0 checkpoints locally and there are no checkpoints onchain there is nothing to do.
785
873
  const noCheckpoints = localPendingCheckpointNumber === 0 && pendingCheckpointNumber === 0;
786
874
  if (noCheckpoints) {
787
- await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
875
+ await this.store.setCheckpointSynchedL1BlockNumber(currentL1BlockNumber);
788
876
  this.log.debug(
789
877
  `No checkpoints to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no checkpoints on chain`,
790
878
  );
@@ -796,7 +884,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
796
884
  // Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
797
885
  // are any state that could be impacted by it. If we have no checkpoints, there is no impact.
798
886
  if (localPendingCheckpointNumber > 0) {
799
- const localPendingCheckpoint = await this.getCheckpoint(localPendingCheckpointNumber);
887
+ const localPendingCheckpoint = await this.store.getCheckpointData(localPendingCheckpointNumber);
800
888
  if (localPendingCheckpoint === undefined) {
801
889
  throw new Error(`Missing checkpoint ${localPendingCheckpointNumber}`);
802
890
  }
@@ -829,14 +917,14 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
829
917
 
830
918
  let tipAfterUnwind = localPendingCheckpointNumber;
831
919
  while (true) {
832
- const candidateCheckpoint = await this.getCheckpoint(tipAfterUnwind);
920
+ const candidateCheckpoint = await this.store.getCheckpointData(tipAfterUnwind);
833
921
  if (candidateCheckpoint === undefined) {
834
922
  break;
835
923
  }
836
924
 
837
- const archiveAtContract = await this.rollup.archiveAt(candidateCheckpoint.number);
925
+ const archiveAtContract = await this.rollup.archiveAt(candidateCheckpoint.checkpointNumber);
838
926
  this.log.trace(
839
- `Checking local checkpoint ${candidateCheckpoint.number} with archive ${candidateCheckpoint.archive.root}`,
927
+ `Checking local checkpoint ${candidateCheckpoint.checkpointNumber} with archive ${candidateCheckpoint.archive.root}`,
840
928
  {
841
929
  archiveAtContract,
842
930
  archiveLocal: candidateCheckpoint.archive.root.toString(),
@@ -882,6 +970,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
882
970
  this.l1Addresses,
883
971
  this.instrumentation,
884
972
  this.log,
973
+ !this.initialSyncComplete, // isHistoricalSync
885
974
  );
886
975
 
887
976
  if (retrievedCheckpoints.length === 0) {
@@ -981,19 +1070,18 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
981
1070
  validCheckpoints.flatMap(c => c.checkpoint.blocks),
982
1071
  );
983
1072
  } catch (err) {
984
- if (err instanceof InitialBlockNumberNotSequentialError) {
985
- const { previousBlockNumber, newBlockNumber } = err;
986
- const previousBlock = previousBlockNumber
987
- ? await this.store.getPublishedBlock(BlockNumber(previousBlockNumber))
1073
+ if (err instanceof InitialCheckpointNumberNotSequentialError) {
1074
+ const { previousCheckpointNumber, newCheckpointNumber } = err;
1075
+ const previousCheckpoint = previousCheckpointNumber
1076
+ ? await this.store.getCheckpointData(CheckpointNumber(previousCheckpointNumber))
988
1077
  : undefined;
989
- const updatedL1SyncPoint = previousBlock?.l1.blockNumber ?? this.l1constants.l1StartBlock;
1078
+ const updatedL1SyncPoint = previousCheckpoint?.l1.blockNumber ?? this.l1constants.l1StartBlock;
990
1079
  await this.store.setBlockSynchedL1BlockNumber(updatedL1SyncPoint);
991
1080
  this.log.warn(
992
- `Attempting to insert block ${newBlockNumber} with previous block ${previousBlockNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
1081
+ `Attempting to insert checkpoint ${newCheckpointNumber} with previous block ${previousCheckpointNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
993
1082
  {
994
- previousBlockNumber,
995
- previousBlockHash: await previousBlock?.block.hash(),
996
- newBlockNumber,
1083
+ previousCheckpointNumber,
1084
+ newCheckpointNumber,
997
1085
  updatedL1SyncPoint,
998
1086
  },
999
1087
  );
@@ -1038,13 +1126,16 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1038
1126
  // We suspect an L1 reorg that added checkpoints *behind* us. If that is the case, it must have happened between
1039
1127
  // the last checkpoint we saw and the current one, so we reset the last synched L1 block number. In the edge case
1040
1128
  // we don't have one, we go back 2 L1 epochs, which is the deepest possible reorg (assuming Casper is working).
1041
- const latestLocalCheckpoint =
1042
- lastRetrievedCheckpoint ??
1043
- (latestLocalCheckpointNumber > 0
1044
- ? await this.getPublishedCheckpoints(latestLocalCheckpointNumber, 1).then(([c]) => c)
1045
- : undefined);
1046
- const targetL1BlockNumber = latestLocalCheckpoint?.l1.blockNumber ?? maxBigint(currentL1BlockNumber - 64n, 0n);
1047
- const latestLocalCheckpointArchive = latestLocalCheckpoint?.checkpoint.archive.root.toString();
1129
+ let latestLocalCheckpointArchive: string | undefined = undefined;
1130
+ let targetL1BlockNumber = maxBigint(currentL1BlockNumber - 64n, 0n);
1131
+ if (lastRetrievedCheckpoint) {
1132
+ latestLocalCheckpointArchive = lastRetrievedCheckpoint.checkpoint.archive.root.toString();
1133
+ targetL1BlockNumber = lastRetrievedCheckpoint.l1.blockNumber;
1134
+ } else if (latestLocalCheckpointNumber > 0) {
1135
+ const checkpoint = await this.store.getRangeOfCheckpoints(latestLocalCheckpointNumber, 1).then(([c]) => c);
1136
+ latestLocalCheckpointArchive = checkpoint.archive.root.toString();
1137
+ targetL1BlockNumber = checkpoint.l1.blockNumber;
1138
+ }
1048
1139
  this.log.warn(
1049
1140
  `Failed to reach checkpoint ${pendingCheckpointNumber} at ${currentL1BlockNumber} (latest is ${latestLocalCheckpointNumber}). ` +
1050
1141
  `Rolling back last synched L1 block number to ${targetL1BlockNumber}.`,
@@ -1130,15 +1221,22 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1130
1221
  const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1131
1222
  const blocks: L2Block[] = [];
1132
1223
 
1133
- // Walk the list of blocks backwards and filter by slots matching the requested epoch.
1134
- // We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
1135
- let block = await this.getBlock(await this.store.getSynchedL2BlockNumber());
1136
- const slot = (b: L2Block) => b.header.globalVariables.slotNumber;
1137
- while (block && slot(block) >= start) {
1138
- if (slot(block) <= end) {
1139
- blocks.push(block);
1224
+ // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
1225
+ // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
1226
+ let checkpoint = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
1227
+ const slot = (b: CheckpointData) => b.header.slotNumber;
1228
+ while (checkpoint && slot(checkpoint) >= start) {
1229
+ if (slot(checkpoint) <= end) {
1230
+ // push the blocks on backwards
1231
+ const endBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
1232
+ for (let i = endBlock; i >= checkpoint.startBlock; i--) {
1233
+ const block = await this.getBlock(BlockNumber(i));
1234
+ if (block) {
1235
+ blocks.push(block);
1236
+ }
1237
+ }
1140
1238
  }
1141
- block = await this.getBlock(BlockNumber(block.number - 1));
1239
+ checkpoint = await this.store.getCheckpointData(CheckpointNumber(checkpoint.checkpointNumber - 1));
1142
1240
  }
1143
1241
 
1144
1242
  return blocks.reverse();
@@ -1148,17 +1246,22 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1148
1246
  const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1149
1247
  const blocks: BlockHeader[] = [];
1150
1248
 
1151
- // Walk the list of blocks backwards and filter by slots matching the requested epoch.
1152
- // We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
1153
- let number = await this.store.getSynchedL2BlockNumber();
1154
- let header = await this.getBlockHeader(number);
1155
- const slot = (b: BlockHeader) => b.globalVariables.slotNumber;
1156
- while (header && slot(header) >= start) {
1157
- if (slot(header) <= end) {
1158
- blocks.push(header);
1249
+ // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
1250
+ // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
1251
+ let checkpoint = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
1252
+ const slot = (b: CheckpointData) => b.header.slotNumber;
1253
+ while (checkpoint && slot(checkpoint) >= start) {
1254
+ if (slot(checkpoint) <= end) {
1255
+ // push the blocks on backwards
1256
+ const endBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
1257
+ for (let i = endBlock; i >= checkpoint.startBlock; i--) {
1258
+ const block = await this.getBlockHeader(BlockNumber(i));
1259
+ if (block) {
1260
+ blocks.push(block);
1261
+ }
1262
+ }
1159
1263
  }
1160
- number = BlockNumber(number - 1);
1161
- header = await this.getBlockHeader(number);
1264
+ checkpoint = await this.store.getCheckpointData(CheckpointNumber(checkpoint.checkpointNumber - 1));
1162
1265
  }
1163
1266
  return blocks.reverse();
1164
1267
  }
@@ -1196,37 +1299,6 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1196
1299
  return this.initialSyncComplete;
1197
1300
  }
1198
1301
 
1199
- public async getPublishedCheckpoints(
1200
- from: CheckpointNumber,
1201
- limit: number,
1202
- proven?: boolean,
1203
- ): Promise<PublishedCheckpoint[]> {
1204
- // TODO: Implement this properly. This only works when we have one block per checkpoint.
1205
- const blocks = await this.getPublishedBlocks(BlockNumber(from), limit, proven);
1206
- return blocks.map(b => b.toPublishedCheckpoint());
1207
- }
1208
-
1209
- public async getCheckpointByArchive(archive: Fr): Promise<Checkpoint | undefined> {
1210
- // TODO: Implement this properly. This only works when we have one block per checkpoint.
1211
- return (await this.getPublishedBlockByArchive(archive))?.block.toCheckpoint();
1212
- }
1213
-
1214
- public async getCheckpoints(from: CheckpointNumber, limit: number, proven?: boolean): Promise<Checkpoint[]> {
1215
- const published = await this.getPublishedCheckpoints(from, limit, proven);
1216
- return published.map(p => p.checkpoint);
1217
- }
1218
-
1219
- public async getCheckpoint(number: CheckpointNumber): Promise<Checkpoint | undefined> {
1220
- if (number < 0) {
1221
- number = await this.getSynchedCheckpointNumber();
1222
- }
1223
- if (number === 0) {
1224
- return undefined;
1225
- }
1226
- const published = await this.getPublishedCheckpoints(number, 1);
1227
- return published[0]?.checkpoint;
1228
- }
1229
-
1230
1302
  public async getCheckpointHeader(number: CheckpointNumber | 'latest'): Promise<CheckpointHeader | undefined> {
1231
1303
  if (number === 'latest') {
1232
1304
  number = await this.getSynchedCheckpointNumber();
@@ -1234,88 +1306,46 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1234
1306
  if (number === 0) {
1235
1307
  return undefined;
1236
1308
  }
1237
- const checkpoint = await this.getCheckpoint(number);
1238
- return checkpoint?.header;
1309
+ const checkpoint = await this.store.getCheckpointData(number);
1310
+ if (!checkpoint) {
1311
+ return undefined;
1312
+ }
1313
+ return checkpoint.header;
1239
1314
  }
1240
1315
 
1241
1316
  public getCheckpointNumber(): Promise<CheckpointNumber> {
1242
1317
  return this.getSynchedCheckpointNumber();
1243
1318
  }
1244
1319
 
1245
- public async getSynchedCheckpointNumber(): Promise<CheckpointNumber> {
1246
- // TODO: Create store and apis for checkpoints.
1247
- // Checkpoint number will no longer be the same as the block number once we support multiple blocks per checkpoint.
1248
- return CheckpointNumber(await this.store.getSynchedL2BlockNumber());
1320
+ public getSynchedCheckpointNumber(): Promise<CheckpointNumber> {
1321
+ return this.store.getSynchedCheckpointNumber();
1249
1322
  }
1250
1323
 
1251
- public async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
1252
- // TODO: Create store and apis for checkpoints.
1253
- // Proven checkpoint number will no longer be the same as the proven block number once we support multiple blocks per checkpoint.
1254
- return CheckpointNumber(await this.store.getProvenL2BlockNumber());
1324
+ public getProvenCheckpointNumber(): Promise<CheckpointNumber> {
1325
+ return this.store.getProvenCheckpointNumber();
1255
1326
  }
1256
1327
 
1257
1328
  public setProvenCheckpointNumber(checkpointNumber: CheckpointNumber): Promise<void> {
1258
- // TODO: Create store and apis for checkpoints.
1259
- // Proven checkpoint number will no longer be the same as the proven block number once we support multiple blocks per checkpoint.
1260
- return this.store.setProvenL2BlockNumber(BlockNumber.fromCheckpointNumber(checkpointNumber));
1329
+ return this.store.setProvenCheckpointNumber(checkpointNumber);
1261
1330
  }
1262
1331
 
1263
1332
  public unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
1264
- // TODO: Create store and apis for checkpoints.
1265
- // This only works when we have one block per checkpoint.
1266
- return this.store.unwindBlocks(BlockNumber.fromCheckpointNumber(from), checkpointsToUnwind);
1333
+ return this.store.unwindCheckpoints(from, checkpointsToUnwind);
1267
1334
  }
1268
1335
 
1269
- public getLastBlockNumberInCheckpoint(checkpointNumber: CheckpointNumber): Promise<BlockNumber> {
1270
- // TODO: Create store and apis for checkpoints.
1271
- // Checkpoint number will no longer be the same as the block number once we support multiple blocks per checkpoint.
1272
- return Promise.resolve(BlockNumber.fromCheckpointNumber(checkpointNumber));
1336
+ public async getLastBlockNumberInCheckpoint(checkpointNumber: CheckpointNumber): Promise<BlockNumber | undefined> {
1337
+ const checkpointData = await this.store.getCheckpointData(checkpointNumber);
1338
+ if (!checkpointData) {
1339
+ return undefined;
1340
+ }
1341
+ return BlockNumber(checkpointData.startBlock + checkpointData.numBlocks - 1);
1273
1342
  }
1274
1343
 
1275
1344
  public addCheckpoints(
1276
1345
  checkpoints: PublishedCheckpoint[],
1277
1346
  pendingChainValidationStatus?: ValidateBlockResult,
1278
1347
  ): Promise<boolean> {
1279
- // TODO: Create store and apis for checkpoints.
1280
- // This only works when we have one block per checkpoint.
1281
- return this.store.addBlocks(
1282
- checkpoints.map(p => PublishedL2Block.fromPublishedCheckpoint(p)),
1283
- pendingChainValidationStatus,
1284
- );
1285
- }
1286
-
1287
- public async getCheckpointsForEpoch(epochNumber: EpochNumber): Promise<Checkpoint[]> {
1288
- // TODO: Create store and apis for checkpoints.
1289
- // This only works when we have one block per checkpoint.
1290
- const blocks = await this.getBlocksForEpoch(epochNumber);
1291
- return blocks.map(b => b.toCheckpoint());
1292
- }
1293
-
1294
- /**
1295
- * Gets up to `limit` amount of L2 blocks starting from `from`.
1296
- * @param from - Number of the first block to return (inclusive).
1297
- * @param limit - The number of blocks to return.
1298
- * @param proven - If true, only return blocks that have been proven.
1299
- * @returns The requested L2 blocks.
1300
- */
1301
- public getBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<L2Block[]> {
1302
- return this.getPublishedBlocks(from, limit, proven).then(blocks => blocks.map(b => b.block));
1303
- }
1304
-
1305
- /** Equivalent to getBlocks but includes publish data. */
1306
- public async getPublishedBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
1307
- const limitWithProven = proven
1308
- ? Math.min(limit, Math.max((await this.store.getProvenL2BlockNumber()) - from + 1, 0))
1309
- : limit;
1310
- return limitWithProven === 0 ? [] : await this.store.getPublishedBlocks(from, limitWithProven);
1311
- }
1312
-
1313
- public getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1314
- return this.store.getPublishedBlockByHash(blockHash);
1315
- }
1316
-
1317
- public getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1318
- return this.store.getPublishedBlockByArchive(archive);
1348
+ return this.store.addCheckpoints(checkpoints, pendingChainValidationStatus);
1319
1349
  }
1320
1350
 
1321
1351
  public getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
@@ -1331,7 +1361,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1331
1361
  * @param number - The block number to return.
1332
1362
  * @returns The requested L2 block.
1333
1363
  */
1334
- public async getBlock(number: BlockNumber): Promise<L2Block | undefined> {
1364
+ public async getL2BlockNew(number: BlockNumber): Promise<L2BlockNew | undefined> {
1335
1365
  // If the number provided is -ve, then return the latest block.
1336
1366
  if (number < 0) {
1337
1367
  number = await this.store.getSynchedL2BlockNumber();
@@ -1339,8 +1369,8 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1339
1369
  if (number === 0) {
1340
1370
  return undefined;
1341
1371
  }
1342
- const publishedBlock = await this.store.getPublishedBlock(number);
1343
- return publishedBlock?.block;
1372
+ const publishedBlock = await this.store.store.getBlock(number);
1373
+ return publishedBlock;
1344
1374
  }
1345
1375
 
1346
1376
  public async getBlockHeader(number: BlockNumber | 'latest'): Promise<BlockHeader | undefined> {
@@ -1354,6 +1384,21 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1354
1384
  return headers.length === 0 ? undefined : headers[0];
1355
1385
  }
1356
1386
 
1387
+ getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
1388
+ return this.store.getCheckpointedBlock(number);
1389
+ }
1390
+
1391
+ getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
1392
+ return this.store.getCheckpointedBlockByHash(blockHash);
1393
+ }
1394
+
1395
+ getProvenBlockNumber(): Promise<BlockNumber> {
1396
+ return this.store.getProvenBlockNumber();
1397
+ }
1398
+ getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
1399
+ return this.store.getCheckpointedBlockByArchive(archive);
1400
+ }
1401
+
1357
1402
  public getTxEffect(txHash: TxHash) {
1358
1403
  return this.store.getTxEffect(txHash);
1359
1404
  }
@@ -1392,19 +1437,11 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1392
1437
 
1393
1438
  /**
1394
1439
  * Gets the number of the latest L2 block processed by the block source implementation.
1440
+ * This includes both checkpointed and uncheckpointed blocks.
1395
1441
  * @returns The number of the latest L2 block processed by the block source implementation.
1396
1442
  */
1397
1443
  public getBlockNumber(): Promise<BlockNumber> {
1398
- return this.store.getSynchedL2BlockNumber();
1399
- }
1400
-
1401
- public getProvenBlockNumber(): Promise<BlockNumber> {
1402
- return this.store.getProvenL2BlockNumber();
1403
- }
1404
-
1405
- /** Forcefully updates the last proven block number. Use for testing. */
1406
- public setProvenBlockNumber(blockNumber: BlockNumber): Promise<void> {
1407
- return this.store.setProvenL2BlockNumber(blockNumber);
1444
+ return this.store.getLatestBlockNumber();
1408
1445
  }
1409
1446
 
1410
1447
  public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
@@ -1514,24 +1551,24 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1514
1551
  }
1515
1552
 
1516
1553
  public async rollbackTo(targetL2BlockNumber: BlockNumber): Promise<void> {
1554
+ // TODO(pw/mbps): This still assumes 1 block per checkpoint
1517
1555
  const currentBlocks = await this.getL2Tips();
1518
1556
  const currentL2Block = currentBlocks.latest.number;
1519
1557
  const currentProvenBlock = currentBlocks.proven.number;
1520
- // const currentFinalizedBlock = currentBlocks.finalized.number;
1521
1558
 
1522
1559
  if (targetL2BlockNumber >= currentL2Block) {
1523
1560
  throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
1524
1561
  }
1525
1562
  const blocksToUnwind = currentL2Block - targetL2BlockNumber;
1526
- const targetL2Block = await this.store.getPublishedBlock(targetL2BlockNumber);
1563
+ const targetL2Block = await this.store.getCheckpointedBlock(targetL2BlockNumber);
1527
1564
  if (!targetL2Block) {
1528
1565
  throw new Error(`Target L2 block ${targetL2BlockNumber} not found`);
1529
1566
  }
1530
1567
  const targetL1BlockNumber = targetL2Block.l1.blockNumber;
1531
1568
  const targetCheckpointNumber = CheckpointNumber.fromBlockNumber(targetL2BlockNumber);
1532
1569
  const targetL1BlockHash = await this.getL1BlockHash(targetL1BlockNumber);
1533
- this.log.info(`Unwinding ${blocksToUnwind} blocks from L2 block ${currentL2Block}`);
1534
- await this.store.unwindBlocks(BlockNumber(currentL2Block), blocksToUnwind);
1570
+ this.log.info(`Unwinding ${blocksToUnwind} checkpoints from L2 block ${currentL2Block}`);
1571
+ await this.store.unwindCheckpoints(CheckpointNumber(currentL2Block), blocksToUnwind);
1535
1572
  this.log.info(`Unwinding L1 to L2 messages to checkpoint ${targetCheckpointNumber}`);
1536
1573
  await this.store.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
1537
1574
  this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
@@ -1539,7 +1576,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1539
1576
  await this.store.setMessageSynchedL1Block({ l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash });
1540
1577
  if (targetL2BlockNumber < currentProvenBlock) {
1541
1578
  this.log.info(`Clearing proven L2 block number`);
1542
- await this.store.setProvenL2BlockNumber(BlockNumber.ZERO);
1579
+ await this.store.setProvenCheckpointNumber(CheckpointNumber.ZERO);
1543
1580
  }
1544
1581
  // TODO(palla/reorg): Set the finalized block when we add support for it.
1545
1582
  // if (targetL2BlockNumber < currentFinalizedBlock) {
@@ -1547,6 +1584,150 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1547
1584
  // await this.store.setFinalizedL2BlockNumber(0);
1548
1585
  // }
1549
1586
  }
1587
+
1588
+ public async getPublishedCheckpoints(
1589
+ checkpointNumber: CheckpointNumber,
1590
+ limit: number,
1591
+ ): Promise<PublishedCheckpoint[]> {
1592
+ const checkpoints = await this.store.getRangeOfCheckpoints(checkpointNumber, limit);
1593
+ const blocks = (
1594
+ await Promise.all(checkpoints.map(ch => this.store.getBlocksForCheckpoint(ch.checkpointNumber)))
1595
+ ).filter(isDefined);
1596
+
1597
+ const fullCheckpoints: PublishedCheckpoint[] = [];
1598
+ for (let i = 0; i < checkpoints.length; i++) {
1599
+ const blocksForCheckpoint = blocks[i];
1600
+ const checkpoint = checkpoints[i];
1601
+ const fullCheckpoint = new Checkpoint(
1602
+ checkpoint.archive,
1603
+ checkpoint.header,
1604
+ blocksForCheckpoint,
1605
+ checkpoint.checkpointNumber,
1606
+ );
1607
+ const publishedCheckpoint = new PublishedCheckpoint(
1608
+ fullCheckpoint,
1609
+ checkpoint.l1,
1610
+ checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
1611
+ );
1612
+ fullCheckpoints.push(publishedCheckpoint);
1613
+ }
1614
+ return fullCheckpoints;
1615
+ }
1616
+
1617
+ public async getCheckpointsForEpoch(epochNumber: EpochNumber): Promise<Checkpoint[]> {
1618
+ const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1619
+ const checkpoints: Checkpoint[] = [];
1620
+
1621
+ // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
1622
+ // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
1623
+ let checkpointData = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
1624
+ const slot = (b: CheckpointData) => b.header.slotNumber;
1625
+ while (checkpointData && slot(checkpointData) >= start) {
1626
+ if (slot(checkpointData) <= end) {
1627
+ // push the checkpoints on backwards
1628
+ const [checkpoint] = await this.getPublishedCheckpoints(checkpointData.checkpointNumber, 1);
1629
+ checkpoints.push(checkpoint.checkpoint);
1630
+ }
1631
+ checkpointData = await this.store.getCheckpointData(CheckpointNumber(checkpointData.checkpointNumber - 1));
1632
+ }
1633
+
1634
+ return checkpoints.reverse();
1635
+ }
1636
+
1637
+ /* Legacy APIs */
1638
+
1639
+ public async getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1640
+ const checkpointedBlock = await this.store.getCheckpointedBlockByHash(blockHash);
1641
+ return this.buildOldBlockFromCheckpointedBlock(checkpointedBlock);
1642
+ }
1643
+ public async getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1644
+ const checkpointedBlock = await this.store.getCheckpointedBlockByArchive(archive);
1645
+ return this.buildOldBlockFromCheckpointedBlock(checkpointedBlock);
1646
+ }
1647
+
1648
+ /**
1649
+ * Gets up to `limit` amount of L2 blocks starting from `from`.
1650
+ * @param from - Number of the first block to return (inclusive).
1651
+ * @param limit - The number of blocks to return.
1652
+ * @param proven - If true, only return blocks that have been proven.
1653
+ * @returns The requested L2 blocks.
1654
+ */
1655
+ public async getBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<L2Block[]> {
1656
+ const publishedBlocks = await this.getPublishedBlocks(from, limit, proven);
1657
+ return publishedBlocks.map(x => x.block);
1658
+ }
1659
+
1660
+ public async getPublishedBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
1661
+ const checkpoints = await this.store.getRangeOfCheckpoints(CheckpointNumber(from), limit);
1662
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber();
1663
+ const blocks = (
1664
+ await Promise.all(checkpoints.map(ch => this.store.getBlocksForCheckpoint(ch.checkpointNumber)))
1665
+ ).filter(isDefined);
1666
+
1667
+ const olbBlocks: PublishedL2Block[] = [];
1668
+ for (let i = 0; i < checkpoints.length; i++) {
1669
+ const blockForCheckpoint = blocks[i][0];
1670
+ const checkpoint = checkpoints[i];
1671
+ if (checkpoint.checkpointNumber > provenCheckpointNumber && proven === true) {
1672
+ // this checkpointisn't proven and we only want proven
1673
+ continue;
1674
+ }
1675
+ const oldCheckpoint = new Checkpoint(
1676
+ blockForCheckpoint.archive,
1677
+ checkpoint.header,
1678
+ [blockForCheckpoint],
1679
+ checkpoint.checkpointNumber,
1680
+ );
1681
+ const oldBlock = L2Block.fromCheckpoint(oldCheckpoint);
1682
+ const publishedBlock = new PublishedL2Block(
1683
+ oldBlock,
1684
+ checkpoint.l1,
1685
+ checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
1686
+ );
1687
+ olbBlocks.push(publishedBlock);
1688
+ }
1689
+ return olbBlocks;
1690
+ }
1691
+
1692
+ private async buildOldBlockFromCheckpointedBlock(
1693
+ checkpointedBlock: CheckpointedL2Block | undefined,
1694
+ ): Promise<PublishedL2Block | undefined> {
1695
+ if (!checkpointedBlock) {
1696
+ return undefined;
1697
+ }
1698
+ const checkpoint = await this.store.getCheckpointData(checkpointedBlock.checkpointNumber);
1699
+ if (!checkpoint) {
1700
+ return checkpoint;
1701
+ }
1702
+ const fullCheckpoint = new Checkpoint(
1703
+ checkpointedBlock?.block.archive,
1704
+ checkpoint?.header,
1705
+ [checkpointedBlock.block],
1706
+ checkpoint.checkpointNumber,
1707
+ );
1708
+ const oldBlock = L2Block.fromCheckpoint(fullCheckpoint);
1709
+ const published = new PublishedL2Block(
1710
+ oldBlock,
1711
+ checkpoint.l1,
1712
+ checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
1713
+ );
1714
+ return published;
1715
+ }
1716
+
1717
+ public async getBlock(number: BlockNumber): Promise<L2Block | undefined> {
1718
+ // If the number provided is -ve, then return the latest block.
1719
+ if (number < 0) {
1720
+ number = await this.store.getSynchedL2BlockNumber();
1721
+ }
1722
+ if (number === 0) {
1723
+ return undefined;
1724
+ }
1725
+ const publishedBlocks = await this.getPublishedBlocks(number, 1);
1726
+ if (publishedBlocks.length === 0) {
1727
+ return undefined;
1728
+ }
1729
+ return publishedBlocks[0].block;
1730
+ }
1550
1731
  }
1551
1732
 
1552
1733
  enum Operation {
@@ -1577,11 +1758,13 @@ export class ArchiverStoreHelper
1577
1758
  | 'close'
1578
1759
  | 'transactionAsync'
1579
1760
  | 'addBlocks'
1761
+ | 'getBlock'
1762
+ | 'getBlocks'
1580
1763
  >
1581
1764
  {
1582
1765
  #log = createLogger('archiver:block-helper');
1583
1766
 
1584
- constructor(protected readonly store: ArchiverDataStore) {}
1767
+ constructor(public readonly store: ArchiverDataStore) {}
1585
1768
 
1586
1769
  /**
1587
1770
  * Extracts and stores contract classes out of ContractClassPublished events emitted by the class registry contract.
@@ -1719,7 +1902,23 @@ export class ArchiverStoreHelper
1719
1902
  return true;
1720
1903
  }
1721
1904
 
1722
- public addBlocks(blocks: PublishedL2Block[], pendingChainValidationStatus?: ValidateBlockResult): Promise<boolean> {
1905
+ private async addBlockDataToDB(block: L2BlockNew) {
1906
+ const contractClassLogs = block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1907
+ // ContractInstancePublished event logs are broadcast in privateLogs.
1908
+ const privateLogs = block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1909
+ const publicLogs = block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1910
+
1911
+ return (
1912
+ await Promise.all([
1913
+ this.#updatePublishedContractClasses(contractClassLogs, block.number, Operation.Store),
1914
+ this.#updateDeployedContractInstances(privateLogs, block.number, Operation.Store),
1915
+ this.#updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, Operation.Store),
1916
+ this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.number),
1917
+ ])
1918
+ ).every(Boolean);
1919
+ }
1920
+
1921
+ public addBlocks(blocks: L2BlockNew[], pendingChainValidationStatus?: ValidateBlockResult): Promise<boolean> {
1723
1922
  // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
1724
1923
  // or if the previous block is not in the store.
1725
1924
  return this.store.transactionAsync(async () => {
@@ -1729,25 +1928,10 @@ export class ArchiverStoreHelper
1729
1928
  // Update the pending chain validation status if provided
1730
1929
  pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
1731
1930
  // Add any logs emitted during the retrieved blocks
1732
- this.store.addLogs(blocks.map(block => block.block)),
1931
+ this.store.addLogs(blocks),
1733
1932
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1734
- ...blocks.map(async block => {
1735
- const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1736
- // ContractInstancePublished event logs are broadcast in privateLogs.
1737
- const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1738
- const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1739
- return (
1740
- await Promise.all([
1741
- this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Store),
1742
- this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
1743
- this.#updateUpdatedContractInstances(
1744
- publicLogs,
1745
- block.block.header.globalVariables.timestamp,
1746
- Operation.Store,
1747
- ),
1748
- this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.block.number),
1749
- ])
1750
- ).every(Boolean);
1933
+ ...blocks.map(block => {
1934
+ return this.addBlockDataToDB(block);
1751
1935
  }),
1752
1936
  ]);
1753
1937
 
@@ -1755,59 +1939,102 @@ export class ArchiverStoreHelper
1755
1939
  });
1756
1940
  }
1757
1941
 
1758
- public async unwindBlocks(from: BlockNumber, blocksToUnwind: number): Promise<boolean> {
1759
- const last = await this.getSynchedL2BlockNumber();
1760
- if (from != last) {
1761
- throw new Error(`Cannot unwind blocks from block ${from} when the last block is ${last}`);
1942
+ public addCheckpoints(
1943
+ checkpoints: PublishedCheckpoint[],
1944
+ pendingChainValidationStatus?: ValidateBlockResult,
1945
+ ): Promise<boolean> {
1946
+ // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
1947
+ // or if the previous block is not in the store.
1948
+ return this.store.transactionAsync(async () => {
1949
+ await this.store.addCheckpoints(checkpoints);
1950
+ const allBlocks = checkpoints.flatMap((ch: PublishedCheckpoint) => ch.checkpoint.blocks);
1951
+
1952
+ const opResults = await Promise.all([
1953
+ // Update the pending chain validation status if provided
1954
+ pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
1955
+ // Add any logs emitted during the retrieved blocks
1956
+ this.store.addLogs(allBlocks),
1957
+ // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1958
+ ...allBlocks.map(block => {
1959
+ return this.addBlockDataToDB(block);
1960
+ }),
1961
+ ]);
1962
+
1963
+ return opResults.every(Boolean);
1964
+ });
1965
+ }
1966
+
1967
+ public async unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
1968
+ if (checkpointsToUnwind <= 0) {
1969
+ throw new Error(`Cannot unwind ${checkpointsToUnwind} blocks`);
1762
1970
  }
1763
- if (blocksToUnwind <= 0) {
1764
- throw new Error(`Cannot unwind ${blocksToUnwind} blocks`);
1971
+
1972
+ const last = await this.getSynchedCheckpointNumber();
1973
+ if (from != last) {
1974
+ throw new Error(`Cannot unwind checkpoints from checkpoint ${from} when the last checkpoint is ${last}`);
1765
1975
  }
1766
1976
 
1767
- // from - blocksToUnwind = the new head, so + 1 for what we need to remove
1768
- const blocks = await this.getPublishedBlocks(BlockNumber(from - blocksToUnwind + 1), blocksToUnwind);
1977
+ const blocks = [];
1978
+ const lastCheckpointNumber = from + checkpointsToUnwind - 1;
1979
+ for (let checkpointNumber = from; checkpointNumber <= lastCheckpointNumber; checkpointNumber++) {
1980
+ const blocksForCheckpoint = await this.store.getBlocksForCheckpoint(checkpointNumber);
1981
+ if (!blocksForCheckpoint) {
1982
+ continue;
1983
+ }
1984
+ blocks.push(...blocksForCheckpoint);
1985
+ }
1769
1986
 
1770
1987
  const opResults = await Promise.all([
1771
1988
  // Prune rolls back to the last proven block, which is by definition valid
1772
1989
  this.store.setPendingChainValidationStatus({ valid: true }),
1773
1990
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1774
1991
  ...blocks.map(async block => {
1775
- const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1992
+ const contractClassLogs = block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1776
1993
  // ContractInstancePublished event logs are broadcast in privateLogs.
1777
- const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1778
- const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1994
+ const privateLogs = block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1995
+ const publicLogs = block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1779
1996
 
1780
1997
  return (
1781
1998
  await Promise.all([
1782
- this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Delete),
1783
- this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Delete),
1784
- this.#updateUpdatedContractInstances(
1785
- publicLogs,
1786
- block.block.header.globalVariables.timestamp,
1787
- Operation.Delete,
1788
- ),
1999
+ this.#updatePublishedContractClasses(contractClassLogs, block.number, Operation.Delete),
2000
+ this.#updateDeployedContractInstances(privateLogs, block.number, Operation.Delete),
2001
+ this.#updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, Operation.Delete),
1789
2002
  ])
1790
2003
  ).every(Boolean);
1791
2004
  }),
1792
2005
 
1793
- this.store.deleteLogs(blocks.map(b => b.block)),
1794
- this.store.unwindBlocks(from, blocksToUnwind),
2006
+ this.store.deleteLogs(blocks),
2007
+ this.store.unwindCheckpoints(from, checkpointsToUnwind),
1795
2008
  ]);
1796
2009
 
1797
2010
  return opResults.every(Boolean);
1798
2011
  }
1799
2012
 
1800
- getPublishedBlocks(from: BlockNumber, limit: number): Promise<PublishedL2Block[]> {
1801
- return this.store.getPublishedBlocks(from, limit);
2013
+ getCheckpointData(checkpointNumber: CheckpointNumber): Promise<CheckpointData | undefined> {
2014
+ return this.store.getCheckpointData(checkpointNumber);
1802
2015
  }
1803
- getPublishedBlock(number: BlockNumber): Promise<PublishedL2Block | undefined> {
1804
- return this.store.getPublishedBlock(number);
2016
+
2017
+ getRangeOfCheckpoints(from: CheckpointNumber, limit: number): Promise<CheckpointData[]> {
2018
+ return this.store.getRangeOfCheckpoints(from, limit);
2019
+ }
2020
+
2021
+ getCheckpointedL2BlockNumber(): Promise<BlockNumber> {
2022
+ return this.store.getCheckpointedL2BlockNumber();
2023
+ }
2024
+ getSynchedCheckpointNumber(): Promise<CheckpointNumber> {
2025
+ return this.store.getSynchedCheckpointNumber();
2026
+ }
2027
+ setCheckpointSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
2028
+ return this.store.setCheckpointSynchedL1BlockNumber(l1BlockNumber);
2029
+ }
2030
+ getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
2031
+ return this.store.getCheckpointedBlock(number);
1805
2032
  }
1806
- getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1807
- return this.store.getPublishedBlockByHash(blockHash);
2033
+ getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
2034
+ return this.store.getCheckpointedBlockByHash(blockHash);
1808
2035
  }
1809
- getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1810
- return this.store.getPublishedBlockByArchive(archive);
2036
+ getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
2037
+ return this.store.getCheckpointedBlockByArchive(archive);
1811
2038
  }
1812
2039
  getBlockHeaders(from: BlockNumber, limit: number): Promise<BlockHeader[]> {
1813
2040
  return this.store.getBlockHeaders(from, limit);
@@ -1818,6 +2045,18 @@ export class ArchiverStoreHelper
1818
2045
  getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1819
2046
  return this.store.getBlockHeaderByArchive(archive);
1820
2047
  }
2048
+ getBlockByHash(blockHash: Fr): Promise<L2BlockNew | undefined> {
2049
+ return this.store.getBlockByHash(blockHash);
2050
+ }
2051
+ getBlockByArchive(archive: Fr): Promise<L2BlockNew | undefined> {
2052
+ return this.store.getBlockByArchive(archive);
2053
+ }
2054
+ getLatestBlockNumber(): Promise<BlockNumber> {
2055
+ return this.store.getLatestBlockNumber();
2056
+ }
2057
+ getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2BlockNew[] | undefined> {
2058
+ return this.store.getBlocksForCheckpoint(checkpointNumber);
2059
+ }
1821
2060
  getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
1822
2061
  return this.store.getTxEffect(txHash);
1823
2062
  }
@@ -1843,16 +2082,19 @@ export class ArchiverStoreHelper
1843
2082
  return this.store.getContractClassLogs(filter);
1844
2083
  }
1845
2084
  getSynchedL2BlockNumber(): Promise<BlockNumber> {
1846
- return this.store.getSynchedL2BlockNumber();
2085
+ return this.store.getCheckpointedL2BlockNumber();
2086
+ }
2087
+ getProvenCheckpointNumber(): Promise<CheckpointNumber> {
2088
+ return this.store.getProvenCheckpointNumber();
1847
2089
  }
1848
- getProvenL2BlockNumber(): Promise<BlockNumber> {
1849
- return this.store.getProvenL2BlockNumber();
2090
+ getProvenBlockNumber(): Promise<BlockNumber> {
2091
+ return this.store.getProvenBlockNumber();
1850
2092
  }
1851
- setProvenL2BlockNumber(l2BlockNumber: BlockNumber): Promise<void> {
1852
- return this.store.setProvenL2BlockNumber(l2BlockNumber);
2093
+ setProvenCheckpointNumber(checkpointNumber: CheckpointNumber): Promise<void> {
2094
+ return this.store.setProvenCheckpointNumber(checkpointNumber);
1853
2095
  }
1854
2096
  setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
1855
- return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
2097
+ return this.store.setCheckpointSynchedL1BlockNumber(l1BlockNumber);
1856
2098
  }
1857
2099
  setMessageSynchedL1Block(l1Block: L1BlockId): Promise<void> {
1858
2100
  return this.store.setMessageSynchedL1Block(l1Block);