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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dest/archiver/archiver.d.ts +60 -36
  2. package/dest/archiver/archiver.d.ts.map +1 -1
  3. package/dest/archiver/archiver.js +366 -180
  4. package/dest/archiver/archiver_store.d.ts +79 -23
  5. package/dest/archiver/archiver_store.d.ts.map +1 -1
  6. package/dest/archiver/archiver_store_test_suite.d.ts +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  8. package/dest/archiver/archiver_store_test_suite.js +1624 -251
  9. package/dest/archiver/errors.d.ts +25 -1
  10. package/dest/archiver/errors.d.ts.map +1 -1
  11. package/dest/archiver/errors.js +37 -0
  12. package/dest/archiver/index.d.ts +2 -2
  13. package/dest/archiver/index.d.ts.map +1 -1
  14. package/dest/archiver/kv_archiver_store/block_store.d.ts +49 -17
  15. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  16. package/dest/archiver/kv_archiver_store/block_store.js +320 -83
  17. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +29 -27
  18. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  19. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +50 -26
  20. package/dest/archiver/kv_archiver_store/log_store.d.ts +4 -4
  21. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  22. package/dest/archiver/l1/data_retrieval.d.ts +11 -8
  23. package/dest/archiver/l1/data_retrieval.d.ts.map +1 -1
  24. package/dest/archiver/l1/data_retrieval.js +25 -17
  25. package/dest/archiver/structs/published.d.ts +1 -2
  26. package/dest/archiver/structs/published.d.ts.map +1 -1
  27. package/dest/test/mock_l2_block_source.d.ts +3 -2
  28. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  29. package/dest/test/mock_l2_block_source.js +8 -15
  30. package/package.json +13 -13
  31. package/src/archiver/archiver.ts +464 -222
  32. package/src/archiver/archiver_store.ts +88 -22
  33. package/src/archiver/archiver_store_test_suite.ts +1626 -232
  34. package/src/archiver/errors.ts +64 -0
  35. package/src/archiver/index.ts +1 -1
  36. package/src/archiver/kv_archiver_store/block_store.ts +435 -94
  37. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +62 -38
  38. package/src/archiver/kv_archiver_store/log_store.ts +4 -4
  39. package/src/archiver/l1/data_retrieval.ts +27 -13
  40. package/src/archiver/structs/published.ts +0 -1
  41. package/src/test/mock_l2_block_source.ts +9 -16
@@ -1,5 +1,7 @@
1
1
  import {
2
+ INITIAL_CHECKPOINT_NUMBER,
2
3
  INITIAL_L2_BLOCK_NUM,
4
+ MAX_NOTE_HASHES_PER_TX,
3
5
  NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
4
6
  PRIVATE_LOG_SIZE_IN_FIELDS,
5
7
  } from '@aztec/constants';
@@ -13,15 +15,15 @@ import { toArray } from '@aztec/foundation/iterable';
13
15
  import { sleep } from '@aztec/foundation/sleep';
14
16
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
15
17
  import {
18
+ CheckpointedL2Block,
16
19
  CommitteeAttestation,
17
20
  EthAddress,
18
- L2Block,
19
21
  L2BlockHash,
20
- PublishedL2Block,
22
+ L2BlockNew,
21
23
  type ValidateBlockResult,
22
24
  randomBlockInfo,
23
- wrapDataInBlock,
24
25
  } from '@aztec/stdlib/block';
26
+ import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
25
27
  import {
26
28
  type ContractClassPublic,
27
29
  type ContractInstanceWithAddress,
@@ -30,17 +32,27 @@ import {
30
32
  } from '@aztec/stdlib/contract';
31
33
  import { ContractClassLog, LogId, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
32
34
  import { InboxLeaf } from '@aztec/stdlib/messaging';
35
+ import { CheckpointHeader } from '@aztec/stdlib/rollup';
33
36
  import {
34
37
  makeContractClassPublic,
35
38
  makeExecutablePrivateFunctionWithMembershipProof,
36
39
  makeUtilityFunctionWithMembershipProof,
37
40
  } from '@aztec/stdlib/testing';
38
41
  import '@aztec/stdlib/testing/jest';
39
- import { type IndexedTxEffect, TxEffect, TxHash } from '@aztec/stdlib/tx';
42
+ import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
43
+ import { type IndexedTxEffect, PartialStateReference, StateReference, TxEffect, TxHash } from '@aztec/stdlib/tx';
40
44
 
41
45
  import { makeInboxMessage, makeInboxMessages } from '../test/mock_structs.js';
42
46
  import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
43
- import { BlockNumberNotSequentialError, InitialBlockNumberNotSequentialError } from './errors.js';
47
+ import {
48
+ BlockArchiveNotConsistentError,
49
+ BlockIndexNotSequentialError,
50
+ BlockNumberNotSequentialError,
51
+ CheckpointNumberNotConsistentError,
52
+ CheckpointNumberNotSequentialError,
53
+ InitialBlockNumberNotSequentialError,
54
+ InitialCheckpointNumberNotSequentialError,
55
+ } from './errors.js';
44
56
  import { MessageStoreError } from './kv_archiver_store/message_store.js';
45
57
  import type { InboxMessage } from './structs/inbox_message.js';
46
58
 
@@ -54,218 +66,1572 @@ export function describeArchiverDataStore(
54
66
  ) {
55
67
  describe(testName, () => {
56
68
  let store: ArchiverDataStore;
57
- let blocks: PublishedL2Block[];
58
-
59
- const blockTests: [number, number, () => PublishedL2Block[]][] = [
60
- [1, 1, () => blocks.slice(0, 1)],
61
- [10, 1, () => blocks.slice(9, 10)],
62
- [1, 10, () => blocks.slice(0, 10)],
63
- [2, 5, () => blocks.slice(1, 6)],
64
- [5, 2, () => blocks.slice(4, 6)],
69
+ let publishedCheckpoints: PublishedCheckpoint[];
70
+
71
+ const blockNumberTests: [number, () => L2BlockNew][] = [
72
+ [1, () => publishedCheckpoints[0].checkpoint.blocks[0]],
73
+ [10, () => publishedCheckpoints[9].checkpoint.blocks[0]],
74
+ [5, () => publishedCheckpoints[4].checkpoint.blocks[0]],
65
75
  ];
66
76
 
67
77
  const makeBlockHash = (blockNumber: number) => `0x${blockNumber.toString(16).padStart(64, '0')}`;
68
78
 
69
- const makePublished = (block: L2Block, l1BlockNumber: number): PublishedL2Block =>
70
- PublishedL2Block.fromFields({
71
- block: block,
72
- l1: {
73
- blockNumber: BigInt(l1BlockNumber),
74
- blockHash: makeBlockHash(l1BlockNumber),
75
- timestamp: BigInt(l1BlockNumber * 1000),
76
- },
77
- attestations: times(3, CommitteeAttestation.random),
78
- });
79
-
80
- const expectBlocksEqual = (actual: PublishedL2Block[], expected: PublishedL2Block[]) => {
81
- expect(actual.length).toEqual(expected.length);
82
- for (let i = 0; i < expected.length; i++) {
83
- const expectedBlock = expected[i];
84
- const actualBlock = actual[i];
85
- expect(actualBlock.l1).toEqual(expectedBlock.l1);
86
- expect(actualBlock.block.equals(expectedBlock.block)).toBe(true);
87
- expect(actualBlock.attestations.every((a, i) => a.equals(expectedBlock.attestations[i]))).toBe(true);
88
- }
79
+ // Create a state reference with properly calculated noteHashTree.nextAvailableLeafIndex
80
+ // This is needed because the log store calculates dataStartIndexForBlock as:
81
+ // noteHashTree.nextAvailableLeafIndex - txEffects.length * MAX_NOTE_HASHES_PER_TX
82
+ // If nextAvailableLeafIndex is too small (random values 0-1000), this becomes negative
83
+ const makeStateForBlock = (blockNumber: number, txsPerBlock: number): StateReference => {
84
+ // Ensure nextAvailableLeafIndex is large enough for all blocks up to this point
85
+ const noteHashIndex = blockNumber * txsPerBlock * MAX_NOTE_HASHES_PER_TX;
86
+ return new StateReference(
87
+ AppendOnlyTreeSnapshot.random(),
88
+ new PartialStateReference(
89
+ new AppendOnlyTreeSnapshot(Fr.random(), noteHashIndex),
90
+ AppendOnlyTreeSnapshot.random(),
91
+ AppendOnlyTreeSnapshot.random(),
92
+ ),
93
+ );
94
+ };
95
+
96
+ const makePublishedCheckpoint = (checkpoint: Checkpoint, l1BlockNumber: number): PublishedCheckpoint => {
97
+ return new PublishedCheckpoint(
98
+ checkpoint,
99
+ new L1PublishedData(BigInt(l1BlockNumber), BigInt(l1BlockNumber * 1000), makeBlockHash(l1BlockNumber)),
100
+ times(3, CommitteeAttestation.random),
101
+ );
102
+ };
103
+
104
+ const expectCheckpointedBlockEquals = (
105
+ actual: CheckpointedL2Block,
106
+ expectedBlock: L2BlockNew,
107
+ expectedCheckpoint: PublishedCheckpoint,
108
+ ) => {
109
+ expect(actual.l1).toEqual(expectedCheckpoint.l1);
110
+ expect(actual.block.header.equals(expectedBlock.header)).toBe(true);
111
+ expect(actual.checkpointNumber).toEqual(expectedCheckpoint.checkpoint.number);
112
+ expect(actual.attestations.every((a, i) => a.equals(expectedCheckpoint.attestations[i]))).toBe(true);
89
113
  };
90
114
 
91
115
  beforeEach(async () => {
92
116
  store = await getStore();
93
- blocks = await timesParallel(10, async i => makePublished(await L2Block.random(BlockNumber(i + 1)), i + 10));
117
+ // Create checkpoints sequentially to ensure archive roots are chained properly.
118
+ // Each block's header.lastArchive must equal the previous block's archive.
119
+ publishedCheckpoints = [];
120
+ const txsPerBlock = 4;
121
+ for (let i = 0; i < 10; i++) {
122
+ const blockNumber = i + 1;
123
+ const previousArchive = i > 0 ? publishedCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
124
+ const checkpoint = await Checkpoint.random(CheckpointNumber(i + 1), {
125
+ numBlocks: 1,
126
+ startBlockNumber: blockNumber,
127
+ previousArchive,
128
+ txsPerBlock,
129
+ state: makeStateForBlock(blockNumber, txsPerBlock),
130
+ // Ensure each tx has public logs for getPublicLogs tests
131
+ txOptions: { numPublicCallsPerTx: 2, numPublicLogsPerCall: 2 },
132
+ });
133
+ publishedCheckpoints.push(makePublishedCheckpoint(checkpoint, i + 10));
134
+ }
94
135
  });
95
136
 
96
- describe('addBlocks', () => {
97
- it('returns success when adding blocks', async () => {
98
- await expect(store.addBlocks(blocks)).resolves.toBe(true);
137
+ describe('addCheckpoints', () => {
138
+ it('returns success when adding checkpoints', async () => {
139
+ await expect(store.addCheckpoints(publishedCheckpoints)).resolves.toBe(true);
99
140
  });
100
141
 
101
- it('allows duplicate blocks', async () => {
102
- await store.addBlocks(blocks);
103
- await expect(store.addBlocks(blocks)).resolves.toBe(true);
142
+ it('throws on duplicate checkpoints', async () => {
143
+ await store.addCheckpoints(publishedCheckpoints);
144
+ await expect(store.addCheckpoints(publishedCheckpoints)).rejects.toThrow(
145
+ InitialCheckpointNumberNotSequentialError,
146
+ );
104
147
  });
105
148
 
106
149
  it('throws an error if the previous block does not exist in the store', async () => {
107
- const block = makePublished(await L2Block.random(BlockNumber(2)), 2);
108
- await expect(store.addBlocks([block])).rejects.toThrow(InitialBlockNumberNotSequentialError);
109
- await expect(store.getPublishedBlocks(BlockNumber(1), 10)).resolves.toEqual([]);
150
+ const checkpoint = await Checkpoint.random(CheckpointNumber(2), { numBlocks: 1, startBlockNumber: 2 });
151
+ const block = makePublishedCheckpoint(checkpoint, 2);
152
+ await expect(store.addCheckpoints([block])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
153
+ await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
110
154
  });
111
155
 
112
156
  it('throws an error if there is a gap in the blocks being added', async () => {
113
- const blocks = [
114
- makePublished(await L2Block.random(BlockNumber(1)), 1),
115
- makePublished(await L2Block.random(BlockNumber(3)), 3),
116
- ];
117
- await expect(store.addBlocks(blocks)).rejects.toThrow(BlockNumberNotSequentialError);
118
- await expect(store.getPublishedBlocks(BlockNumber(1), 10)).resolves.toEqual([]);
157
+ const checkpoint1 = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 });
158
+ const checkpoint3 = await Checkpoint.random(CheckpointNumber(3), { numBlocks: 1, startBlockNumber: 3 });
159
+ const checkpoints = [makePublishedCheckpoint(checkpoint1, 1), makePublishedCheckpoint(checkpoint3, 3)];
160
+ await expect(store.addCheckpoints(checkpoints)).rejects.toThrow(CheckpointNumberNotSequentialError);
161
+ await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
162
+ });
163
+
164
+ it('throws an error if blocks within a checkpoint are not sequential', async () => {
165
+ // Create a checkpoint with non-sequential block numbers (block 1 and block 3, skipping block 2)
166
+ const block1 = await L2BlockNew.random(BlockNumber(1), { checkpointNumber: CheckpointNumber(1) });
167
+ const block3 = await L2BlockNew.random(BlockNumber(3), { checkpointNumber: CheckpointNumber(1) });
168
+
169
+ const checkpoint = new Checkpoint(
170
+ AppendOnlyTreeSnapshot.random(),
171
+ CheckpointHeader.random(),
172
+ [block1, block3],
173
+ CheckpointNumber(1),
174
+ );
175
+ const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
176
+
177
+ await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockNumberNotSequentialError);
178
+ await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
179
+ });
180
+
181
+ it('throws an error if blocks within a checkpoint do not have sequential indexes', async () => {
182
+ // Create a checkpoint with non-sequential indexes
183
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
184
+ checkpointNumber: CheckpointNumber(1),
185
+ indexWithinCheckpoint: 0,
186
+ });
187
+ const block3 = await L2BlockNew.random(BlockNumber(2), {
188
+ checkpointNumber: CheckpointNumber(1),
189
+ indexWithinCheckpoint: 2,
190
+ });
191
+
192
+ const checkpoint = new Checkpoint(
193
+ AppendOnlyTreeSnapshot.random(),
194
+ CheckpointHeader.random(),
195
+ [block1, block3],
196
+ CheckpointNumber(1),
197
+ );
198
+ const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
199
+
200
+ await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockIndexNotSequentialError);
201
+ await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
202
+ });
203
+
204
+ it('throws an error if blocks within a checkpoint do not start from index 0', async () => {
205
+ // Create a checkpoint with non-sequential indexes
206
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
207
+ checkpointNumber: CheckpointNumber(1),
208
+ indexWithinCheckpoint: 1,
209
+ });
210
+ const block3 = await L2BlockNew.random(BlockNumber(2), {
211
+ checkpointNumber: CheckpointNumber(1),
212
+ indexWithinCheckpoint: 2,
213
+ });
214
+
215
+ const checkpoint = new Checkpoint(
216
+ AppendOnlyTreeSnapshot.random(),
217
+ CheckpointHeader.random(),
218
+ [block1, block3],
219
+ CheckpointNumber(1),
220
+ );
221
+ const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
222
+
223
+ await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockIndexNotSequentialError);
224
+ await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
225
+ });
226
+
227
+ it('throws an error if block has invalid checkpoint index', async () => {
228
+ // Create a block wit an invalid checkpoint index
229
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
230
+ checkpointNumber: CheckpointNumber(1),
231
+ indexWithinCheckpoint: -1,
232
+ });
233
+
234
+ const checkpoint = new Checkpoint(
235
+ AppendOnlyTreeSnapshot.random(),
236
+ CheckpointHeader.random(),
237
+ [block1],
238
+ CheckpointNumber(1),
239
+ );
240
+ const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
241
+
242
+ await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockIndexNotSequentialError);
243
+ await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
244
+ });
245
+
246
+ it('throws an error if checkpoint has invalid initial number', async () => {
247
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
248
+ checkpointNumber: CheckpointNumber(2),
249
+ indexWithinCheckpoint: 0,
250
+ });
251
+
252
+ const checkpoint = new Checkpoint(
253
+ AppendOnlyTreeSnapshot.random(),
254
+ CheckpointHeader.random(),
255
+ [block1],
256
+ CheckpointNumber(2),
257
+ );
258
+ const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
259
+
260
+ await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(
261
+ InitialCheckpointNumberNotSequentialError,
262
+ );
263
+ });
264
+
265
+ it('allows the correct initial checkpoint', async () => {
266
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
267
+ checkpointNumber: CheckpointNumber(1),
268
+ indexWithinCheckpoint: 0,
269
+ });
270
+
271
+ const checkpoint = new Checkpoint(
272
+ AppendOnlyTreeSnapshot.random(),
273
+ CheckpointHeader.random(),
274
+ [block1],
275
+ CheckpointNumber(1),
276
+ );
277
+ const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
278
+
279
+ await expect(store.addCheckpoints([publishedCheckpoint])).resolves.toBe(true);
280
+ });
281
+
282
+ it('throws on duplicate initial checkpoint', async () => {
283
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
284
+ checkpointNumber: CheckpointNumber(1),
285
+ indexWithinCheckpoint: 0,
286
+ });
287
+
288
+ const block2 = await L2BlockNew.random(BlockNumber(1), {
289
+ checkpointNumber: CheckpointNumber(1),
290
+ indexWithinCheckpoint: 0,
291
+ });
292
+
293
+ const checkpoint = new Checkpoint(
294
+ AppendOnlyTreeSnapshot.random(),
295
+ CheckpointHeader.random(),
296
+ [block1],
297
+ CheckpointNumber(1),
298
+ );
299
+ const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
300
+
301
+ const checkpoint2 = new Checkpoint(
302
+ AppendOnlyTreeSnapshot.random(),
303
+ CheckpointHeader.random(),
304
+ [block2],
305
+ CheckpointNumber(1),
306
+ );
307
+ const publishedCheckpoint2 = makePublishedCheckpoint(checkpoint2, 10);
308
+
309
+ await expect(store.addCheckpoints([publishedCheckpoint])).resolves.toBe(true);
310
+ await expect(store.addCheckpoints([publishedCheckpoint2])).rejects.toThrow(
311
+ InitialCheckpointNumberNotSequentialError,
312
+ );
119
313
  });
