@aztec/archiver 0.0.1-commit.9b94fc1 → 0.0.1-commit.c7c42ec

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 (123) hide show
  1. package/dest/archiver/archiver.d.ts +96 -79
  2. package/dest/archiver/archiver.d.ts.map +1 -1
  3. package/dest/archiver/archiver.js +451 -214
  4. package/dest/archiver/archiver_store.d.ts +100 -47
  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 +1867 -386
  9. package/dest/archiver/config.d.ts +5 -4
  10. package/dest/archiver/config.d.ts.map +1 -1
  11. package/dest/archiver/config.js +10 -3
  12. package/dest/archiver/errors.d.ts +25 -1
  13. package/dest/archiver/errors.d.ts.map +1 -1
  14. package/dest/archiver/errors.js +37 -0
  15. package/dest/archiver/index.d.ts +2 -2
  16. package/dest/archiver/index.d.ts.map +1 -1
  17. package/dest/archiver/instrumentation.d.ts +3 -1
  18. package/dest/archiver/instrumentation.d.ts.map +1 -1
  19. package/dest/archiver/instrumentation.js +11 -0
  20. package/dest/archiver/kv_archiver_store/block_store.d.ts +51 -18
  21. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  22. package/dest/archiver/kv_archiver_store/block_store.js +324 -87
  23. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -2
  24. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  25. package/dest/archiver/kv_archiver_store/contract_class_store.js +1 -1
  26. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +2 -2
  27. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  28. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +46 -56
  29. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  30. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +65 -48
  31. package/dest/archiver/kv_archiver_store/log_store.d.ts +12 -16
  32. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  33. package/dest/archiver/kv_archiver_store/log_store.js +149 -84
  34. package/dest/archiver/kv_archiver_store/message_store.d.ts +6 -5
  35. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  36. package/dest/archiver/kv_archiver_store/message_store.js +15 -14
  37. package/dest/archiver/l1/bin/retrieve-calldata.d.ts +3 -0
  38. package/dest/archiver/l1/bin/retrieve-calldata.d.ts.map +1 -0
  39. package/dest/archiver/l1/bin/retrieve-calldata.js +149 -0
  40. package/dest/archiver/l1/calldata_retriever.d.ts +112 -0
  41. package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -0
  42. package/dest/archiver/l1/calldata_retriever.js +471 -0
  43. package/dest/archiver/l1/data_retrieval.d.ts +90 -0
  44. package/dest/archiver/l1/data_retrieval.d.ts.map +1 -0
  45. package/dest/archiver/{data_retrieval.js → l1/data_retrieval.js} +52 -107
  46. package/dest/archiver/l1/debug_tx.d.ts +19 -0
  47. package/dest/archiver/l1/debug_tx.d.ts.map +1 -0
  48. package/dest/archiver/l1/debug_tx.js +73 -0
  49. package/dest/archiver/l1/spire_proposer.d.ts +70 -0
  50. package/dest/archiver/l1/spire_proposer.d.ts.map +1 -0
  51. package/dest/archiver/l1/spire_proposer.js +157 -0
  52. package/dest/archiver/l1/trace_tx.d.ts +97 -0
  53. package/dest/archiver/l1/trace_tx.d.ts.map +1 -0
  54. package/dest/archiver/l1/trace_tx.js +91 -0
  55. package/dest/archiver/l1/types.d.ts +12 -0
  56. package/dest/archiver/l1/types.d.ts.map +1 -0
  57. package/dest/archiver/l1/types.js +3 -0
  58. package/dest/archiver/l1/validate_trace.d.ts +29 -0
  59. package/dest/archiver/l1/validate_trace.d.ts.map +1 -0
  60. package/dest/archiver/l1/validate_trace.js +150 -0
  61. package/dest/archiver/structs/inbox_message.d.ts +4 -4
  62. package/dest/archiver/structs/inbox_message.d.ts.map +1 -1
  63. package/dest/archiver/structs/inbox_message.js +6 -5
  64. package/dest/archiver/structs/published.d.ts +1 -2
  65. package/dest/archiver/structs/published.d.ts.map +1 -1
  66. package/dest/factory.d.ts +2 -2
  67. package/dest/factory.d.ts.map +1 -1
  68. package/dest/factory.js +4 -3
  69. package/dest/index.d.ts +2 -2
  70. package/dest/index.d.ts.map +1 -1
  71. package/dest/index.js +1 -1
  72. package/dest/test/mock_archiver.d.ts +16 -8
  73. package/dest/test/mock_archiver.d.ts.map +1 -1
  74. package/dest/test/mock_archiver.js +19 -14
  75. package/dest/test/mock_l1_to_l2_message_source.d.ts +7 -6
  76. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  77. package/dest/test/mock_l1_to_l2_message_source.js +10 -9
  78. package/dest/test/mock_l2_block_source.d.ts +18 -7
  79. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  80. package/dest/test/mock_l2_block_source.js +37 -23
  81. package/dest/test/mock_structs.d.ts +3 -2
  82. package/dest/test/mock_structs.d.ts.map +1 -1
  83. package/dest/test/mock_structs.js +9 -8
  84. package/package.json +17 -16
  85. package/src/archiver/archiver.ts +595 -296
  86. package/src/archiver/archiver_store.ts +113 -46
  87. package/src/archiver/archiver_store_test_suite.ts +1932 -353
  88. package/src/archiver/config.ts +15 -10
  89. package/src/archiver/errors.ts +64 -0
  90. package/src/archiver/index.ts +1 -1
  91. package/src/archiver/instrumentation.ts +14 -0
  92. package/src/archiver/kv_archiver_store/block_store.ts +442 -101
  93. package/src/archiver/kv_archiver_store/contract_class_store.ts +1 -1
  94. package/src/archiver/kv_archiver_store/contract_instance_store.ts +1 -1
  95. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +87 -71
  96. package/src/archiver/kv_archiver_store/log_store.ts +209 -99
  97. package/src/archiver/kv_archiver_store/message_store.ts +21 -18
  98. package/src/archiver/l1/README.md +98 -0
  99. package/src/archiver/l1/bin/retrieve-calldata.ts +182 -0
  100. package/src/archiver/l1/calldata_retriever.ts +641 -0
  101. package/src/archiver/{data_retrieval.ts → l1/data_retrieval.ts} +101 -165
  102. package/src/archiver/l1/debug_tx.ts +99 -0
  103. package/src/archiver/l1/spire_proposer.ts +160 -0
  104. package/src/archiver/l1/trace_tx.ts +128 -0
  105. package/src/archiver/l1/types.ts +13 -0
  106. package/src/archiver/l1/validate_trace.ts +211 -0
  107. package/src/archiver/structs/inbox_message.ts +8 -8
  108. package/src/archiver/structs/published.ts +0 -1
  109. package/src/factory.ts +4 -3
  110. package/src/index.ts +1 -1
  111. package/src/test/fixtures/debug_traceTransaction-multicall3.json +88 -0
  112. package/src/test/fixtures/debug_traceTransaction-multiplePropose.json +153 -0
  113. package/src/test/fixtures/debug_traceTransaction-proxied.json +122 -0
  114. package/src/test/fixtures/trace_transaction-multicall3.json +65 -0
  115. package/src/test/fixtures/trace_transaction-multiplePropose.json +319 -0
  116. package/src/test/fixtures/trace_transaction-proxied.json +128 -0
  117. package/src/test/fixtures/trace_transaction-randomRevert.json +216 -0
  118. package/src/test/mock_archiver.ts +22 -16
  119. package/src/test/mock_l1_to_l2_message_source.ts +10 -9
  120. package/src/test/mock_l2_block_source.ts +45 -25
  121. package/src/test/mock_structs.ts +10 -9
  122. package/dest/archiver/data_retrieval.d.ts +0 -79
  123. package/dest/archiver/data_retrieval.d.ts.map +0 -1
@@ -1,24 +1,23 @@
1
- import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
1
+ import type { BlobClientInterface } from '@aztec/blob-client/client';
2
+ import { GENESIS_BLOCK_HEADER_HASH } from '@aztec/constants';
2
3
  import { EpochCache } from '@aztec/epoch-cache';
3
- import {
4
- BlockTagTooOldError,
5
- InboxContract,
6
- type L1BlockId,
7
- RollupContract,
8
- type ViemPublicClient,
9
- createEthereumChain,
10
- } from '@aztec/ethereum';
4
+ import { createEthereumChain } from '@aztec/ethereum/chain';
5
+ import { BlockTagTooOldError, InboxContract, RollupContract } from '@aztec/ethereum/contracts';
6
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
7
+ import type { L1BlockId } from '@aztec/ethereum/l1-types';
8
+ import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
11
9
  import { maxBigint } from '@aztec/foundation/bigint';
12
- import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
10
+ import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
13
11
  import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
14
12
  import { merge, pick } from '@aztec/foundation/collection';
13
+ import { Fr } from '@aztec/foundation/curves/bn254';
15
14
  import type { EthAddress } from '@aztec/foundation/eth-address';
16
- import { Fr } from '@aztec/foundation/fields';
17
15
  import { type Logger, createLogger } from '@aztec/foundation/log';
18
16
  import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
19
17
  import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
20
18
  import { count } from '@aztec/foundation/string';
21
19
  import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
20
+ import { isDefined } from '@aztec/foundation/types';
22
21
  import type { CustomRange } from '@aztec/kv-store';
23
22
  import { RollupAbi } from '@aztec/l1-artifacts';
