@aztec/archiver 0.0.1-commit.1bb068fb5 → 0.0.1-commit.1dcfe2301

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 (110) hide show
  1. package/README.md +12 -6
  2. package/dest/archiver.d.ts +12 -8
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +83 -114
  5. package/dest/config.d.ts +3 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +3 -2
  8. package/dest/errors.d.ts +34 -10
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +45 -16
  11. package/dest/factory.d.ts +4 -5
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +29 -26
  14. package/dest/index.d.ts +2 -1
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +1 -0
  17. package/dest/l1/bin/retrieve-calldata.js +32 -28
  18. package/dest/l1/calldata_retriever.d.ts +73 -52
  19. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  20. package/dest/l1/calldata_retriever.js +191 -261
  21. package/dest/l1/data_retrieval.d.ts +9 -10
  22. package/dest/l1/data_retrieval.d.ts.map +1 -1
  23. package/dest/l1/data_retrieval.js +32 -32
  24. package/dest/l1/spire_proposer.d.ts +5 -5
  25. package/dest/l1/spire_proposer.d.ts.map +1 -1
  26. package/dest/l1/spire_proposer.js +9 -17
  27. package/dest/modules/data_source_base.d.ts +14 -7
  28. package/dest/modules/data_source_base.d.ts.map +1 -1
  29. package/dest/modules/data_source_base.js +39 -77
  30. package/dest/modules/data_store_updater.d.ts +25 -12
  31. package/dest/modules/data_store_updater.d.ts.map +1 -1
  32. package/dest/modules/data_store_updater.js +125 -94
  33. package/dest/modules/instrumentation.d.ts +18 -3
  34. package/dest/modules/instrumentation.d.ts.map +1 -1
  35. package/dest/modules/instrumentation.js +36 -8
  36. package/dest/modules/l1_synchronizer.d.ts +7 -9
  37. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  38. package/dest/modules/l1_synchronizer.js +179 -137
  39. package/dest/modules/validation.d.ts +1 -1
  40. package/dest/modules/validation.d.ts.map +1 -1
  41. package/dest/modules/validation.js +2 -2
  42. package/dest/store/block_store.d.ts +66 -28
  43. package/dest/store/block_store.d.ts.map +1 -1
  44. package/dest/store/block_store.js +357 -136
  45. package/dest/store/contract_class_store.d.ts +2 -3
  46. package/dest/store/contract_class_store.d.ts.map +1 -1
  47. package/dest/store/contract_class_store.js +7 -67
  48. package/dest/store/contract_instance_store.d.ts +1 -1
  49. package/dest/store/contract_instance_store.d.ts.map +1 -1
  50. package/dest/store/contract_instance_store.js +6 -2
  51. package/dest/store/kv_archiver_store.d.ts +61 -24
  52. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  53. package/dest/store/kv_archiver_store.js +75 -27
  54. package/dest/store/l2_tips_cache.d.ts +20 -0
  55. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  56. package/dest/store/l2_tips_cache.js +109 -0
  57. package/dest/store/log_store.d.ts +6 -3
  58. package/dest/store/log_store.d.ts.map +1 -1
  59. package/dest/store/log_store.js +95 -20
  60. package/dest/store/message_store.d.ts +5 -1
  61. package/dest/store/message_store.d.ts.map +1 -1
  62. package/dest/store/message_store.js +21 -9
  63. package/dest/test/fake_l1_state.d.ts +21 -1
  64. package/dest/test/fake_l1_state.d.ts.map +1 -1
  65. package/dest/test/fake_l1_state.js +133 -26
  66. package/dest/test/mock_archiver.d.ts +1 -1
  67. package/dest/test/mock_archiver.d.ts.map +1 -1
  68. package/dest/test/mock_archiver.js +3 -2
  69. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  70. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  71. package/dest/test/mock_l1_to_l2_message_source.js +2 -1
  72. package/dest/test/mock_l2_block_source.d.ts +26 -5
  73. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  74. package/dest/test/mock_l2_block_source.js +160 -89
  75. package/dest/test/mock_structs.d.ts +4 -1
  76. package/dest/test/mock_structs.d.ts.map +1 -1
  77. package/dest/test/mock_structs.js +13 -1
  78. package/dest/test/noop_l1_archiver.d.ts +4 -1
  79. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  80. package/dest/test/noop_l1_archiver.js +5 -2
  81. package/package.json +13 -13
  82. package/src/archiver.ts +106 -139
  83. package/src/config.ts +9 -2
  84. package/src/errors.ts +70 -26
  85. package/src/factory.ts +30 -16
  86. package/src/index.ts +1 -0
  87. package/src/l1/README.md +25 -68
  88. package/src/l1/bin/retrieve-calldata.ts +40 -27
  89. package/src/l1/calldata_retriever.ts +250 -383
  90. package/src/l1/data_retrieval.ts +28 -37
  91. package/src/l1/spire_proposer.ts +7 -15
  92. package/src/modules/data_source_base.ts +78 -98
  93. package/src/modules/data_store_updater.ts +138 -124
  94. package/src/modules/instrumentation.ts +48 -9
  95. package/src/modules/l1_synchronizer.ts +203 -172
  96. package/src/modules/validation.ts +2 -2
  97. package/src/store/block_store.ts +451 -172
  98. package/src/store/contract_class_store.ts +8 -106
  99. package/src/store/contract_instance_store.ts +8 -5
  100. package/src/store/kv_archiver_store.ts +115 -41
  101. package/src/store/l2_tips_cache.ts +134 -0
  102. package/src/store/log_store.ts +128 -32
  103. package/src/store/message_store.ts +27 -10
  104. package/src/structs/inbox_message.ts +1 -1
  105. package/src/test/fake_l1_state.ts +178 -30
  106. package/src/test/mock_archiver.ts +3 -2
  107. package/src/test/mock_l1_to_l2_message_source.ts +1 -0
  108. package/src/test/mock_l2_block_source.ts +209 -82
  109. package/src/test/mock_structs.ts +20 -6
  110. package/src/test/noop_l1_archiver.ts +7 -2