120
314
  });
121
315
 
122
- describe('unwindBlocks', () => {
123
- it('unwinding blocks will remove blocks from the chain', async () => {
124
- await store.addBlocks(blocks);
125
- const blockNumber = await store.getSynchedL2BlockNumber();
316
+ describe('unwindcheckpoints', () => {
317
+ it('unwinding checkpoints will remove checkpoints from the chain', async () => {
318
+ await store.addCheckpoints(publishedCheckpoints);
319
+ const checkpointNumber = await store.getSynchedCheckpointNumber();
320
+ const lastCheckpoint = publishedCheckpoints.at(-1)!;
321
+ const lastBlockNumber = lastCheckpoint.checkpoint.blocks[0].number;
126
322
 
127
- expectBlocksEqual(await store.getPublishedBlocks(blockNumber, 1), [blocks[blocks.length - 1]]);
323
+ // Verify block exists before unwinding
324
+ const retrievedBlock = await store.getCheckpointedBlock(lastBlockNumber);
325
+ expect(retrievedBlock).toBeDefined();
326
+ expect(retrievedBlock!.block.header.equals(lastCheckpoint.checkpoint.blocks[0].header)).toBe(true);
327
+ expect(retrievedBlock!.checkpointNumber).toEqual(checkpointNumber);
128
328
 
129
- await store.unwindBlocks(blockNumber, 1);
329
+ await store.unwindCheckpoints(checkpointNumber, 1);
130
330
 
131
- expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1);
132
- expect(await store.getPublishedBlocks(blockNumber, 1)).toEqual([]);
331
+ expect(await store.getSynchedCheckpointNumber()).toBe(checkpointNumber - 1);
332
+ await expect(store.getCheckpointedBlock(lastBlockNumber)).resolves.toBeUndefined();
133
333
  });
134
334
 
135
335
  it('can unwind multiple empty blocks', async () => {
136
- const emptyBlocks = await timesParallel(10, async i =>
137
- makePublished(await L2Block.random(BlockNumber(i + 1), 0), i + 10),
138
- );
139
- await store.addBlocks(emptyBlocks);
140
- expect(await store.getSynchedL2BlockNumber()).toBe(10);
336
+ // Create checkpoints sequentially to chain archive roots
337
+ const emptyCheckpoints: PublishedCheckpoint[] = [];
338
+ for (let i = 0; i < 10; i++) {
339
+ const previousArchive = i > 0 ? emptyCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
340
+ const checkpoint = await Checkpoint.random(CheckpointNumber(i + 1), {
341
+ numBlocks: 1,
342
+ startBlockNumber: i + 1,
343
+ txsPerBlock: 0,
344
+ previousArchive,
345
+ });
346
+ emptyCheckpoints.push(makePublishedCheckpoint(checkpoint, i + 10));
347
+ }
348
+ await store.addCheckpoints(emptyCheckpoints);
349
+ expect(await store.getSynchedCheckpointNumber()).toBe(10);
141
350
 
142
- await store.unwindBlocks(BlockNumber(10), 3);
143
- expect(await store.getSynchedL2BlockNumber()).toBe(7);
144
- expect((await store.getPublishedBlocks(BlockNumber(1), 10)).map(b => b.block.number)).toEqual([
351
+ await store.unwindCheckpoints(CheckpointNumber(10), 3);
352
+ expect(await store.getSynchedCheckpointNumber()).toBe(7);
353
+ expect((await store.getRangeOfCheckpoints(CheckpointNumber(1), 10)).map(b => b.checkpointNumber)).toEqual([
145
354
  1, 2, 3, 4, 5, 6, 7,
146
355
  ]);
147
356
  });
148
357
 
149
- it('refuses to unwind blocks if the tip is not the last block', async () => {
150
- await store.addBlocks(blocks);
151
- await expect(store.unwindBlocks(BlockNumber(5), 1)).rejects.toThrow(/can only unwind blocks from the tip/i);
358
+ it('refuses to unwind checkpoints if the tip is not the last checkpoint', async () => {
359
+ await store.addCheckpoints(publishedCheckpoints);
360
+ await expect(store.unwindCheckpoints(CheckpointNumber(5), 1)).rejects.toThrow(
361
+ /can only unwind checkpoints from the tip/i,
362
+ );
152
363
  });
153
364
 
154
365
  it('unwound blocks and headers cannot be retrieved by hash or archive', async () => {
155
- await store.addBlocks(blocks);
156
- const lastBlock = blocks[blocks.length - 1];
157
- const blockHash = await lastBlock.block.hash();
158
- const archive = lastBlock.block.archive.root;
366
+ await store.addCheckpoints(publishedCheckpoints);
367
+ const lastCheckpoint = publishedCheckpoints[publishedCheckpoints.length - 1];
368
+ const lastBlock = lastCheckpoint.checkpoint.blocks[0];
369
+ const blockHash = await lastBlock.header.hash();
370
+ const archive = lastBlock.archive.root;
159
371
 
160
372
  // Verify block and header exist before unwinding
161
- expect(await store.getPublishedBlockByHash(blockHash)).toBeDefined();
162
- expect(await store.getPublishedBlockByArchive(archive)).toBeDefined();
163
- expect(await store.getBlockHeaderByHash(blockHash)).toBeDefined();
164
- expect(await store.getBlockHeaderByArchive(archive)).toBeDefined();
373
+ const retrievedByHash = await store.getCheckpointedBlockByHash(blockHash);
374
+ expect(retrievedByHash).toBeDefined();
375
+ expect(retrievedByHash!.block.header.equals(lastBlock.header)).toBe(true);
376
+
377
+ const retrievedByArchive = await store.getCheckpointedBlockByArchive(archive);
378
+ expect(retrievedByArchive).toBeDefined();
379
+ expect(retrievedByArchive!.block.header.equals(lastBlock.header)).toBe(true);
165
380
 
166
- // Unwind the block
167
- await store.unwindBlocks(lastBlock.block.number, 1);
381
+ const headerByHash = await store.getBlockHeaderByHash(blockHash);
382
+ expect(headerByHash).toBeDefined();
383
+ expect(headerByHash!.equals(lastBlock.header)).toBe(true);
384
+
385
+ const headerByArchive = await store.getBlockHeaderByArchive(archive);
386
+ expect(headerByArchive).toBeDefined();
387
+ expect(headerByArchive!.equals(lastBlock.header)).toBe(true);
388
+
389
+ // Unwind the checkpoint
390
+ await store.unwindCheckpoints(lastCheckpoint.checkpoint.number, 1);
168
391
 
169
392
  // Verify neither block nor header can be retrieved after unwinding
170
- expect(await store.getPublishedBlockByHash(blockHash)).toBeUndefined();
171
- expect(await store.getPublishedBlockByArchive(archive)).toBeUndefined();
393
+ expect(await store.getCheckpointedBlockByHash(blockHash)).toBeUndefined();
394
+ expect(await store.getCheckpointedBlockByArchive(archive)).toBeUndefined();
172
395
  expect(await store.getBlockHeaderByHash(blockHash)).toBeUndefined();
173
396
  expect(await store.getBlockHeaderByArchive(archive)).toBeUndefined();
174
397
  });
175
398
  });
176
399
 
177
- describe('getBlocks', () => {
178
- beforeEach(async () => {
179
- await store.addBlocks(blocks);
400
+ describe('multi-block checkpoints', () => {
401
+ it('block number increases correctly when adding checkpoints with multiple blocks', async () => {
402
+ // Create 3 checkpoints: first with 2 blocks, second with 3 blocks, third with 1 block
403
+ // Total blocks: 6, spanning block numbers 1-6
404
+ // Chain archive roots across checkpoints
405
+ const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
406
+ const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
407
+
408
+ const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
409
+ const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
410
+ numBlocks: 3,
411
+ startBlockNumber: 3,
412
+ previousArchive: previousArchive1,
413
+ });
414
+ const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
415
+
416
+ const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
417
+ const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
418
+ numBlocks: 1,
419
+ startBlockNumber: 6,
420
+ previousArchive: previousArchive2,
421
+ });
422
+ const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
423
+
424
+ await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
425
+
426
+ // Checkpoint number should be 3 (the last checkpoint number)
427
+ expect(await store.getSynchedCheckpointNumber()).toBe(3);
428
+ // Block number should be 6 (the last block number across all checkpoints)
429
+ expect(await store.getLatestBlockNumber()).toBe(6);
180
430
  });
181
431
 
182
- it.each(blockTests)('retrieves previously stored blocks', async (start, limit, getExpectedBlocks) => {
183
- expectBlocksEqual(await store.getPublishedBlocks(BlockNumber(start), limit), getExpectedBlocks());
432
+ it('block number decreases correctly when unwinding checkpoints with multiple blocks', async () => {
433
+ // Create 3 checkpoints with varying block counts, chaining archive roots
434
+ const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
435
+ const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
436
+
437
+ const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
438
+ const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
439
+ numBlocks: 3,
440
+ startBlockNumber: 3,
441
+ previousArchive: previousArchive1,
442
+ });
443
+ const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
444
+
445
+ const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
446
+ const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
447
+ numBlocks: 2,
448
+ startBlockNumber: 6,
449
+ previousArchive: previousArchive2,
450
+ });
451
+ const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
452
+
453
+ await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
454
+
455
+ expect(await store.getSynchedCheckpointNumber()).toBe(3);
456
+ expect(await store.getLatestBlockNumber()).toBe(7);
457
+
458
+ // Unwind the last checkpoint (which has 2 blocks)
459
+ await store.unwindCheckpoints(CheckpointNumber(3), 1);
460
+
461
+ expect(await store.getSynchedCheckpointNumber()).toBe(2);
462
+ expect(await store.getLatestBlockNumber()).toBe(5);
463
+
464
+ // Unwind another checkpoint (which has 3 blocks)
465
+ await store.unwindCheckpoints(CheckpointNumber(2), 1);
466
+
467
+ expect(await store.getSynchedCheckpointNumber()).toBe(1);
468
+ expect(await store.getLatestBlockNumber()).toBe(2);
184
469
  });
185
470
 
186
- it('returns an empty array if no blocks are found', async () => {
187
- await expect(store.getPublishedBlocks(BlockNumber(12), 1)).resolves.toEqual([]);
471
+ it('unwinding multiple checkpoints with multiple blocks in one go', async () => {
472
+ // Create 4 checkpoints with varying block counts, chaining archive roots
473
+ // Checkpoint 1: blocks 1-2 (2 blocks)
474
+ // Checkpoint 2: blocks 3-5 (3 blocks)
475
+ // Checkpoint 3: blocks 6-7 (2 blocks)
476
+ // Checkpoint 4: blocks 8-10 (3 blocks)
477
+ // Total: 10 blocks across 4 checkpoints
478
+ const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
479
+ const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
480
+
481
+ const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
482
+ const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
483
+ numBlocks: 3,
484
+ startBlockNumber: 3,
485
+ previousArchive: previousArchive1,
486
+ });
487
+ const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
488
+
489
+ const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
490
+ const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
491
+ numBlocks: 2,
492
+ startBlockNumber: 6,
493
+ previousArchive: previousArchive2,
494
+ });
495
+ const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
496
+
497
+ const previousArchive3 = checkpoint3Cp.blocks.at(-1)!.archive;
498
+ const checkpoint4Cp = await Checkpoint.random(CheckpointNumber(4), {
499
+ numBlocks: 3,
500
+ startBlockNumber: 8,
501
+ previousArchive: previousArchive3,
502
+ });
503
+ const checkpoint4 = makePublishedCheckpoint(checkpoint4Cp, 13);
504
+
505
+ await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3, checkpoint4]);
506
+
507
+ expect(await store.getSynchedCheckpointNumber()).toBe(4);
508
+ expect(await store.getLatestBlockNumber()).toBe(10);
509
+
510
+ // Unwind 2 checkpoints at once (checkpoints 3 and 4, which together have 5 blocks)
511
+ await store.unwindCheckpoints(CheckpointNumber(4), 2);
512
+
513
+ expect(await store.getSynchedCheckpointNumber()).toBe(2);
514
+ expect(await store.getLatestBlockNumber()).toBe(5);
515
+
516
+ // Verify blocks 1-5 still exist (from checkpoints 1 and 2)
517
+ for (let blockNumber = 1; blockNumber <= 5; blockNumber++) {
518
+ expect(await store.getCheckpointedBlock(blockNumber)).toBeDefined();
519
+ }
520
+
521
+ // Verify blocks 6-10 are gone (from checkpoints 3 and 4)
522
+ for (let blockNumber = 6; blockNumber <= 10; blockNumber++) {
523
+ expect(await store.getCheckpointedBlock(blockNumber)).toBeUndefined();
524
+ }
525
+
526
+ // Unwind remaining 2 checkpoints at once (checkpoints 1 and 2, which together have 5 blocks)
527
+ await store.unwindCheckpoints(CheckpointNumber(2), 2);
528
+
529
+ expect(await store.getSynchedCheckpointNumber()).toBe(0);
530
+ expect(await store.getLatestBlockNumber()).toBe(0);
531
+
532
+ // Verify all blocks are gone
533
+ for (let blockNumber = 1; blockNumber <= 10; blockNumber++) {
534
+ expect(await store.getCheckpointedBlock(blockNumber)).toBeUndefined();
535
+ }
188
536
  });
189
537
 
190
- it('throws an error if limit is invalid', async () => {
191
- await expect(store.getPublishedBlocks(BlockNumber(1), 0)).rejects.toThrow('Invalid limit: 0');
538
+ it('getCheckpointedBlock returns correct checkpoint info for blocks within multi-block checkpoints', async () => {
539
+ // Create checkpoints with chained archive roots
540
+ // Create a checkpoint with 3 blocks
541
+ const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 });
542
+ const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
543
+
544
+ // Create another checkpoint with 2 blocks
545
+ const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
546
+ const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
547
+ numBlocks: 2,
548
+ startBlockNumber: 4,
549
+ previousArchive: previousArchive1,
550
+ });
551
+ const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
552
+
553
+ await store.addCheckpoints([checkpoint1, checkpoint2]);
554
+
555
+ // Check blocks from the first checkpoint (blocks 1, 2, 3)
556
+ for (let i = 0; i < 3; i++) {
557
+ const blockNumber = i + 1;
558
+ const retrievedBlock = await store.getCheckpointedBlock(blockNumber);
559
+
560
+ expect(retrievedBlock).toBeDefined();
561
+ expect(retrievedBlock!.checkpointNumber).toBe(1);
562
+ expect(retrievedBlock!.block.number).toBe(blockNumber);
563
+ expect(retrievedBlock!.l1).toEqual(checkpoint1.l1);
564
+ expect(retrievedBlock!.attestations.every((a, j) => a.equals(checkpoint1.attestations[j]))).toBe(true);
565
+ }
566
+
567
+ // Check blocks from the second checkpoint (blocks 4, 5)
568
+ for (let i = 0; i < 2; i++) {
569
+ const blockNumber = i + 4;
570
+ const retrievedBlock = await store.getCheckpointedBlock(blockNumber);
571
+
572
+ expect(retrievedBlock).toBeDefined();
573
+ expect(retrievedBlock!.checkpointNumber).toBe(2);
574
+ expect(retrievedBlock!.block.number).toBe(blockNumber);
575
+ expect(retrievedBlock!.l1).toEqual(checkpoint2.l1);
576
+ expect(retrievedBlock!.attestations.every((a, j) => a.equals(checkpoint2.attestations[j]))).toBe(true);
577
+ }
192
578
  });
193
579
 
194
- it('throws an error if `from` it is out of range', async () => {
195
- await expect(store.getPublishedBlocks((INITIAL_L2_BLOCK_NUM - 100) as BlockNumber, 1)).rejects.toThrow(
196
- 'Invalid start: -99',
580
+ it('getCheckpointedBlockByHash returns correct checkpoint info for blocks within multi-block checkpoints', async () => {
581
+ const checkpoint = makePublishedCheckpoint(
582
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
583
+ 10,
197
584
  );
585
+
586
+ await store.addCheckpoints([checkpoint]);
587
+
588
+ // Check each block by its hash
589
+ for (let i = 0; i < checkpoint.checkpoint.blocks.length; i++) {
590
+ const block = checkpoint.checkpoint.blocks[i];
591
+ const blockHash = await block.header.hash();
592
+ const retrievedBlock = await store.getCheckpointedBlockByHash(blockHash);
593
+
594
+ expect(retrievedBlock).toBeDefined();
595
+ expect(retrievedBlock!.checkpointNumber).toBe(1);
596
+ expect(retrievedBlock!.block.number).toBe(i + 1);
597
+ expect(retrievedBlock!.l1).toEqual(checkpoint.l1);
598
+ }
198
599
  });
199
600
 
200
- it('throws an error if unexpected initial block number is found', async () => {
201
- await store.addBlocks([makePublished(await L2Block.random(BlockNumber(21)), 31)], { force: true });
202
- await expect(store.getPublishedBlocks(BlockNumber(20), 1)).rejects.toThrow(`mismatch`);
601
+ it('getCheckpointedBlockByArchive returns correct checkpoint info for blocks within multi-block checkpoints', async () => {
602
+ const checkpoint = makePublishedCheckpoint(
603
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
604
+ 10,
605
+ );
606
+
607
+ await store.addCheckpoints([checkpoint]);
608
+
609
+ // Check each block by its archive root
610
+ for (let i = 0; i < checkpoint.checkpoint.blocks.length; i++) {
611
+ const block = checkpoint.checkpoint.blocks[i];
612
+ const archive = block.archive.root;
613
+ const retrievedBlock = await store.getCheckpointedBlockByArchive(archive);
614
+
615
+ expect(retrievedBlock).toBeDefined();
616
+ expect(retrievedBlock!.checkpointNumber).toBe(1);
617
+ expect(retrievedBlock!.block.number).toBe(i + 1);
618
+ expect(retrievedBlock!.l1).toEqual(checkpoint.l1);
619
+ }
203
620
  });
204
621
 
205
- it('throws an error if a gap is found', async () => {
206
- await store.addBlocks(
207
- [
208
- makePublished(await L2Block.random(BlockNumber(20)), 30),
209
- makePublished(await L2Block.random(BlockNumber(22)), 32),
210
- ],
211
- { force: true },
622
+ it('unwinding a multi-block checkpoint removes all its blocks', async () => {
623
+ const checkpoint = makePublishedCheckpoint(
624
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
625
+ 10,
212
626
  );
213
- await expect(store.getPublishedBlocks(BlockNumber(20), 2)).rejects.toThrow(`mismatch`);
627
+
628
+ await store.addCheckpoints([checkpoint]);
629
+
630
+ // Verify all 3 blocks exist
631
+ for (let blockNumber = 1; blockNumber <= 3; blockNumber++) {
632
+ expect(await store.getCheckpointedBlock(blockNumber)).toBeDefined();
633
+ }
634
+
635
+ // Unwind the checkpoint
636
+ await store.unwindCheckpoints(CheckpointNumber(1), 1);
637
+
638
+ // Verify all 3 blocks are removed
639
+ for (let blockNumber = 1; blockNumber <= 3; blockNumber++) {
640
+ expect(await store.getCheckpointedBlock(blockNumber)).toBeUndefined();
641
+ }
642
+
643
+ expect(await store.getSynchedCheckpointNumber()).toBe(0);
644
+ expect(await store.getLatestBlockNumber()).toBe(0);
214
645
  });
215
646
  });
216
647
 
217
- describe('getPublishedBlockByHash', () => {
648
+ describe('uncheckpointed blocks', () => {
649
+ it('can add blocks independently before a checkpoint arrives', async () => {
650
+ // First, establish some checkpointed blocks (checkpoint 1 with blocks 1-3)
651
+ const checkpoint1 = makePublishedCheckpoint(
652
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
653
+ 10,
654
+ );
655
+ await store.addCheckpoints([checkpoint1]);
656
+
657
+ expect(await store.getSynchedCheckpointNumber()).toBe(1);
658
+ expect(await store.getLatestBlockNumber()).toBe(3);
659
+
660
+ // Now add blocks 4, 5, 6 independently (without a checkpoint) for upcoming checkpoint 2
661
+ // Chain archive roots from the last block of checkpoint 1
662
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
663
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
664
+ checkpointNumber: CheckpointNumber(2),
665
+ indexWithinCheckpoint: 0,
666
+ lastArchive: lastBlockArchive,
667
+ });
668
+ const block5 = await L2BlockNew.random(BlockNumber(5), {
669
+ checkpointNumber: CheckpointNumber(2),
670
+ indexWithinCheckpoint: 1,
671
+ lastArchive: block4.archive,
672
+ });
673
+ const block6 = await L2BlockNew.random(BlockNumber(6), {
674
+ checkpointNumber: CheckpointNumber(2),
675
+ indexWithinCheckpoint: 2,
676
+ lastArchive: block5.archive,
677
+ });
678
+
679
+ await store.addBlocks([block4, block5, block6]);
680
+
681
+ // Checkpoint number should still be 1 (no new checkpoint added)
682
+ expect(await store.getSynchedCheckpointNumber()).toBe(1);
683
+ // But latest block number should be 6
684
+ expect(await store.getLatestBlockNumber()).toBe(6);
685
+ });
686
+
687
+ it('getBlock retrieves uncheckpointed blocks', async () => {
688
+ // First, establish some checkpointed blocks
689
+ const checkpoint1 = makePublishedCheckpoint(
690
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
691
+ 10,
692
+ );
693
+ await store.addCheckpoints([checkpoint1]);
694
+
695
+ // Add uncheckpointed blocks for upcoming checkpoint 2, chaining archive roots
696
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
697
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
698
+ checkpointNumber: CheckpointNumber(2),
699
+ indexWithinCheckpoint: 0,
700
+ lastArchive: lastBlockArchive,
701
+ });
702
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
703
+ checkpointNumber: CheckpointNumber(2),
704
+ indexWithinCheckpoint: 1,
705
+ lastArchive: block3.archive,
706
+ });
707
+ await store.addBlocks([block3, block4]);
708
+
709
+ // getBlock should work for both checkpointed and uncheckpointed blocks
710
+ expect((await store.getBlock(1))?.number).toBe(1);
711
+ expect((await store.getBlock(2))?.number).toBe(2);
712
+ expect(await store.getBlock(3)).toEqual(block3);
713
+ expect(await store.getBlock(4)).toEqual(block4);
714
+ expect(await store.getBlock(5)).toBeUndefined();
715
+
716
+ const block5 = await L2BlockNew.random(BlockNumber(5), {
717
+ checkpointNumber: CheckpointNumber(2),
718
+ indexWithinCheckpoint: 2,
719
+ lastArchive: block4.archive,
720
+ });
721
+ await store.addBlocks([block5]);
722
+
723
+ // Verify the uncheckpointed blocks have correct data
724
+ const retrieved3 = await store.getBlock(3);
725
+ expect(retrieved3!.number).toBe(3);
726
+ expect(retrieved3).toEqual(block3);
727
+ const retrieved4 = await store.getBlock(4);
728
+ expect(retrieved4!.number).toBe(4);
729
+ expect(retrieved4).toEqual(block4);
730
+ const retrieved5 = await store.getBlock(5);
731
+ expect(retrieved5!.number).toBe(5);
732
+ expect(retrieved5).toEqual(block5);
733
+ });
734
+
735
+ it('getBlockByHash retrieves uncheckpointed blocks', async () => {
736
+ // Add uncheckpointed blocks (no checkpoints at all) for initial checkpoint 1, chaining archive roots
737
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
738
+ checkpointNumber: CheckpointNumber(1),
739
+ indexWithinCheckpoint: 0,
740
+ });
741
+ const block2 = await L2BlockNew.random(BlockNumber(2), {
742
+ checkpointNumber: CheckpointNumber(1),
743
+ indexWithinCheckpoint: 1,
744
+ lastArchive: block1.archive,
745
+ });
746
+ await store.addBlocks([block1, block2]);
747
+
748
+ // getBlockByHash should work for uncheckpointed blocks
749
+ const hash1 = await block1.header.hash();
750
+ const hash2 = await block2.header.hash();
751
+
752
+ const retrieved1 = await store.getBlockByHash(hash1);
753
+ expect(retrieved1).toEqual(block1);
754
+
755
+ const retrieved2 = await store.getBlockByHash(hash2);
756
+ expect(retrieved2).toEqual(block2);
757
+ });
758
+
759
+ it('getBlockByArchive retrieves uncheckpointed blocks', async () => {
760
+ // Add uncheckpointed blocks for initial checkpoint 1, chaining archive roots
761
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
762
+ checkpointNumber: CheckpointNumber(1),
763
+ indexWithinCheckpoint: 0,
764
+ });
765
+ const block2 = await L2BlockNew.random(BlockNumber(2), {
766
+ checkpointNumber: CheckpointNumber(1),
767
+ indexWithinCheckpoint: 1,
768
+ lastArchive: block1.archive,
769
+ });
770
+ await store.addBlocks([block1, block2]);
771
+
772
+ // getBlockByArchive should work for uncheckpointed blocks
773
+ const archive1 = block1.archive.root;
774
+ const archive2 = block2.archive.root;
775
+
776
+ const retrieved1 = await store.getBlockByArchive(archive1);
777
+ expect(retrieved1).toEqual(block1);
778
+
779
+ const retrieved2 = await store.getBlockByArchive(archive2);
780
+ expect(retrieved2).toEqual(block2);
781
+ });
782
+
783
+ it('getCheckpointedBlock returns undefined for uncheckpointed blocks', async () => {
784
+ // Add a checkpoint with blocks 1-2
785
+ const checkpoint1 = makePublishedCheckpoint(
786
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
787
+ 10,
788
+ );
789
+ await store.addCheckpoints([checkpoint1]);
790
+
791
+ // Add uncheckpointed blocks 3-4 for upcoming checkpoint 2, chaining archive roots
792
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
793
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
794
+ checkpointNumber: CheckpointNumber(2),
795
+ indexWithinCheckpoint: 0,
796
+ lastArchive: lastBlockArchive,
797
+ });
798
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
799
+ checkpointNumber: CheckpointNumber(2),
800
+ indexWithinCheckpoint: 1,
801
+ lastArchive: block3.archive,
802
+ });
803
+ await store.addBlocks([block3, block4]);
804
+
805
+ // getCheckpointedBlock should work for checkpointed blocks
806
+ expect((await store.getCheckpointedBlock(1))?.block.number).toBe(1);
807
+ expect((await store.getCheckpointedBlock(2))?.block.number).toBe(2);
808
+
809
+ // getCheckpointedBlock should return undefined for uncheckpointed blocks
810
+ expect(await store.getCheckpointedBlock(3)).toBeUndefined();
811
+ expect(await store.getCheckpointedBlock(4)).toBeUndefined();
812
+
813
+ // But getBlock should work for all blocks
814
+ expect(await store.getBlock(3)).toEqual(block3);
815
+ expect(await store.getBlock(4)).toEqual(block4);
816
+ });
817
+
818
+ it('getCheckpointedBlockByHash returns undefined for uncheckpointed blocks', async () => {
819
+ // Add uncheckpointed blocks for initial checkpoint 1
820
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
821
+ checkpointNumber: CheckpointNumber(1),
822
+ indexWithinCheckpoint: 0,
823
+ });
824
+ await store.addBlocks([block1]);
825
+
826
+ const hash = await block1.header.hash();
827
+
828
+ // getCheckpointedBlockByHash should return undefined
829
+ expect(await store.getCheckpointedBlockByHash(hash)).toBeUndefined();
830
+
831
+ // But getBlockByHash should work
832
+ expect(await store.getBlockByHash(hash)).toEqual(block1);
833
+ });
834
+
835
+ it('getCheckpointedBlockByArchive returns undefined for uncheckpointed blocks', async () => {
836
+ // Add uncheckpointed blocks for initial checkpoint 1
837
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
838
+ checkpointNumber: CheckpointNumber(1),
839
+ indexWithinCheckpoint: 0,
840
+ });
841
+ await store.addBlocks([block1]);
842
+
843
+ const archive = block1.archive.root;
844
+
845
+ // getCheckpointedBlockByArchive should return undefined
846
+ expect(await store.getCheckpointedBlockByArchive(archive)).toBeUndefined();
847
+
848
+ // But getBlockByArchive should work
849
+ expect(await store.getBlockByArchive(archive)).toEqual(block1);
850
+ });
851
+
852
+ it('checkpoint adopts previously added uncheckpointed blocks', async () => {
853
+ // Add blocks 1-3 without a checkpoint (for initial checkpoint 1), chaining archive roots
854
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
855
+ checkpointNumber: CheckpointNumber(1),
856
+ indexWithinCheckpoint: 0,
857
+ });
858
+ const block2 = await L2BlockNew.random(BlockNumber(2), {
859
+ checkpointNumber: CheckpointNumber(1),
860
+ indexWithinCheckpoint: 1,
861
+ lastArchive: block1.archive,
862
+ });
863
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
864
+ checkpointNumber: CheckpointNumber(1),
865
+ indexWithinCheckpoint: 2,
866
+ lastArchive: block2.archive,
867
+ });
868
+ await store.addBlocks([block1, block2, block3]);
869
+
870
+ expect(await store.getSynchedCheckpointNumber()).toBe(0);
871
+ expect(await store.getLatestBlockNumber()).toBe(3);
872
+
873
+ // getCheckpointedBlock should return undefined for all
874
+ expect(await store.getCheckpointedBlock(1)).toBeUndefined();
875
+ expect(await store.getCheckpointedBlock(2)).toBeUndefined();
876
+ expect(await store.getCheckpointedBlock(3)).toBeUndefined();
877
+
878
+ // Now add a checkpoint that covers blocks 1-3
879
+ const checkpoint1 = makePublishedCheckpoint(
880
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
881
+ 10,
882
+ );
883
+ await store.addCheckpoints([checkpoint1]);
884
+
885
+ expect(await store.getSynchedCheckpointNumber()).toBe(1);
886
+ expect(await store.getLatestBlockNumber()).toBe(3);
887
+
888
+ // Now getCheckpointedBlock should work for all blocks
889
+ const checkpointed1 = await store.getCheckpointedBlock(1);
890
+ expect(checkpointed1).toBeDefined();
891
+ expect(checkpointed1!.checkpointNumber).toBe(1);
892
+ expect(checkpointed1!.l1).toEqual(checkpoint1.l1);
893
+
894
+ const checkpointed2 = await store.getCheckpointedBlock(2);
895
+ expect(checkpointed2).toBeDefined();
896
+ expect(checkpointed2!.checkpointNumber).toBe(1);
897
+
898
+ const checkpointed3 = await store.getCheckpointedBlock(3);
899
+ expect(checkpointed3).toBeDefined();
900
+ expect(checkpointed3!.checkpointNumber).toBe(1);
901
+ });
902
+
903
+ it('can add more uncheckpointed blocks after a checkpoint and then checkpoint them', async () => {
904
+ // Start with checkpoint 1 covering blocks 1-2
905
+ const checkpoint1 = makePublishedCheckpoint(
906
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
907
+ 10,
908
+ );
909
+ await store.addCheckpoints([checkpoint1]);
910
+
911
+ // Add uncheckpointed blocks 3-5 for the upcoming checkpoint 2, chaining archive roots
912
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
913
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
914
+ checkpointNumber: CheckpointNumber(2),
915
+ indexWithinCheckpoint: 0,
916
+ lastArchive: lastBlockArchive,
917
+ });
918
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
919
+ checkpointNumber: CheckpointNumber(2),
920
+ indexWithinCheckpoint: 1,
921
+ lastArchive: block3.archive,
922
+ });
923
+ const block5 = await L2BlockNew.random(BlockNumber(5), {
924
+ checkpointNumber: CheckpointNumber(2),
925
+ indexWithinCheckpoint: 2,
926
+ lastArchive: block4.archive,
927
+ });
928
+ await store.addBlocks([block3, block4, block5]);
929
+
930
+ expect(await store.getSynchedCheckpointNumber()).toBe(1);
931
+ expect(await store.getLatestBlockNumber()).toBe(5);
932
+
933
+ // Blocks 3-5 are not checkpointed yet
934
+ expect(await store.getCheckpointedBlock(3)).toBeUndefined();
935
+ expect(await store.getCheckpointedBlock(4)).toBeUndefined();
936
+ expect(await store.getCheckpointedBlock(5)).toBeUndefined();
937
+
938
+ // Add checkpoint 2 covering blocks 3-5, chaining from checkpoint1
939
+ const checkpoint2 = makePublishedCheckpoint(
940
+ await Checkpoint.random(CheckpointNumber(2), {
941
+ numBlocks: 3,
942
+ startBlockNumber: 3,
943
+ previousArchive: lastBlockArchive,
944
+ }),
945
+ 11,
946
+ );
947
+ await store.addCheckpoints([checkpoint2]);
948
+
949
+ expect(await store.getSynchedCheckpointNumber()).toBe(2);
950
+ expect(await store.getLatestBlockNumber()).toBe(5);
951
+
952
+ // Now blocks 3-5 should be checkpointed with checkpoint 2's info
953
+ const checkpointed3 = await store.getCheckpointedBlock(3);
954
+ expect(checkpointed3).toBeDefined();
955
+ expect(checkpointed3!.checkpointNumber).toBe(2);
956
+ expect(checkpointed3!.l1).toEqual(checkpoint2.l1);
957
+
958
+ const checkpointed4 = await store.getCheckpointedBlock(4);
959
+ expect(checkpointed4).toBeDefined();
960
+ expect(checkpointed4!.checkpointNumber).toBe(2);
961
+
962
+ const checkpointed5 = await store.getCheckpointedBlock(5);
963
+ expect(checkpointed5).toBeDefined();
964
+ expect(checkpointed5!.checkpointNumber).toBe(2);
965
+ });
966
+
967
+ it('getBlocks retrieves both checkpointed and uncheckpointed blocks', async () => {
968
+ // Add checkpoint with blocks 1-2
969
+ const checkpoint1 = makePublishedCheckpoint(
970
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
971
+ 10,
972
+ );
973
+ await store.addCheckpoints([checkpoint1]);
974
+
975
+ // Add uncheckpointed blocks 3-4 for the upcoming checkpoint 2, chaining archive roots
976
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
977
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
978
+ checkpointNumber: CheckpointNumber(2),
979
+ indexWithinCheckpoint: 0,
980
+ lastArchive: lastBlockArchive,
981
+ });
982
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
983
+ checkpointNumber: CheckpointNumber(2),
984
+ indexWithinCheckpoint: 1,
985
+ lastArchive: block3.archive,
986
+ });
987
+ await store.addBlocks([block3, block4]);
988
+
989
+ // getBlocks should retrieve all blocks
990
+ const allBlocks = await store.getBlocks(1, 10);
991
+ expect(allBlocks.length).toBe(4);
992
+ expect(allBlocks.map(b => b.number)).toEqual([1, 2, 3, 4]);
993
+ });
994
+ });
995
+
996
+ describe('addBlocks validation', () => {
997
+ it('throws if blocks have different checkpoint numbers', async () => {
998
+ // First, establish checkpoint 1 with blocks 1-2
999
+ const checkpoint1 = makePublishedCheckpoint(
1000
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1001
+ 10,
1002
+ );
1003
+ await store.addCheckpoints([checkpoint1]);
1004
+
1005
+ // Try to add blocks 3 and 4 with different checkpoint numbers
1006
+ // Chain archives correctly to test the checkpoint number validation
1007
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
1008
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1009
+ checkpointNumber: CheckpointNumber(2),
1010
+ indexWithinCheckpoint: 0,
1011
+ lastArchive: lastBlockArchive,
1012
+ });
1013
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
1014
+ checkpointNumber: CheckpointNumber(3),
1015
+ indexWithinCheckpoint: 1,
1016
+ lastArchive: block3.archive,
1017
+ });
1018
+
1019
+ await expect(store.addBlocks([block3, block4])).rejects.toThrow(CheckpointNumberNotConsistentError);
1020
+ });
1021
+
1022
+ it('throws if checkpoint number is not the current checkpoint', async () => {
1023
+ // First, establish checkpoint 1 with blocks 1-2
1024
+ const checkpoint1 = makePublishedCheckpoint(
1025
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1026
+ 10,
1027
+ );
1028
+ await store.addCheckpoints([checkpoint1]);
1029
+
1030
+ // Try to add blocks for checkpoint 3 (skipping checkpoint 2)
1031
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1032
+ checkpointNumber: CheckpointNumber(3),
1033
+ indexWithinCheckpoint: 0,
1034
+ });
1035
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
1036
+ checkpointNumber: CheckpointNumber(3),
1037
+ indexWithinCheckpoint: 1,
1038
+ });
1039
+
1040
+ await expect(store.addBlocks([block3, block4])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
1041
+ });
1042
+
1043
+ it('allows blocks with the same checkpoint number for the current checkpoint', async () => {
1044
+ // First, establish checkpoint 1 with blocks 1-2
1045
+ const checkpoint1 = makePublishedCheckpoint(
1046
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1047
+ 10,
1048
+ );
1049
+ await store.addCheckpoints([checkpoint1]);
1050
+
1051
+ // Add blocks 3 and 4 with consistent checkpoint number (2), chaining archive roots
1052
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
1053
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1054
+ checkpointNumber: CheckpointNumber(2),
1055
+ indexWithinCheckpoint: 0,
1056
+ lastArchive: lastBlockArchive,
1057
+ });
1058
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
1059
+ checkpointNumber: CheckpointNumber(2),
1060
+ indexWithinCheckpoint: 1,
1061
+ lastArchive: block3.archive,
1062
+ });
1063
+
1064
+ await expect(store.addBlocks([block3, block4])).resolves.toBe(true);
1065
+
1066
+ // Verify blocks were added
1067
+ expect(await store.getBlock(3)).toEqual(block3);
1068
+ expect(await store.getBlock(4)).toEqual(block4);
1069
+ });
1070
+
1071
+ it('allows blocks for the initial checkpoint when store is empty', async () => {
1072
+ // Add blocks for the initial checkpoint (1), chaining archive roots
1073
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
1074
+ checkpointNumber: CheckpointNumber(1),
1075
+ indexWithinCheckpoint: 0,
1076
+ });
1077
+ const block2 = await L2BlockNew.random(BlockNumber(2), {
1078
+ checkpointNumber: CheckpointNumber(1),
1079
+ indexWithinCheckpoint: 1,
1080
+ lastArchive: block1.archive,
1081
+ });
1082
+
1083
+ await expect(store.addBlocks([block1, block2])).resolves.toBe(true);
1084
+
1085
+ // Verify blocks were added
1086
+ expect(await store.getBlock(1)).toEqual(block1);
1087
+ expect(await store.getBlock(2)).toEqual(block2);
1088
+ expect(await store.getLatestBlockNumber()).toBe(2);
1089
+ });
1090
+
1091
+ it('throws if initial block is duplicated across calls', async () => {
1092
+ // Add blocks for the initial checkpoint (1)
1093
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
1094
+ checkpointNumber: CheckpointNumber(1),
1095
+ indexWithinCheckpoint: 0,
1096
+ });
1097
+ const block2 = await L2BlockNew.random(BlockNumber(1), {
1098
+ checkpointNumber: CheckpointNumber(1),
1099
+ indexWithinCheckpoint: 0,
1100
+ });
1101
+
1102
+ await expect(store.addBlocks([block1])).resolves.toBe(true);
1103
+ await expect(store.addBlocks([block2])).rejects.toThrow(InitialBlockNumberNotSequentialError);
1104
+ });
1105
+
1106
+ it('throws if first block has wrong checkpoint number when store is empty', async () => {
1107
+ // Try to add blocks for checkpoint 2 when store is empty (should start at 1)
1108
+ const block1 = await L2BlockNew.random(BlockNumber(1), {
1109
+ checkpointNumber: CheckpointNumber(2),
1110
+ indexWithinCheckpoint: 0,
1111
+ });
1112
+ const block2 = await L2BlockNew.random(BlockNumber(2), {
1113
+ checkpointNumber: CheckpointNumber(2),
1114
+ indexWithinCheckpoint: 1,
1115
+ });
1116
+
1117
+ await expect(store.addBlocks([block1, block2])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
1118
+ });
1119
+
1120
+ it('allows adding more blocks to the same checkpoint in separate calls', async () => {
1121
+ // First, establish checkpoint 1 with blocks 1-2
1122
+ const checkpoint1 = makePublishedCheckpoint(
1123
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1124
+ 10,
1125
+ );
1126
+ await store.addCheckpoints([checkpoint1]);
1127
+
1128
+ // Add block 3 for checkpoint 2, chaining archive roots
1129
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
1130
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1131
+ checkpointNumber: CheckpointNumber(2),
1132
+ indexWithinCheckpoint: 0,
1133
+ lastArchive: lastBlockArchive,
1134
+ });
1135
+ await expect(store.addBlocks([block3])).resolves.toBe(true);
1136
+
1137
+ // Add block 4 for the same checkpoint 2 in a separate call
1138
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
1139
+ checkpointNumber: CheckpointNumber(2),
1140
+ indexWithinCheckpoint: 1,
1141
+ lastArchive: block3.archive,
1142
+ });
1143
+ await expect(store.addBlocks([block4])).resolves.toBe(true);
1144
+
1145
+ expect(await store.getLatestBlockNumber()).toBe(4);
1146
+ });
1147
+
1148
+ it('throws if adding blocks in separate calls with non-consecutive indexes', async () => {
1149
+ // First, establish checkpoint 1 with blocks 1-2
1150
+ const checkpoint1 = makePublishedCheckpoint(
1151
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1152
+ 10,
1153
+ );
1154
+ await store.addCheckpoints([checkpoint1]);
1155
+
1156
+ // Add block 3 for checkpoint 2, chaining archive roots
1157
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
1158
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1159
+ checkpointNumber: CheckpointNumber(2),
1160
+ indexWithinCheckpoint: 0,
1161
+ lastArchive: lastBlockArchive,
1162
+ });
1163
+ await expect(store.addBlocks([block3])).resolves.toBe(true);
1164
+
1165
+ // Add block 4 for the same checkpoint 2 in a separate call but with a missing index
1166
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
1167
+ checkpointNumber: CheckpointNumber(2),
1168
+ indexWithinCheckpoint: 2,
1169
+ lastArchive: block3.archive,
1170
+ });
1171
+ await expect(store.addBlocks([block4])).rejects.toThrow(BlockIndexNotSequentialError);
1172
+
1173
+ expect(await store.getLatestBlockNumber()).toBe(3);
1174
+ });
1175
+
1176
+ it('throws if second batch of blocks has different checkpoint number than first batch', async () => {
1177
+ // First, establish checkpoint 1 with blocks 1-2
1178
+ const checkpoint1 = makePublishedCheckpoint(
1179
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1180
+ 10,
1181
+ );
1182
+ await store.addCheckpoints([checkpoint1]);
1183
+
1184
+ // Add block 3 for checkpoint 2, chaining archive roots
1185
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
1186
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1187
+ checkpointNumber: CheckpointNumber(2),
1188
+ indexWithinCheckpoint: 0,
1189
+ lastArchive: lastBlockArchive,
1190
+ });
1191
+ await store.addBlocks([block3]);
1192
+
1193
+ // Try to add block 4 for checkpoint 3 (should fail because current checkpoint is still 2)
1194
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
1195
+ checkpointNumber: CheckpointNumber(3),
1196
+ indexWithinCheckpoint: 0,
1197
+ lastArchive: block3.archive,
1198
+ });
1199
+ await expect(store.addBlocks([block4])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
1200
+ });
1201
+
1202
+ it('force option bypasses checkpoint number validation', async () => {
1203
+ // First, establish checkpoint 1 with blocks 1-2
1204
+ const checkpoint1 = makePublishedCheckpoint(
1205
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1206
+ 10,
1207
+ );
1208
+ await store.addCheckpoints([checkpoint1]);
1209
+
1210
+ // Add blocks with different checkpoint numbers using force option, chaining archive roots
1211
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
1212
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1213
+ checkpointNumber: CheckpointNumber(2),
1214
+ indexWithinCheckpoint: 0,
1215
+ lastArchive: lastBlockArchive,
1216
+ });
1217
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
1218
+ checkpointNumber: CheckpointNumber(5),
1219
+ indexWithinCheckpoint: 0,
1220
+ lastArchive: block3.archive,
1221
+ });
1222
+
1223
+ await expect(store.addBlocks([block3, block4], { force: true })).resolves.toBe(true);
1224
+ });
1225
+
1226
+ it('force option bypasses blockindex number validation', async () => {
1227
+ // First, establish checkpoint 1 with blocks 1-2
1228
+ const checkpoint1 = makePublishedCheckpoint(
1229
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1230
+ 10,
1231
+ );
1232
+ await store.addCheckpoints([checkpoint1]);
1233
+
1234
+ // Add blocks with different checkpoint numbers using force option, chaining archive roots
1235
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
1236
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1237
+ checkpointNumber: CheckpointNumber(2),
1238
+ indexWithinCheckpoint: 0,
1239
+ lastArchive: lastBlockArchive,
1240
+ });
1241
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
1242
+ checkpointNumber: CheckpointNumber(2),
1243
+ indexWithinCheckpoint: 2,
1244
+ lastArchive: block3.archive,
1245
+ });
1246
+
1247
+ await expect(store.addBlocks([block3, block4], { force: true })).resolves.toBe(true);
1248
+ });
1249
+
1250
+ it('throws if adding blocks with non-consecutive archives', async () => {
1251
+ // First, establish checkpoint 1 with blocks 1-2
1252
+ const checkpoint1 = makePublishedCheckpoint(
1253
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1254
+ 10,
1255
+ );
1256
+ await store.addCheckpoints([checkpoint1]);
1257
+
1258
+ // Add block 3 for checkpoint 2 with incorrect archive
1259
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1260
+ checkpointNumber: CheckpointNumber(2),
1261
+ indexWithinCheckpoint: 0,
1262
+ });
1263
+ await expect(store.addBlocks([block3])).rejects.toThrow(BlockArchiveNotConsistentError);
1264
+
1265
+ expect(await store.getLatestBlockNumber()).toBe(2);
1266
+ });
1267
+
1268
+ it('throws if adding blocks with non-consecutive archives across calls', async () => {
1269
+ // First, establish checkpoint 1 with blocks 1-2
1270
+ const checkpoint1 = makePublishedCheckpoint(
1271
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1272
+ 10,
1273
+ );
1274
+ await store.addCheckpoints([checkpoint1]);
1275
+
1276
+ // Add block 3 for checkpoint 2 with correct archive
1277
+ const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
1278
+ const block3 = await L2BlockNew.random(BlockNumber(3), {
1279
+ checkpointNumber: CheckpointNumber(2),
1280
+ indexWithinCheckpoint: 0,
1281
+ lastArchive: lastBlockArchive,
1282
+ });
1283
+ await expect(store.addBlocks([block3])).resolves.toBe(true);
1284
+
1285
+ // Add block 4 with incorrect archive (should fail)
1286
+ const block4 = await L2BlockNew.random(BlockNumber(4), {
1287
+ checkpointNumber: CheckpointNumber(2),
1288
+ indexWithinCheckpoint: 1,
1289
+ lastArchive: AppendOnlyTreeSnapshot.random(),
1290
+ });
1291
+ await expect(store.addBlocks([block4])).rejects.toThrow(BlockArchiveNotConsistentError);
1292
+
1293
+ expect(await store.getLatestBlockNumber()).toBe(3);
1294
+ });
1295
+ });
1296
+
1297
+ describe('getBlocksForCheckpoint', () => {
1298
+ it('returns blocks for a single-block checkpoint', async () => {
1299
+ const checkpoint = makePublishedCheckpoint(
1300
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }),
1301
+ 10,
1302
+ );
1303
+ await store.addCheckpoints([checkpoint]);
1304
+
1305
+ const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(1));
1306
+ expect(blocks).toBeDefined();
1307
+ expect(blocks!.length).toBe(1);
1308
+ expect(blocks![0].number).toBe(1);
1309
+ });
1310
+
1311
+ it('returns all blocks for a multi-block checkpoint', async () => {
1312
+ const checkpoint = makePublishedCheckpoint(
1313
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 4, startBlockNumber: 1 }),
1314
+ 10,
1315
+ );
1316
+ await store.addCheckpoints([checkpoint]);
1317
+
1318
+ const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(1));
1319
+ expect(blocks).toBeDefined();
1320
+ expect(blocks!.length).toBe(4);
1321
+ expect(blocks!.map(b => b.number)).toEqual([1, 2, 3, 4]);
1322
+ });
1323
+
1324
+ it('returns correct blocks for different checkpoints', async () => {
1325
+ // Create checkpoints with chained archive roots
1326
+ // Checkpoint 1: blocks 1-2
1327
+ const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
1328
+ const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
1329
+
1330
+ // Checkpoint 2: blocks 3-5
1331
+ const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
1332
+ const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
1333
+ numBlocks: 3,
1334
+ startBlockNumber: 3,
1335
+ previousArchive: previousArchive1,
1336
+ });
1337
+ const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
1338
+
1339
+ // Checkpoint 3: blocks 6-7
1340
+ const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
1341
+ const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
1342
+ numBlocks: 2,
1343
+ startBlockNumber: 6,
1344
+ previousArchive: previousArchive2,
1345
+ });
1346
+ const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
1347
+
1348
+ await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
1349
+
1350
+ const blocks1 = await store.getBlocksForCheckpoint(CheckpointNumber(1));
1351
+ expect(blocks1).toBeDefined();
1352
+ expect(blocks1!.map(b => b.number)).toEqual([1, 2]);
1353
+
1354
+ const blocks2 = await store.getBlocksForCheckpoint(CheckpointNumber(2));
1355
+ expect(blocks2).toBeDefined();
1356
+ expect(blocks2!.map(b => b.number)).toEqual([3, 4, 5]);
1357
+
1358
+ const blocks3 = await store.getBlocksForCheckpoint(CheckpointNumber(3));
1359
+ expect(blocks3).toBeDefined();
1360
+ expect(blocks3!.map(b => b.number)).toEqual([6, 7]);
1361
+ });
1362
+
1363
+ it('returns undefined for non-existent checkpoint', async () => {
1364
+ const checkpoint = makePublishedCheckpoint(
1365
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1366
+ 10,
1367
+ );
1368
+ await store.addCheckpoints([checkpoint]);
1369
+
1370
+ const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(5));
1371
+ expect(blocks).toBeUndefined();
1372
+ });
1373
+
1374
+ it('returns undefined when no checkpoints exist', async () => {
1375
+ const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(1));
1376
+ expect(blocks).toBeUndefined();
1377
+ });
1378
+ });
1379
+
1380
+ describe('getRangeOfCheckpoints', () => {
1381
+ it('returns empty array when no checkpoints exist', async () => {
1382
+ const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
1383
+ expect(checkpoints).toEqual([]);
1384
+ });
1385
+
1386
+ it('returns single checkpoint', async () => {
1387
+ const checkpoint = makePublishedCheckpoint(
1388
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1389
+ 10,
1390
+ );
1391
+ await store.addCheckpoints([checkpoint]);
1392
+
1393
+ const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
1394
+ expect(checkpoints.length).toBe(1);
1395
+ expect(checkpoints[0].checkpointNumber).toBe(1);
1396
+ expect(checkpoints[0].startBlock).toBe(1);
1397
+ expect(checkpoints[0].numBlocks).toBe(2);
1398
+ });
1399
+
1400
+ it('returns multiple checkpoints in order', async () => {
1401
+ // Create checkpoints with chained archive roots
1402
+ const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
1403
+ const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
1404
+
1405
+ const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
1406
+ const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
1407
+ numBlocks: 3,
1408
+ startBlockNumber: 3,
1409
+ previousArchive: previousArchive1,
1410
+ });
1411
+ const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
1412
+
1413
+ const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
1414
+ const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
1415
+ numBlocks: 1,
1416
+ startBlockNumber: 6,
1417
+ previousArchive: previousArchive2,
1418
+ });
1419
+ const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
1420
+
1421
+ await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
1422
+
1423
+ const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
1424
+ expect(checkpoints.length).toBe(3);
1425
+ expect(checkpoints.map(c => c.checkpointNumber)).toEqual([1, 2, 3]);
1426
+ expect(checkpoints.map(c => c.startBlock)).toEqual([1, 3, 6]);
1427
+ expect(checkpoints.map(c => c.numBlocks)).toEqual([2, 3, 1]);
1428
+ });
1429
+
1430
+ it('respects the from parameter', async () => {
1431
+ // Create checkpoints with chained archive roots
1432
+ const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
1433
+ const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
1434
+
1435
+ const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
1436
+ const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
1437
+ numBlocks: 2,
1438
+ startBlockNumber: 3,
1439
+ previousArchive: previousArchive1,
1440
+ });
1441
+ const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
1442
+
1443
+ const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
1444
+ const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
1445
+ numBlocks: 2,
1446
+ startBlockNumber: 5,
1447
+ previousArchive: previousArchive2,
1448
+ });
1449
+ const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
1450
+
1451
+ await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
1452
+
1453
+ // Start from checkpoint 2
1454
+ const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(2), 10);
1455
+ expect(checkpoints.length).toBe(2);
1456
+ expect(checkpoints.map(c => c.checkpointNumber)).toEqual([2, 3]);
1457
+ });
1458
+
1459
+ it('respects the limit parameter', async () => {
1460
+ // Create checkpoints with chained archive roots
1461
+ const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 });
1462
+ const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
1463
+
1464
+ const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
1465
+ const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
1466
+ numBlocks: 1,
1467
+ startBlockNumber: 2,
1468
+ previousArchive: previousArchive1,
1469
+ });
1470
+ const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
1471
+
1472
+ const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
1473
+ const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
1474
+ numBlocks: 1,
1475
+ startBlockNumber: 3,
1476
+ previousArchive: previousArchive2,
1477
+ });
1478
+ const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
1479
+
1480
+ const previousArchive3 = checkpoint3Cp.blocks.at(-1)!.archive;
1481
+ const checkpoint4Cp = await Checkpoint.random(CheckpointNumber(4), {
1482
+ numBlocks: 1,
1483
+ startBlockNumber: 4,
1484
+ previousArchive: previousArchive3,
1485
+ });
1486
+ const checkpoint4 = makePublishedCheckpoint(checkpoint4Cp, 13);
1487
+
1488
+ await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3, checkpoint4]);
1489
+
1490
+ // Only get 2 checkpoints
1491
+ const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 2);
1492
+ expect(checkpoints.length).toBe(2);
1493
+ expect(checkpoints.map(c => c.checkpointNumber)).toEqual([1, 2]);
1494
+ });
1495
+
1496
+ it('returns correct checkpoint data including L1 info', async () => {
1497
+ const checkpoint = makePublishedCheckpoint(
1498
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
1499
+ 42,
1500
+ );
1501
+ await store.addCheckpoints([checkpoint]);
1502
+
1503
+ const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 1);
1504
+ expect(checkpoints.length).toBe(1);
1505
+
1506
+ const data = checkpoints[0];
1507
+ expect(data.checkpointNumber).toBe(1);
1508
+ expect(data.startBlock).toBe(1);
1509
+ expect(data.numBlocks).toBe(3);
1510
+ expect(data.l1.blockNumber).toBe(42n);
1511
+ expect(data.header.equals(checkpoint.checkpoint.header)).toBe(true);
1512
+ expect(data.archive.equals(checkpoint.checkpoint.archive)).toBe(true);
1513
+ });
1514
+
1515
+ it('returns empty array when from is beyond available checkpoints', async () => {
1516
+ const checkpoint = makePublishedCheckpoint(
1517
+ await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
1518
+ 10,
1519
+ );
1520
+ await store.addCheckpoints([checkpoint]);
1521
+
1522
+ const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(5), 10);
1523
+ expect(checkpoints).toEqual([]);
1524
+ });
1525
+
1526
+ it('works correctly after unwinding checkpoints', async () => {
1527
+ // Create checkpoints with chained archive roots
1528
+ const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
1529
+ const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
1530
+
1531
+ const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
1532
+ const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
1533
+ numBlocks: 2,
1534
+ startBlockNumber: 3,
1535
+ previousArchive: previousArchive1,
1536
+ });
1537
+ const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
1538
+
1539
+ const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
1540
+ const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
1541
+ numBlocks: 2,
1542
+ startBlockNumber: 5,
1543
+ previousArchive: previousArchive2,
1544
+ });
1545
+ const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
1546
+
1547
+ await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
1548
+
1549
+ // Unwind checkpoint 3
1550
+ await store.unwindCheckpoints(CheckpointNumber(3), 1);
1551
+
1552
+ const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
1553
+ expect(checkpoints.length).toBe(2);
1554
+ expect(checkpoints.map(c => c.checkpointNumber)).toEqual([1, 2]);
1555
+ });
1556
+ });
1557
+
1558
+ describe('getCheckpointedBlock', () => {
1559
+ beforeEach(async () => {
1560
+ await store.addCheckpoints(publishedCheckpoints);
1561
+ });
1562
+
1563
+ it.each(blockNumberTests)('retrieves previously stored block %i', async (blockNumber, getExpectedBlock) => {
1564
+ const retrievedBlock = await store.getCheckpointedBlock(blockNumber);
1565
+ const expectedBlock = getExpectedBlock();
1566
+ const expectedCheckpoint = publishedCheckpoints[blockNumber - 1];
1567
+
1568
+ expect(retrievedBlock).toBeDefined();
1569
+ expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
1570
+ });
1571
+
1572
+ it('returns undefined if block is not found', async () => {
1573
+ await expect(store.getCheckpointedBlock(12)).resolves.toBeUndefined();
1574
+ });
1575
+
1576
+ it('returns undefined for block number 0', async () => {
1577
+ await expect(store.getCheckpointedBlock(0)).resolves.toBeUndefined();
1578
+ });
1579
+ });
1580
+
1581
+ describe('getCheckpointedBlockByHash', () => {
218
1582
  beforeEach(async () => {
219
- await store.addBlocks(blocks);
1583
+ await store.addCheckpoints(publishedCheckpoints);
220
1584
  });
221
1585
 
222
1586
  it('retrieves a block by its hash', async () => {
223
- const expectedBlock = blocks[5];
224
- const blockHash = await expectedBlock.block.hash();
225
- const retrievedBlock = await store.getPublishedBlockByHash(blockHash);
1587
+ const expectedCheckpoint = publishedCheckpoints[5];
1588
+ const expectedBlock = expectedCheckpoint.checkpoint.blocks[0];
1589
+ const blockHash = await expectedBlock.header.hash();
1590
+ const retrievedBlock = await store.getCheckpointedBlockByHash(blockHash);
226
1591
 
227
1592
  expect(retrievedBlock).toBeDefined();
228
- expectBlocksEqual([retrievedBlock!], [expectedBlock]);
1593
+ expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
229
1594
  });
230
1595
 
231
1596
  it('returns undefined for non-existent block hash', async () => {
232
1597
  const nonExistentHash = Fr.random();
233
- await expect(store.getPublishedBlockByHash(nonExistentHash)).resolves.toBeUndefined();
1598
+ await expect(store.getCheckpointedBlockByHash(nonExistentHash)).resolves.toBeUndefined();
234
1599
  });
235
1600
  });