24
23
  import {
@@ -34,14 +33,17 @@ import type { FunctionSelector } from '@aztec/stdlib/abi';
34
33
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
35
34
  import {
36
35
  type ArchiverEmitter,
36
+ CheckpointedL2Block,
37
+ CommitteeAttestation,
37
38
  L2Block,
38
- type L2BlockId,
39
+ L2BlockNew,
40
+ type L2BlockSink,
39
41
  type L2BlockSource,
40
42
  L2BlockSourceEvents,
41
43
  type L2Tips,
42
44
  PublishedL2Block,
43
45
  } from '@aztec/stdlib/block';
44
- import type { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
46
+ import { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
45
47
  import {
46
48
  type ContractClassPublic,
47
49
  type ContractDataSource,
@@ -62,8 +64,16 @@ import {
62
64
  } from '@aztec/stdlib/epoch-helpers';
63
65
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
64
66
  import type { L2LogsSource } from '@aztec/stdlib/interfaces/server';
65
- import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs';
66
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
67
+ import {
68
+ ContractClassLog,
69
+ type LogFilter,
70
+ type PrivateLog,
71
+ type PublicLog,
72
+ type SiloedTag,
73
+ Tag,
74
+ TxScopedL2Log,
75
+ } from '@aztec/stdlib/logs';
76
+ import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
67
77
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
68
78
  import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
69
79
  import type { UInt64 } from '@aztec/stdlib/types';
@@ -81,14 +91,16 @@ import { type GetContractReturnType, type Hex, createPublicClient, fallback, htt
81
91
 
82
92
  import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
83
93
  import type { ArchiverConfig } from './config.js';
94
+ import { InitialCheckpointNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
95
+ import { ArchiverInstrumentation } from './instrumentation.js';
96
+ import type { CheckpointData } from './kv_archiver_store/block_store.js';
84
97
  import {
85
98
  retrieveCheckpointsFromRollup,
86
99
  retrieveL1ToL2Message,
87
100
  retrieveL1ToL2Messages,
88
101
  retrievedToPublishedCheckpoint,
89
- } from './data_retrieval.js';
90
- import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './errors.js';
91
- import { ArchiverInstrumentation } from './instrumentation.js';
102
+ } from './l1/data_retrieval.js';
103
+ import { validateAndLogTraceAvailability } from './l1/validate_trace.js';
92
104
  import type { InboxMessage } from './structs/inbox_message.js';
93
105
  import { type ValidateBlockResult, validateCheckpointAttestations } from './validation.js';
94
106
 
@@ -97,9 +109,16 @@ import { type ValidateBlockResult, validateCheckpointAttestations } from './vali
97
109
  */
98
110
  export type ArchiveSource = L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource;
99
111
 
112
+ /** Request to add a block to the archiver, queued for processing by the sync loop. */
113
+ type AddBlockRequest = {
114
+ block: L2BlockNew;
115
+ resolve: () => void;
116
+ reject: (err: Error) => void;
117
+ };
118
+
100
119
  export type ArchiverDeps = {
101
120
  telemetry?: TelemetryClient;
102
- blobSinkClient: BlobSinkClientInterface;
121
+ blobClient: BlobClientInterface;
103
122
  epochCache?: EpochCache;
104
123
  dateProvider?: DateProvider;
105
124
  };
@@ -110,13 +129,14 @@ function mapArchiverConfig(config: Partial<ArchiverConfig>) {
110
129
  batchSize: config.archiverBatchSize,
111
130
  skipValidateBlockAttestations: config.skipValidateBlockAttestations,
112
131
  maxAllowedEthClientDriftSeconds: config.maxAllowedEthClientDriftSeconds,
132
+ ethereumAllowNoDebugHosts: config.ethereumAllowNoDebugHosts,
113
133
  };
114
134
  }
115
135
 
116
136
  type RollupStatus = {
117
- provenCheckpointNumber: number;
137
+ provenCheckpointNumber: CheckpointNumber;
118
138
  provenArchive: Hex;
119
- pendingCheckpointNumber: number;
139
+ pendingCheckpointNumber: CheckpointNumber;
120
140
  pendingArchive: Hex;
121
141
  validationResult: ValidateBlockResult | undefined;
122
142
  lastRetrievedCheckpoint?: PublishedCheckpoint;
@@ -128,7 +148,10 @@ type RollupStatus = {
128
148
  * Responsible for handling robust L1 polling so that other components do not need to
129
149
  * concern themselves with it.
130
150
  */
131
- export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implements ArchiveSource, Traceable {
151
+ export class Archiver
152
+ extends (EventEmitter as new () => ArchiverEmitter)
153
+ implements ArchiveSource, L2BlockSink, Traceable
154
+ {
132
155
  /** A loop in which we will be continually fetching new checkpoints. */
133
156
  private runningPromise: RunningPromise;
134
157
 
@@ -142,11 +165,15 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
142
165
  private initialSyncComplete: boolean = false;
143
166
  private initialSyncPromise: PromiseWithResolvers<void>;
144
167
 
168
+ /** Queue of blocks to be added to the store, processed by the sync loop. */
169
+ private blockQueue: AddBlockRequest[] = [];
170
+
145
171
  public readonly tracer: Tracer;
146
172
 
147
173
  /**
148
174
  * Creates a new instance of the Archiver.
149
175
  * @param publicClient - A client for interacting with the Ethereum node.
176
+ * @param debugClient - A client for interacting with the Ethereum node for debug/trace methods.
150
177
  * @param rollupAddress - Ethereum address of the rollup contract.
151
178
  * @param inboxAddress - Ethereum address of the inbox contract.
152
179
  * @param registryAddress - Ethereum address of the registry contract.
@@ -156,15 +183,20 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
156
183
  */
157
184
  constructor(
158
185
  private readonly publicClient: ViemPublicClient,
159
- private readonly l1Addresses: { rollupAddress: EthAddress; inboxAddress: EthAddress; registryAddress: EthAddress },
186
+ private readonly debugClient: ViemPublicDebugClient,
187
+ private readonly l1Addresses: Pick<
188
+ L1ContractAddresses,
189
+ 'rollupAddress' | 'inboxAddress' | 'registryAddress' | 'governanceProposerAddress' | 'slashFactoryAddress'
190
+ > & { slashingProposerAddress: EthAddress },
160
191
  readonly dataStore: ArchiverDataStore,
161
192
  private config: {
162
193
  pollingIntervalMs: number;
163
194
  batchSize: number;
164
195
  skipValidateBlockAttestations?: boolean;
165
196
  maxAllowedEthClientDriftSeconds: number;
197
+ ethereumAllowNoDebugHosts?: boolean;
166
198
  },
167
- private readonly blobSinkClient: BlobSinkClientInterface,
199
+ private readonly blobClient: BlobClientInterface,
168
200
  private readonly epochCache: EpochCache,
169
201
  private readonly dateProvider: DateProvider,
170
202
  private readonly instrumentation: ArchiverInstrumentation,
@@ -206,18 +238,28 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
206
238
  const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
207
239
  const publicClient = createPublicClient({
208
240
  chain: chain.chainInfo,
209
- transport: fallback(config.l1RpcUrls.map(url => http(url))),
241
+ transport: fallback(config.l1RpcUrls.map(url => http(url, { batch: false }))),
210
242
  pollingInterval: config.viemPollingIntervalMS,
211
243
  });
212
244
 
245
+ // Create debug client using debug RPC URLs if available, otherwise fall back to regular RPC URLs
246
+ const debugRpcUrls = config.l1DebugRpcUrls.length > 0 ? config.l1DebugRpcUrls : config.l1RpcUrls;
247
+ const debugClient = createPublicClient({
248
+ chain: chain.chainInfo,
249
+ transport: fallback(debugRpcUrls.map(url => http(url, { batch: false }))),
250
+ pollingInterval: config.viemPollingIntervalMS,
251
+ }) as ViemPublicDebugClient;
252
+
213
253
  const rollup = new RollupContract(publicClient, config.l1Contracts.rollupAddress);
214
254
 
215
- const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, genesisArchiveRoot] = await Promise.all([
216
- rollup.getL1StartBlock(),
217
- rollup.getL1GenesisTime(),
218
- rollup.getProofSubmissionEpochs(),
219
- rollup.getGenesisArchiveTreeRoot(),
220
- ] as const);
255
+ const [l1StartBlock, l1GenesisTime, proofSubmissionEpochs, genesisArchiveRoot, slashingProposerAddress] =
256
+ await Promise.all([
257
+ rollup.getL1StartBlock(),
258
+ rollup.getL1GenesisTime(),
259
+ rollup.getProofSubmissionEpochs(),
260
+ rollup.getGenesisArchiveTreeRoot(),
261
+ rollup.getSlashingProposerAddress(),
262
+ ] as const);
221
263
 
222
264
  const l1StartBlockHash = await publicClient
223
265
  .getBlock({ blockNumber: l1StartBlock, includeTransactions: false })
@@ -237,7 +279,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
237
279
  };
238
280
 
239
281
  const opts = merge(
240
- { pollingIntervalMs: 10_000, batchSize: 100, maxAllowedEthClientDriftSeconds: 300 },
282
+ {
283
+ pollingIntervalMs: 10_000,
284
+ batchSize: 100,
285
+ maxAllowedEthClientDriftSeconds: 300,
286
+ ethereumAllowNoDebugHosts: false,
287
+ },
241
288
  mapArchiverConfig(config),
242
289
  );
243
290
 
@@ -246,10 +293,11 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
246
293
 
247
294
  const archiver = new Archiver(
248
295
  publicClient,
249
- config.l1Contracts,
296
+ debugClient,
297
+ { ...config.l1Contracts, slashingProposerAddress },
250
298
  archiverStore,
251
299
  opts,
252
- deps.blobSinkClient,
300
+ deps.blobClient,
253
301
  epochCache,
254
302
  deps.dateProvider ?? new DateProvider(),
255
303
  await ArchiverInstrumentation.new(telemetry, () => archiverStore.estimateSize()),
@@ -273,16 +321,17 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
273
321
  throw new Error('Archiver is already running');
274
322
  }
275
323
 
276
- await this.blobSinkClient.testSources();
324
+ await this.blobClient.testSources();
277
325
  await this.testEthereumNodeSynced();
326
+ await validateAndLogTraceAvailability(this.debugClient, this.config.ethereumAllowNoDebugHosts ?? false);
278
327
 
279
328
  // Log initial state for the archiver
280
329
  const { l1StartBlock } = this.l1constants;
281
330
  const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
282
- const currentL2Block = await this.getBlockNumber();
331
+ const currentL2Checkpoint = await this.getSynchedCheckpointNumber();
283
332
  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 },
333
+ `Starting archiver sync to rollup contract ${this.l1Addresses.rollupAddress.toString()} from L1 block ${blocksSynchedTo} and L2 checkpoint ${currentL2Checkpoint}`,
334
+ { blocksSynchedTo, messagesSynchedTo, currentL2Checkpoint },
286
335
  );
287
336
 
288
337
  // Start sync loop, and return the wait for initial sync if we are asked to block until synced
@@ -296,6 +345,51 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
296
345
  return this.runningPromise.trigger();
297
346
  }
298
347
 
348
+ /**
349
+ * Queues a block to be added to the archiver store and triggers processing.
350
+ * The block will be processed by the sync loop.
351
+ * Implements the L2BlockSink interface.
352
+ * @param block - The L2 block to add.
353
+ * @returns A promise that resolves when the block has been added to the store, or rejects on error.
354
+ */
355
+ public addBlock(block: L2BlockNew): Promise<void> {
356
+ return new Promise<void>((resolve, reject) => {
357
+ this.blockQueue.push({ block, resolve, reject });
358
+ this.log.debug(`Queued block ${block.number} for processing`);
359
+ // Trigger an immediate sync, but don't wait for it - the promise resolves when the block is processed
360
+ this.syncImmediate().catch(err => {
361
+ this.log.error(`Sync immediate call failed: ${err}`);
362
+ });
363
+ });
364
+ }
365
+
366
+ /**
367
+ * Processes all queued blocks, adding them to the store.
368
+ * Called at the beginning of each sync iteration.
369
+ * Blocks are processed in the order they were queued.
370
+ */
371
+ private async processQueuedBlocks(): Promise<void> {
372
+ if (this.blockQueue.length === 0) {
373
+ return;
374
+ }
375
+
376
+ // Take all blocks from the queue
377
+ const queuedItems = this.blockQueue.splice(0, this.blockQueue.length);
378
+ this.log.debug(`Processing ${queuedItems.length} queued block(s)`);
379
+
380
+ // Process each block individually to properly resolve/reject each promise
381
+ for (const { block, resolve, reject } of queuedItems) {
382
+ try {
383
+ await this.store.addBlocks([block]);
384
+ this.log.debug(`Added block ${block.number} to store`);
385
+ resolve();
386
+ } catch (err: any) {
387
+ this.log.error(`Failed to add block ${block.number} to store: ${err.message}`);
388
+ reject(err);
389
+ }
390
+ }
391
+ }
392
+
299
393
  public waitForInitialSync() {
300
394
  return this.initialSyncPromise.promise;
301
395
  }
@@ -315,11 +409,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
315
409
  }
316
410
  }
317
411
 
318
- /**
319
- * Fetches logs from L1 contracts and processes them.
320
- */
321
- @trackSpan('Archiver.sync')
322
- private async sync() {
412
+ private async syncFromL1() {
323
413
  /**
324
414
  * We keep track of three "pointers" to L1 blocks:
325
415
  * 1. the last L1 block that published an L2 block
@@ -405,7 +495,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
405
495
  // past it, since otherwise we'll keep downloading it and reprocessing it on every iteration until
406
496
  // we get a valid checkpoint to advance the syncpoint.
407
497
  if (!rollupStatus.validationResult?.valid && rollupStatus.lastL1BlockWithCheckpoint !== undefined) {
408
- await this.store.setBlockSynchedL1BlockNumber(rollupStatus.lastL1BlockWithCheckpoint);
498
+ await this.store.setCheckpointSynchedL1BlockNumber(rollupStatus.lastL1BlockWithCheckpoint);
409
499
  }
410
500
 
411
501
  // And lastly we check if we are missing any checkpoints behind us due to a possible L1 reorg.
@@ -439,6 +529,17 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
439
529
  }
440
530
  }
441
531
 
532
+ /**
533
+ * Fetches logs from L1 contracts and processes them.
534
+ */
535
+ @trackSpan('Archiver.sync')
536
+ private async sync() {
537
+ // Process any queued blocks first, before doing L1 sync
538
+ await this.processQueuedBlocks();
539
+ // Now perform L1 sync
540
+ await this.syncFromL1();
541
+ }
542
+
442
543
  /** Queries the rollup contract on whether a prune can be executed on the immediate next L1 block. */
443
544
  private async canPrune(currentL1BlockNumber: bigint, currentL1Timestamp: bigint) {
444
545
  const time = (currentL1Timestamp ?? 0n) + BigInt(this.l1constants.ethereumSlotDuration);
@@ -455,7 +556,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
455
556
 
456
557
  /** Checks if there'd be a reorg for the next checkpoint submission and start pruning now. */
457
558
  private async handleEpochPrune(
458
- provenCheckpointNumber: number,
559
+ provenCheckpointNumber: CheckpointNumber,
459
560
  currentL1BlockNumber: bigint,
460
561
  currentL1Timestamp: bigint,
461
562
  ) {
@@ -465,9 +566,9 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
465
566
 
466
567
  if (canPrune) {
467
568
  const timer = new Timer();
468
- const pruneFrom = provenCheckpointNumber + 1;
569
+ const pruneFrom = CheckpointNumber(provenCheckpointNumber + 1);
469
570
 
470
- const header = await this.getCheckpointHeader(Number(pruneFrom));
571
+ const header = await this.getCheckpointHeader(pruneFrom);
471
572
  if (header === undefined) {
472
573
  throw new Error(`Missing checkpoint header ${pruneFrom}`);
473
574
  }
@@ -477,13 +578,26 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
477
578
 
478
579
  const checkpointsToUnwind = localPendingCheckpointNumber - provenCheckpointNumber;
479
580
 
480
- const checkpoints = await this.getCheckpoints(Number(provenCheckpointNumber) + 1, Number(checkpointsToUnwind));
581
+ const checkpointPromises = Array.from({ length: checkpointsToUnwind })
582
+ .fill(0)
583
+ .map((_, i) => this.store.getCheckpointData(CheckpointNumber(i + pruneFrom)));
584
+ const checkpoints = await Promise.all(checkpointPromises);
585
+
586
+ const blockPromises = await Promise.all(
587
+ checkpoints
588
+ .filter(isDefined)
589
+ .map(cp => this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber))),
590
+ );
591
+ const newBlocks = blockPromises.filter(isDefined).flat();
592
+
593
+ // TODO(pw/mbps): Don't convert to legacy blocks here
594
+ const blocks: L2Block[] = (await Promise.all(newBlocks.map(x => this.getBlock(x.number)))).filter(isDefined);
481
595
 
482
596
  // Emit an event for listening services to react to the chain prune
483
597
  this.emit(L2BlockSourceEvents.L2PruneDetected, {
484
598
  type: L2BlockSourceEvents.L2PruneDetected,
485
599
  epochNumber: pruneFromEpochNumber,
486
- blocks: checkpoints.flatMap(c => L2Block.fromCheckpoint(c)),
600
+ blocks,
487
601
  });
488
602
 
489
603
  this.log.debug(
@@ -594,7 +708,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
594
708
  // Log stats for messages retrieved (if any).
595
709
  if (messageCount > 0) {
596
710
  this.log.info(
597
- `Retrieved ${messageCount} new L1 to L2 messages up to message with index ${lastMessage?.index} for L2 block ${lastMessage?.l2BlockNumber}`,
711
+ `Retrieved ${messageCount} new L1 to L2 messages up to message with index ${lastMessage?.index} for checkpoint ${lastMessage?.checkpointNumber}`,
598
712
  { lastMessage, messageCount },
599
713
  );
600
714
  }
@@ -681,9 +795,9 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
681
795
  rollupPendingCheckpointNumber,
682
796
  pendingArchive,
683
797
  archiveForLocalPendingCheckpointNumber,
684
- ] = await this.rollup.status(BigInt(localPendingCheckpointNumber), { blockNumber: currentL1BlockNumber });
685
- const provenCheckpointNumber = Number(rollupProvenCheckpointNumber);
686
- const pendingCheckpointNumber = Number(rollupPendingCheckpointNumber);
798
+ ] = await this.rollup.status(localPendingCheckpointNumber, { blockNumber: currentL1BlockNumber });
799
+ const provenCheckpointNumber = CheckpointNumber.fromBigInt(rollupProvenCheckpointNumber);
800
+ const pendingCheckpointNumber = CheckpointNumber.fromBigInt(rollupPendingCheckpointNumber);
687
801
  const rollupStatus = {
688
802
  provenCheckpointNumber,
689
803
  provenArchive,
@@ -711,17 +825,18 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
711
825
  }
712
826
  }
713
827
 
714
- const localCheckpointForDestinationProvenCheckpointNumber = await this.getCheckpoint(provenCheckpointNumber);
828
+ const localCheckpointForDestinationProvenCheckpointNumber =
829
+ await this.store.getCheckpointData(provenCheckpointNumber);
715
830
 
716
831
  // 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
832
  // synched checkpoint when requesting L2Tips from the archiver. This is the only place where the proven checkpoint is set.
718
833
  const synched = await this.getSynchedCheckpointNumber();
719
834
  if (
720
835
  localCheckpointForDestinationProvenCheckpointNumber &&
721
- synched < localCheckpointForDestinationProvenCheckpointNumber.number
836
+ synched < localCheckpointForDestinationProvenCheckpointNumber.checkpointNumber
722
837
  ) {
723
838
  this.log.error(
724
- `Hit local checkpoint greater than last synched checkpoint: ${localCheckpointForDestinationProvenCheckpointNumber.number} > ${synched}`,
839
+ `Hit local checkpoint greater than last synched checkpoint: ${localCheckpointForDestinationProvenCheckpointNumber.checkpointNumber} > ${synched}`,
725
840
  );
726
841
  }
727
842
 
@@ -731,7 +846,6 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
731
846
  }`,
732
847
  );
733
848
 
734
- const lastProvenBlockNumber = await this.getLastBlockNumberInCheckpoint(provenCheckpointNumber);
735
849
  if (
736
850
  localCheckpointForDestinationProvenCheckpointNumber &&
737
851
  provenArchive === localCheckpointForDestinationProvenCheckpointNumber.archive.root.toString()
@@ -744,25 +858,29 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
744
858
  });
745
859
  const provenSlotNumber = localCheckpointForDestinationProvenCheckpointNumber.header.slotNumber;
746
860
  const provenEpochNumber: EpochNumber = getEpochAtSlot(provenSlotNumber, this.l1constants);
861
+ const lastBlockNumberInCheckpoint =
862
+ localCheckpointForDestinationProvenCheckpointNumber.startBlock +
863
+ localCheckpointForDestinationProvenCheckpointNumber.numBlocks -
864
+ 1;
747
865
 
748
866
  this.emit(L2BlockSourceEvents.L2BlockProven, {
749
867
  type: L2BlockSourceEvents.L2BlockProven,
750
- blockNumber: BigInt(lastProvenBlockNumber),
868
+ blockNumber: BlockNumber(lastBlockNumberInCheckpoint),
751
869
  slotNumber: provenSlotNumber,
752
870
  epochNumber: provenEpochNumber,
753
871
  });
872
+ this.instrumentation.updateLastProvenBlock(lastBlockNumberInCheckpoint);
754
873
  } else {
755
874
  this.log.trace(`Proven checkpoint ${provenCheckpointNumber} already stored.`);
756
875
  }
757
876
  }
758
- this.instrumentation.updateLastProvenBlock(lastProvenBlockNumber);
759
877
  };
760
878
 
761
879
  // This is an edge case that we only hit if there are no proposed checkpoints.
762
880
  // If we have 0 checkpoints locally and there are no checkpoints onchain there is nothing to do.
763
881
  const noCheckpoints = localPendingCheckpointNumber === 0 && pendingCheckpointNumber === 0;
764
882
  if (noCheckpoints) {
765
- await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
883
+ await this.store.setCheckpointSynchedL1BlockNumber(currentL1BlockNumber);
766
884
  this.log.debug(
767
885
  `No checkpoints to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}, no checkpoints on chain`,
768
886
  );
@@ -774,7 +892,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
774
892
  // Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
775
893
  // are any state that could be impacted by it. If we have no checkpoints, there is no impact.
776
894
  if (localPendingCheckpointNumber > 0) {
777
- const localPendingCheckpoint = await this.getCheckpoint(localPendingCheckpointNumber);
895
+ const localPendingCheckpoint = await this.store.getCheckpointData(localPendingCheckpointNumber);
778
896
  if (localPendingCheckpoint === undefined) {
779
897
  throw new Error(`Missing checkpoint ${localPendingCheckpointNumber}`);
780
898
  }
@@ -807,14 +925,14 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
807
925
 
808
926
  let tipAfterUnwind = localPendingCheckpointNumber;
809
927
  while (true) {
810
- const candidateCheckpoint = await this.getCheckpoint(tipAfterUnwind);
928
+ const candidateCheckpoint = await this.store.getCheckpointData(tipAfterUnwind);
811
929
  if (candidateCheckpoint === undefined) {
812
930
  break;
813
931
  }
814
932
 
815
- const archiveAtContract = await this.rollup.archiveAt(BigInt(candidateCheckpoint.number));
933
+ const archiveAtContract = await this.rollup.archiveAt(candidateCheckpoint.checkpointNumber);
816
934
  this.log.trace(
817
- `Checking local checkpoint ${candidateCheckpoint.number} with archive ${candidateCheckpoint.archive.root}`,
935
+ `Checking local checkpoint ${candidateCheckpoint.checkpointNumber} with archive ${candidateCheckpoint.archive.root}`,
818
936
  {
819
937
  archiveAtContract,
820
938
  archiveLocal: candidateCheckpoint.archive.root.toString(),
@@ -849,14 +967,18 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
849
967
 
850
968
  this.log.trace(`Retrieving checkpoints from L1 block ${searchStartBlock} to ${searchEndBlock}`);
851
969
 
852
- // TODO(md): Retrieve from blob sink then from consensus client, then from peers
970
+ // TODO(md): Retrieve from blob client then from consensus client, then from peers
853
971
  const retrievedCheckpoints = await retrieveCheckpointsFromRollup(
854
972
  this.rollup.getContract() as GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
855
973
  this.publicClient,
856
- this.blobSinkClient,
974
+ this.debugClient,
975
+ this.blobClient,
857
976
  searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
858
977
  searchEndBlock,
978
+ this.l1Addresses,
979
+ this.instrumentation,
859
980
  this.log,
981
+ !this.initialSyncComplete, // isHistoricalSync
860
982
  );
861
983
 
862
984
  if (retrievedCheckpoints.length === 0) {
@@ -916,6 +1038,25 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
916
1038
  continue;
917
1039
  }
918
1040
 
1041
+ // Check the inHash of the checkpoint against the l1->l2 messages.
1042
+ // The messages should've been synced up to the currentL1BlockNumber and must be available for the published
1043
+ // checkpoints we just retrieved.
1044
+ const l1ToL2Messages = await this.getL1ToL2Messages(published.checkpoint.number);
1045
+ const computedInHash = computeInHashFromL1ToL2Messages(l1ToL2Messages);
1046
+ const publishedInHash = published.checkpoint.header.contentCommitment.inHash;
1047
+ if (!computedInHash.equals(publishedInHash)) {
1048
+ this.log.fatal(`Mismatch inHash for checkpoint ${published.checkpoint.number}`, {
1049
+ checkpointHash: published.checkpoint.hash(),
1050
+ l1BlockNumber: published.l1.blockNumber,
1051
+ computedInHash,
1052
+ publishedInHash,
1053
+ });
1054
+ // Throwing an error since this is most likely caused by a bug.
1055
+ throw new Error(
1056
+ `Mismatch inHash for checkpoint ${published.checkpoint.number}. Expected ${computedInHash} but got ${publishedInHash}`,
1057
+ );
1058
+ }
1059
+
919
1060
  validCheckpoints.push(published);
920
1061
  this.log.debug(
921
1062
  `Ingesting new checkpoint ${published.checkpoint.number} with ${published.checkpoint.blocks.length} blocks`,
@@ -937,19 +1078,18 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
937
1078
  validCheckpoints.flatMap(c => c.checkpoint.blocks),
938
1079
  );
939
1080
  } catch (err) {
940
- if (err instanceof InitialBlockNumberNotSequentialError) {
941
- const { previousBlockNumber, newBlockNumber } = err;
942
- const previousBlock = previousBlockNumber
943
- ? await this.store.getPublishedBlock(previousBlockNumber)
1081
+ if (err instanceof InitialCheckpointNumberNotSequentialError) {
1082
+ const { previousCheckpointNumber, newCheckpointNumber } = err;
1083
+ const previousCheckpoint = previousCheckpointNumber
1084
+ ? await this.store.getCheckpointData(CheckpointNumber(previousCheckpointNumber))
944
1085
  : undefined;
945
- const updatedL1SyncPoint = previousBlock?.l1.blockNumber ?? this.l1constants.l1StartBlock;
1086
+ const updatedL1SyncPoint = previousCheckpoint?.l1.blockNumber ?? this.l1constants.l1StartBlock;
946
1087
  await this.store.setBlockSynchedL1BlockNumber(updatedL1SyncPoint);
947
1088
  this.log.warn(
948
- `Attempting to insert block ${newBlockNumber} with previous block ${previousBlockNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
1089
+ `Attempting to insert checkpoint ${newCheckpointNumber} with previous block ${previousCheckpointNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`,
949
1090
  {
950
- previousBlockNumber,
951
- previousBlockHash: await previousBlock?.block.hash(),
952
- newBlockNumber,
1091
+ previousCheckpointNumber,
1092
+ newCheckpointNumber,
953
1093
  updatedL1SyncPoint,
954
1094
  },
955
1095
  );
@@ -969,7 +1109,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
969
1109
  });
970
1110
  }
971
1111
  lastRetrievedCheckpoint = validCheckpoints.at(-1) ?? lastRetrievedCheckpoint;
972
- lastL1BlockWithCheckpoint = publishedCheckpoints.at(-1)?.l1.blockNumber ?? lastL1BlockWithCheckpoint;
1112
+ lastL1BlockWithCheckpoint = retrievedCheckpoints.at(-1)?.l1.blockNumber ?? lastL1BlockWithCheckpoint;
973
1113
  } while (searchEndBlock < currentL1BlockNumber);
974
1114
 
975
1115
  // Important that we update AFTER inserting the blocks.
@@ -994,13 +1134,16 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
994
1134
  // We suspect an L1 reorg that added checkpoints *behind* us. If that is the case, it must have happened between
995
1135
  // the last checkpoint we saw and the current one, so we reset the last synched L1 block number. In the edge case
996
1136
  // 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)
1001
- : undefined);
1002
- const targetL1BlockNumber = latestLocalCheckpoint?.l1.blockNumber ?? maxBigint(currentL1BlockNumber - 64n, 0n);
1003
- const latestLocalCheckpointArchive = latestLocalCheckpoint?.checkpoint.archive.root.toString();
1137
+ let latestLocalCheckpointArchive: string | undefined = undefined;
1138
+ let targetL1BlockNumber = maxBigint(currentL1BlockNumber - 64n, 0n);
1139
+ if (lastRetrievedCheckpoint) {
1140
+ latestLocalCheckpointArchive = lastRetrievedCheckpoint.checkpoint.archive.root.toString();
1141
+ targetL1BlockNumber = lastRetrievedCheckpoint.l1.blockNumber;
1142
+ } else if (latestLocalCheckpointNumber > 0) {
1143
+ const checkpoint = await this.store.getRangeOfCheckpoints(latestLocalCheckpointNumber, 1).then(([c]) => c);
1144
+ latestLocalCheckpointArchive = checkpoint.archive.root.toString();
1145
+ targetL1BlockNumber = checkpoint.l1.blockNumber;
1146
+ }
1004
1147
  this.log.warn(
1005
1148
  `Failed to reach checkpoint ${pendingCheckpointNumber} at ${currentL1BlockNumber} (latest is ${latestLocalCheckpointNumber}). ` +
1006
1149
  `Rolling back last synched L1 block number to ${targetL1BlockNumber}.`,
@@ -1086,15 +1229,22 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1086
1229
  const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1087
1230
  const blocks: L2Block[] = [];
1088
1231
 
1089
- // Walk the list of blocks backwards and filter by slots matching the requested epoch.
1090
- // We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
1091
- let block = await this.getBlock(await this.store.getSynchedL2BlockNumber());
1092
- const slot = (b: L2Block) => b.header.globalVariables.slotNumber;
1093
- while (block && slot(block) >= start) {
1094
- if (slot(block) <= end) {
1095
- blocks.push(block);
1232
+ // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
1233
+ // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
1234
+ let checkpoint = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
1235
+ const slot = (b: CheckpointData) => b.header.slotNumber;
1236
+ while (checkpoint && slot(checkpoint) >= start) {
1237
+ if (slot(checkpoint) <= end) {
1238
+ // push the blocks on backwards
1239
+ const endBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
1240
+ for (let i = endBlock; i >= checkpoint.startBlock; i--) {
1241
+ const block = await this.getBlock(BlockNumber(i));
1242
+ if (block) {
1243
+ blocks.push(block);
1244
+ }
1245
+ }
1096
1246
  }
1097
- block = await this.getBlock(block.number - 1);
1247
+ checkpoint = await this.store.getCheckpointData(CheckpointNumber(checkpoint.checkpointNumber - 1));
1098
1248
  }
1099
1249
 
1100
1250
  return blocks.reverse();
@@ -1104,16 +1254,22 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1104
1254
  const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1105
1255
  const blocks: BlockHeader[] = [];
1106
1256
 
1107
- // Walk the list of blocks backwards and filter by slots matching the requested epoch.
1108
- // We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
1109
- let number = await this.store.getSynchedL2BlockNumber();
1110
- let header = await this.getBlockHeader(number);
1111
- const slot = (b: BlockHeader) => b.globalVariables.slotNumber;
1112
- while (header && slot(header) >= start) {
1113
- if (slot(header) <= end) {
1114
- blocks.push(header);
1257
+ // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
1258
+ // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
1259
+ let checkpoint = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
1260
+ const slot = (b: CheckpointData) => b.header.slotNumber;
1261
+ while (checkpoint && slot(checkpoint) >= start) {
1262
+ if (slot(checkpoint) <= end) {
1263
+ // push the blocks on backwards
1264
+ const endBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
1265
+ for (let i = endBlock; i >= checkpoint.startBlock; i--) {
1266
+ const block = await this.getBlockHeader(BlockNumber(i));
1267
+ if (block) {
1268
+ blocks.push(block);
1269
+ }
1270
+ }
1115
1271
  }
1116
- header = await this.getBlockHeader(--number);
1272
+ checkpoint = await this.store.getCheckpointData(CheckpointNumber(checkpoint.checkpointNumber - 1));
1117
1273
  }
1118
1274
  return blocks.reverse();
1119
1275
  }
@@ -1151,102 +1307,53 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1151
1307
  return this.initialSyncComplete;
1152
1308
  }
1153
1309
 
1154
- public async getPublishedCheckpoints(from: number, limit: number, proven?: boolean): Promise<PublishedCheckpoint[]> {
1155
- const blocks = await this.getPublishedBlocks(from, limit, proven);
1156
- return blocks.map(b => b.toPublishedCheckpoint());
1157
- }
1158
-
1159
- public async getCheckpoints(from: number, limit: number, proven?: boolean): Promise<Checkpoint[]> {
1160
- const published = await this.getPublishedCheckpoints(from, limit, proven);
1161
- return published.map(p => p.checkpoint);
1162
- }
1163
-
1164
- public async getCheckpoint(number: number): Promise<Checkpoint | undefined> {
1165
- if (number < 0) {
1310
+ public async getCheckpointHeader(number: CheckpointNumber | 'latest'): Promise<CheckpointHeader | undefined> {
1311
+ if (number === 'latest') {
1166
1312
  number = await this.getSynchedCheckpointNumber();
1167
1313
  }
1168
1314
  if (number === 0) {
1169
1315
  return undefined;
1170
1316
  }
1171
- const published = await this.getPublishedCheckpoints(number, 1);
1172
- return published[0]?.checkpoint;
1173
- }
1174
-
1175
- public async getCheckpointHeader(number: number | 'latest'): Promise<CheckpointHeader | undefined> {
1176
- if (number === 'latest') {
1177
- number = await this.getSynchedCheckpointNumber();
1178
- }
1179
- if (number === 0) {
1317
+ const checkpoint = await this.store.getCheckpointData(number);
1318
+ if (!checkpoint) {
1180
1319
  return undefined;
1181
1320
  }
1182
- const checkpoint = await this.getCheckpoint(number);
1183
- return checkpoint?.header;
1321
+ return checkpoint.header;
1184
1322
  }
1185
1323
 
1186
- public getCheckpointNumber(): Promise<number> {
1324
+ public getCheckpointNumber(): Promise<CheckpointNumber> {
1187
1325
  return this.getSynchedCheckpointNumber();
1188
1326
  }
1189
1327
 
1190
- public getSynchedCheckpointNumber(): Promise<number> {
1191
- // TODO: Checkpoint number will no longer be the same as the block number once we support multiple blocks per checkpoint.
1192
- return this.store.getSynchedL2BlockNumber();
1328
+ public getSynchedCheckpointNumber(): Promise<CheckpointNumber> {
1329
+ return this.store.getSynchedCheckpointNumber();
1193
1330
  }
1194
1331
 
1195
- public getProvenCheckpointNumber(): Promise<number> {
1196
- // TODO: Proven checkpoint number will no longer be the same as the proven block number once we support multiple blocks per checkpoint.
1197
- return this.store.getProvenL2BlockNumber();
1332
+ public getProvenCheckpointNumber(): Promise<CheckpointNumber> {
1333
+ return this.store.getProvenCheckpointNumber();
1198
1334
  }
1199
1335
 
1200
- public setProvenCheckpointNumber(checkpointNumber: number): Promise<void> {
1201
- // TODO: Proven checkpoint number will no longer be the same as the proven block number once we support multiple blocks per checkpoint.
1202
- return this.store.setProvenL2BlockNumber(checkpointNumber);
1336
+ public setProvenCheckpointNumber(checkpointNumber: CheckpointNumber): Promise<void> {
1337
+ return this.store.setProvenCheckpointNumber(checkpointNumber);
1203
1338
  }
1204
1339
 
1205
- public unwindCheckpoints(from: number, checkpointsToUnwind: number): Promise<boolean> {
1206
- // TODO: This only works if we have one block per checkpoint.
1207
- return this.store.unwindBlocks(from, checkpointsToUnwind);
1340
+ public unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
1341
+ return this.store.unwindCheckpoints(from, checkpointsToUnwind);
1208
1342
  }
1209
1343
 
1210
- public getLastBlockNumberInCheckpoint(checkpointNumber: number): Promise<number> {
1211
- // TODO: Checkpoint number will no longer be the same as the block number once we support multiple blocks per checkpoint.
1212
- return Promise.resolve(checkpointNumber);
1344
+ public async getLastBlockNumberInCheckpoint(checkpointNumber: CheckpointNumber): Promise<BlockNumber | undefined> {
1345
+ const checkpointData = await this.store.getCheckpointData(checkpointNumber);
1346
+ if (!checkpointData) {
1347
+ return undefined;
1348
+ }
1349
+ return BlockNumber(checkpointData.startBlock + checkpointData.numBlocks - 1);
1213
1350
  }
1214
1351
 
1215
1352
  public addCheckpoints(
1216
1353
  checkpoints: PublishedCheckpoint[],
1217
1354
  pendingChainValidationStatus?: ValidateBlockResult,
1218
1355
  ): Promise<boolean> {
1219
- return this.store.addBlocks(
1220
- checkpoints.map(p => PublishedL2Block.fromPublishedCheckpoint(p)),
1221
- pendingChainValidationStatus,
1222
- );
1223
- }
1224
-
1225
- /**
1226
- * Gets up to `limit` amount of L2 blocks starting from `from`.
1227
- * @param from - Number of the first block to return (inclusive).
1228
- * @param limit - The number of blocks to return.
1229
- * @param proven - If true, only return blocks that have been proven.
1230
- * @returns The requested L2 blocks.
1231
- */
1232
- public getBlocks(from: number, limit: number, proven?: boolean): Promise<L2Block[]> {
1233
- return this.getPublishedBlocks(from, limit, proven).then(blocks => blocks.map(b => b.block));
1234
- }
1235
-
1236
- /** Equivalent to getBlocks but includes publish data. */
1237
- public async getPublishedBlocks(from: number, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
1238
- const limitWithProven = proven
1239
- ? Math.min(limit, Math.max((await this.store.getProvenL2BlockNumber()) - from + 1, 0))
1240
- : limit;
1241
- return limitWithProven === 0 ? [] : await this.store.getPublishedBlocks(from, limitWithProven);
1242
- }
1243
-
1244
- public getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1245
- return this.store.getPublishedBlockByHash(blockHash);
1246
- }
1247
-
1248
- public getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1249
- return this.store.getPublishedBlockByArchive(archive);
1356
+ return this.store.addCheckpoints(checkpoints, pendingChainValidationStatus);
1250
1357
  }
1251
1358
 
1252
1359
  public getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
@@ -1262,7 +1369,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1262
1369
  * @param number - The block number to return.
1263
1370
  * @returns The requested L2 block.
1264
1371
  */
1265
- public async getBlock(number: number): Promise<L2Block | undefined> {
1372
+ public async getL2BlockNew(number: BlockNumber): Promise<L2BlockNew | undefined> {
1266
1373
  // If the number provided is -ve, then return the latest block.
1267
1374
  if (number < 0) {
1268
1375
  number = await this.store.getSynchedL2BlockNumber();
@@ -1270,11 +1377,11 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1270
1377
  if (number === 0) {
1271
1378
  return undefined;
1272
1379
  }
1273
- const publishedBlock = await this.store.getPublishedBlock(number);
1274
- return publishedBlock?.block;
1380
+ const publishedBlock = await this.store.store.getBlock(number);
1381
+ return publishedBlock;
1275
1382
  }
1276
1383
 
1277
- public async getBlockHeader(number: number | 'latest'): Promise<BlockHeader | undefined> {
1384
+ public async getBlockHeader(number: BlockNumber | 'latest'): Promise<BlockHeader | undefined> {
1278
1385
  if (number === 'latest') {
1279
1386
  number = await this.store.getSynchedL2BlockNumber();
1280
1387
  }
@@ -1285,6 +1392,21 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1285
1392
  return headers.length === 0 ? undefined : headers[0];
1286
1393
  }
1287
1394
 
1395
+ getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
1396
+ return this.store.getCheckpointedBlock(number);
1397
+ }
1398
+
1399
+ getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
1400
+ return this.store.getCheckpointedBlockByHash(blockHash);
1401
+ }
1402
+
1403
+ getProvenBlockNumber(): Promise<BlockNumber> {
1404
+ return this.store.getProvenBlockNumber();
1405
+ }
1406
+ getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
1407
+ return this.store.getCheckpointedBlockByArchive(archive);
1408
+ }
1409
+
1288
1410
  public getTxEffect(txHash: TxHash) {
1289
1411
  return this.store.getTxEffect(txHash);
1290
1412
  }
@@ -1293,24 +1415,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1293
1415
  return this.store.getSettledTxReceipt(txHash);
1294
1416
  }
1295
1417
 
1296
- /**
1297
- * Retrieves all private logs from up to `limit` blocks, starting from the block number `from`.
1298
- * @param from - The block number from which to begin retrieving logs.
1299
- * @param limit - The maximum number of blocks to retrieve logs from.
1300
- * @returns An array of private logs from the specified range of blocks.
1301
- */
1302
- public getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
1303
- return this.store.getPrivateLogs(from, limit);
1418
+ getPrivateLogsByTags(tags: SiloedTag[]): Promise<TxScopedL2Log[][]> {
1419
+ return this.store.getPrivateLogsByTags(tags);
1304
1420
  }
1305
1421
 
1306
- /**
1307
- * Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
1308
- * @param tags - The tags to filter the logs by.
1309
- * @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
1310
- * that tag.
1311
- */
1312
- getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
1313
- return this.store.getLogsByTags(tags);
1422
+ getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise<TxScopedL2Log[][]> {
1423
+ return this.store.getPublicLogsByTagsFromContract(contractAddress, tags);
1314
1424
  }
1315
1425
 
1316
1426
  /**
@@ -1333,19 +1443,11 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1333
1443
 
1334
1444
  /**
1335
1445
  * Gets the number of the latest L2 block processed by the block source implementation.
1446
+ * This includes both checkpointed and uncheckpointed blocks.
1336
1447
  * @returns The number of the latest L2 block processed by the block source implementation.
1337
1448
  */
1338
- public getBlockNumber(): Promise<number> {
1339
- return this.store.getSynchedL2BlockNumber();
1340
- }
1341
-
1342
- public getProvenBlockNumber(): Promise<number> {
1343
- return this.store.getProvenL2BlockNumber();
1344
- }
1345
-
1346
- /** Forcefully updates the last proven block number. Use for testing. */
1347
- public setProvenBlockNumber(blockNumber: number): Promise<void> {
1348
- return this.store.setProvenL2BlockNumber(blockNumber);
1449
+ public getBlockNumber(): Promise<BlockNumber> {
1450
+ return this.store.getLatestBlockNumber();
1349
1451
  }
1350
1452
 
1351
1453
  public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
@@ -1373,12 +1475,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1373
1475
  }
1374
1476
 
1375
1477
  /**
1376
- * Gets L1 to L2 message (to be) included in a given block.
1377
- * @param blockNumber - L2 block number to get messages for.
1478
+ * Gets L1 to L2 message (to be) included in a given checkpoint.
1479
+ * @param checkpointNumber - Checkpoint number to get messages for.
1378
1480
  * @returns The L1 to L2 messages/leaves of the messages subtree (throws if not found).
1379
1481
  */
1380
- getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
1381
- return this.store.getL1ToL2Messages(blockNumber);
1482
+ getL1ToL2Messages(checkpointNumber: CheckpointNumber): Promise<Fr[]> {
1483
+ return this.store.getL1ToL2Messages(checkpointNumber);
1382
1484
  }
1383
1485
 
1384
1486
  /**
@@ -1419,7 +1521,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1419
1521
  // TODO(#13569): Compute proper finalized block number based on L1 finalized block.
1420
1522
  // We just force it 2 epochs worth of proven data for now.
1421
1523
  // NOTE: update end-to-end/src/e2e_epochs/epochs_empty_blocks.test.ts as that uses finalized blocks in computations
1422
- const finalizedBlockNumber = Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0);
1524
+ const finalizedBlockNumber = BlockNumber(Math.max(provenBlockNumber - this.l1constants.epochDuration * 2, 0));
1423
1525
 
1424
1526
  const [latestBlockHeader, provenBlockHeader, finalizedBlockHeader] = await Promise.all([
1425
1527
  latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
@@ -1443,52 +1545,44 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1443
1545
  );
1444
1546
  }
1445
1547
 
1446
- const latestBlockHeaderHash = await latestBlockHeader?.hash();
1447
- const provenBlockHeaderHash = await provenBlockHeader?.hash();
1448
- const finalizedBlockHeaderHash = await finalizedBlockHeader?.hash();
1548
+ const latestBlockHeaderHash = (await latestBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1549
+ const provenBlockHeaderHash = (await provenBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1550
+ const finalizedBlockHeaderHash = (await finalizedBlockHeader?.hash()) ?? GENESIS_BLOCK_HEADER_HASH;
1449
1551
 
1450
1552
  return {
1451
- latest: {
1452
- number: latestBlockNumber,
1453
- hash: latestBlockHeaderHash?.toString(),
1454
- } as L2BlockId,
1455
- proven: {
1456
- number: provenBlockNumber,
1457
- hash: provenBlockHeaderHash?.toString(),
1458
- } as L2BlockId,
1459
- finalized: {
1460
- number: finalizedBlockNumber,
1461
- hash: finalizedBlockHeaderHash?.toString(),
1462
- } as L2BlockId,
1553
+ latest: { number: latestBlockNumber, hash: latestBlockHeaderHash.toString() },
1554
+ proven: { number: provenBlockNumber, hash: provenBlockHeaderHash.toString() },
1555
+ finalized: { number: finalizedBlockNumber, hash: finalizedBlockHeaderHash.toString() },
1463
1556
  };
1464
1557
  }
1465
1558
 
1466
- public async rollbackTo(targetL2BlockNumber: number): Promise<void> {
1559
+ public async rollbackTo(targetL2BlockNumber: BlockNumber): Promise<void> {
1560
+ // TODO(pw/mbps): This still assumes 1 block per checkpoint
1467
1561
  const currentBlocks = await this.getL2Tips();
1468
1562
  const currentL2Block = currentBlocks.latest.number;
1469
1563
  const currentProvenBlock = currentBlocks.proven.number;
1470
- // const currentFinalizedBlock = currentBlocks.finalized.number;
1471
1564
 
1472
1565
  if (targetL2BlockNumber >= currentL2Block) {
1473
1566
  throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`);
1474
1567
  }
1475
1568
  const blocksToUnwind = currentL2Block - targetL2BlockNumber;
1476
- const targetL2Block = await this.store.getPublishedBlock(targetL2BlockNumber);
1569
+ const targetL2Block = await this.store.getCheckpointedBlock(targetL2BlockNumber);
1477
1570
  if (!targetL2Block) {
1478
1571
  throw new Error(`Target L2 block ${targetL2BlockNumber} not found`);
1479
1572
  }
1480
1573
  const targetL1BlockNumber = targetL2Block.l1.blockNumber;
1574
+ const targetCheckpointNumber = CheckpointNumber.fromBlockNumber(targetL2BlockNumber);
1481
1575
  const targetL1BlockHash = await this.getL1BlockHash(targetL1BlockNumber);
1482
- this.log.info(`Unwinding ${blocksToUnwind} blocks from L2 block ${currentL2Block}`);
1483
- await this.store.unwindBlocks(currentL2Block, blocksToUnwind);
1484
- this.log.info(`Unwinding L1 to L2 messages to ${targetL2BlockNumber}`);
1485
- await this.store.rollbackL1ToL2MessagesToL2Block(targetL2BlockNumber);
1576
+ this.log.info(`Unwinding ${blocksToUnwind} checkpoints from L2 block ${currentL2Block}`);
1577
+ await this.store.unwindCheckpoints(CheckpointNumber(currentL2Block), blocksToUnwind);
1578
+ this.log.info(`Unwinding L1 to L2 messages to checkpoint ${targetCheckpointNumber}`);
1579
+ await this.store.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
1486
1580
  this.log.info(`Setting L1 syncpoints to ${targetL1BlockNumber}`);
1487
1581
  await this.store.setBlockSynchedL1BlockNumber(targetL1BlockNumber);
1488
1582
  await this.store.setMessageSynchedL1Block({ l1BlockNumber: targetL1BlockNumber, l1BlockHash: targetL1BlockHash });
1489
1583
  if (targetL2BlockNumber < currentProvenBlock) {
1490
1584
  this.log.info(`Clearing proven L2 block number`);
1491
- await this.store.setProvenL2BlockNumber(0);
1585
+ await this.store.setProvenCheckpointNumber(CheckpointNumber.ZERO);
1492
1586
  }
1493
1587
  // TODO(palla/reorg): Set the finalized block when we add support for it.
1494
1588
  // if (targetL2BlockNumber < currentFinalizedBlock) {
@@ -1496,6 +1590,150 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1496
1590
  // await this.store.setFinalizedL2BlockNumber(0);
1497
1591
  // }
1498
1592
  }
1593
+
1594
+ public async getPublishedCheckpoints(
1595
+ checkpointNumber: CheckpointNumber,
1596
+ limit: number,
1597
+ ): Promise<PublishedCheckpoint[]> {
1598
+ const checkpoints = await this.store.getRangeOfCheckpoints(checkpointNumber, limit);
1599
+ const blocks = (
1600
+ await Promise.all(checkpoints.map(ch => this.store.getBlocksForCheckpoint(ch.checkpointNumber)))
1601
+ ).filter(isDefined);
1602
+
1603
+ const fullCheckpoints: PublishedCheckpoint[] = [];
1604
+ for (let i = 0; i < checkpoints.length; i++) {
1605
+ const blocksForCheckpoint = blocks[i];
1606
+ const checkpoint = checkpoints[i];
1607
+ const fullCheckpoint = new Checkpoint(
1608
+ checkpoint.archive,
1609
+ checkpoint.header,
1610
+ blocksForCheckpoint,
1611
+ checkpoint.checkpointNumber,
1612
+ );
1613
+ const publishedCheckpoint = new PublishedCheckpoint(
1614
+ fullCheckpoint,
1615
+ checkpoint.l1,
1616
+ checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
1617
+ );
1618
+ fullCheckpoints.push(publishedCheckpoint);
1619
+ }
1620
+ return fullCheckpoints;
1621
+ }
1622
+
1623
+ public async getCheckpointsForEpoch(epochNumber: EpochNumber): Promise<Checkpoint[]> {
1624
+ const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1constants);
1625
+ const checkpoints: Checkpoint[] = [];
1626
+
1627
+ // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
1628
+ // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
1629
+ let checkpointData = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
1630
+ const slot = (b: CheckpointData) => b.header.slotNumber;
1631
+ while (checkpointData && slot(checkpointData) >= start) {
1632
+ if (slot(checkpointData) <= end) {
1633
+ // push the checkpoints on backwards
1634
+ const [checkpoint] = await this.getPublishedCheckpoints(checkpointData.checkpointNumber, 1);
1635
+ checkpoints.push(checkpoint.checkpoint);
1636
+ }
1637
+ checkpointData = await this.store.getCheckpointData(CheckpointNumber(checkpointData.checkpointNumber - 1));
1638
+ }
1639
+
1640
+ return checkpoints.reverse();
1641
+ }
1642
+
1643
+ /* Legacy APIs */
1644
+
1645
+ public async getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1646
+ const checkpointedBlock = await this.store.getCheckpointedBlockByHash(blockHash);
1647
+ return this.buildOldBlockFromCheckpointedBlock(checkpointedBlock);
1648
+ }
1649
+ public async getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1650
+ const checkpointedBlock = await this.store.getCheckpointedBlockByArchive(archive);
1651
+ return this.buildOldBlockFromCheckpointedBlock(checkpointedBlock);
1652
+ }
1653
+
1654
+ /**
1655
+ * Gets up to `limit` amount of L2 blocks starting from `from`.
1656
+ * @param from - Number of the first block to return (inclusive).
1657
+ * @param limit - The number of blocks to return.
1658
+ * @param proven - If true, only return blocks that have been proven.
1659
+ * @returns The requested L2 blocks.
1660
+ */
1661
+ public async getBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<L2Block[]> {
1662
+ const publishedBlocks = await this.getPublishedBlocks(from, limit, proven);
1663
+ return publishedBlocks.map(x => x.block);
1664
+ }
1665
+
1666
+ public async getPublishedBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<PublishedL2Block[]> {
1667
+ const checkpoints = await this.store.getRangeOfCheckpoints(CheckpointNumber(from), limit);
1668
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber();
1669
+ const blocks = (
1670
+ await Promise.all(checkpoints.map(ch => this.store.getBlocksForCheckpoint(ch.checkpointNumber)))
1671
+ ).filter(isDefined);
1672
+
1673
+ const olbBlocks: PublishedL2Block[] = [];
1674
+ for (let i = 0; i < checkpoints.length; i++) {
1675
+ const blockForCheckpoint = blocks[i][0];
1676
+ const checkpoint = checkpoints[i];
1677
+ if (checkpoint.checkpointNumber > provenCheckpointNumber && proven === true) {
1678
+ // this checkpointisn't proven and we only want proven
1679
+ continue;
1680
+ }
1681
+ const oldCheckpoint = new Checkpoint(
1682
+ blockForCheckpoint.archive,
1683
+ checkpoint.header,
1684
+ [blockForCheckpoint],
1685
+ checkpoint.checkpointNumber,
1686
+ );
1687
+ const oldBlock = L2Block.fromCheckpoint(oldCheckpoint);
1688
+ const publishedBlock = new PublishedL2Block(
1689
+ oldBlock,
1690
+ checkpoint.l1,
1691
+ checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
1692
+ );
1693
+ olbBlocks.push(publishedBlock);
1694
+ }
1695
+ return olbBlocks;
1696
+ }
1697
+
1698
+ private async buildOldBlockFromCheckpointedBlock(
1699
+ checkpointedBlock: CheckpointedL2Block | undefined,
1700
+ ): Promise<PublishedL2Block | undefined> {
1701
+ if (!checkpointedBlock) {
1702
+ return undefined;
1703
+ }
1704
+ const checkpoint = await this.store.getCheckpointData(checkpointedBlock.checkpointNumber);
1705
+ if (!checkpoint) {
1706
+ return checkpoint;
1707
+ }
1708
+ const fullCheckpoint = new Checkpoint(
1709
+ checkpointedBlock?.block.archive,
1710
+ checkpoint?.header,
1711
+ [checkpointedBlock.block],
1712
+ checkpoint.checkpointNumber,
1713
+ );
1714
+ const oldBlock = L2Block.fromCheckpoint(fullCheckpoint);
1715
+ const published = new PublishedL2Block(
1716
+ oldBlock,
1717
+ checkpoint.l1,
1718
+ checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
1719
+ );
1720
+ return published;
1721
+ }
1722
+
1723
+ public async getBlock(number: BlockNumber): Promise<L2Block | undefined> {
1724
+ // If the number provided is -ve, then return the latest block.
1725
+ if (number < 0) {
1726
+ number = await this.store.getSynchedL2BlockNumber();
1727
+ }
1728
+ if (number === 0) {
1729
+ return undefined;
1730
+ }
1731
+ const publishedBlocks = await this.getPublishedBlocks(number, 1);
1732
+ if (publishedBlocks.length === 0) {
1733
+ return undefined;
1734
+ }
1735
+ return publishedBlocks[0].block;
1736
+ }
1499
1737
  }
1500
1738
 
1501
1739
  enum Operation {
@@ -1526,17 +1764,19 @@ export class ArchiverStoreHelper
1526
1764
  | 'close'
1527
1765
  | 'transactionAsync'
1528
1766
  | 'addBlocks'
1767
+ | 'getBlock'
1768
+ | 'getBlocks'
1529
1769
  >
1530
1770
  {
1531
1771
  #log = createLogger('archiver:block-helper');
1532
1772
 
1533
- constructor(protected readonly store: ArchiverDataStore) {}
1773
+ constructor(public readonly store: ArchiverDataStore) {}
1534
1774
 
1535
1775
  /**
1536
1776
  * Extracts and stores contract classes out of ContractClassPublished events emitted by the class registry contract.
1537
1777
  * @param allLogs - All logs emitted in a bunch of blocks.
1538
1778
  */
1539
- async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: number, operation: Operation) {
1779
+ async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: BlockNumber, operation: Operation) {
1540
1780
  const contractClassPublishedEvents = allLogs
1541
1781
  .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
1542
1782
  .map(log => ContractClassPublishedEvent.fromLog(log));
@@ -1561,7 +1801,7 @@ export class ArchiverStoreHelper
1561
1801
  * Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
1562
1802
  * @param allLogs - All logs emitted in a bunch of blocks.
1563
1803
  */
1564
- async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: number, operation: Operation) {
1804
+ async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: BlockNumber, operation: Operation) {
1565
1805
  const contractInstances = allLogs
1566
1806
  .filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
1567
1807
  .map(log => ContractInstancePublishedEvent.fromLog(log))
@@ -1614,7 +1854,7 @@ export class ArchiverStoreHelper
1614
1854
  * @param _blockNum - The block number
1615
1855
  * @returns
1616
1856
  */
1617
- async #storeBroadcastedIndividualFunctions(allLogs: ContractClassLog[], _blockNum: number) {
1857
+ async #storeBroadcastedIndividualFunctions(allLogs: ContractClassLog[], _blockNum: BlockNumber) {
1618
1858
  // Filter out private and utility function broadcast events
1619
1859
  const privateFnEvents = allLogs
1620
1860
  .filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
@@ -1668,7 +1908,23 @@ export class ArchiverStoreHelper
1668
1908
  return true;
1669
1909
  }
1670
1910
 
1671
- public addBlocks(blocks: PublishedL2Block[], pendingChainValidationStatus?: ValidateBlockResult): Promise<boolean> {
1911
+ private async addBlockDataToDB(block: L2BlockNew) {
1912
+ const contractClassLogs = block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1913
+ // ContractInstancePublished event logs are broadcast in privateLogs.
1914
+ const privateLogs = block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1915
+ const publicLogs = block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1916
+
1917
+ return (
1918
+ await Promise.all([
1919
+ this.#updatePublishedContractClasses(contractClassLogs, block.number, Operation.Store),
1920
+ this.#updateDeployedContractInstances(privateLogs, block.number, Operation.Store),
1921
+ this.#updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, Operation.Store),
1922
+ this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.number),
1923
+ ])
1924
+ ).every(Boolean);
1925
+ }
1926
+
1927
+ public addBlocks(blocks: L2BlockNew[], pendingChainValidationStatus?: ValidateBlockResult): Promise<boolean> {
1672
1928
  // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
1673
1929
  // or if the previous block is not in the store.
1674
1930
  return this.store.transactionAsync(async () => {
@@ -1678,25 +1934,10 @@ export class ArchiverStoreHelper
1678
1934
  // Update the pending chain validation status if provided
1679
1935
  pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
1680
1936
  // Add any logs emitted during the retrieved blocks
1681
- this.store.addLogs(blocks.map(block => block.block)),
1937
+ this.store.addLogs(blocks),
1682
1938
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1683
- ...blocks.map(async block => {
1684
- const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1685
- // ContractInstancePublished event logs are broadcast in privateLogs.
1686
- const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1687
- const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1688
- return (
1689
- await Promise.all([
1690
- this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Store),
1691
- this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
1692
- this.#updateUpdatedContractInstances(
1693
- publicLogs,
1694
- block.block.header.globalVariables.timestamp,
1695
- Operation.Store,
1696
- ),
1697
- this.#storeBroadcastedIndividualFunctions(contractClassLogs, block.block.number),
1698
- ])
1699
- ).every(Boolean);
1939
+ ...blocks.map(block => {
1940
+ return this.addBlockDataToDB(block);
1700
1941
  }),
1701
1942
  ]);
1702
1943
 
@@ -1704,61 +1945,104 @@ export class ArchiverStoreHelper
1704
1945
  });
1705
1946
  }
1706
1947
 
1707
- public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
1708
- const last = await this.getSynchedL2BlockNumber();
1709
- if (from != last) {
1710
- throw new Error(`Cannot unwind blocks from block ${from} when the last block is ${last}`);
1948
+ public addCheckpoints(
1949
+ checkpoints: PublishedCheckpoint[],
1950
+ pendingChainValidationStatus?: ValidateBlockResult,
1951
+ ): Promise<boolean> {
1952
+ // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
1953
+ // or if the previous block is not in the store.
1954
+ return this.store.transactionAsync(async () => {
1955
+ await this.store.addCheckpoints(checkpoints);
1956
+ const allBlocks = checkpoints.flatMap((ch: PublishedCheckpoint) => ch.checkpoint.blocks);
1957
+
1958
+ const opResults = await Promise.all([
1959
+ // Update the pending chain validation status if provided
1960
+ pendingChainValidationStatus && this.store.setPendingChainValidationStatus(pendingChainValidationStatus),
1961
+ // Add any logs emitted during the retrieved blocks
1962
+ this.store.addLogs(allBlocks),
1963
+ // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1964
+ ...allBlocks.map(block => {
1965
+ return this.addBlockDataToDB(block);
1966
+ }),
1967
+ ]);
1968
+
1969
+ return opResults.every(Boolean);
1970
+ });
1971
+ }
1972
+
1973
+ public async unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number): Promise<boolean> {
1974
+ if (checkpointsToUnwind <= 0) {
1975
+ throw new Error(`Cannot unwind ${checkpointsToUnwind} blocks`);
1711
1976
  }
1712
- if (blocksToUnwind <= 0) {
1713
- throw new Error(`Cannot unwind ${blocksToUnwind} blocks`);
1977
+
1978
+ const last = await this.getSynchedCheckpointNumber();
1979
+ if (from != last) {
1980
+ throw new Error(`Cannot unwind checkpoints from checkpoint ${from} when the last checkpoint is ${last}`);
1714
1981
  }
1715
1982
 
1716
- // from - blocksToUnwind = the new head, so + 1 for what we need to remove
1717
- const blocks = await this.getPublishedBlocks(from - blocksToUnwind + 1, blocksToUnwind);
1983
+ const blocks = [];
1984
+ const lastCheckpointNumber = from + checkpointsToUnwind - 1;
1985
+ for (let checkpointNumber = from; checkpointNumber <= lastCheckpointNumber; checkpointNumber++) {
1986
+ const blocksForCheckpoint = await this.store.getBlocksForCheckpoint(checkpointNumber);
1987
+ if (!blocksForCheckpoint) {
1988
+ continue;
1989
+ }
1990
+ blocks.push(...blocksForCheckpoint);
1991
+ }
1718
1992
 
1719
1993
  const opResults = await Promise.all([
1720
1994
  // Prune rolls back to the last proven block, which is by definition valid
1721
1995
  this.store.setPendingChainValidationStatus({ valid: true }),
1722
1996
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1723
1997
  ...blocks.map(async block => {
1724
- const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1998
+ const contractClassLogs = block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1725
1999
  // ContractInstancePublished event logs are broadcast in privateLogs.
1726
- const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1727
- const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
2000
+ const privateLogs = block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
2001
+ const publicLogs = block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1728
2002
 
1729
2003
  return (
1730
2004
  await Promise.all([
1731
- this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Delete),
1732
- this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Delete),
1733
- this.#updateUpdatedContractInstances(
1734
- publicLogs,
1735
- block.block.header.globalVariables.timestamp,
1736
- Operation.Delete,
1737
- ),
2005
+ this.#updatePublishedContractClasses(contractClassLogs, block.number, Operation.Delete),
2006
+ this.#updateDeployedContractInstances(privateLogs, block.number, Operation.Delete),
2007
+ this.#updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, Operation.Delete),
1738
2008
  ])
1739
2009
  ).every(Boolean);
1740
2010
  }),
1741
2011
 
1742
- this.store.deleteLogs(blocks.map(b => b.block)),
1743
- this.store.unwindBlocks(from, blocksToUnwind),
2012
+ this.store.deleteLogs(blocks),
2013
+ this.store.unwindCheckpoints(from, checkpointsToUnwind),
1744
2014
  ]);
1745
2015
 
1746
2016
  return opResults.every(Boolean);
1747
2017
  }
1748
2018
 
1749
- getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
1750
- return this.store.getPublishedBlocks(from, limit);
2019
+ getCheckpointData(checkpointNumber: CheckpointNumber): Promise<CheckpointData | undefined> {
2020
+ return this.store.getCheckpointData(checkpointNumber);
1751
2021
  }
1752
- getPublishedBlock(number: number): Promise<PublishedL2Block | undefined> {
1753
- return this.store.getPublishedBlock(number);
2022
+
2023
+ getRangeOfCheckpoints(from: CheckpointNumber, limit: number): Promise<CheckpointData[]> {
2024
+ return this.store.getRangeOfCheckpoints(from, limit);
2025
+ }
2026
+
2027
+ getCheckpointedL2BlockNumber(): Promise<BlockNumber> {
2028
+ return this.store.getCheckpointedL2BlockNumber();
2029
+ }
2030
+ getSynchedCheckpointNumber(): Promise<CheckpointNumber> {
2031
+ return this.store.getSynchedCheckpointNumber();
2032
+ }
2033
+ setCheckpointSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
2034
+ return this.store.setCheckpointSynchedL1BlockNumber(l1BlockNumber);
2035
+ }
2036
+ getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
2037
+ return this.store.getCheckpointedBlock(number);
1754
2038
  }
1755
- getPublishedBlockByHash(blockHash: Fr): Promise<PublishedL2Block | undefined> {
1756
- return this.store.getPublishedBlockByHash(blockHash);
2039
+ getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
2040
+ return this.store.getCheckpointedBlockByHash(blockHash);
1757
2041
  }
1758
- getPublishedBlockByArchive(archive: Fr): Promise<PublishedL2Block | undefined> {
1759
- return this.store.getPublishedBlockByArchive(archive);
2042
+ getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
2043
+ return this.store.getCheckpointedBlockByArchive(archive);
1760
2044
  }
1761
- getBlockHeaders(from: number, limit: number): Promise<BlockHeader[]> {
2045
+ getBlockHeaders(from: BlockNumber, limit: number): Promise<BlockHeader[]> {
1762
2046
  return this.store.getBlockHeaders(from, limit);
1763
2047
  }
1764
2048
  getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
@@ -1767,6 +2051,18 @@ export class ArchiverStoreHelper
1767
2051
  getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1768
2052
  return this.store.getBlockHeaderByArchive(archive);
1769
2053
  }
2054
+ getBlockByHash(blockHash: Fr): Promise<L2BlockNew | undefined> {
2055
+ return this.store.getBlockByHash(blockHash);
2056
+ }
2057
+ getBlockByArchive(archive: Fr): Promise<L2BlockNew | undefined> {
2058
+ return this.store.getBlockByArchive(archive);
2059
+ }
2060
+ getLatestBlockNumber(): Promise<BlockNumber> {
2061
+ return this.store.getLatestBlockNumber();
2062
+ }
2063
+ getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2BlockNew[] | undefined> {
2064
+ return this.store.getBlocksForCheckpoint(checkpointNumber);
2065
+ }
1770
2066
  getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
1771
2067
  return this.store.getTxEffect(txHash);
1772
2068
  }