@@ -144,7 +144,7 @@ export async function retrievedToPublishedCheckpoint({
144
144
  * @param blobClient - The blob client client for fetching blob data.
145
145
  * @param searchStartBlock - The block number to use for starting the search.
146
146
  * @param searchEndBlock - The highest block number that we should search up to.
147
- * @param contractAddresses - The contract addresses (governanceProposerAddress, slashFactoryAddress, slashingProposerAddress).
147
+ * @param contractAddresses - The contract addresses (governanceProposerAddress, slashingProposerAddress).
148
148
  * @param instrumentation - The archiver instrumentation instance.
149
149
  * @param logger - The logger instance.
150
150
  * @param isHistoricalSync - Whether this is a historical sync.
@@ -157,11 +157,6 @@ export async function retrieveCheckpointsFromRollup(
157
157
  blobClient: BlobClientInterface,
158
158
  searchStartBlock: bigint,
159
159
  searchEndBlock: bigint,
160
- contractAddresses: {
161
- governanceProposerAddress: EthAddress;
162
- slashFactoryAddress?: EthAddress;
163
- slashingProposerAddress: EthAddress;
164
- },
165
160
  instrumentation: ArchiverInstrumentation,
166
161
  logger: Logger = createLogger('archiver'),
167
162
  isHistoricalSync: boolean = false,
@@ -205,7 +200,6 @@ export async function retrieveCheckpointsFromRollup(
205
200
  blobClient,
206
201
  checkpointProposedLogs,
207
202
  rollupConstants,
208
- contractAddresses,
209
203
  instrumentation,
210
204
  logger,
211
205
  isHistoricalSync,
@@ -226,7 +220,6 @@ export async function retrieveCheckpointsFromRollup(
226
220
  * @param blobClient - The blob client client for fetching blob data.
227
221
  * @param logs - CheckpointProposed logs.
228
222
  * @param rollupConstants - The rollup constants (chainId, version, targetCommitteeSize).
229
- * @param contractAddresses - The contract addresses (governanceProposerAddress, slashFactoryAddress, slashingProposerAddress).
230
223
  * @param instrumentation - The archiver instrumentation instance.
231
224
  * @param logger - The logger instance.
232
225
  * @param isHistoricalSync - Whether this is a historical sync.
@@ -239,11 +232,6 @@ async function processCheckpointProposedLogs(
239
232
  blobClient: BlobClientInterface,
240
233
  logs: CheckpointProposedLog[],
241
234
  { chainId, version, targetCommitteeSize }: { chainId: Fr; version: Fr; targetCommitteeSize: number },
242
- contractAddresses: {
243
- governanceProposerAddress: EthAddress;
244
- slashFactoryAddress?: EthAddress;
245
- slashingProposerAddress: EthAddress;
246
- },
247
235
  instrumentation: ArchiverInstrumentation,
248
236
  logger: Logger,
249
237
  isHistoricalSync: boolean,
@@ -255,7 +243,7 @@ async function processCheckpointProposedLogs(
255
243
  targetCommitteeSize,
256
244
  instrumentation,
257
245
  logger,
258
- { ...contractAddresses, rollupAddress: EthAddress.fromString(rollup.address) },
246
+ EthAddress.fromString(rollup.address),
259
247
  );
260
248
 
261
249
  await asyncPool(10, logs, async log => {
@@ -266,10 +254,9 @@ async function processCheckpointProposedLogs(
266
254
 
267
255
  // The value from the event and contract will match only if the checkpoint is in the chain.
268
256
  if (archive.equals(archiveFromChain)) {
269
- // Build expected hashes object (fields may be undefined for backwards compatibility with older events)
270
257
  const expectedHashes = {
271
- attestationsHash: log.args.attestationsHash?.toString(),
272
- payloadDigest: log.args.payloadDigest?.toString(),
258
+ attestationsHash: log.args.attestationsHash.toString() as Hex,
259
+ payloadDigest: log.args.payloadDigest.toString() as Hex,
273
260
  };
274
261
 
275
262
  const checkpoint = await calldataRetriever.getCheckpointFromRollupTx(
@@ -278,6 +265,9 @@ async function processCheckpointProposedLogs(
278
265
  checkpointNumber,
279
266
  expectedHashes,
280
267
  );
268
+ const { timestamp, parentBeaconBlockRoot } = await getL1Block(publicClient, log.l1BlockNumber);
269
+ const l1 = new L1PublishedData(log.l1BlockNumber, timestamp, log.l1BlockHash.toString());
270
+
281
271
  const checkpointBlobData = await getCheckpointBlobDataFromBlobs(
282
272
  blobClient,
283
273
  checkpoint.blockHash,
@@ -285,12 +275,8 @@ async function processCheckpointProposedLogs(
285
275
  checkpointNumber,
286
276
  logger,
287
277
  isHistoricalSync,
288
- );
289
-
290
- const l1 = new L1PublishedData(
291
- log.l1BlockNumber,
292
- await getL1BlockTime(publicClient, log.l1BlockNumber),
293
- log.l1BlockHash.toString(),
278
+ parentBeaconBlockRoot,
279
+ timestamp,
294
280
  );
295
281
 
296
282
  retrievedCheckpoints.push({ ...checkpoint, checkpointBlobData, l1, chainId, version });
@@ -311,9 +297,12 @@ async function processCheckpointProposedLogs(
311
297
  return retrievedCheckpoints;
312
298
  }
313
299
 
314
- export async function getL1BlockTime(publicClient: ViemPublicClient, blockNumber: bigint): Promise<bigint> {
300
+ export async function getL1Block(
301
+ publicClient: ViemPublicClient,
302
+ blockNumber: bigint,
303
+ ): Promise<{ timestamp: bigint; parentBeaconBlockRoot: string | undefined }> {
315
304
  const block = await publicClient.getBlock({ blockNumber, includeTransactions: false });
316
- return block.timestamp;
305
+ return { timestamp: block.timestamp, parentBeaconBlockRoot: block.parentBeaconBlockRoot };
317
306
  }
318
307
 
319
308
  export async function getCheckpointBlobDataFromBlobs(
@@ -323,8 +312,14 @@ export async function getCheckpointBlobDataFromBlobs(
323
312
  checkpointNumber: CheckpointNumber,
324
313
  logger: Logger,
325
314
  isHistoricalSync: boolean,
315
+ parentBeaconBlockRoot?: string,
316
+ l1BlockTimestamp?: bigint,
326
317
  ): Promise<CheckpointBlobData> {
327
- const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, { isHistoricalSync });
318
+ const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, {
319
+ isHistoricalSync,
320
+ parentBeaconBlockRoot,
321
+ l1BlockTimestamp,
322
+ });
328
323
  if (blobBodies.length === 0) {
329
324
  throw new NoBlobBodiesFoundError(checkpointNumber);
330
325
  }
@@ -349,14 +344,10 @@ export async function getCheckpointBlobDataFromBlobs(
349
344
  /** Given an L1 to L2 message, retrieves its corresponding event from the Inbox within a specific block range. */
350
345
  export async function retrieveL1ToL2Message(
351
346
  inbox: InboxContract,
352
- leaf: Fr,
353
- fromBlock: bigint,
354
- toBlock: bigint,
347
+ message: InboxMessage,
355
348
  ): Promise<InboxMessage | undefined> {
356
- const logs = await inbox.getMessageSentEventByHash(leaf.toString(), fromBlock, toBlock);
357
-
358
- const messages = mapLogsInboxMessage(logs);
359
- return messages.length > 0 ? messages[0] : undefined;
349
+ const log = await inbox.getMessageSentEventByHash(message.leaf.toString(), message.l1BlockHash.toString());
350
+ return log && mapLogInboxMessage(log);
360
351
  }
361
352
 
362
353
  /**
@@ -379,22 +370,22 @@ export async function retrieveL1ToL2Messages(
379
370
  break;
380
371
  }
381
372
 
382
- retrievedL1ToL2Messages.push(...mapLogsInboxMessage(messageSentLogs));
373
+ retrievedL1ToL2Messages.push(...messageSentLogs.map(mapLogInboxMessage));
383
374
  searchStartBlock = messageSentLogs.at(-1)!.l1BlockNumber + 1n;
384
375
  }
385
376
 
386
377
  return retrievedL1ToL2Messages;
387
378
  }
388
379
 
389
- function mapLogsInboxMessage(logs: MessageSentLog[]): InboxMessage[] {
390
- return logs.map(log => ({
380
+ function mapLogInboxMessage(log: MessageSentLog): InboxMessage {
381
+ return {
391
382
  index: log.args.index,
392
383
  leaf: log.args.leaf,
393
384
  l1BlockNumber: log.l1BlockNumber,
394
385
  l1BlockHash: log.l1BlockHash,
395
386
  checkpointNumber: log.args.checkpointNumber,
396
387
  rollingHash: log.args.rollingHash,
397
- }));
388
+ };
398
389
  }
399
390
 
400
391
  /** Retrieves L2ProofVerified events from the rollup contract. */
@@ -87,17 +87,17 @@ export async function verifyProxyImplementation(
87
87
  /**
88
88
  * Attempts to decode transaction as a Spire Proposer Multicall.
89
89
  * Spire Proposer is a proxy contract that wraps multiple calls.
90
- * Returns the target address and calldata of the wrapped call if validation succeeds and there is a single call.
90
+ * Returns all wrapped calls if validation succeeds (caller handles hash matching to find the propose call).
91
91
  * @param tx - The transaction to decode
92
92
  * @param publicClient - The viem public client for proxy verification
93
93
  * @param logger - Logger instance
94
- * @returns Object with 'to' and 'data' of the wrapped call, or undefined if validation fails
94
+ * @returns Array of wrapped calls with 'to' and 'data', or undefined if not a valid Spire Proposer tx
95
95
  */
96
- export async function getCallFromSpireProposer(
96
+ export async function getCallsFromSpireProposer(
97
97
  tx: Transaction,
98
98
  publicClient: { getStorageAt: (params: { address: Hex; slot: Hex }) => Promise<Hex | undefined> },
99
99
  logger: Logger,
100
- ): Promise<{ to: Hex; data: Hex } | undefined> {
100
+ ): Promise<{ to: Hex; data: Hex }[] | undefined> {
101
101
  const txHash = tx.hash;
102
102
 
103
103
  try {
@@ -141,17 +141,9 @@ export async function getCallFromSpireProposer(
141
141
 
142
142
  const [calls] = spireArgs;
143
143
 
144
- // Validate exactly ONE call (see ./README.md for rationale)
145
- if (calls.length !== 1) {
146
- logger.warn(`Spire Proposer multicall must contain exactly one call (got ${calls.length})`, { txHash });
147
- return undefined;
148
- }
149
-
150
- const call = calls[0];
151
-
152
- // Successfully extracted the single wrapped call
153
- logger.trace(`Decoded Spire Proposer with single call to ${call.target}`, { txHash });
154
- return { to: call.target, data: call.data };
144
+ // Return all wrapped calls (hash matching in the caller determines which is the propose call)
145
+ logger.trace(`Decoded Spire Proposer with ${calls.length} call(s)`, { txHash });
146
+ return calls.map(call => ({ to: call.target, data: call.data }));
155
147
  } catch (err) {
156
148
  // Any decoding error triggers fallback to trace
157
149
  logger.warn(`Failed to decode Spire Proposer: ${err}`, { txHash });
@@ -1,11 +1,18 @@
1
+ import { range } from '@aztec/foundation/array';
1
2
  import { BlockNumber, CheckpointNumber, type EpochNumber, type SlotNumber } from '@aztec/foundation/branded-types';
2
3
  import type { Fr } from '@aztec/foundation/curves/bn254';
3
4
  import type { EthAddress } from '@aztec/foundation/eth-address';
4
5
  import { isDefined } from '@aztec/foundation/types';
5
6
  import type { FunctionSelector } from '@aztec/stdlib/abi';
6
7
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
7
- import { type BlockHash, CheckpointedL2Block, CommitteeAttestation, L2Block, type L2Tips } from '@aztec/stdlib/block';
8
- import { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
8
+ import { type BlockData, type BlockHash, CheckpointedL2Block, L2Block, type L2Tips } from '@aztec/stdlib/block';
9
+ import {
10
+ Checkpoint,
11
+ type CheckpointData,
12
+ type CommonCheckpointData,
13
+ type ProposedCheckpointData,
14
+ PublishedCheckpoint,
15
+ } from '@aztec/stdlib/checkpoint';
9
16
  import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
10
17
  import { type L1RollupConstants, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
11
18
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
@@ -17,7 +24,6 @@ import type { BlockHeader, IndexedTxEffect, TxHash, TxReceipt } from '@aztec/std
17
24
  import type { UInt64 } from '@aztec/stdlib/types';
18
25
 
19
26
  import type { ArchiverDataSource } from '../interfaces.js';
20
- import type { CheckpointData } from '../store/block_store.js';
21
27
  import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
22
28
  import type { ValidateCheckpointResult } from './validation.js';
23
29
 
@@ -46,9 +52,9 @@ export abstract class ArchiverDataSourceBase
46
52
 
47
53
  abstract getL2Tips(): Promise<L2Tips>;
48
54
 
49
- abstract getL2SlotNumber(): Promise<SlotNumber | undefined>;
55
+ abstract getSyncedL2SlotNumber(): Promise<SlotNumber | undefined>;
50
56
 
51
- abstract getL2EpochNumber(): Promise<EpochNumber | undefined>;
57
+ abstract getSyncedL2EpochNumber(): Promise<EpochNumber | undefined>;
52
58
 
53
59
  abstract isEpochComplete(epochNumber: EpochNumber): Promise<boolean>;
54
60
 
@@ -114,7 +120,7 @@ export abstract class ArchiverDataSourceBase
114
120
  if (!checkpointData) {
115
121
  return undefined;
116
122
  }
117
- return BlockNumber(checkpointData.startBlock + checkpointData.numBlocks - 1);
123
+ return BlockNumber(checkpointData.startBlock + checkpointData.blockCount - 1);
118
124
  }
119
125
 
120
126
  public getCheckpointedBlocks(from: BlockNumber, limit: number): Promise<CheckpointedL2Block[]> {
@@ -129,6 +135,14 @@ export abstract class ArchiverDataSourceBase
129
135
  return this.store.getBlockHeaderByArchive(archive);
130
136
  }
131
137
 
138
+ public getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
139
+ return this.store.getBlockData(number);
140
+ }
141
+
142
+ public getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
143
+ return this.store.getBlockDataByArchive(archive);
144
+ }
145
+
132
146
  public async getL2Block(number: BlockNumber): Promise<L2Block | undefined> {
133
147
  // If the number provided is -ve, then return the latest block.
134
148
  if (number < 0) {
@@ -146,7 +160,15 @@ export abstract class ArchiverDataSourceBase
146
160
  }
147
161
 
148
162
  public getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
149
- return this.store.getSettledTxReceipt(txHash);
163
+ return this.store.getSettledTxReceipt(txHash, this.l1Constants);
164
+ }
165
+
166
+ public getProposedCheckpoint(): Promise<CommonCheckpointData | undefined> {
167
+ return this.store.getProposedCheckpoint();
168
+ }
169
+
170
+ public getProposedCheckpointOnly(): Promise<ProposedCheckpointData | undefined> {
171
+ return this.store.getProposedCheckpointOnly();
150
172
  }
151
173
 
152
174
  public isPendingChainInvalid(): Promise<boolean> {
@@ -157,16 +179,21 @@ export abstract class ArchiverDataSourceBase
157
179
  return (await this.store.getPendingChainValidationStatus()) ?? { valid: true };
158
180
  }
159
181
 
160
- public getPrivateLogsByTags(tags: SiloedTag[], page?: number): Promise<TxScopedL2Log[][]> {
161
- return this.store.getPrivateLogsByTags(tags, page);
182
+ public getPrivateLogsByTags(
183
+ tags: SiloedTag[],
184
+ page?: number,
185
+ upToBlockNumber?: BlockNumber,
186
+ ): Promise<TxScopedL2Log[][]> {
187
+ return this.store.getPrivateLogsByTags(tags, page, upToBlockNumber);
162
188
  }
163
189
 
164
190
  public getPublicLogsByTagsFromContract(
165
191
  contractAddress: AztecAddress,
166
192
  tags: Tag[],
167
193
  page?: number,
194
+ upToBlockNumber?: BlockNumber,
168
195
  ): Promise<TxScopedL2Log[][]> {
169
- return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page);
196
+ return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
170
197
  }
171
198
 
172
199
  public getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
@@ -223,28 +250,21 @@ export abstract class ArchiverDataSourceBase
223
250
 
224
251
  public async getCheckpoints(checkpointNumber: CheckpointNumber, limit: number): Promise<PublishedCheckpoint[]> {
225
252
  const checkpoints = await this.store.getRangeOfCheckpoints(checkpointNumber, limit);
226
- const blocks = (
227
- await Promise.all(checkpoints.map(ch => this.store.getBlocksForCheckpoint(ch.checkpointNumber)))
228
- ).filter(isDefined);
229
-
230
- const fullCheckpoints: PublishedCheckpoint[] = [];
231
- for (let i = 0; i < checkpoints.length; i++) {
232
- const blocksForCheckpoint = blocks[i];
233
- const checkpoint = checkpoints[i];
234
- const fullCheckpoint = new Checkpoint(
235
- checkpoint.archive,
236
- checkpoint.header,
237
- blocksForCheckpoint,
238
- checkpoint.checkpointNumber,
239
- );
240
- const publishedCheckpoint = new PublishedCheckpoint(
241
- fullCheckpoint,
242
- checkpoint.l1,
243
- checkpoint.attestations.map(x => CommitteeAttestation.fromBuffer(x)),
244
- );
245
- fullCheckpoints.push(publishedCheckpoint);
253
+ return Promise.all(checkpoints.map(ch => this.getPublishedCheckpointFromCheckpointData(ch)));
254
+ }
255
+
256
+ private async getPublishedCheckpointFromCheckpointData(checkpoint: CheckpointData): Promise<PublishedCheckpoint> {
257
+ const blocksForCheckpoint = await this.store.getBlocksForCheckpoint(checkpoint.checkpointNumber);
258
+ if (!blocksForCheckpoint) {
259
+ throw new Error(`Blocks for checkpoint ${checkpoint.checkpointNumber} not found`);
246
260
  }
247
- return fullCheckpoints;
261
+ const fullCheckpoint = new Checkpoint(
262
+ checkpoint.archive,
263
+ checkpoint.header,
264
+ blocksForCheckpoint,
265
+ checkpoint.checkpointNumber,
266
+ );
267
+ return new PublishedCheckpoint(fullCheckpoint, checkpoint.l1, checkpoint.attestations);
248
268
  }
249
269
 
250
270
  public getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
@@ -252,84 +272,44 @@ export abstract class ArchiverDataSourceBase
252
272
  }
253
273
 
254
274
  public async getCheckpointedBlocksForEpoch(epochNumber: EpochNumber): Promise<CheckpointedL2Block[]> {
255
- if (!this.l1Constants) {
256
- throw new Error('L1 constants not set');
257
- }
258
-
259
- const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1Constants);
260
- const blocks: CheckpointedL2Block[] = [];
261
-
262
- // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
263
- // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
264
- let checkpoint = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
265
- const slot = (b: CheckpointData) => b.header.slotNumber;
266
- while (checkpoint && slot(checkpoint) >= start) {
267
- if (slot(checkpoint) <= end) {
268
- // push the blocks on backwards
269
- const endBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
270
- for (let i = endBlock; i >= checkpoint.startBlock; i--) {
271
- const checkpointedBlock = await this.getCheckpointedBlock(BlockNumber(i));
272
- if (checkpointedBlock) {
273
- blocks.push(checkpointedBlock);
274
- }
275
- }
276
- }
277
- checkpoint = await this.store.getCheckpointData(CheckpointNumber(checkpoint.checkpointNumber - 1));
278
- }
279
-
280
- return blocks.reverse();
275
+ const checkpointsData = await this.getCheckpointsDataForEpoch(epochNumber);
276
+ const blocks = await Promise.all(
277
+ checkpointsData.flatMap(checkpoint =>
278
+ range(checkpoint.blockCount, checkpoint.startBlock).map(blockNumber =>
279
+ this.getCheckpointedBlock(BlockNumber(blockNumber)),
280
+ ),
281
+ ),
282
+ );
283
+ return blocks.filter(isDefined);
281
284
  }
282
285
 
283
286
  public async getCheckpointedBlockHeadersForEpoch(epochNumber: EpochNumber): Promise<BlockHeader[]> {
284
- if (!this.l1Constants) {
285
- throw new Error('L1 constants not set');
286
- }
287
-
288
- const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1Constants);
289
- const blocks: BlockHeader[] = [];
290
-
291
- // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
292
- // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
293
- let checkpoint = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
294
- const slot = (b: CheckpointData) => b.header.slotNumber;
295
- while (checkpoint && slot(checkpoint) >= start) {
296
- if (slot(checkpoint) <= end) {
297
- // push the blocks on backwards
298
- const endBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
299
- for (let i = endBlock; i >= checkpoint.startBlock; i--) {
300
- const block = await this.getBlockHeader(BlockNumber(i));
301
- if (block) {
302
- blocks.push(block);
303
- }
304
- }
305
- }
306
- checkpoint = await this.store.getCheckpointData(CheckpointNumber(checkpoint.checkpointNumber - 1));
307
- }
308
- return blocks.reverse();
287
+ const checkpointsData = await this.getCheckpointsDataForEpoch(epochNumber);
288
+ const blocks = await Promise.all(
289
+ checkpointsData.flatMap(checkpoint =>
290
+ range(checkpoint.blockCount, checkpoint.startBlock).map(blockNumber =>
291
+ this.getBlockHeader(BlockNumber(blockNumber)),
292
+ ),
293
+ ),
294
+ );
295
+ return blocks.filter(isDefined);
309
296
  }
310
297
 
311
298
  public async getCheckpointsForEpoch(epochNumber: EpochNumber): Promise<Checkpoint[]> {
299
+ const checkpointsData = await this.getCheckpointsDataForEpoch(epochNumber);
300
+ return Promise.all(
301
+ checkpointsData.map(data => this.getPublishedCheckpointFromCheckpointData(data).then(p => p.checkpoint)),
302
+ );
303
+ }
304
+
305
+ /** Returns checkpoint data for all checkpoints whose slot falls within the given epoch. */
306
+ public getCheckpointsDataForEpoch(epochNumber: EpochNumber): Promise<CheckpointData[]> {
312
307
  if (!this.l1Constants) {
313
308
  throw new Error('L1 constants not set');
314
309
  }
315
310
 
316
311
  const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1Constants);
317
- const checkpoints: Checkpoint[] = [];
318
-
319
- // Walk the list of checkpoints backwards and filter by slots matching the requested epoch.
320
- // We'll typically ask for checkpoints for a very recent epoch, so we shouldn't need an index here.
321
- let checkpointData = await this.store.getCheckpointData(await this.store.getSynchedCheckpointNumber());
322
- const slot = (b: CheckpointData) => b.header.slotNumber;
323
- while (checkpointData && slot(checkpointData) >= start) {
324
- if (slot(checkpointData) <= end) {
325
- // push the checkpoints on backwards
326
- const [checkpoint] = await this.getCheckpoints(checkpointData.checkpointNumber, 1);
327
- checkpoints.push(checkpoint.checkpoint);
328
- }
329
- checkpointData = await this.store.getCheckpointData(CheckpointNumber(checkpointData.checkpointNumber - 1));
330
- }
331
-
332
- return checkpoints.reverse();
312
+ return this.store.getCheckpointDataForSlotRange(start, end);
333
313
  }
334
314
 
335
315
  public async getBlock(number: BlockNumber): Promise<L2Block | undefined> {