@aztec/archiver 0.0.1-commit.b655e406 → 0.0.1-commit.d3ec352c

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 (81) hide show
  1. package/dest/archiver/archiver.d.ts +53 -40
  2. package/dest/archiver/archiver.d.ts.map +1 -1
  3. package/dest/archiver/archiver.js +333 -221
  4. package/dest/archiver/archiver_store.d.ts +16 -15
  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 +85 -84
  9. package/dest/archiver/config.d.ts +1 -1
  10. package/dest/archiver/config.d.ts.map +1 -1
  11. package/dest/archiver/config.js +5 -0
  12. package/dest/archiver/data_retrieval.d.ts +18 -17
  13. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  14. package/dest/archiver/data_retrieval.js +111 -86
  15. package/dest/archiver/errors.d.ts +1 -1
  16. package/dest/archiver/errors.d.ts.map +1 -1
  17. package/dest/archiver/index.d.ts +1 -1
  18. package/dest/archiver/instrumentation.d.ts +3 -3
  19. package/dest/archiver/instrumentation.d.ts.map +1 -1
  20. package/dest/archiver/kv_archiver_store/block_store.d.ts +9 -8
  21. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  22. package/dest/archiver/kv_archiver_store/block_store.js +8 -7
  23. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +1 -1
  24. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  25. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +1 -1
  26. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  27. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +18 -17
  28. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  29. package/dest/archiver/kv_archiver_store/log_store.d.ts +1 -1
  30. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  31. package/dest/archiver/kv_archiver_store/log_store.js +3 -2
  32. package/dest/archiver/kv_archiver_store/message_store.d.ts +1 -1
  33. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  34. package/dest/archiver/structs/data_retrieval.d.ts +1 -1
  35. package/dest/archiver/structs/inbox_message.d.ts +3 -3
  36. package/dest/archiver/structs/inbox_message.d.ts.map +1 -1
  37. package/dest/archiver/structs/inbox_message.js +2 -1
  38. package/dest/archiver/structs/published.d.ts +3 -2
  39. package/dest/archiver/structs/published.d.ts.map +1 -1
  40. package/dest/archiver/validation.d.ts +10 -4
  41. package/dest/archiver/validation.d.ts.map +1 -1
  42. package/dest/archiver/validation.js +29 -21
  43. package/dest/factory.d.ts +1 -1
  44. package/dest/factory.d.ts.map +1 -1
  45. package/dest/factory.js +3 -2
  46. package/dest/index.d.ts +2 -2
  47. package/dest/index.d.ts.map +1 -1
  48. package/dest/index.js +1 -1
  49. package/dest/rpc/index.d.ts +2 -2
  50. package/dest/test/index.d.ts +1 -1
  51. package/dest/test/mock_archiver.d.ts +14 -5
  52. package/dest/test/mock_archiver.d.ts.map +1 -1
  53. package/dest/test/mock_archiver.js +19 -10
  54. package/dest/test/mock_l1_to_l2_message_source.d.ts +4 -2
  55. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  56. package/dest/test/mock_l1_to_l2_message_source.js +7 -2
  57. package/dest/test/mock_l2_block_source.d.ts +14 -9
  58. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  59. package/dest/test/mock_l2_block_source.js +20 -7
  60. package/dest/test/mock_structs.d.ts +1 -1
  61. package/dest/test/mock_structs.d.ts.map +1 -1
  62. package/dest/test/mock_structs.js +3 -2
  63. package/package.json +17 -17
  64. package/src/archiver/archiver.ts +448 -290
  65. package/src/archiver/archiver_store.ts +19 -14
  66. package/src/archiver/archiver_store_test_suite.ts +111 -79
  67. package/src/archiver/config.ts +5 -0
  68. package/src/archiver/data_retrieval.ts +157 -125
  69. package/src/archiver/instrumentation.ts +2 -2
  70. package/src/archiver/kv_archiver_store/block_store.ts +17 -16
  71. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +16 -15
  72. package/src/archiver/kv_archiver_store/log_store.ts +3 -2
  73. package/src/archiver/structs/inbox_message.ts +5 -4
  74. package/src/archiver/structs/published.ts +2 -1
  75. package/src/archiver/validation.ts +52 -27
  76. package/src/factory.ts +3 -2
  77. package/src/index.ts +1 -1
  78. package/src/test/mock_archiver.ts +22 -11
  79. package/src/test/mock_l1_to_l2_message_source.ts +9 -3
  80. package/src/test/mock_l2_block_source.ts +30 -13
  81. package/src/test/mock_structs.ts +3 -2
@@ -1,4 +1,5 @@
1
1
  import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
2
+ import { GENESIS_BLOCK_HEADER_HASH } from '@aztec/constants';
2
3
  import { EpochCache } from '@aztec/epoch-cache';