@@ -1776,17 +2072,17 @@ export class ArchiverStoreHelper
1776
2072
  addL1ToL2Messages(messages: InboxMessage[]): Promise<void> {
1777
2073
  return this.store.addL1ToL2Messages(messages);
1778
2074
  }
1779
- getL1ToL2Messages(blockNumber: number): Promise<Fr[]> {
1780
- return this.store.getL1ToL2Messages(blockNumber);
2075
+ getL1ToL2Messages(checkpointNumber: CheckpointNumber): Promise<Fr[]> {
2076
+ return this.store.getL1ToL2Messages(checkpointNumber);
1781
2077
  }
1782
2078
  getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise<bigint | undefined> {
1783
2079
  return this.store.getL1ToL2MessageIndex(l1ToL2Message);
1784
2080
  }
1785
- getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
1786
- return this.store.getPrivateLogs(from, limit);
2081
+ getPrivateLogsByTags(tags: SiloedTag[]): Promise<TxScopedL2Log[][]> {
2082
+ return this.store.getPrivateLogsByTags(tags);
1787
2083
  }
1788
- getLogsByTags(tags: Fr[], logsPerTag?: number): Promise<TxScopedL2Log[][]> {
1789
- return this.store.getLogsByTags(tags, logsPerTag);
2084
+ getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise<TxScopedL2Log[][]> {
2085
+ return this.store.getPublicLogsByTagsFromContract(contractAddress, tags);
1790
2086
  }
1791
2087
  getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
1792
2088
  return this.store.getPublicLogs(filter);
@@ -1794,17 +2090,20 @@ export class ArchiverStoreHelper
1794
2090
  getContractClassLogs(filter: LogFilter): Promise<GetContractClassLogsResponse> {
1795
2091
  return this.store.getContractClassLogs(filter);
1796
2092
  }
1797
- getSynchedL2BlockNumber(): Promise<number> {
1798
- return this.store.getSynchedL2BlockNumber();
2093
+ getSynchedL2BlockNumber(): Promise<BlockNumber> {
2094
+ return this.store.getCheckpointedL2BlockNumber();
2095
+ }
2096
+ getProvenCheckpointNumber(): Promise<CheckpointNumber> {
2097
+ return this.store.getProvenCheckpointNumber();
1799
2098
  }
1800
- getProvenL2BlockNumber(): Promise<number> {
1801
- return this.store.getProvenL2BlockNumber();
2099
+ getProvenBlockNumber(): Promise<BlockNumber> {
2100
+ return this.store.getProvenBlockNumber();
1802
2101
  }
1803
- setProvenL2BlockNumber(l2BlockNumber: number): Promise<void> {
1804
- return this.store.setProvenL2BlockNumber(l2BlockNumber);
2102
+ setProvenCheckpointNumber(checkpointNumber: CheckpointNumber): Promise<void> {
2103
+ return this.store.setProvenCheckpointNumber(checkpointNumber);
1805
2104
  }
1806
2105
  setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
1807
- return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
2106
+ return this.store.setCheckpointSynchedL1BlockNumber(l1BlockNumber);
1808
2107
  }
1809
2108
  setMessageSynchedL1Block(l1Block: L1BlockId): Promise<void> {
1810
2109
  return this.store.setMessageSynchedL1Block(l1Block);
@@ -1836,8 +2135,8 @@ export class ArchiverStoreHelper
1836
2135
  estimateSize(): Promise<{ mappingSize: number; physicalFileSize: number; actualSize: number; numItems: number }> {
1837
2136
  return this.store.estimateSize();
1838
2137
  }
1839
- rollbackL1ToL2MessagesToL2Block(targetBlockNumber: number): Promise<void> {
1840
- return this.store.rollbackL1ToL2MessagesToL2Block(targetBlockNumber);
2138
+ rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber: CheckpointNumber): Promise<void> {
2139
+ return this.store.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
1841
2140
  }
1842
2141
  iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {
1843
2142
  return this.store.iterateL1ToL2Messages(range);