236
1601
 
237
- describe('getPublishedBlockByArchive', () => {
1602
+ describe('getCheckpointedBlockByArchive', () => {
238
1603
  beforeEach(async () => {
239
- await store.addBlocks(blocks);
1604
+ await store.addCheckpoints(publishedCheckpoints);
240
1605
  });
241
1606
 
242
1607
  it('retrieves a block by its archive root', async () => {
243
- const expectedBlock = blocks[3];
244
- const archive = expectedBlock.block.archive.root;
245
- const retrievedBlock = await store.getPublishedBlockByArchive(archive);
1608
+ const expectedCheckpoint = publishedCheckpoints[3];
1609
+ const expectedBlock = expectedCheckpoint.checkpoint.blocks[0];
1610
+ const archive = expectedBlock.archive.root;
1611
+ const retrievedBlock = await store.getCheckpointedBlockByArchive(archive);
246
1612
 
247
1613
  expect(retrievedBlock).toBeDefined();
248
- expectBlocksEqual([retrievedBlock!], [expectedBlock]);
1614
+ expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
249
1615
  });
250
1616
 
251
1617
  it('returns undefined for non-existent archive root', async () => {
252
1618
  const nonExistentArchive = Fr.random();
253
- await expect(store.getPublishedBlockByArchive(nonExistentArchive)).resolves.toBeUndefined();
1619
+ await expect(store.getCheckpointedBlockByArchive(nonExistentArchive)).resolves.toBeUndefined();
254
1620
  });
255
1621
  });
256
1622
 
257
1623
  describe('getBlockHeaderByHash', () => {
258
1624
  beforeEach(async () => {
259
- await store.addBlocks(blocks);
1625
+ await store.addCheckpoints(publishedCheckpoints);
260
1626
  });
261
1627
 
262
1628
  it('retrieves a block header by its hash', async () => {
263
- const expectedBlock = blocks[7];
264
- const blockHash = await expectedBlock.block.hash();
1629
+ const expectedBlock = publishedCheckpoints[7].checkpoint.blocks[0];
1630
+ const blockHash = await expectedBlock.header.hash();
265
1631
  const retrievedHeader = await store.getBlockHeaderByHash(blockHash);
266
1632
 
267
1633
  expect(retrievedHeader).toBeDefined();
268
- expect(retrievedHeader!.equals(expectedBlock.block.getBlockHeader())).toBe(true);
1634
+ expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true);
269
1635
  });
270
1636
 
271
1637
  it('returns undefined for non-existent block hash', async () => {
@@ -276,16 +1642,16 @@ export function describeArchiverDataStore(
276
1642
 
277
1643
  describe('getBlockHeaderByArchive', () => {
278
1644
  beforeEach(async () => {
279
- await store.addBlocks(blocks);
1645
+ await store.addCheckpoints(publishedCheckpoints);
280
1646
  });
281
1647
 
282
1648
  it('retrieves a block header by its archive root', async () => {
283
- const expectedBlock = blocks[2];
284
- const archive = expectedBlock.block.archive.root;
1649
+ const expectedBlock = publishedCheckpoints[2].checkpoint.blocks[0];
1650
+ const archive = expectedBlock.archive.root;
285
1651
  const retrievedHeader = await store.getBlockHeaderByArchive(archive);
286
1652
 
287
1653
  expect(retrievedHeader).toBeDefined();
288
- expect(retrievedHeader!.equals(expectedBlock.block.getBlockHeader())).toBe(true);
1654
+ expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true);
289
1655
  });
290
1656
 
291
1657
  it('returns undefined for non-existent archive root', async () => {
@@ -294,14 +1660,16 @@ export function describeArchiverDataStore(
294
1660
  });
295
1661
  });
296
1662
 
297
- describe('getSyncedL2BlockNumber', () => {
298
- it('returns the block number before INITIAL_L2_BLOCK_NUM if no blocks have been added', async () => {
299
- await expect(store.getSynchedL2BlockNumber()).resolves.toEqual(INITIAL_L2_BLOCK_NUM - 1);
1663
+ describe('getSynchedCheckpointNumber', () => {
1664
+ it('returns the checkpoint number before INITIAL_CHECKPOINT_NUMBER if no checkpoints have been added', async () => {
1665
+ await expect(store.getSynchedCheckpointNumber()).resolves.toEqual(INITIAL_CHECKPOINT_NUMBER - 1);
300
1666
  });
301
1667
 
302
- it("returns the most recently added block's number", async () => {
303
- await store.addBlocks(blocks);
304
- await expect(store.getSynchedL2BlockNumber()).resolves.toEqual(blocks.at(-1)!.block.number);
1668
+ it('returns the most recently added checkpoint number', async () => {
1669
+ await store.addCheckpoints(publishedCheckpoints);
1670
+ await expect(store.getSynchedCheckpointNumber()).resolves.toEqual(
1671
+ publishedCheckpoints.at(-1)!.checkpoint.number,
1672
+ );
305
1673
  });
306
1674
  });
307
1675
 
@@ -314,7 +1682,7 @@ export function describeArchiverDataStore(
314
1682
  });
315
1683
 
316
1684
  it('returns the L1 block number in which the most recent L2 block was published', async () => {
317
- await store.addBlocks(blocks);
1685
+ await store.addCheckpoints(publishedCheckpoints);
318
1686
  await expect(store.getSynchPoint()).resolves.toEqual({
319
1687
  blocksSynchedTo: 19n,
320
1688
  messagesSynchedTo: undefined,
@@ -347,14 +1715,15 @@ export function describeArchiverDataStore(
347
1715
 
348
1716
  describe('addLogs', () => {
349
1717
  it('adds private & public logs', async () => {
350
- const block = blocks[0].block;
351
- await expect(store.addLogs([block])).resolves.toEqual(true);
1718
+ const checkpoint = publishedCheckpoints[0];
1719
+ await store.addCheckpoints([checkpoint]);
1720
+ await expect(store.addLogs(checkpoint.checkpoint.blocks)).resolves.toEqual(true);
352
1721
  });
353
1722
  });
354
1723
 
355
1724
  it('deleteLogs', async () => {
356
- const block = blocks[0].block;
357
- await store.addBlocks([blocks[0]]);
1725
+ const block = publishedCheckpoints[0].checkpoint.blocks[0];
1726
+ await store.addBlocks([block]);
358
1727
  await expect(store.addLogs([block])).resolves.toEqual(true);
359
1728
 
360
1729
  expect((await store.getPublicLogs({ fromBlock: BlockNumber(1) })).logs.length).toEqual(
@@ -368,23 +1737,25 @@ export function describeArchiverDataStore(
368
1737
  });
369
1738
 
370
1739
  describe('getTxEffect', () => {
1740
+ const getBlock = (i: number) => publishedCheckpoints[i].checkpoint.blocks[0];
1741
+
371
1742
  beforeEach(async () => {
372
- await store.addLogs(blocks.map(b => b.block));
373
- await store.addBlocks(blocks);
1743
+ await store.addLogs(publishedCheckpoints.flatMap(x => x.checkpoint.blocks));
1744
+ await store.addCheckpoints(publishedCheckpoints);
374
1745
  });
375
1746
 
376
1747
  it.each([
377
- () => ({ data: blocks[0].block.body.txEffects[0], block: blocks[0].block, txIndexInBlock: 0 }),
378
- () => ({ data: blocks[9].block.body.txEffects[3], block: blocks[9].block, txIndexInBlock: 3 }),
379
- () => ({ data: blocks[3].block.body.txEffects[1], block: blocks[3].block, txIndexInBlock: 1 }),
380
- () => ({ data: blocks[5].block.body.txEffects[2], block: blocks[5].block, txIndexInBlock: 2 }),
381
- () => ({ data: blocks[1].block.body.txEffects[0], block: blocks[1].block, txIndexInBlock: 0 }),
1748
+ () => ({ data: getBlock(0).body.txEffects[0], block: getBlock(0), txIndexInBlock: 0 }),
1749
+ () => ({ data: getBlock(9).body.txEffects[3], block: getBlock(9), txIndexInBlock: 3 }),
1750
+ () => ({ data: getBlock(3).body.txEffects[1], block: getBlock(3), txIndexInBlock: 1 }),
1751
+ () => ({ data: getBlock(5).body.txEffects[2], block: getBlock(5), txIndexInBlock: 2 }),
1752
+ () => ({ data: getBlock(1).body.txEffects[0], block: getBlock(1), txIndexInBlock: 0 }),
382
1753
  ])('retrieves a previously stored transaction', async getExpectedTx => {
383
1754
  const { data, block, txIndexInBlock } = getExpectedTx();
384
1755
  const expectedTx: IndexedTxEffect = {
385
1756
  data,
386
1757
  l2BlockNumber: block.number,
387
- l2BlockHash: L2BlockHash.fromField(await block.hash()),
1758
+ l2BlockHash: L2BlockHash.fromField(await block.header.hash()),
388
1759
  txIndexInBlock,
389
1760
  };
390
1761
  const actualTx = await store.getTxEffect(data.txHash);
@@ -396,16 +1767,16 @@ export function describeArchiverDataStore(
396
1767
  });
397
1768
 
398
1769
  it.each([
399
- () => wrapDataInBlock(blocks[0].block.body.txEffects[0], blocks[0].block),
400
- () => wrapDataInBlock(blocks[9].block.body.txEffects[3], blocks[9].block),
401
- () => wrapDataInBlock(blocks[3].block.body.txEffects[1], blocks[3].block),
402
- () => wrapDataInBlock(blocks[5].block.body.txEffects[2], blocks[5].block),
403
- () => wrapDataInBlock(blocks[1].block.body.txEffects[0], blocks[1].block),
404
- ])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
405
- await store.unwindBlocks(BlockNumber(blocks.length), blocks.length);
406
-
407
- const expectedTx = await getExpectedTx();
408
- const actualTx = await store.getTxEffect(expectedTx.data.txHash);
1770
+ () => getBlock(0).body.txEffects[0],
1771
+ () => getBlock(9).body.txEffects[3],
1772
+ () => getBlock(3).body.txEffects[1],
1773
+ () => getBlock(5).body.txEffects[2],
1774
+ () => getBlock(1).body.txEffects[0],
1775
+ ])('tries to retrieves a previously stored transaction after deleted', async getTxEffect => {
1776
+ await store.unwindCheckpoints(CheckpointNumber(publishedCheckpoints.length), publishedCheckpoints.length);
1777
+
1778
+ const txEffect = getTxEffect();
1779
+ const actualTx = await store.getTxEffect(txEffect.txHash);
409
1780
  expect(actualTx).toEqual(undefined);
410
1781
  });
411
1782
 
@@ -414,17 +1785,17 @@ export function describeArchiverDataStore(
414
1785
  });
415
1786
 
416
1787
  it('does not fail if the block is unwound while requesting a tx', async () => {
417
- const expectedTx = await wrapDataInBlock(blocks[1].block.body.txEffects[0], blocks[1].block);
1788
+ const txEffect = getBlock(1).body.txEffects[0];
418
1789
  let done = false;
419
1790
  void (async () => {
420
1791
  while (!done) {
421
- void store.getTxEffect(expectedTx.data.txHash);
1792
+ void store.getTxEffect(txEffect.txHash);
422
1793
  await sleep(1);
423
1794
  }
424
1795
  })();
425
- await store.unwindBlocks(BlockNumber(blocks.length), blocks.length);
1796
+ await store.unwindCheckpoints(CheckpointNumber(publishedCheckpoints.length), publishedCheckpoints.length);
426
1797
  done = true;
427
- expect(await store.getTxEffect(expectedTx.data.txHash)).toEqual(undefined);
1798
+ expect(await store.getTxEffect(txEffect.txHash)).toEqual(undefined);
428
1799
  });
429
1800
  });
430
1801
 
@@ -828,12 +2199,12 @@ export function describeArchiverDataStore(
828
2199
  });
829
2200
 
830
2201
  describe('getLogsByTags', () => {
831
- const numBlocks = 3;
2202
+ const numBlocksForLogs = 3;
832
2203
  const numTxsPerBlock = 4;
833
2204
  const numPrivateLogsPerTx = 3;
834
2205
  const numPublicLogsPerTx = 2;
835
2206
 
836
- let blocks: PublishedL2Block[];
2207
+ let logsCheckpoints: PublishedCheckpoint[];
837
2208
 
838
2209
  const makeTag = (blockNumber: number, txIndex: number, logIndex: number, isPublic = false) =>
839
2210
  blockNumber === 1 && txIndex === 0 && logIndex === 0
@@ -867,8 +2238,16 @@ export function describeArchiverDataStore(
867
2238
  });
868
2239
  };
869
2240
 
870
- const mockBlockWithLogs = async (blockNumber: number): Promise<PublishedL2Block> => {
871
- const block = await L2Block.random(BlockNumber(blockNumber));
2241
+ const mockCheckpointWithLogs = async (
2242
+ blockNumber: number,
2243
+ previousArchive?: AppendOnlyTreeSnapshot,
2244
+ ): Promise<PublishedCheckpoint> => {
2245
+ const block = await L2BlockNew.random(BlockNumber(blockNumber), {
2246
+ checkpointNumber: CheckpointNumber(blockNumber),
2247
+ indexWithinCheckpoint: 0,
2248
+ state: makeStateForBlock(blockNumber, numTxsPerBlock),
2249
+ ...(previousArchive ? { lastArchive: previousArchive } : {}),
2250
+ });
872
2251
  block.header.globalVariables.blockNumber = BlockNumber(blockNumber);
873
2252
 
874
2253
  block.body.txEffects = await timesParallel(numTxsPerBlock, async (txIndex: number) => {
@@ -878,22 +2257,25 @@ export function describeArchiverDataStore(
878
2257
  return txEffect;
879
2258
  });
880
2259
 
881
- return PublishedL2Block.fromFields({
882
- block: block,
883
- attestations: times(3, CommitteeAttestation.random),
884
- l1: {
885
- blockNumber: BigInt(blockNumber),
886
- blockHash: makeBlockHash(blockNumber),
887
- timestamp: BigInt(blockNumber),
888
- },
889
- });
2260
+ const checkpoint = new Checkpoint(
2261
+ AppendOnlyTreeSnapshot.random(),
2262
+ CheckpointHeader.random(),
2263
+ [block],
2264
+ CheckpointNumber(blockNumber),
2265
+ );
2266
+ return makePublishedCheckpoint(checkpoint, blockNumber);
890
2267
  };
891
2268
 
892
2269
  beforeEach(async () => {
893
- blocks = await timesParallel(numBlocks, (index: number) => mockBlockWithLogs(index + 1));
2270
+ // Create checkpoints sequentially to chain archive roots
2271
+ logsCheckpoints = [];
2272
+ for (let i = 0; i < numBlocksForLogs; i++) {
2273
+ const previousArchive = i > 0 ? logsCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
2274
+ logsCheckpoints.push(await mockCheckpointWithLogs(i + 1, previousArchive));
2275
+ }
894
2276
 
895
- await store.addBlocks(blocks);
896
- await store.addLogs(blocks.map(b => b.block));
2277
+ await store.addCheckpoints(logsCheckpoints);
2278
+ await store.addLogs(logsCheckpoints.flatMap(p => p.checkpoint.blocks));
897
2279
  });
898
2280
 
899
2281
  it('is possible to batch request private logs via tags', async () => {
@@ -905,7 +2287,7 @@ export function describeArchiverDataStore(
905
2287
  [
906
2288
  expect.objectContaining({
907
2289
  blockNumber: 2,
908
- blockHash: L2BlockHash.fromField(await blocks[2 - 1].block.hash()),
2290
+ blockHash: L2BlockHash.fromField(await logsCheckpoints[2 - 1].checkpoint.blocks[0].header.hash()),
909
2291
  log: makePrivateLog(tags[0]),
910
2292
  isFromPublic: false,
911
2293
  }),
@@ -913,7 +2295,7 @@ export function describeArchiverDataStore(
913
2295
  [
914
2296
  expect.objectContaining({
915
2297
  blockNumber: 1,
916
- blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
2298
+ blockHash: L2BlockHash.fromField(await logsCheckpoints[1 - 1].checkpoint.blocks[0].header.hash()),
917
2299
  log: makePrivateLog(tags[1]),
918
2300
  isFromPublic: false,
919
2301
  }),
@@ -931,13 +2313,13 @@ export function describeArchiverDataStore(
931
2313
  [
932
2314
  expect.objectContaining({
933
2315
  blockNumber: 1,
934
- blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
2316
+ blockHash: L2BlockHash.fromField(await logsCheckpoints[1 - 1].checkpoint.blocks[0].header.hash()),
935
2317
  log: makePrivateLog(tags[0]),
936
2318
  isFromPublic: false,
937
2319
  }),
938
2320
  expect.objectContaining({
939
2321
  blockNumber: 1,
940
- blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
2322
+ blockHash: L2BlockHash.fromField(await logsCheckpoints[1 - 1].checkpoint.blocks[0].header.hash()),
941
2323
  log: makePublicLog(tags[0]),
942
2324
  isFromPublic: true,
943
2325
  }),
@@ -948,14 +2330,16 @@ export function describeArchiverDataStore(
948
2330
  it('is possible to batch request logs that have the same tag but different content', async () => {
949
2331
  const tags = [makeTag(1, 2, 1)];
950
2332
 
951
- // Create a block containing logs that have the same tag as the blocks before.
952
- const newBlockNumber = numBlocks;
953
- const newBlock = await mockBlockWithLogs(newBlockNumber);
954
- const newLog = newBlock.block.body.txEffects[1].privateLogs[1];
2333
+ // Create a checkpoint containing logs that have the same tag as the checkpoints before.
2334
+ // Chain from the last checkpoint's archive
2335
+ const newBlockNumber = numBlocksForLogs + 1;
2336
+ const previousArchive = logsCheckpoints[logsCheckpoints.length - 1].checkpoint.blocks[0].archive;
2337
+ const newCheckpoint = await mockCheckpointWithLogs(newBlockNumber, previousArchive);
2338
+ const newLog = newCheckpoint.checkpoint.blocks[0].body.txEffects[1].privateLogs[1];
955
2339
  newLog.fields[0] = tags[0];
956
- newBlock.block.body.txEffects[1].privateLogs[1] = newLog;
957
- await store.addBlocks([newBlock]);
958
- await store.addLogs([newBlock.block]);
2340
+ newCheckpoint.checkpoint.blocks[0].body.txEffects[1].privateLogs[1] = newLog;
2341
+ await store.addCheckpoints([newCheckpoint]);
2342
+ await store.addLogs([newCheckpoint.checkpoint.blocks[0]]);
959
2343
 
960
2344
  const logsByTags = await store.getLogsByTags(tags);
961
2345
 
@@ -963,13 +2347,13 @@ export function describeArchiverDataStore(
963
2347
  [
964
2348
  expect.objectContaining({
965
2349
  blockNumber: 1,
966
- blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
2350
+ blockHash: L2BlockHash.fromField(await logsCheckpoints[1 - 1].checkpoint.blocks[0].header.hash()),
967
2351
  log: makePrivateLog(tags[0]),
968
2352
  isFromPublic: false,
969
2353
  }),
970
2354
  expect.objectContaining({
971
2355
  blockNumber: newBlockNumber,
972
- blockHash: L2BlockHash.fromField(await blocks[newBlockNumber - 1].block.hash()),
2356
+ blockHash: L2BlockHash.fromField(await newCheckpoint.checkpoint.blocks[0].header.hash()),
973
2357
  log: newLog,
974
2358
  isFromPublic: false,
975
2359
  }),
@@ -989,7 +2373,7 @@ export function describeArchiverDataStore(
989
2373
  [
990
2374
  expect.objectContaining({
991
2375
  blockNumber: 1,
992
- blockHash: L2BlockHash.fromField(await blocks[1 - 1].block.hash()),
2376
+ blockHash: L2BlockHash.fromField(await logsCheckpoints[1 - 1].checkpoint.blocks[0].header.hash()),
993
2377
  log: makePrivateLog(tags[1]),
994
2378
  isFromPublic: false,
995
2379
  }),
@@ -999,34 +2383,33 @@ export function describeArchiverDataStore(
999
2383
  });
1000
2384
 
1001
2385
  describe('getPublicLogs', () => {
1002
- const txsPerBlock = 4;
1003
- const numPublicFunctionCalls = 3;
1004
- const numPublicLogs = 2;
1005
- const numBlocks = 10;
1006
- let blocks: PublishedL2Block[];
2386
+ const numBlocksForPublicLogs = 10;
1007
2387
 
1008
- beforeEach(async () => {
1009
- blocks = await timesParallel(numBlocks, async (index: number) =>
1010
- PublishedL2Block.fromFields({
1011
- block: await L2Block.random(BlockNumber(index + 1), txsPerBlock, numPublicFunctionCalls, numPublicLogs),
1012
- l1: { blockNumber: BigInt(index), blockHash: makeBlockHash(index), timestamp: BigInt(index) },
1013
- attestations: times(3, CommitteeAttestation.random),
1014
- }),
1015
- );
2388
+ // Helper to get total public logs per tx from a block
2389
+ const getPublicLogsPerTx = (block: L2BlockNew, txIndex: number) =>
2390
+ block.body.txEffects[txIndex].publicLogs.length;
2391
+
2392
+ // Helper to get number of txs in a block
2393
+ const getTxsPerBlock = (block: L2BlockNew) => block.body.txEffects.length;
1016
2394
 
1017
- await store.addBlocks(blocks);
1018
- await store.addLogs(blocks.map(b => b.block));
2395
+ beforeEach(async () => {
2396
+ // Use the outer publishedCheckpoints for log tests
2397
+ for (let i = 0; i < numBlocksForPublicLogs; i++) {
2398
+ await store.addCheckpoints([publishedCheckpoints[i]]);
2399
+ await store.addLogs(publishedCheckpoints[i].checkpoint.blocks);
2400
+ }
1019
2401
  });
1020
2402
 
1021
2403
  it('no logs returned if deleted ("txHash" filter param is respected variant)', async () => {
1022
2404
  // get random tx
1023
- const targetBlockIndex = randomInt(numBlocks);
1024
- const targetTxIndex = randomInt(txsPerBlock);
1025
- const targetTxHash = blocks[targetBlockIndex].block.body.txEffects[targetTxIndex].txHash;
2405
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2406
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2407
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2408
+ const targetTxHash = targetBlock.body.txEffects[targetTxIndex].txHash;
1026
2409
 
1027
2410
  await Promise.all([
1028
- store.unwindBlocks(BlockNumber(blocks.length), blocks.length),
1029
- store.deleteLogs(blocks.map(b => b.block)),
2411
+ store.unwindCheckpoints(CheckpointNumber(numBlocksForPublicLogs), numBlocksForPublicLogs),
2412
+ store.deleteLogs(publishedCheckpoints.slice(0, numBlocksForPublicLogs).flatMap(b => b.checkpoint.blocks)),
1030
2413
  ]);
1031
2414
 
1032
2415
  const response = await store.getPublicLogs({ txHash: targetTxHash });
@@ -1038,16 +2421,17 @@ export function describeArchiverDataStore(
1038
2421
 
1039
2422
  it('"txHash" filter param is respected', async () => {
1040
2423
  // get random tx
1041
- const targetBlockIndex = randomInt(numBlocks);
1042
- const targetTxIndex = randomInt(txsPerBlock);
1043
- const targetTxHash = blocks[targetBlockIndex].block.body.txEffects[targetTxIndex].txHash;
2424
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2425
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2426
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2427
+ const targetTxHash = targetBlock.body.txEffects[targetTxIndex].txHash;
1044
2428
 
1045
2429
  const response = await store.getPublicLogs({ txHash: targetTxHash });
1046
2430
  const logs = response.logs;
1047
2431
 
1048
2432
  expect(response.maxLogsHit).toBeFalsy();
1049
2433
 
1050
- const expectedNumLogs = numPublicFunctionCalls * numPublicLogs;
2434
+ const expectedNumLogs = getPublicLogsPerTx(targetBlock, targetTxIndex);
1051
2435
  expect(logs.length).toEqual(expectedNumLogs);
1052
2436
 
1053
2437
  const targeBlockNumber = targetBlockIndex + INITIAL_L2_BLOCK_NUM;
@@ -1058,8 +2442,8 @@ export function describeArchiverDataStore(
1058
2442
  });
1059
2443
 
1060
2444
  it('returns block hash on public log ids', async () => {
1061
- const targetBlock = blocks[0].block;
1062
- const expectedBlockHash = L2BlockHash.fromField(await targetBlock.hash());
2445
+ const targetBlock = publishedCheckpoints[0].checkpoint.blocks[0];
2446
+ const expectedBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
1063
2447
 
1064
2448
  const logs = (await store.getPublicLogs({ fromBlock: targetBlock.number, toBlock: targetBlock.number + 1 }))
1065
2449
  .logs;
@@ -1078,7 +2462,12 @@ export function describeArchiverDataStore(
1078
2462
 
1079
2463
  expect(response.maxLogsHit).toBeFalsy();
1080
2464
 
1081
- const expectedNumLogs = txsPerBlock * numPublicFunctionCalls * numPublicLogs * (toBlock - fromBlock);
2465
+ // Compute expected logs from the blocks in range
2466
+ let expectedNumLogs = 0;
2467
+ for (let i = fromBlock - 1; i < toBlock - 1; i++) {
2468
+ const block = publishedCheckpoints[i].checkpoint.blocks[0];
2469
+ expectedNumLogs += block.body.txEffects.reduce((sum, tx) => sum + tx.publicLogs.length, 0);
2470
+ }
1082
2471
  expect(logs.length).toEqual(expectedNumLogs);
1083
2472
 
1084
2473
  for (const log of logs) {
@@ -1090,11 +2479,12 @@ export function describeArchiverDataStore(
1090
2479
 
1091
2480
  it('"contractAddress" filter param is respected', async () => {
1092
2481
  // Get a random contract address from the logs
1093
- const targetBlockIndex = randomInt(numBlocks);
1094
- const targetTxIndex = randomInt(txsPerBlock);
1095
- const targetLogIndex = randomInt(numPublicLogs * numPublicFunctionCalls);
2482
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2483
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2484
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2485
+ const targetLogIndex = randomInt(getPublicLogsPerTx(targetBlock, targetTxIndex));
1096
2486
  const targetContractAddress =
1097
- blocks[targetBlockIndex].block.body.txEffects[targetTxIndex].publicLogs[targetLogIndex].contractAddress;
2487
+ targetBlock.body.txEffects[targetTxIndex].publicLogs[targetLogIndex].contractAddress;
1098
2488
 
1099
2489
  const response = await store.getPublicLogs({ contractAddress: targetContractAddress });
1100
2490
 
@@ -1107,10 +2497,12 @@ export function describeArchiverDataStore(
1107
2497
 
1108
2498
  it('"afterLog" filter param is respected', async () => {
1109
2499
  // Get a random log as reference
1110
- const targetBlockIndex = randomInt(numBlocks);
1111
- const targetTxIndex = randomInt(txsPerBlock);
1112
- const targetLogIndex = randomInt(numPublicLogs);
1113
- const targetBlockHash = L2BlockHash.fromField(await blocks[targetBlockIndex].block.hash());
2500
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2501
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2502
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2503
+ const numLogsInTx = targetBlock.body.txEffects[targetTxIndex].publicLogs.length;
2504
+ const targetLogIndex = numLogsInTx > 0 ? randomInt(numLogsInTx) : 0;
2505
+ const targetBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
1114
2506
 
1115
2507
  const afterLog = new LogId(
1116
2508
  BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM),
@@ -1164,7 +2556,7 @@ export function describeArchiverDataStore(
1164
2556
  // both "fromBlock" and "toBlock" get correctly capped to range and logs from all blocks are returned
1165
2557
  logs = (await store.getPublicLogs({ fromBlock: -100 as BlockNumber, toBlock: +100 })).logs;
1166
2558
  blockNumbers = new Set(logs.map(log => log.id.blockNumber));
1167
- expect(blockNumbers.size).toBe(numBlocks);
2559
+ expect(blockNumbers.size).toBe(numBlocksForPublicLogs);
1168
2560
 
1169
2561
  // intersecting with "afterLog" works
1170
2562
  logs = (
@@ -1197,10 +2589,12 @@ export function describeArchiverDataStore(
1197
2589
 
1198
2590
  it('"txIndex" and "logIndex" are respected when "afterLog.blockNumber" is equal to "fromBlock"', async () => {
1199
2591
  // Get a random log as reference
1200
- const targetBlockIndex = randomInt(numBlocks);
1201
- const targetTxIndex = randomInt(txsPerBlock);
1202
- const targetLogIndex = randomInt(numPublicLogs);
1203
- const targetBlockHash = L2BlockHash.fromField(await blocks[targetBlockIndex].block.hash());
2592
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2593
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2594
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2595
+ const numLogsInTx = targetBlock.body.txEffects[targetTxIndex].publicLogs.length;
2596
+ const targetLogIndex = numLogsInTx > 0 ? randomInt(numLogsInTx) : 0;
2597
+ const targetBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
1204
2598
 
1205
2599
  const afterLog = new LogId(
1206
2600
  BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM),
@@ -1228,13 +2622,13 @@ export function describeArchiverDataStore(
1228
2622
  });
1229
2623
 
1230
2624
  describe('getContractClassLogs', () => {
1231
- let targetBlock: L2Block;
2625
+ let targetBlock: L2BlockNew;
1232
2626
  let expectedContractClassLog: ContractClassLog;
1233
2627
 
1234
2628
  beforeEach(async () => {
1235
- await store.addBlocks(blocks);
2629
+ await store.addCheckpoints(publishedCheckpoints);
1236
2630
 
1237
- targetBlock = blocks[0].block;
2631
+ targetBlock = publishedCheckpoints[0].checkpoint.blocks[0];
1238
2632
  expectedContractClassLog = await ContractClassLog.random();
1239
2633
  targetBlock.body.txEffects.forEach((txEffect, index) => {
1240
2634
  txEffect.contractClassLogs = index === 0 ? [expectedContractClassLog] : [];
@@ -1253,7 +2647,7 @@ export function describeArchiverDataStore(
1253
2647
  expect(result.logs).toHaveLength(1);
1254
2648
 
1255
2649
  const [{ id, log }] = result.logs;
1256
- const expectedBlockHash = L2BlockHash.fromField(await targetBlock.hash());
2650
+ const expectedBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
1257
2651
 
1258
2652
  expect(id.blockHash.equals(expectedBlockHash)).toBe(true);
1259
2653
  expect(id.blockNumber).toEqual(targetBlock.number);