3
4
  import {
4
5
  BlockTagTooOldError,
@@ -9,6 +10,7 @@ import {
9
10
  createEthereumChain,
10
11
  } from '@aztec/ethereum';
11
12
  import { maxBigint } from '@aztec/foundation/bigint';
13
+ import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
12
14
  import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
13
15
  import { merge, pick } from '@aztec/foundation/collection';
14
16
  import type { EthAddress } from '@aztec/foundation/eth-address';
@@ -16,7 +18,6 @@ import { Fr } from '@aztec/foundation/fields';
16
18
  import { type Logger, createLogger } from '@aztec/foundation/log';
17
19
  import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
18
20
  import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
19
- import { sleep } from '@aztec/foundation/sleep';
20
21
  import { count } from '@aztec/foundation/string';
21
22
  import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
22
23
  import type { CustomRange } from '@aztec/kv-store';
@@ -34,12 +35,13 @@ import type { FunctionSelector } from '@aztec/stdlib/abi';
34
35
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
35
36
  import {
36
37
  type ArchiverEmitter,
37
- type L2Block,
38
- type L2BlockId,
38
+ L2Block,
39
39
  type L2BlockSource,
40
40
  L2BlockSourceEvents,
41
41
  type L2Tips,
42
+ PublishedL2Block,
42
43
  } from '@aztec/stdlib/block';
44
+ import type { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
43
45
  import {
44
46
  type ContractClassPublic,
45
47
  type ContractDataSource,
@@ -62,10 +64,10 @@ import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec
62
64
  import type { L2LogsSource } from '@aztec/stdlib/interfaces/server';
63
65
  import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs';
64
66
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
67
+ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
65
68
  import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
66
69
  import type { UInt64 } from '@aztec/stdlib/types';
67
70
  import {
68
- Attributes,
69
71
  type TelemetryClient,
70
72
  type Traceable,
71
73
  type Tracer,
@@ -75,21 +77,20 @@ import {
75
77
 
76
78
  import { EventEmitter } from 'events';
77
79
  import groupBy from 'lodash.groupby';
78
- import { type GetContractReturnType, createPublicClient, fallback, http } from 'viem';
80
+ import { type GetContractReturnType, type Hex, createPublicClient, fallback, http } from 'viem';
79
81
 
80
82
  import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
81
83
  import type { ArchiverConfig } from './config.js';
82
84
  import {
83
- retrieveBlocksFromRollup,
85
+ retrieveCheckpointsFromRollup,
84
86
  retrieveL1ToL2Message,
85
87
  retrieveL1ToL2Messages,
86
- retrievedBlockToPublishedL2Block,
88
+ retrievedToPublishedCheckpoint,
87
89
  } from './data_retrieval.js';
88
90
  import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
89
91
  import { ArchiverInstrumentation } from './instrumentation.js';
90
92
  import type { InboxMessage } from './structs/inbox_message.js';
91
- import type { PublishedL2Block } from './structs/published.js';
92
- import { type ValidateBlockResult, validateBlockAttestations } from './validation.js';
93
+ import { type ValidateBlockResult, validateCheckpointAttestations } from './validation.js';
93
94
 
94
95
  /**
95
96
  * Helper interface to combine all sources this archiver implementation provides.
@@ -108,17 +109,28 @@ function mapArchiverConfig(config: Partial<ArchiverConfig>) {
108
109
  pollingIntervalMs: config.archiverPollingIntervalMS,
109
110
  batchSize: config.archiverBatchSize,
110
111
  skipValidateBlockAttestations: config.skipValidateBlockAttestations,
112
+ maxAllowedEthClientDriftSeconds: config.maxAllowedEthClientDriftSeconds,
111
113
  };
112
114
  }
113
115
 
116
+ type RollupStatus = {
117
+ provenCheckpointNumber: CheckpointNumber;
118
+ provenArchive: Hex;
119
+ pendingCheckpointNumber: CheckpointNumber;
120
+ pendingArchive: Hex;
121
+ validationResult: ValidateBlockResult | undefined;
122
+ lastRetrievedCheckpoint?: PublishedCheckpoint;
123
+ lastL1BlockWithCheckpoint?: bigint;
124
+ };
125
+
114
126
  /**
115
- * Pulls L2 blocks in a non-blocking manner and provides interface for their retrieval.
127
+ * Pulls checkpoints in a non-blocking manner and provides interface for their retrieval.
116
128
  * Responsible for handling robust L1 polling so that other components do not need to
117
129
  * concern themselves with it.
118
130
  */
119
131
  export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implements ArchiveSource, Traceable {
120
- /** A loop in which we will be continually fetching new L2 blocks. */
121
- private runningPromise?: RunningPromise;
132
+ /** A loop in which we will be continually fetching new checkpoints. */
133
+ private runningPromise: RunningPromise;
122
134
 
123
135
  private rollup: RollupContract;
124
136
  private inbox: InboxContract;
@@ -146,9 +158,15 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
146
158
  private readonly publicClient: ViemPublicClient,
147
159
  private readonly l1Addresses: { rollupAddress: EthAddress; inboxAddress: EthAddress; registryAddress: EthAddress },
148
160
  readonly dataStore: ArchiverDataStore,
149
- private config: { pollingIntervalMs: number; batchSize: number; skipValidateBlockAttestations?: boolean },
161
+ private config: {
162
+ pollingIntervalMs: number;
163
+ batchSize: number;
164
+ skipValidateBlockAttestations?: boolean;
165
+ maxAllowedEthClientDriftSeconds: number;
166
+ },
150
167
  private readonly blobSinkClient: BlobSinkClientInterface,
151
168
  private readonly epochCache: EpochCache,
169
+ private readonly dateProvider: DateProvider,
152
170
  private readonly instrumentation: ArchiverInstrumentation,
153
171
  private readonly l1constants: L1RollupConstants & { l1StartBlockHash: Buffer32; genesisArchiveRoot: Fr },
154
172
  private readonly log: Logger = createLogger('archiver'),
@@ -161,6 +179,15 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
161
179
  this.rollup = new RollupContract(publicClient, l1Addresses.rollupAddress);
162
180
  this.inbox = new InboxContract(publicClient, l1Addresses.inboxAddress);
163
181
  this.initialSyncPromise = promiseWithResolvers();
182
+
183
+ // Running promise starts with a small interval inbetween runs, so all iterations needed for the initial sync
184
+ // are done as fast as possible. This then gets updated once the initial sync completes.
185
+ this.runningPromise = new RunningPromise(
186
+ () => this.sync(),
187
+ this.log,
188
+ this.config.pollingIntervalMs / 10,
189
+ makeLoggingErrorHandler(this.log, NoBlobBodiesFoundError, BlockTagTooOldError),
190
+ );
164
191
  }
165
192
 
166
193
  /**
@@ -209,7 +236,10 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
209
236
  genesisArchiveRoot: Fr.fromHexString(genesisArchiveRoot),
210
237
  };
211
238
 
212
- const opts = merge({ pollingIntervalMs: 10_000, batchSize: 100 }, mapArchiverConfig(config));
239
+ const opts = merge(
240
+ { pollingIntervalMs: 10_000, batchSize: 100, maxAllowedEthClientDriftSeconds: 300 },
241
+ mapArchiverConfig(config),
242
+ );
213
243
 
214
244
  const epochCache = deps.epochCache ?? (await EpochCache.create(config.l1Contracts.rollupAddress, config, deps));
215
245
  const telemetry = deps.telemetry ?? getTelemetryClient();
@@ -221,6 +251,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
221
251
  opts,
222
252
  deps.blobSinkClient,
223
253
  epochCache,
254
+ deps.dateProvider ?? new DateProvider(),
224
255
  await ArchiverInstrumentation.new(telemetry, () => archiverStore.estimateSize()),
225
256
  l1Constants,
226
257
  );
@@ -238,38 +269,30 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
238
269
  * @param blockUntilSynced - If true, blocks until the archiver has fully synced.
239
270
  */
240
271
  public async start(blockUntilSynced: boolean): Promise<void> {
241
- if (this.runningPromise) {
272
+ if (this.runningPromise.isRunning()) {
242
273
  throw new Error('Archiver is already running');
243
274
  }
244
275
 
245
276
  await this.blobSinkClient.testSources();
246
-
247
- if (blockUntilSynced) {
248
- while (!(await this.syncSafe(true))) {
249
- this.log.info(`Retrying initial archiver sync in ${this.config.pollingIntervalMs}ms`);
250
- await sleep(this.config.pollingIntervalMs);
251
- }
252
- }
253
-
254
- this.runningPromise = new RunningPromise(
255
- () => this.sync(false),
256
- this.log,
257
- this.config.pollingIntervalMs,
258
- makeLoggingErrorHandler(
259
- this.log,
260
- // Ignored errors will not log to the console
261
- // We ignore NoBlobBodiesFound as the message may not have been passed to the blob sink yet
262
- NoBlobBodiesFoundError,
263
- ),
277
+ await this.testEthereumNodeSynced();
278
+
279
+ // Log initial state for the archiver
280
+ const { l1StartBlock } = this.l1constants;
281
+ const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
282
+ const currentL2Block = await this.getBlockNumber();
283
+ this.log.info(
284
+ `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo} and L2 block ${currentL2Block}`,
285
+ { blocksSynchedTo, messagesSynchedTo, currentL2Block },
264
286
  );
265
287
 
288
+ // Start sync loop, and return the wait for initial sync if we are asked to block until synced
266
289
  this.runningPromise.start();
290
+ if (blockUntilSynced) {
291
+ return this.waitForInitialSync();
292
+ }
267
293
  }
268
294
 
269
295
  public syncImmediate() {
270
- if (!this.runningPromise) {
271
- throw new Error('Archiver is not running');
272
- }
273
296
  return this.runningPromise.trigger();
274
297
  }
275
298
 
@@ -277,27 +300,26 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
277
300
  return this.initialSyncPromise.promise;
278
301
  }
279
302
 
280
- private async syncSafe(initialRun: boolean) {
281
- try {
282
- await this.sync(initialRun);
283
- return true;
284
- } catch (error) {
285
- if (error instanceof NoBlobBodiesFoundError) {
286
- this.log.error(`Error syncing archiver: ${error.message}`);
287
- } else if (error instanceof BlockTagTooOldError) {
288
- this.log.warn(`Re-running archiver sync: ${error.message}`);
289
- } else {
290
- this.log.error('Error during archiver sync', error);
291
- }
292
- return false;
303
+ /** Checks that the ethereum node we are connected to has a latest timestamp no more than the allowed drift. Throw if not. */
304
+ private async testEthereumNodeSynced() {
305
+ const maxAllowedDelay = this.config.maxAllowedEthClientDriftSeconds;
306
+ if (maxAllowedDelay === 0) {
307
+ return;
308
+ }
309
+ const { number, timestamp: l1Timestamp } = await this.publicClient.getBlock({ includeTransactions: false });
310
+ const currentTime = BigInt(this.dateProvider.nowInSeconds());
311
+ if (currentTime - l1Timestamp > BigInt(maxAllowedDelay)) {
312
+ throw new Error(
313
+ `Ethereum node is out of sync (last block synced ${number} at ${l1Timestamp} vs current time ${currentTime})`,
314
+ );
293
315
  }
294
316
  }
295
317
 
296
318
  /**
297
319
  * Fetches logs from L1 contracts and processes them.
298
320
  */
299
- @trackSpan('Archiver.sync', initialRun => ({ [Attributes.INITIAL_SYNC]: initialRun }))
300
- private async sync(initialRun: boolean) {
321
+ @trackSpan('Archiver.sync')
322
+ private async sync() {
301
323
  /**
302
324
  * We keep track of three "pointers" to L1 blocks:
303
325
  * 1. the last L1 block that published an L2 block
@@ -307,8 +329,6 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
307
329
  * We do this to deal with L1 data providers that are eventually consistent (e.g. Infura).
308
330
  * We guard against seeing block X with no data at one point, and later, the provider processes the block and it has data.
309
331
  * The archiver will stay back, until there's data on L1 that will move the pointers forward.
310
- *
311
- * This code does not handle reorgs.
312
332
  */
313
333
  const { l1StartBlock, l1StartBlockHash } = this.l1constants;
314
334
  const {
@@ -320,13 +340,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
320
340
  const currentL1BlockNumber = currentL1Block.number;
321
341
  const currentL1BlockHash = Buffer32.fromString(currentL1Block.hash);
322
342
 
323
- if (initialRun) {
324
- this.log.info(
325
- `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo}` +
326
- ` to current L1 block ${currentL1BlockNumber} with hash ${currentL1BlockHash.toString()}`,
327
- { blocksSynchedTo, messagesSynchedTo },
328
- );
329
- }
343
+ this.log.trace(`Starting new archiver sync iteration`, {
344
+ blocksSynchedTo,
345
+ messagesSynchedTo,
346
+ currentL1BlockNumber,
347
+ currentL1BlockHash,
348
+ });
330
349
 
331
350
  // ********** Ensuring Consistency of data pulled from L1 **********
332
351
 
@@ -356,28 +375,45 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
356
375
  ? (await this.publicClient.getBlock({ blockNumber: currentL1BlockNumber })).timestamp
357
376
  : this.l1Timestamp;
358
377
 
359
- // ********** Events that are processed per L2 block **********
378
+ // Warn if the latest L1 block timestamp is too old
379
+ const maxAllowedDelay = this.config.maxAllowedEthClientDriftSeconds;
380
+ const now = this.dateProvider.nowInSeconds();
381
+ if (maxAllowedDelay > 0 && Number(currentL1Timestamp) <= now - maxAllowedDelay) {
382
+ this.log.warn(
383
+ `Latest L1 block ${currentL1BlockNumber} timestamp ${currentL1Timestamp} is too old. Make sure your Ethereum node is synced.`,
384
+ { currentL1BlockNumber, currentL1Timestamp, now, maxAllowedDelay },
385
+ );
386
+ }
387
+
388
+ // ********** Events that are processed per checkpoint **********
360
389
  if (currentL1BlockNumber > blocksSynchedTo) {
361
- // First we retrieve new L2 blocks and store them in the DB. This will also update the
362
- // pending chain validation status, proven block number, and synched L1 block number.
363
- const rollupStatus = await this.handleL2blocks(blocksSynchedTo, currentL1BlockNumber);
390
+ // First we retrieve new checkpoints and L2 blocks and store them in the DB. This will also update the
391
+ // pending chain validation status, proven checkpoint number, and synched L1 block number.
392
+ const rollupStatus = await this.handleCheckpoints(blocksSynchedTo, currentL1BlockNumber);
364
393
  // Then we prune the current epoch if it'd reorg on next submission.
365
- // Note that we don't do this before retrieving L2 blocks because we may need to retrieve
366
- // blocks from more than 2 epochs ago, so we want to make sure we have the latest view of
394
+ // Note that we don't do this before retrieving checkpoints because we may need to retrieve
395
+ // checkpoints from more than 2 epochs ago, so we want to make sure we have the latest view of
367
396
  // the chain locally before we start unwinding stuff. This can be optimized by figuring out
368
- // up to which point we're pruning, and then requesting L2 blocks up to that point only.
397
+ // up to which point we're pruning, and then requesting checkpoints up to that point only.
369
398
  const { rollupCanPrune } = await this.handleEpochPrune(
370
- rollupStatus.provenBlockNumber,
399
+ rollupStatus.provenCheckpointNumber,
371
400
  currentL1BlockNumber,
372
401
  currentL1Timestamp,
373
402
  );
374
403
 
375
- // And lastly we check if we are missing any L2 blocks behind us due to a possible L1 reorg.
404
+ // If the last checkpoint we processed had an invalid attestation, we manually advance the L1 syncpoint
405
+ // past it, since otherwise we'll keep downloading it and reprocessing it on every iteration until
406
+ // we get a valid checkpoint to advance the syncpoint.
407
+ if (!rollupStatus.validationResult?.valid && rollupStatus.lastL1BlockWithCheckpoint !== undefined) {
408
+ await this.store.setBlockSynchedL1BlockNumber(rollupStatus.lastL1BlockWithCheckpoint);
409
+ }
410
+
411
+ // And lastly we check if we are missing any checkpoints behind us due to a possible L1 reorg.
376
412
  // We only do this if rollup cant prune on the next submission. Otherwise we will end up
377
- // re-syncing the blocks we have just unwound above. We also dont do this if the last block is invalid,
413
+ // re-syncing the checkpoints we have just unwound above. We also dont do this if the last checkpoint is invalid,
378
414
  // since the archiver will rightfully refuse to sync up to it.
379
415
  if (!rollupCanPrune && rollupStatus.validationResult?.valid) {
380
- await this.checkForNewBlocksBeforeL1SyncPoint(rollupStatus, blocksSynchedTo, currentL1BlockNumber);
416
+ await this.checkForNewCheckpointsBeforeL1SyncPoint(rollupStatus, blocksSynchedTo, currentL1BlockNumber);
381
417
  }
382
418
 
383
419
  this.instrumentation.updateL1BlockHeight(currentL1BlockNumber);
@@ -388,15 +424,18 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
388
424
  // but the corresponding blocks have not been processed (see #12631).
389
425
  this.l1Timestamp = currentL1Timestamp;
390
426
  this.l1BlockNumber = currentL1BlockNumber;
391
- this.initialSyncComplete = true;
392
- this.initialSyncPromise.resolve();
393
427
 
394
- if (initialRun) {
395
- this.log.info(`Initial archiver sync to L1 block ${currentL1BlockNumber} complete.`, {
428
+ // We resolve the initial sync only once we've caught up with the latest L1 block number (with 1 block grace)
429
+ // so if the initial sync took too long, we still go for another iteration.
430
+ if (!this.initialSyncComplete && currentL1BlockNumber + 1n >= (await this.publicClient.getBlockNumber())) {
431
+ this.log.info(`Initial archiver sync to L1 block ${currentL1BlockNumber} complete`, {
396
432
  l1BlockNumber: currentL1BlockNumber,
397
433
  syncPoint: await this.store.getSynchPoint(),
398
434
  ...(await this.getL2Tips()),
399
435
  });
436
+ this.runningPromise.setPollingIntervalMS(this.config.pollingIntervalMs);
437
+ this.initialSyncComplete = true;
438
+ this.initialSyncPromise.resolve();
400
439
  }
401
440
  }
402
441
 
@@ -414,43 +453,47 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
414
453
  return result;
415
454
  }
416
455
 
417
- /** Checks if there'd be a reorg for the next block submission and start pruning now. */
418
- private async handleEpochPrune(provenBlockNumber: number, currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
456
+ /** Checks if there'd be a reorg for the next checkpoint submission and start pruning now. */
457
+ private async handleEpochPrune(
458
+ provenCheckpointNumber: CheckpointNumber,
459
+ currentL1BlockNumber: bigint,
460
+ currentL1Timestamp: bigint,
461
+ ) {
419
462
  const rollupCanPrune = await this.canPrune(currentL1BlockNumber, currentL1Timestamp);
420
- const localPendingBlockNumber = await this.getBlockNumber();
421
- const canPrune = localPendingBlockNumber > provenBlockNumber && rollupCanPrune;
463
+ const localPendingCheckpointNumber = await this.getSynchedCheckpointNumber();
464
+ const canPrune = localPendingCheckpointNumber > provenCheckpointNumber && rollupCanPrune;
422
465
 
423
466
  if (canPrune) {
424
467
  const timer = new Timer();
425
- const pruneFrom = provenBlockNumber + 1;
468
+ const pruneFrom = CheckpointNumber(provenCheckpointNumber + 1);
426
469
 
427
- const header = await this.getBlockHeader(Number(pruneFrom));
470
+ const header = await this.getCheckpointHeader(pruneFrom);
428
471
  if (header === undefined) {
429
- throw new Error(`Missing block header ${pruneFrom}`);
472
+ throw new Error(`Missing checkpoint header ${pruneFrom}`);
430
473
  }
431
474
 
432
- const pruneFromSlotNumber = header.globalVariables.slotNumber.toBigInt();
433
- const pruneFromEpochNumber = getEpochAtSlot(pruneFromSlotNumber, this.l1constants);
475
+ const pruneFromSlotNumber = header.slotNumber;
476
+ const pruneFromEpochNumber: EpochNumber = getEpochAtSlot(pruneFromSlotNumber, this.l1constants);
434
477
 
435
- const blocksToUnwind = localPendingBlockNumber - provenBlockNumber;
478
+ const checkpointsToUnwind = localPendingCheckpointNumber - provenCheckpointNumber;
436
479
 
437
- const blocks = await this.getBlocks(Number(provenBlockNumber) + 1, Number(blocksToUnwind));
480
+ const checkpoints = await this.getCheckpoints(pruneFrom, checkpointsToUnwind);
438
481
 
439
482
  // Emit an event for listening services to react to the chain prune
440
483
  this.emit(L2BlockSourceEvents.L2PruneDetected, {
441
484
  type: L2BlockSourceEvents.L2PruneDetected,
442
485
  epochNumber: pruneFromEpochNumber,
443
- blocks,
486
+ blocks: checkpoints.flatMap(c => L2Block.fromCheckpoint(c)),
444
487
  });
445
488
 
446
489
  this.log.debug(
447
- `L2 prune from ${provenBlockNumber + 1} to ${localPendingBlockNumber} will occur on next block submission.`,
490
+ `L2 prune from ${provenCheckpointNumber + 1} to ${localPendingCheckpointNumber} will occur on next checkpoint submission.`,
448
491
  );
449
- await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
492
+ await this.unwindCheckpoints(localPendingCheckpointNumber, checkpointsToUnwind);
450
493
  this.log.warn(
451
- `Unwound ${count(blocksToUnwind, 'block')} from L2 block ${localPendingBlockNumber} ` +
452
- `to ${provenBlockNumber} due to predicted reorg at L1 block ${currentL1BlockNumber}. ` +
453
- `Updated L2 latest block is ${await this.getBlockNumber()}.`,
494
+ `Unwound ${count(checkpointsToUnwind, 'checkpoint')} from checkpoint ${localPendingCheckpointNumber} ` +
495
+ `to ${provenCheckpointNumber} due to predicted reorg at L1 block ${currentL1BlockNumber}. ` +
496
+ `Updated latest checkpoint is ${await this.getSynchedCheckpointNumber()}.`,
454
497
  );
455
498
  this.instrumentation.processPrune(timer.ms());
456
499
  // TODO(palla/reorg): Do we need to set the block synched L1 block number here?
@@ -497,7 +540,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
497
540
  remoteMessagesState.totalMessagesInserted === localMessagesInserted &&
498
541
  remoteMessagesState.messagesRollingHash.equals(localLastMessage?.rollingHash ?? Buffer16.ZERO)
499
542
  ) {
500
- this.log.debug(
543
+ this.log.trace(
501
544
  `No L1 to L2 messages to query between L1 blocks ${messagesSyncPoint.l1BlockNumber} and ${currentL1BlockNumber}.`,
502
545
  );
503
546
  return;
@@ -629,167 +672,185 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
629
672
  return Buffer32.fromString(block.hash);
630
673
  }
631
674
 
632
- private async handleL2blocks(blocksSynchedTo: bigint, currentL1BlockNumber: bigint) {
633
- const localPendingBlockNumber = await this.getBlockNumber();
675
+ private async handleCheckpoints(blocksSynchedTo: bigint, currentL1BlockNumber: bigint): Promise<RollupStatus> {
676
+ const localPendingCheckpointNumber = await this.getSynchedCheckpointNumber();
634
677
  const initialValidationResult: ValidateBlockResult | undefined = await this.store.getPendingChainValidationStatus();
635
- const [provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive, archiveForLocalPendingBlockNumber] =
636
- await this.rollup.status(BigInt(localPendingBlockNumber), { blockNumber: currentL1BlockNumber });
678
+ const [
679
+ rollupProvenCheckpointNumber,
680
+ provenArchive,
681
+ rollupPendingCheckpointNumber,
682
+ pendingArchive,
683
+ archiveForLocalPendingCheckpointNumber,
684
+ ] = await this.rollup.status(localPendingCheckpointNumber, { blockNumber: currentL1BlockNumber });
685
+ const provenCheckpointNumber = CheckpointNumber.fromBigInt(rollupProvenCheckpointNumber);
686
+ const pendingCheckpointNumber = CheckpointNumber.fromBigInt(rollupPendingCheckpointNumber);
637
687
  const rollupStatus = {
638
- provenBlockNumber: Number(provenBlockNumber),
688
+ provenCheckpointNumber,
639
689
  provenArchive,
640
- pendingBlockNumber: Number(pendingBlockNumber),
690
+ pendingCheckpointNumber,
641
691
  pendingArchive,
642
692
  validationResult: initialValidationResult,
643
693
  };
644
694
  this.log.trace(`Retrieved rollup status at current L1 block ${currentL1BlockNumber}.`, {
645
- localPendingBlockNumber,
695
+ localPendingCheckpointNumber,
646
696
  blocksSynchedTo,
647
697
  currentL1BlockNumber,
648
- archiveForLocalPendingBlockNumber,
698
+ archiveForLocalPendingCheckpointNumber,
649
699
  ...rollupStatus,
650
700
  });
651
701
 
652
- const updateProvenBlock = async () => {
653
- // Annoying edge case: if proven block is moved back to 0 due to a reorg at the beginning of the chain,
654
- // we need to set it to zero. This is an edge case because we dont have a block zero (initial block is one),
655
- // so localBlockForDestinationProvenBlockNumber would not be found below.
656
- if (provenBlockNumber === 0n) {
657
- const localProvenBlockNumber = await this.store.getProvenL2BlockNumber();
658
- if (localProvenBlockNumber !== Number(provenBlockNumber)) {
659
- await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
660
- this.log.info(`Rolled back proven chain to block ${provenBlockNumber}`, { provenBlockNumber });
702
+ const updateProvenCheckpoint = async () => {
703
+ // Annoying edge case: if proven checkpoint is moved back to 0 due to a reorg at the beginning of the chain,
704
+ // we need to set it to zero. This is an edge case because we dont have a checkpoint zero (initial checkpoint is one),
705
+ // so localCheckpointForDestinationProvenCheckpointNumber would not be found below.
706
+ if (provenCheckpointNumber === 0) {
707
+ const localProvenCheckpointNumber = await this.getProvenCheckpointNumber();
708
+ if (localProvenCheckpointNumber !== provenCheckpointNumber) {
709
+ await this.setProvenCheckpointNumber(provenCheckpointNumber);
710
+ this.log.info(`Rolled back proven chain to checkpoint ${provenCheckpointNumber}`, { provenCheckpointNumber });
661
711
  }
662
712
  }
663
713
 
664
- const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber));
714
+ const localCheckpointForDestinationProvenCheckpointNumber = await this.getCheckpoint(provenCheckpointNumber);
665
715
 
666
- // Sanity check. I've hit what seems to be a state where the proven block is set to a value greater than the latest
667
- // synched block when requesting L2Tips from the archiver. This is the only place where the proven block is set.
668
- const synched = await this.store.getSynchedL2BlockNumber();
669
- if (localBlockForDestinationProvenBlockNumber && synched < localBlockForDestinationProvenBlockNumber?.number) {
716
+ // Sanity check. I've hit what seems to be a state where the proven checkpoint is set to a value greater than the latest
717
+ // synched checkpoint when requesting L2Tips from the archiver. This is the only place where the proven checkpoint is set.
718
+ const synched = await this.getSynchedCheckpointNumber();
719
+ if (
720
+ localCheckpointForDestinationProvenCheckpointNumber &&
721
+ synched < localCheckpointForDestinationProvenCheckpointNumber.number
722
+ ) {
670
723
  this.log.error(
671
- `Hit local block greater than last synched block: ${localBlockForDestinationProvenBlockNumber.number} > ${synched}`,
724
+ `Hit local checkpoint greater than last synched checkpoint: ${localCheckpointForDestinationProvenCheckpointNumber.number} > ${synched}`,
672
725
  );
673
726
  }
674
727
 
675
728
  this.log.trace(
676
- `Local block for remote proven block ${provenBlockNumber} is ${
677
- localBlockForDestinationProvenBlockNumber?.archive.root.toString() ?? 'undefined'
729
+ `Local checkpoint for remote proven checkpoint ${provenCheckpointNumber} is ${
730
+ localCheckpointForDestinationProvenCheckpointNumber?.archive.root.toString() ?? 'undefined'
678
731
  }`,
679
732
  );
680
733
 
734
+ const lastProvenBlockNumber = await this.getLastBlockNumberInCheckpoint(provenCheckpointNumber);
681
735
  if (
682
- localBlockForDestinationProvenBlockNumber &&
683
- provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString()
736
+ localCheckpointForDestinationProvenCheckpointNumber &&
737
+ provenArchive === localCheckpointForDestinationProvenCheckpointNumber.archive.root.toString()
684
738
  ) {
685
- const localProvenBlockNumber = await this.store.getProvenL2BlockNumber();
686
- if (localProvenBlockNumber !== Number(provenBlockNumber)) {
687
- await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
688
- this.log.info(`Updated proven chain to block ${provenBlockNumber}`, {
689
- provenBlockNumber,
739
+ const localProvenCheckpointNumber = await this.getProvenCheckpointNumber();
740
+ if (localProvenCheckpointNumber !== provenCheckpointNumber) {
741
+ await this.setProvenCheckpointNumber(provenCheckpointNumber);
742
+ this.log.info(`Updated proven chain to checkpoint ${provenCheckpointNumber}`, {
743
+ provenCheckpointNumber,
690
744
  });
691
- const provenSlotNumber =
692
- localBlockForDestinationProvenBlockNumber.header.globalVariables.slotNumber.toBigInt();
693
- const provenEpochNumber = getEpochAtSlot(provenSlotNumber, this.l1constants);
745
+ const provenSlotNumber = localCheckpointForDestinationProvenCheckpointNumber.header.slotNumber;
746
+ const provenEpochNumber: EpochNumber = getEpochAtSlot(provenSlotNumber, this.l1constants);
747
+
694
748
  this.emit(L2BlockSourceEvents.L2BlockProven, {
695
749
  type: L2BlockSourceEvents.L2BlockProven,
696
- blockNumber: provenBlockNumber,
750
+ blockNumber: lastProvenBlockNumber,
697
751
  slotNumber: provenSlotNumber,
698
752
  epochNumber: provenEpochNumber,
699
753
  });
700
754
  } else {
701
- this.log.trace(`Proven block ${provenBlockNumber} already stored.`);
755
+ this.log.trace(`Proven checkpoint ${provenCheckpointNumber} already stored.`);
702
756
  }
703
757
  }
704
- this.instrumentation.updateLastProvenBlock(Number(provenBlockNumber));
758
+ this.instrumentation.updateLastProvenBlock(lastProvenBlockNumber);
705
759
  };
706
760
 
707
- // This is an edge case that we only hit if there are no proposed blocks.
708
- // If we have 0 blocks locally and there are no blocks onchain there is nothing to do.
709
- const noBlocks = localPendingBlockNumber === 0 && pendingBlockNumber === 0n;
710
- if (noBlocks) {
761
+ // This is an edge case that we only hit if there are no proposed checkpoints.
762
+ // If we have 0 checkpoints locally and there are no checkpoints onchain there is nothing to do.
763
+ const noCheckpoints = localPendingCheckpointNumber === 0 && pendingCheckpointNumber === 0;
764
+ if (noCheckpoints) {
711
765
  await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
712
766
  this.log.debug(
713
- `No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no blocks on chain`,
767
+ `No checkpoints to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no checkpoints on chain`,
714
768
  );
715
769
  return rollupStatus;
716
770
  }
717
771
 
718
- await updateProvenBlock();
772
+ await updateProvenCheckpoint();
719
773
 
720
774
  // Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
721
- // are any state that could be impacted by it. If we have no blocks, there is no impact.
722
- if (localPendingBlockNumber > 0) {
723
- const localPendingBlock = await this.getBlock(localPendingBlockNumber);
724
- if (localPendingBlock === undefined) {
725
- throw new Error(`Missing block ${localPendingBlockNumber}`);
775
+ // are any state that could be impacted by it. If we have no checkpoints, there is no impact.
776
+ if (localPendingCheckpointNumber > 0) {
777
+ const localPendingCheckpoint = await this.getCheckpoint(localPendingCheckpointNumber);
778
+ if (localPendingCheckpoint === undefined) {
779
+ throw new Error(`Missing checkpoint ${localPendingCheckpointNumber}`);
726
780
  }
727
781
 
728
- const localPendingArchiveRoot = localPendingBlock.archive.root.toString();
729
- const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingArchiveRoot;
730
- if (noBlockSinceLast) {
782
+ const localPendingArchiveRoot = localPendingCheckpoint.archive.root.toString();
783
+ const noCheckpointSinceLast = localPendingCheckpoint && pendingArchive === localPendingArchiveRoot;
784
+ if (noCheckpointSinceLast) {
731
785
  // We believe the following line causes a problem when we encounter L1 re-orgs.
732
786
  // Basically, by setting the synched L1 block number here, we are saying that we have
733
- // processed all blocks up to the current L1 block number and we will not attempt to retrieve logs from
787
+ // processed all checkpoints up to the current L1 block number and we will not attempt to retrieve logs from
734
788
  // this block again (or any blocks before).
735
- // However, in the re-org scenario, our L1 node is temporarily lying to us and we end up potentially missing blocks
789
+ // However, in the re-org scenario, our L1 node is temporarily lying to us and we end up potentially missing checkpoints.
736
790
  // We must only set this block number based on actually retrieved logs.
737
791
  // TODO(#8621): Tackle this properly when we handle L1 Re-orgs.
738
792
  // await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
739
- this.log.debug(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
793
+ this.log.debug(`No checkpoints to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
740
794
  return rollupStatus;
741
795
  }
742
796
 
743
- const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingArchiveRoot;
744
- if (!localPendingBlockInChain) {
745
- // If our local pending block tip is not in the chain on L1 a "prune" must have happened
797
+ const localPendingCheckpointInChain = archiveForLocalPendingCheckpointNumber === localPendingArchiveRoot;
798
+ if (!localPendingCheckpointInChain) {
799
+ // If our local pending checkpoint tip is not in the chain on L1 a "prune" must have happened
746
800
  // or the L1 have reorged.
747
801
  // In any case, we have to figure out how far into the past the action will take us.
748
- // For simplicity here, we will simply rewind until we end in a block that is also on the chain on L1.
802
+ // For simplicity here, we will simply rewind until we end in a checkpoint that is also on the chain on L1.
749
803
  this.log.debug(
750
- `L2 prune has been detected due to local pending block ${localPendingBlockNumber} not in chain`,
751
- { localPendingBlockNumber, localPendingArchiveRoot, archiveForLocalPendingBlockNumber },
804
+ `L2 prune has been detected due to local pending checkpoint ${localPendingCheckpointNumber} not in chain`,
805
+ { localPendingCheckpointNumber, localPendingArchiveRoot, archiveForLocalPendingCheckpointNumber },
752
806
  );
753
807
 
754
- let tipAfterUnwind = localPendingBlockNumber;
808
+ let tipAfterUnwind = localPendingCheckpointNumber;
755
809
  while (true) {
756
- const candidateBlock = await this.getBlock(Number(tipAfterUnwind));
757
- if (candidateBlock === undefined) {
810
+ const candidateCheckpoint = await this.getCheckpoint(tipAfterUnwind);
811
+ if (candidateCheckpoint === undefined) {
758
812
  break;
759
813
  }
760
814
 
761
- const archiveAtContract = await this.rollup.archiveAt(BigInt(candidateBlock.number));
762
-
763
- if (archiveAtContract === candidateBlock.archive.root.toString()) {
815
+ const archiveAtContract = await this.rollup.archiveAt(candidateCheckpoint.number);
816
+ this.log.trace(
817
+ `Checking local checkpoint ${candidateCheckpoint.number} with archive ${candidateCheckpoint.archive.root}`,
818
+ {
819
+ archiveAtContract,
820
+ archiveLocal: candidateCheckpoint.archive.root.toString(),
821
+ },
822
+ );
823
+ if (archiveAtContract === candidateCheckpoint.archive.root.toString()) {
764
824
  break;
765
825
  }
766
826
  tipAfterUnwind--;
767
827
  }
768
828
 
769
- const blocksToUnwind = localPendingBlockNumber - tipAfterUnwind;
770
- await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
829
+ const checkpointsToUnwind = localPendingCheckpointNumber - tipAfterUnwind;
830
+ await this.unwindCheckpoints(localPendingCheckpointNumber, checkpointsToUnwind);
771
831
 
772
832
  this.log.warn(
773
- `Unwound ${count(blocksToUnwind, 'block')} from L2 block ${localPendingBlockNumber} ` +
774
- `due to mismatched block hashes at L1 block ${currentL1BlockNumber}. ` +
775
- `Updated L2 latest block is ${await this.getBlockNumber()}.`,
833
+ `Unwound ${count(checkpointsToUnwind, 'checkpoint')} from checkpoint ${localPendingCheckpointNumber} ` +
834
+ `due to mismatched checkpoint hashes at L1 block ${currentL1BlockNumber}. ` +
835
+ `Updated L2 latest checkpoint is ${await this.getSynchedCheckpointNumber()}.`,
776
836
  );
777
837
  }
778
838
  }
779
839
 
780
- // Retrieve L2 blocks in batches. Each batch is estimated to accommodate up to L2 'blockBatchSize' blocks,
840
+ // Retrieve checkpoints in batches. Each batch is estimated to accommodate up to 'blockBatchSize' L1 blocks,
781
841
  // computed using the L2 block time vs the L1 block time.
782
842
  let searchStartBlock: bigint = blocksSynchedTo;
783
843
  let searchEndBlock: bigint = blocksSynchedTo;
784
- let lastRetrievedBlock: PublishedL2Block | undefined;
844
+ let lastRetrievedCheckpoint: PublishedCheckpoint | undefined;
845
+ let lastL1BlockWithCheckpoint: bigint | undefined = undefined;
785
846
 
786
847
  do {
787
848
  [searchStartBlock, searchEndBlock] = this.nextRange(searchEndBlock, currentL1BlockNumber);
788
849
 
789
- this.log.trace(`Retrieving L2 blocks from L1 block ${searchStartBlock} to ${searchEndBlock}`);
850
+ this.log.trace(`Retrieving checkpoints from L1 block ${searchStartBlock} to ${searchEndBlock}`);
790
851
 
791
852
  // TODO(md): Retrieve from blob sink then from consensus client, then from peers
792
- const retrievedBlocks = await retrieveBlocksFromRollup(
853
+ const retrievedCheckpoints = await retrieveCheckpointsFromRollup(
793
854
  this.rollup.getContract() as GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
794
855
  this.publicClient,
795
856
  this.blobSinkClient,
@@ -798,31 +859,35 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
798
859
  this.log,
799
860
  );
800
861
 
801
- if (retrievedBlocks.length === 0) {
862
+ if (retrievedCheckpoints.length === 0) {
802
863
  // We are not calling `setBlockSynchedL1BlockNumber` because it may cause sync issues if based off infura.
803
864
  // See further details in earlier comments.
804
- this.log.trace(`Retrieved no new L2 blocks from L1 block ${searchStartBlock} to ${searchEndBlock}`);
865
+ this.log.trace(`Retrieved no new checkpoints from L1 block ${searchStartBlock} to ${searchEndBlock}`);
805
866
  continue;
806
867
  }
807
868
 
808
- const lastProcessedL1BlockNumber = retrievedBlocks[retrievedBlocks.length - 1].l1.blockNumber;
809
869
  this.log.debug(
810
- `Retrieved ${retrievedBlocks.length} new L2 blocks between L1 blocks ${searchStartBlock} and ${searchEndBlock} with last processed L1 block ${lastProcessedL1BlockNumber}.`,
870
+ `Retrieved ${retrievedCheckpoints.length} new checkpoints between L1 blocks ${searchStartBlock} and ${searchEndBlock}`,
871
+ {
872
+ lastProcessedCheckpoint: retrievedCheckpoints[retrievedCheckpoints.length - 1].l1,
873
+ searchStartBlock,
874
+ searchEndBlock,
875
+ },
811
876
  );
812
877
 
813
- const publishedBlocks = await Promise.all(retrievedBlocks.map(b => retrievedBlockToPublishedL2Block(b)));
814
- const validBlocks: PublishedL2Block[] = [];
878
+ const publishedCheckpoints = await Promise.all(retrievedCheckpoints.map(b => retrievedToPublishedCheckpoint(b)));
879
+ const validCheckpoints: PublishedCheckpoint[] = [];
815
880
 
816
- for (const block of publishedBlocks) {
881
+ for (const published of publishedCheckpoints) {
817
882
  const validationResult = this.config.skipValidateBlockAttestations
818
883
  ? { valid: true as const }
819
- : await validateBlockAttestations(block, this.epochCache, this.l1constants, this.log);
884
+ : await validateCheckpointAttestations(published, this.epochCache, this.l1constants, this.log);
820
885
 
821
- // Only update the validation result if it has changed, so we can keep track of the first invalid block
822
- // in case there is a sequence of more than one invalid block, as we need to invalidate the first one.
823
- // There is an exception though: if an invalid block is invalidated and replaced with another invalid block,
886
+ // Only update the validation result if it has changed, so we can keep track of the first invalid checkpoint
887
+ // in case there is a sequence of more than one invalid checkpoint, as we need to invalidate the first one.
888
+ // There is an exception though: if a checkpoint is invalidated and replaced with another invalid checkpoint,
824
889
  // we need to update the validation result, since we need to be able to invalidate the new one.
825
- // See test 'chain progresses if an invalid block is invalidated with an invalid one' for more info.
890
+ // See test 'chain progresses if an invalid checkpoint is invalidated with an invalid one' for more info.
826
891
  if (
827
892
  rollupStatus.validationResult?.valid !== validationResult.valid ||
828
893
  (!rollupStatus.validationResult.valid &&
@@ -833,9 +898,9 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
833
898
  }
834
899
 
835
900
  if (!validationResult.valid) {
836
- this.log.warn(`Skipping block ${block.block.number} due to invalid attestations`, {
837
- blockHash: block.block.hash(),
838
- l1BlockNumber: block.l1.blockNumber,
901
+ this.log.warn(`Skipping checkpoint ${published.checkpoint.number} due to invalid attestations`, {
902
+ checkpointHash: published.checkpoint.hash(),
903
+ l1BlockNumber: published.l1.blockNumber,
839
904
  ...pick(validationResult, 'reason'),
840
905
  });
841
906
 
@@ -845,34 +910,37 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
845
910
  validationResult,
846
911
  });
847
912
 
848
- // We keep consuming blocks if we find an invalid one, since we do not listen for BlockInvalidated events
849
- // We just pretend the invalid ones are not there and keep consuming the next blocks
850
- // Note that this breaks if the committee ever attests to a descendant of an invalid block
913
+ // We keep consuming checkpoints if we find an invalid one, since we do not listen for CheckpointInvalidated events
914
+ // We just pretend the invalid ones are not there and keep consuming the next checkpoints
915
+ // Note that this breaks if the committee ever attests to a descendant of an invalid checkpoint
851
916
  continue;
852
917
  }
853
918
 
854
- validBlocks.push(block);
855
- this.log.debug(`Ingesting new L2 block ${block.block.number} with ${block.block.body.txEffects.length} txs`, {
856
- blockHash: block.block.hash(),
857
- l1BlockNumber: block.l1.blockNumber,
858
- ...block.block.header.globalVariables.toInspect(),
859
- ...block.block.getStats(),
860
- });
919
+ validCheckpoints.push(published);
920
+ this.log.debug(
921
+ `Ingesting new checkpoint ${published.checkpoint.number} with ${published.checkpoint.blocks.length} blocks`,
922
+ {
923
+ checkpointHash: published.checkpoint.hash(),
924
+ l1BlockNumber: published.l1.blockNumber,
925
+ ...published.checkpoint.header.toInspect(),
926
+ blocks: published.checkpoint.blocks.map(b => b.getStats()),
927
+ },
928
+ );
861
929
  }
862
930
 
863
931
  try {
864
932
  const updatedValidationResult =
865
933
  rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
866
- const [processDuration] = await elapsed(() => this.store.addBlocks(validBlocks, updatedValidationResult));
934
+ const [processDuration] = await elapsed(() => this.addCheckpoints(validCheckpoints, updatedValidationResult));
867
935
  this.instrumentation.processNewBlocks(
868
- processDuration / validBlocks.length,
869
- validBlocks.map(b => b.block),
936
+ processDuration / validCheckpoints.length,
937
+ validCheckpoints.flatMap(c => c.checkpoint.blocks),
870
938
  );
871
939
  } catch (err) {
872
940
  if (err instanceof InitialBlockNumberNotSequentialError) {
873
941
  const { previousBlockNumber, newBlockNumber } = err;
874
942
  const previousBlock = previousBlockNumber
875
- ? await this.store.getPublishedBlock(previousBlockNumber)
943
+ ? await this.store.getPublishedBlock(BlockNumber(previousBlockNumber))
876
944
  : undefined;
877
945
  const updatedL1SyncPoint = previousBlock?.l1.blockNumber ?? this.l1constants.l1StartBlock;
878
946
  await this.store.setBlockSynchedL1BlockNumber(updatedL1SyncPoint);
@@ -889,56 +957,56 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
889
957
  throw err;
890
958
  }
891
959
 
892
- for (const block of validBlocks) {
893
- this.log.info(`Downloaded L2 block ${block.block.number}`, {
894
- blockHash: await block.block.hash(),
895
- blockNumber: block.block.number,
896
- txCount: block.block.body.txEffects.length,
897
- globalVariables: block.block.header.globalVariables.toInspect(),
898
- archiveRoot: block.block.archive.root.toString(),
899
- archiveNextLeafIndex: block.block.archive.nextAvailableLeafIndex,
960
+ for (const checkpoint of validCheckpoints) {
961
+ this.log.info(`Downloaded checkpoint ${checkpoint.checkpoint.number}`, {
962
+ checkpointHash: checkpoint.checkpoint.hash(),
963
+ checkpointNumber: checkpoint.checkpoint.number,
964
+ blockCount: checkpoint.checkpoint.blocks.length,
965
+ txCount: checkpoint.checkpoint.blocks.reduce((acc, b) => acc + b.body.txEffects.length, 0),
966
+ header: checkpoint.checkpoint.header.toInspect(),
967
+ archiveRoot: checkpoint.checkpoint.archive.root.toString(),
968
+ archiveNextLeafIndex: checkpoint.checkpoint.archive.nextAvailableLeafIndex,
900
969
  });
901
970
  }
902
- lastRetrievedBlock = validBlocks.at(-1) ?? lastRetrievedBlock;
971
+ lastRetrievedCheckpoint = validCheckpoints.at(-1) ?? lastRetrievedCheckpoint;
972
+ lastL1BlockWithCheckpoint = publishedCheckpoints.at(-1)?.l1.blockNumber ?? lastL1BlockWithCheckpoint;
903
973
  } while (searchEndBlock < currentL1BlockNumber);
904
974
 
905
975
  // Important that we update AFTER inserting the blocks.
906
- await updateProvenBlock();
976
+ await updateProvenCheckpoint();
907
977
 
908
- return { ...rollupStatus, lastRetrievedBlock };
978
+ return { ...rollupStatus, lastRetrievedCheckpoint, lastL1BlockWithCheckpoint };
909
979
  }
910
980
 
911
- private async checkForNewBlocksBeforeL1SyncPoint(
912
- status: {
913
- lastRetrievedBlock?: PublishedL2Block;
914
- pendingBlockNumber: number;
915
- },
981
+ private async checkForNewCheckpointsBeforeL1SyncPoint(
982
+ status: RollupStatus,
916
983
  blocksSynchedTo: bigint,
917
984
  currentL1BlockNumber: bigint,
918
985
  ) {
919
- const { lastRetrievedBlock, pendingBlockNumber } = status;
920
- // Compare the last L2 block we have (either retrieved in this round or loaded from store) with what the
986
+ const { lastRetrievedCheckpoint, pendingCheckpointNumber } = status;
987
+ // Compare the last checkpoint we have (either retrieved in this round or loaded from store) with what the
921
988
  // rollup contract told us was the latest one (pinned at the currentL1BlockNumber).
922
- const latestLocalL2BlockNumber = lastRetrievedBlock?.block.number ?? (await this.store.getSynchedL2BlockNumber());
923
- if (latestLocalL2BlockNumber < pendingBlockNumber) {
989
+ const latestLocalCheckpointNumber =
990
+ lastRetrievedCheckpoint?.checkpoint.number ?? (await this.getSynchedCheckpointNumber());
991
+ if (latestLocalCheckpointNumber < pendingCheckpointNumber) {
924
992
  // Here we have consumed all logs until the `currentL1Block` we pinned at the beginning of the archiver loop,
925
- // but still havent reached the pending block according to the call to the rollup contract.
926
- // We suspect an L1 reorg that added blocks *behind* us. If that is the case, it must have happened between the
927
- // last L2 block we saw and the current one, so we reset the last synched L1 block number. In the edge case we
928
- // don't have one, we go back 2 L1 epochs, which is the deepest possible reorg (assuming Casper is working).
929
- const latestLocalL2Block =
930
- lastRetrievedBlock ??
931
- (latestLocalL2BlockNumber > 0
932
- ? await this.store.getPublishedBlocks(latestLocalL2BlockNumber, 1).then(([b]) => b)
993
+ // but still haven't reached the pending checkpoint according to the call to the rollup contract.
994
+ // We suspect an L1 reorg that added checkpoints *behind* us. If that is the case, it must have happened between
995
+ // the last checkpoint we saw and the current one, so we reset the last synched L1 block number. In the edge case
996
+ // we don't have one, we go back 2 L1 epochs, which is the deepest possible reorg (assuming Casper is working).
997
+ const latestLocalCheckpoint =
998
+ lastRetrievedCheckpoint ??
999
+ (latestLocalCheckpointNumber > 0
1000
+ ? await this.getPublishedCheckpoints(latestLocalCheckpointNumber, 1).then(([c]) => c)
933
1001
  : undefined);
934
- const targetL1BlockNumber = latestLocalL2Block?.l1.blockNumber ?? maxBigint(currentL1BlockNumber - 64n, 0n);
935
- const latestLocalL2BlockArchive = latestLocalL2Block?.block.archive.root.toString();
1002
+ const targetL1BlockNumber = latestLocalCheckpoint?.l1.blockNumber ?? maxBigint(currentL1BlockNumber - 64n, 0n);
1003
+ const latestLocalCheckpointArchive = latestLocalCheckpoint?.checkpoint.archive.root.toString();
936
1004
  this.log.warn(
937
- `Failed to reach L2 block ${pendingBlockNumber} at ${currentL1BlockNumber} (latest is ${latestLocalL2BlockNumber}). ` +
1005
+ `Failed to reach checkpoint ${pendingCheckpointNumber} at ${currentL1BlockNumber} (latest is ${latestLocalCheckpointNumber}). ` +
938
1006
  `Rolling back last synched L1 block number to ${targetL1BlockNumber}.`,
939
1007
  {
940
- latestLocalL2BlockNumber,
941
- latestLocalL2BlockArchive,
1008
+ latestLocalCheckpointNumber,
1009
+ latestLocalCheckpointArchive,
942
1010
  blocksSynchedTo,
943
1011
  currentL1BlockNumber,
944
1012
  ...status,
@@ -946,18 +1014,15 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
946
1014
  );
947
1015
  await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
948
1016
  } else {
949
- this.log.trace(`No new blocks behind L1 sync point to retrieve.`, {
950
- latestLocalL2BlockNumber,
951
- pendingBlockNumber,
1017
+ this.log.trace(`No new checkpoints behind L1 sync point to retrieve.`, {
1018
+ latestLocalCheckpointNumber,
1019
+ pendingCheckpointNumber,
952
1020
  });
953
1021
  }
954
1022
  }
955
1023
 
956
1024
  /** Resumes the archiver after a stop. */
957
1025
  public resume() {
958
- if (!this.runningPromise) {
959
- throw new Error(`Archiver was never started`);
960
- }
961
1026
  if (this.runningPromise.isRunning()) {
962
1027
  this.log.warn(`Archiver already running`);
963
1028
  }
@@ -971,7 +1036,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
971
1036
  */
972
1037
  public async stop(): Promise<void> {
973
1038
  this.log.debug('Stopping...');
974
- await this.runningPromise?.stop();
1039
+ await this.runningPromise.stop();
975
1040
 
976
1041
  this.log.info('Stopped.');
977
1042
  return Promise.resolve();
@@ -1005,37 +1070,37 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1005
1070
  return Promise.resolve(this.l1Timestamp);
1006
1071
  }
1007
1072
 
1008
- public getL2SlotNumber(): Promise<bigint | undefined> {
1073
+ public getL2SlotNumber(): Promise<SlotNumber | undefined> {
1009
1074
  return Promise.resolve(
1010
1075
  this.l1Timestamp === undefined ? undefined : getSlotAtTimestamp(this.l1Timestamp, this.l1constants),
1011
1076
  );
1012
1077
  }
1013
1078
 
1014
- public getL2EpochNumber(): Promise<bigint | undefined> {
1079
+ public getL2EpochNumber(): Promise<EpochNumber | undefined> {
1015
1080
  return Promise.resolve(
1016
1081
  this.l1Timestamp === undefined ? undefined : getEpochNumberAtTimestamp(this.l1Timestamp, this.l1constants),
1017
1082
  );
1018
1083
  }
1019
1084
 
1020
- public async getBlocksForEpoch(epochNumber: bigint): Promise<L2Block[]> {
1085
+ public async getBlocksForEpoch(epochNumber: EpochNumber): Promise<L2Block[]> {
1021
1086
  const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1022
1087
  const blocks: L2Block[] = [];
1023
1088
 
1024
1089
  // Walk the list of blocks backwards and filter by slots matching the requested epoch.
1025
1090
  // We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
1026
1091
  let block = await this.getBlock(await this.store.getSynchedL2BlockNumber());
1027
- const slot = (b: L2Block) => b.header.globalVariables.slotNumber.toBigInt();
1092
+ const slot = (b: L2Block) => b.header.globalVariables.slotNumber;
1028
1093
  while (block && slot(block) >= start) {
1029
1094
  if (slot(block) <= end) {
1030
1095
  blocks.push(block);
1031
1096
  }
1032
- block = await this.getBlock(block.number - 1);
1097
+ block = await this.getBlock(BlockNumber(block.number - 1));
1033
1098
  }
1034
1099
 
1035
1100
  return blocks.reverse();
1036
1101
  }
1037
1102
 
1038
- public async getBlockHeadersForEpoch(epochNumber: bigint): Promise<BlockHeader[]> {
1103
+ public async getBlockHeadersForEpoch(epochNumber: EpochNumber): Promise<BlockHeader[]> {
1039
1104
  const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1040
1105
  const blocks: BlockHeader[] = [];
1041
1106
 
@@ -1043,20 +1108,21 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1043
1108
  // We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
1044
1109
  let number = await this.store.getSynchedL2BlockNumber();
1045
1110
  let header = await this.getBlockHeader(number);
1046
- const slot = (b: BlockHeader) => b.globalVariables.slotNumber.toBigInt();
1111
+ const slot = (b: BlockHeader) => b.globalVariables.slotNumber;
1047
1112
  while (header && slot(header) >= start) {
1048
1113
  if (slot(header) <= end) {
1049
1114
  blocks.push(header);
1050
1115
  }
1051
- header = await this.getBlockHeader(--number);
1116
+ number = BlockNumber(number - 1);
1117
+ header = await this.getBlockHeader(number);
1052
1118
  }
1053
1119
  return blocks.reverse();
1054
1120
  }
1055
1121
 
1056
- public async isEpochComplete(epochNumber: bigint): Promise<boolean> {
1122
+ public async isEpochComplete(epochNumber: EpochNumber): Promise<boolean> {
1057
1123
  // The epoch is complete if the current L2 block is the last one in the epoch (or later)
1058
1124
  const header = await this.getBlockHeader('latest');
1059
- const slot = header?.globalVariables.slotNumber.toBigInt();
1125
+ const slot = header ? header.globalVariables.slotNumber : undefined;
1060
1126
  const [_startSlot, endSlot] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1061
1127
  if (slot && slot >= endSlot) {
1062
1128
  return true;
@@ -1086,6 +1152,107 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1086
1152
  return this.initialSyncComplete;
1087
1153
  }
1088
1154
 
1155
+ public async getPublishedCheckpoints(
1156
+ from: CheckpointNumber,
1157
+ limit: number,
1158
+ proven?: boolean,
1159
+ ): Promise<PublishedCheckpoint[]> {
1160
+ // TODO: Implement this properly. This only works when we have one block per checkpoint.
1161
+ const blocks = await this.getPublishedBlocks(BlockNumber(from), limit, proven);
1162
+ return blocks.map(b => b.toPublishedCheckpoint());
1163
+ }
1164
+
1165
+ public async getCheckpointByArchive(archive: Fr): Promise<Checkpoint | undefined> {
1166
+ // TODO: Implement this properly. This only works when we have one block per checkpoint.
1167
+ return (await this.getPublishedBlockByArchive(archive))?.block.toCheckpoint();
1168
+ }
1169
+
1170
+ public async getCheckpoints(from: CheckpointNumber, limit: number, proven?: boolean): Promise<Checkpoint[]> {
1171
+ const published = await this.getPublishedCheckpoints(from, limit, proven);
1172
+ return published.map(p => p.checkpoint);
1173
+ }
1174
+
1175
+ public async getCheckpoint(number: CheckpointNumber): Promise<Checkpoint | undefined> {
1176
+ if (number < 0) {
1177
+ number = await this.getSynchedCheckpointNumber();
1178
+ }
1179
+ if (number === 0) {
1180
+ return undefined;
1181
+ }
1182
+ const published = await this.getPublishedCheckpoints(number, 1);
1183
+ return published[0]?.checkpoint;
1184
+ }
1185
+
1186
+ public async getCheckpointHeader(number: CheckpointNumber | 'latest'): Promise<CheckpointHeader | undefined> {
1187
+ if (number === 'latest') {
1188
+ number = await this.getSynchedCheckpointNumber();
1189
+ }
1190
+ if (number === 0) {
1191
+ return undefined;
1192
+ }
1193
+ const checkpoint = await this.getCheckpoint(number);
1194
+ return checkpoint?.header;
1195
+ }
1196
+
1197
+ public getCheckpointNumber(): Promise<CheckpointNumber> {
1198
+ return this.getSynchedCheckpointNumber();
1199
+ }
1200
+
1201
+ public async getSynchedCheckpointNumber(): Promise<CheckpointNumber> {
1202
+ // TODO: Create store and apis for checkpoints.
1203
+ // Checkpoint number will no longer be the same as the block number once we support multiple blocks per checkpoint.
1204
+ return CheckpointNumber(await this.store.getSynchedL2BlockNumber());
1205
+ }
1206
+
1207
+ public async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
1208
+ // TODO: Create store and apis for checkpoints.
1209
+ // Proven checkpoint number will no longer be the same as the proven block number once we support multiple blocks per checkpoint.
1210
+ return CheckpointNumber(await this.store.getProvenL2BlockNumber());
1211
+ }
1212
+
1213
+ public setProvenCheckpointNumber(checkpointNumber: CheckpointNumber): Promise<void> {
1214
+ // TODO: Create store and apis for checkpoints.
1215
+ // Proven checkpoint number will no longer be the same as the proven block number once we support multiple blocks per checkpoint.
1216
+ return this.store.setProvenL2BlockNumber(BlockNumber.fromCheckpointNumber(checkpointNumber));
1217
+ }
1218
+
1219
+ public unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
1220
+ // TODO: Create store and apis for checkpoints.
1221
+ // This only works when we have one block per checkpoint.
1222
+ return this.store.unwindBlocks(BlockNumber.fromCheckpointNumber(from), checkpointsToUnwind);
1223
+ }
1224
+
1225
+ public getLastBlockNumberInCheckpoint(checkpointNumber: CheckpointNumber): Promise<BlockNumber> {
1226
+ // TODO: Create store and apis for checkpoints.
1227
+ // Checkpoint number will no longer be the same as the block number once we support multiple blocks per checkpoint.
1228
+ return Promise.resolve(BlockNumber.fromCheckpointNumber(checkpointNumber));
1229
+ }
1230
+
1231
+ public addCheckpoints(
1232
+ checkpoints: PublishedCheckpoint[],
1233
+ pendingChainValidationStatus?: ValidateBlockResult,
1234
+ ): Promise<boolean> {
1235
+ // TODO: Create store and apis for checkpoints.
1236
+ // This only works when we have one block per checkpoint.
1237
+ return this.store.addBlocks(
1238
+ checkpoints.map(p => PublishedL2Block.fromPublishedCheckpoint(p)),
1239
+ pendingChainValidationStatus,
1240
+ );
1241
+ }
1242
+
1243
+ public async getCheckpointsForEpoch(epochNumber: EpochNumber): Promise<Checkpoint[]> {
1244
+ // TODO: Create store and apis for checkpoints.
1245
+ // This only works when we have one block per checkpoint.
1246
+ const blocks = await this.getBlocksForEpoch(epochNumber);
1247
+ return blocks.map(b => b.toCheckpoint());
1248
+ }
1249
+
1250
+ public getL1ToL2MessagesForCheckpoint(checkpointNumber: CheckpointNumber): Promise<Fr[]> {
1251
+ // TODO: Create dedicated api for checkpoints.
1252
+ // This only works when we have one block per checkpoint.
1253
+ return this.getL1ToL2Messages(BlockNumber.fromCheckpointNumber(checkpointNumber));
1254
+ }
1255
+
1089
1256
  /**
1090
1257
  * Gets up to `limit` amount of L2 blocks starting from `from`.
1091
1258
  * @param from - Number of the first block to return (inclusive).
@@ -1093,12 +1260,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1093
1260
  * @param proven - If true, only return blocks that have been proven.
1094
1261
  * @returns The requested L2 blocks.
1095
1262
  */
1096
- public getBlocks(from: number, limit: number, proven?: boolean): Promise<L2Block[]> {
1263
+ public getBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<L2Block[]> {
1097
1264
  return this.getPublishedBlocks(from, limit, proven).then(blocks => blocks.map(b => b.block));
1098
1265
  }
1099
1266
 
1100
1267
  /** Equivalent to getBlocks but includes publish data. */
1101
- public async getPublishedBlocks(from: number, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
1268
+ public async getPublishedBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
1102
1269
  const limitWithProven = proven
1103
1270
  ? Math.min(limit, Math.max((await this.store.getProvenL2BlockNumber()) - from + 1, 0))
1104
1271
  : limit;
@@ -1126,7 +1293,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1126
1293
  * @param number - The block number to return.
1127
1294
  * @returns The requested L2 block.
1128
1295
  */
1129
- public async getBlock(number: number): Promise<L2Block | undefined> {
1296
+ public async getBlock(number: BlockNumber): Promise<L2Block | undefined> {
1130
1297
  // If the number provided is -ve, then return the latest block.
1131
1298
  if (number < 0) {
1132
1299
  number = await this.store.getSynchedL2BlockNumber();
@@ -1138,7 +1305,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1138
1305
  return publishedBlock?.block;
1139
1306
  }
1140
1307
 
1141
- public async getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
1308
+ public async getBlockHeader(number: BlockNumber | 'latest'): Promise<BlockHeader | undefined> {
1142
1309
  if (number === 'latest') {
1143
1310
  number = await this.store.getSynchedL2BlockNumber();
1144
1311
  }
@@ -1163,7 +1330,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1163
1330
  * @param limit - The maximum number of blocks to retrieve logs from.
1164
1331
  * @returns An array of private logs from the specified range of blocks.
1165
1332
  */
1166
- public getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
1333
+ public getPrivateLogs(from: BlockNumber, limit: number): Promise<PrivateLog[]> {
1167
1334
  return this.store.getPrivateLogs(from, limit);
1168
1335
  }
1169
1336
 
@@ -1199,16 +1366,16 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1199
1366
  * Gets the number of the latest L2 block processed by the block source implementation.
1200
1367
  * @returns The number of the latest L2 block processed by the block source implementation.
1201
1368
  */
1202
- public getBlockNumber(): Promise<number> {
1369
+ public getBlockNumber(): Promise<BlockNumber> {
1203
1370
  return this.store.getSynchedL2BlockNumber();
1204
1371
  }
1205
1372
 
1206
- public getProvenBlockNumber(): Promise<number> {
1373
+ public getProvenBlockNumber(): Promise<BlockNumber> {
1207
1374
  return this.store.getProvenL2BlockNumber();
1208
1375
  }
1209
1376
 
1210
1377
  /** Forcefully updates the last proven block number. Use for testing. */
1211
- public setProvenBlockNumber(blockNumber: number): Promise<void> {
1378
+ public setProvenBlockNumber(blockNumber: BlockNumber): Promise<void> {
1212
1379
  return this.store.setProvenL2BlockNumber(blockNumber);
1213
1380
  }
1214
1381
 
@@ -1241,7 +1408,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1241
1408
  * @param blockNumber - L2 block number to get messages for.
1242
1409
  * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found).
1243
1410
  */
1244
- getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
1411
+ getL1ToL2Messages(blockNumber: BlockNumber): Promise<Fr[]> {
1245
1412
  return this.store.getL1ToL2Messages(blockNumber);
1246
1413
  }
1247
1414
 
@@ -1283,7 +1450,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1283
1450
  // TODO(#13569): Compute proper finalized block number based on L1 finalized block.
1284
1451
  // We just force it 2 epochs worth of proven data for now.
1285
1452
  // NOTE: update end-to-end/src/e2e_epochs/epochs_empty_blocks.test.ts as that uses finalized blocks in computations
1286
- const finalizedBlockNumber = Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0);
1453
+ const finalizedBlockNumber = BlockNumber(Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0));
1287
1454
 
1288
1455
  const [latestBlockHeader, provenBlockHeader, finalizedBlockHeader] = await Promise.all([
1289
1456
  latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
@@ -1307,27 +1474,18 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1307
1474
  );
1308
1475
  }
1309
1476
 
1310
- const latestBlockHeaderHash = await latestBlockHeader?.hash();
1311
- const provenBlockHeaderHash = await provenBlockHeader?.hash();
1312
- const finalizedBlockHeaderHash = await finalizedBlockHeader?.hash();
1477
+ const latestBlockHeaderHash = (await latestBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1478
+ const provenBlockHeaderHash = (await provenBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1479
+ const finalizedBlockHeaderHash = (await finalizedBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1313
1480
 
1314
1481
  return {
1315
- latest: {
1316
- number: latestBlockNumber,
1317
- hash: latestBlockHeaderHash?.toString(),
1318
- } as L2BlockId,
1319
- proven: {
1320
- number: provenBlockNumber,
1321
- hash: provenBlockHeaderHash?.toString(),
1322
- } as L2BlockId,
1323
- finalized: {
1324
- number: finalizedBlockNumber,
1325
- hash: finalizedBlockHeaderHash?.toString(),
1326
- } as L2BlockId,
1482
+ latest: { number: latestBlockNumber, hash: latestBlockHeaderHash.toString() },
1483
+ proven: { number: provenBlockNumber, hash: provenBlockHeaderHash.toString() },
1484
+ finalized: { number: finalizedBlockNumber, hash: finalizedBlockHeaderHash.toString() },
1327
1485
  };
1328
1486
  }
1329
1487
 
1330
- public async rollbackTo(targetL2BlockNumber: number): Promise<void> {
1488
+ public async rollbackTo(targetL2BlockNumber: BlockNumber): Promise<void> {
1331
1489
  const currentBlocks = await this.getL2Tips();
1332
1490
  const currentL2Block = currentBlocks.latest.number;
1333
1491
  const currentProvenBlock = currentBlocks.proven.number;
@@ -1344,7 +1502,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1344
1502
  const targetL1BlockNumber = targetL2Block.l1.blockNumber;
1345
1503
  const targetL1BlockHash = await this.getL1BlockHash(targetL1BlockNumber);
1346
1504
  this.log.info(`Unwinding ${blocksToUnwind} blocks from L2 block ${currentL2Block}`);
1347
- await this.store.unwindBlocks(currentL2Block, blocksToUnwind);
1505
+ await this.store.unwindBlocks(BlockNumber(currentL2Block), blocksToUnwind);
1348
1506
  this.log.info(`Unwinding L1 to L2 messages to ${targetL2BlockNumber}`);
1349
1507
  await this.store.rollbackL1ToL2MessagesToL2Block(targetL2BlockNumber);
1350
1508
  this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
@@ -1352,7 +1510,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1352
1510
  await this.store.setMessageSynchedL1Block({ l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash });
1353
1511
  if (targetL2BlockNumber < currentProvenBlock) {
1354
1512
  this.log.info(`Clearing proven L2 block number`);
1355
- await this.store.setProvenL2BlockNumber(0);
1513
+ await this.store.setProvenL2BlockNumber(BlockNumber.ZERO);
1356
1514
  }
1357
1515
  // TODO(palla/reorg): Set the finalized block when we add support for it.
1358
1516
  // if (targetL2BlockNumber < currentFinalizedBlock) {
@@ -1400,7 +1558,7 @@ export class ArchiverStoreHelper
1400
1558
  * Extracts and stores contract classes out of ContractClassPublished events emitted by the class registry contract.
1401
1559
  * @param allLogs - All logs emitted in a bunch of blocks.
1402
1560
  */
1403
- async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: number, operation: Operation) {
1561
+ async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: BlockNumber, operation: Operation) {
1404
1562
  const contractClassPublishedEvents = allLogs
1405
1563
  .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
1406
1564
  .map(log => ContractClassPublishedEvent.fromLog(log));
@@ -1425,7 +1583,7 @@ export class ArchiverStoreHelper
1425
1583
  * Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
1426
1584
  * @param allLogs - All logs emitted in a bunch of blocks.
1427
1585
  */
1428
- async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: number, operation: Operation) {
1586
+ async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: BlockNumber, operation: Operation) {
1429
1587
  const contractInstances = allLogs
1430
1588
  .filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
1431
1589
  .map(log => ContractInstancePublishedEvent.fromLog(log))
@@ -1478,7 +1636,7 @@ export class ArchiverStoreHelper
1478
1636
  * @param _blockNum - The block number
1479
1637
  * @returns
1480
1638
  */
1481
- async #storeBroadcastedIndividualFunctions(allLogs: ContractClassLog[], _blockNum: number) {
1639
+ async #storeBroadcastedIndividualFunctions(allLogs: ContractClassLog[], _blockNum: BlockNumber) {
1482
1640
  // Filter out private and utility function broadcast events
1483
1641
  const privateFnEvents = allLogs
1484
1642
  .filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
@@ -1568,7 +1726,7 @@ export class ArchiverStoreHelper
1568
1726
  });
1569
1727
  }
1570
1728
 
1571
- public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
1729
+ public async unwindBlocks(from: BlockNumber, blocksToUnwind: number): Promise<boolean> {
1572
1730
  const last = await this.getSynchedL2BlockNumber();
1573
1731
  if (from != last) {
1574
1732
  throw new Error(`Cannot unwind blocks from block ${from} when the last block is ${last}`);
@@ -1578,7 +1736,7 @@ export class ArchiverStoreHelper
1578
1736
  }
1579
1737
 
1580
1738
  // from - blocksToUnwind = the new head, so + 1 for what we need to remove
1581
- const blocks = await this.getPublishedBlocks(from - blocksToUnwind + 1, blocksToUnwind);
1739
+ const blocks = await this.getPublishedBlocks(BlockNumber(from - blocksToUnwind + 1), blocksToUnwind);
1582
1740
 
1583
1741
  const opResults = await Promise.all([
1584
1742
  // Prune rolls back to the last proven block, which is by definition valid
@@ -1610,10 +1768,10 @@ export class ArchiverStoreHelper
1610
1768
  return opResults.every(Boolean);
1611
1769
  }
1612
1770
 
1613
- getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
1771
+ getPublishedBlocks(from: BlockNumber, limit: number): Promise<PublishedL2Block[]> {
1614
1772
  return this.store.getPublishedBlocks(from, limit);
1615
1773
  }
1616
- getPublishedBlock(number: number): Promise<PublishedL2Block | undefined> {
1774
+ getPublishedBlock(number: BlockNumber): Promise<PublishedL2Block | undefined> {
1617
1775
  return this.store.getPublishedBlock(number);
1618
1776
  }
1619
1777
  getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
@@ -1622,7 +1780,7 @@ export class ArchiverStoreHelper
1622
1780
  getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1623
1781
  return this.store.getPublishedBlockByArchive(archive);
1624
1782
  }
1625
- getBlockHeaders(from: number, limit: number): Promise<BlockHeader[]> {
1783
+ getBlockHeaders(from: BlockNumber, limit: number): Promise<BlockHeader[]> {
1626
1784
  return this.store.getBlockHeaders(from, limit);
1627
1785
  }
1628
1786
  getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
@@ -1640,13 +1798,13 @@ export class ArchiverStoreHelper
1640
1798
  addL1ToL2Messages(messages: InboxMessage[]): Promise<void> {
1641
1799
  return this.store.addL1ToL2Messages(messages);
1642
1800
  }
1643
- getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
1801
+ getL1ToL2Messages(blockNumber: BlockNumber): Promise<Fr[]> {
1644
1802
  return this.store.getL1ToL2Messages(blockNumber);
1645
1803
  }
1646
1804
  getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise<bigint | undefined> {
1647
1805
  return this.store.getL1ToL2MessageIndex(l1ToL2Message);
1648
1806
  }
1649
- getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
1807
+ getPrivateLogs(from: BlockNumber, limit: number): Promise<PrivateLog[]> {
1650
1808
  return this.store.getPrivateLogs(from, limit);
1651
1809
  }
1652
1810
  getLogsByTags(tags: Fr[], logsPerTag?: number): Promise<TxScopedL2Log[][]> {
@@ -1658,13 +1816,13 @@ export class ArchiverStoreHelper
1658
1816
  getContractClassLogs(filter: LogFilter): Promise<GetContractClassLogsResponse> {
1659
1817
  return this.store.getContractClassLogs(filter);
1660
1818
  }
1661
- getSynchedL2BlockNumber(): Promise<number> {
1819
+ getSynchedL2BlockNumber(): Promise<BlockNumber> {
1662
1820
  return this.store.getSynchedL2BlockNumber();
1663
1821
  }
1664
- getProvenL2BlockNumber(): Promise<number> {
1822
+ getProvenL2BlockNumber(): Promise<BlockNumber> {
1665
1823
  return this.store.getProvenL2BlockNumber();
1666
1824
  }
1667
- setProvenL2BlockNumber(l2BlockNumber: number): Promise<void> {
1825
+ setProvenL2BlockNumber(l2BlockNumber: BlockNumber): Promise<void> {
1668
1826
  return this.store.setProvenL2BlockNumber(l2BlockNumber);
1669
1827
  }
1670
1828
  setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
@@ -1700,7 +1858,7 @@ export class ArchiverStoreHelper
1700
1858
  estimateSize(): Promise<{ mappingSize: number; physicalFileSize: number; actualSize: number; numItems: number }> {
1701
1859
  return this.store.estimateSize();
1702
1860
  }
1703
- rollbackL1ToL2MessagesToL2Block(targetBlockNumber: number): Promise<void> {
1861
+ rollbackL1ToL2MessagesToL2Block(targetBlockNumber: BlockNumber): Promise<void> {
1704
1862
  return this.store.rollbackL1ToL2MessagesToL2Block(targetBlockNumber);
1705
1863
  }
1706
1864
  iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {