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

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