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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dest/archiver/archiver.d.ts +110 -83
  2. package/dest/archiver/archiver.d.ts.map +1 -1
  3. package/dest/archiver/archiver.js +672 -349
  4. package/dest/archiver/archiver_store.d.ts +100 -47
  5. package/dest/archiver/archiver_store.d.ts.map +1 -1
  6. package/dest/archiver/archiver_store_test_suite.d.ts +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  8. package/dest/archiver/archiver_store_test_suite.js +1871 -389
  9. package/dest/archiver/config.d.ts +5 -4
  10. package/dest/archiver/config.d.ts.map +1 -1
  11. package/dest/archiver/config.js +15 -3
  12. package/dest/archiver/errors.d.ts +25 -1
  13. package/dest/archiver/errors.d.ts.map +1 -1
  14. package/dest/archiver/errors.js +37 -0
  15. package/dest/archiver/index.d.ts +2 -2
  16. package/dest/archiver/index.d.ts.map +1 -1
  17. package/dest/archiver/instrumentation.d.ts +5 -3
  18. package/dest/archiver/instrumentation.d.ts.map +1 -1
  19. package/dest/archiver/instrumentation.js +11 -0
  20. package/dest/archiver/kv_archiver_store/block_store.d.ts +51 -18
  21. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  22. package/dest/archiver/kv_archiver_store/block_store.js +324 -87
  23. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -2
  24. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  25. package/dest/archiver/kv_archiver_store/contract_class_store.js +1 -1
  26. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +2 -2
  27. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  28. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +47 -57
  29. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  30. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +65 -48
  31. package/dest/archiver/kv_archiver_store/log_store.d.ts +12 -16
  32. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  33. package/dest/archiver/kv_archiver_store/log_store.js +149 -84
  34. package/dest/archiver/kv_archiver_store/message_store.d.ts +6 -5
  35. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  36. package/dest/archiver/kv_archiver_store/message_store.js +15 -14
  37. package/dest/archiver/l1/bin/retrieve-calldata.d.ts +3 -0
  38. package/dest/archiver/l1/bin/retrieve-calldata.d.ts.map +1 -0
  39. package/dest/archiver/l1/bin/retrieve-calldata.js +149 -0
  40. package/dest/archiver/l1/calldata_retriever.d.ts +112 -0
  41. package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -0
  42. package/dest/archiver/l1/calldata_retriever.js +471 -0
  43. package/dest/archiver/l1/data_retrieval.d.ts +90 -0
  44. package/dest/archiver/l1/data_retrieval.d.ts.map +1 -0
  45. package/dest/archiver/l1/data_retrieval.js +331 -0
  46. package/dest/archiver/l1/debug_tx.d.ts +19 -0
  47. package/dest/archiver/l1/debug_tx.d.ts.map +1 -0
  48. package/dest/archiver/l1/debug_tx.js +73 -0
  49. package/dest/archiver/l1/spire_proposer.d.ts +70 -0
  50. package/dest/archiver/l1/spire_proposer.d.ts.map +1 -0
  51. package/dest/archiver/l1/spire_proposer.js +157 -0
  52. package/dest/archiver/l1/trace_tx.d.ts +97 -0
  53. package/dest/archiver/l1/trace_tx.d.ts.map +1 -0
  54. package/dest/archiver/l1/trace_tx.js +91 -0
  55. package/dest/archiver/l1/types.d.ts +12 -0
  56. package/dest/archiver/l1/types.d.ts.map +1 -0
  57. package/dest/archiver/l1/types.js +3 -0
  58. package/dest/archiver/l1/validate_trace.d.ts +29 -0
  59. package/dest/archiver/l1/validate_trace.d.ts.map +1 -0
  60. package/dest/archiver/l1/validate_trace.js +150 -0
  61. package/dest/archiver/structs/data_retrieval.d.ts +1 -1
  62. package/dest/archiver/structs/inbox_message.d.ts +4 -4
  63. package/dest/archiver/structs/inbox_message.d.ts.map +1 -1
  64. package/dest/archiver/structs/inbox_message.js +6 -5
  65. package/dest/archiver/structs/published.d.ts +2 -2
  66. package/dest/archiver/structs/published.d.ts.map +1 -1
  67. package/dest/archiver/validation.d.ts +10 -4
  68. package/dest/archiver/validation.d.ts.map +1 -1
  69. package/dest/archiver/validation.js +29 -21
  70. package/dest/factory.d.ts +2 -2
  71. package/dest/factory.d.ts.map +1 -1
  72. package/dest/factory.js +4 -3
  73. package/dest/index.d.ts +2 -2
  74. package/dest/index.d.ts.map +1 -1
  75. package/dest/index.js +1 -1
  76. package/dest/rpc/index.d.ts +2 -2
  77. package/dest/test/index.d.ts +1 -1
  78. package/dest/test/mock_archiver.d.ts +16 -8
  79. package/dest/test/mock_archiver.d.ts.map +1 -1
  80. package/dest/test/mock_archiver.js +19 -14
  81. package/dest/test/mock_l1_to_l2_message_source.d.ts +7 -6
  82. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  83. package/dest/test/mock_l1_to_l2_message_source.js +10 -9
  84. package/dest/test/mock_l2_block_source.d.ts +23 -11
  85. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  86. package/dest/test/mock_l2_block_source.js +38 -24
  87. package/dest/test/mock_structs.d.ts +3 -2
  88. package/dest/test/mock_structs.d.ts.map +1 -1
  89. package/dest/test/mock_structs.js +9 -8
  90. package/package.json +18 -17
  91. package/src/archiver/archiver.ts +884 -449
  92. package/src/archiver/archiver_store.ts +113 -46
  93. package/src/archiver/archiver_store_test_suite.ts +1936 -356
  94. package/src/archiver/config.ts +20 -10
  95. package/src/archiver/errors.ts +64 -0
  96. package/src/archiver/index.ts +1 -1
  97. package/src/archiver/instrumentation.ts +16 -2
  98. package/src/archiver/kv_archiver_store/block_store.ts +442 -101
  99. package/src/archiver/kv_archiver_store/contract_class_store.ts +1 -1
  100. package/src/archiver/kv_archiver_store/contract_instance_store.ts +1 -1
  101. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +87 -71
  102. package/src/archiver/kv_archiver_store/log_store.ts +209 -99
  103. package/src/archiver/kv_archiver_store/message_store.ts +21 -18
  104. package/src/archiver/l1/README.md +98 -0
  105. package/src/archiver/l1/bin/retrieve-calldata.ts +182 -0
  106. package/src/archiver/l1/calldata_retriever.ts +641 -0
  107. package/src/archiver/l1/data_retrieval.ts +512 -0
  108. package/src/archiver/l1/debug_tx.ts +99 -0
  109. package/src/archiver/l1/spire_proposer.ts +160 -0
  110. package/src/archiver/l1/trace_tx.ts +128 -0
  111. package/src/archiver/l1/types.ts +13 -0
  112. package/src/archiver/l1/validate_trace.ts +211 -0
  113. package/src/archiver/structs/inbox_message.ts +8 -8
  114. package/src/archiver/structs/published.ts +1 -1
  115. package/src/archiver/validation.ts +52 -27
  116. package/src/factory.ts +4 -3
  117. package/src/index.ts +1 -1
  118. package/src/test/fixtures/debug_traceTransaction-multicall3.json +88 -0
  119. package/src/test/fixtures/debug_traceTransaction-multiplePropose.json +153 -0
  120. package/src/test/fixtures/debug_traceTransaction-proxied.json +122 -0
  121. package/src/test/fixtures/trace_transaction-multicall3.json +65 -0
  122. package/src/test/fixtures/trace_transaction-multiplePropose.json +319 -0
  123. package/src/test/fixtures/trace_transaction-proxied.json +128 -0
  124. package/src/test/fixtures/trace_transaction-randomRevert.json +216 -0
  125. package/src/test/mock_archiver.ts +22 -16
  126. package/src/test/mock_l1_to_l2_message_source.ts +10 -9
  127. package/src/test/mock_l2_block_source.ts +51 -30
  128. package/src/test/mock_structs.ts +10 -9
  129. package/dest/archiver/data_retrieval.d.ts +0 -79
  130. package/dest/archiver/data_retrieval.d.ts.map +0 -1
  131. package/dest/archiver/data_retrieval.js +0 -362
  132. package/src/archiver/data_retrieval.ts +0 -545
@@ -1,21 +1,25 @@
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
+ import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
3
4
  import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
4
5
  import { times, timesParallel } from '@aztec/foundation/collection';
5
- import { randomInt } from '@aztec/foundation/crypto';
6
- import { Fr } from '@aztec/foundation/fields';
6
+ import { randomInt } from '@aztec/foundation/crypto/random';
7
+ import { Fr } from '@aztec/foundation/curves/bn254';
7
8
  import { toArray } from '@aztec/foundation/iterable';
8
9
  import { sleep } from '@aztec/foundation/sleep';
9
10
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
10
- import { CommitteeAttestation, EthAddress, L2Block, L2BlockHash, PublishedL2Block, randomBlockInfo, wrapInBlock } from '@aztec/stdlib/block';
11
+ import { CommitteeAttestation, EthAddress, L2BlockHash, L2BlockNew, randomBlockInfo } from '@aztec/stdlib/block';
12
+ import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
11
13
  import { SerializableContractInstance, computePublicBytecodeCommitment } from '@aztec/stdlib/contract';
12
- import { LogId, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
14
+ import { ContractClassLog, LogId, PrivateLog, PublicLog, SiloedTag, Tag } from '@aztec/stdlib/logs';
13
15
  import { InboxLeaf } from '@aztec/stdlib/messaging';
16
+ import { CheckpointHeader } from '@aztec/stdlib/rollup';
14
17
  import { makeContractClassPublic, makeExecutablePrivateFunctionWithMembershipProof, makeUtilityFunctionWithMembershipProof } from '@aztec/stdlib/testing';
15
18
  import '@aztec/stdlib/testing/jest';
16
- import { TxEffect, TxHash } from '@aztec/stdlib/tx';
19
+ import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
20
+ import { PartialStateReference, StateReference, TxEffect, TxHash } from '@aztec/stdlib/tx';
17
21
  import { makeInboxMessage, makeInboxMessages } from '../test/mock_structs.js';
18
- import { BlockNumberNotSequentialError, InitialBlockNumberNotSequentialError } from './errors.js';
22
+ import { BlockArchiveNotConsistentError, BlockIndexNotSequentialError, BlockNumberNotSequentialError, CheckpointNumberNotConsistentError, CheckpointNumberNotSequentialError, InitialBlockNumberNotSequentialError, InitialCheckpointNumberNotSequentialError } from './errors.js';
19
23
  import { MessageStoreError } from './kv_archiver_store/message_store.js';
20
24
  /**
21
25
  * @param testName - The name of the test suite.
@@ -23,100 +27,256 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
23
27
  */ export function describeArchiverDataStore(testName, getStore) {
24
28
  describe(testName, ()=>{
25
29
  let store;
26
- let blocks;
27
- const blockTests = [
30
+ let publishedCheckpoints;
31
+ const blockNumberTests = [
28
32
  [
29
33
  1,
30
- 1,
31
- ()=>blocks.slice(0, 1)
32
- ],
33
- [
34
- 10,
35
- 1,
36
- ()=>blocks.slice(9, 10)
34
+ ()=>publishedCheckpoints[0].checkpoint.blocks[0]
37
35
  ],
38
36
  [
39
- 1,
40
37
  10,
41
- ()=>blocks.slice(0, 10)
38
+ ()=>publishedCheckpoints[9].checkpoint.blocks[0]
42
39
  ],
43
40
  [
44
- 2,
45
41
  5,
46
- ()=>blocks.slice(1, 6)
47
- ],
48
- [
49
- 5,
50
- 2,
51
- ()=>blocks.slice(4, 6)
42
+ ()=>publishedCheckpoints[4].checkpoint.blocks[0]
52
43
  ]
53
44
  ];
54
45
  const makeBlockHash = (blockNumber)=>`0x${blockNumber.toString(16).padStart(64, '0')}`;
55
- const makePublished = (block, l1BlockNumber)=>PublishedL2Block.fromFields({
56
- block: block,
57
- l1: {
58
- blockNumber: BigInt(l1BlockNumber),
59
- blockHash: makeBlockHash(l1BlockNumber),
60
- timestamp: BigInt(l1BlockNumber * 1000)
61
- },
62
- attestations: times(3, CommitteeAttestation.random)
63
- });
64
- const expectBlocksEqual = (actual, expected)=>{
65
- expect(actual.length).toEqual(expected.length);
66
- for(let i = 0; i < expected.length; i++){
67
- const expectedBlock = expected[i];
68
- const actualBlock = actual[i];
69
- expect(actualBlock.l1).toEqual(expectedBlock.l1);
70
- expect(actualBlock.block.equals(expectedBlock.block)).toBe(true);
71
- expect(actualBlock.attestations.every((a, i)=>a.equals(expectedBlock.attestations[i]))).toBe(true);
72
- }
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);
73
63
  };
74
64
  beforeEach(async ()=>{
75
65
  store = await getStore();
76
- blocks = await timesParallel(10, async (i)=>makePublished(await L2Block.random(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
+ }
77
87
  });
78
- describe('addBlocks', ()=>{
79
- it('returns success when adding blocks', async ()=>{
80
- 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);
81
91
  });
82
- it('allows duplicate blocks', async ()=>{
83
- await store.addBlocks(blocks);
84
- 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);
85
95
  });
86
96
  it('throws an error if the previous block does not exist in the store', async ()=>{
87
- const block = makePublished(await L2Block.random(2), 2);
88
- 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([
89
103
  block
90
- ])).rejects.toThrow(InitialBlockNumberNotSequentialError);
91
- await expect(store.getPublishedBlocks(1, 10)).resolves.toEqual([]);
104
+ ])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
105
+ await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
92
106
  });
93
107
  it('throws an error if there is a gap in the blocks being added', async ()=>{
94
- const blocks = [
95
- makePublished(await L2Block.random(1), 1),
96
- makePublished(await L2Block.random(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)
97
119
  ];
98
- await expect(store.addBlocks(blocks)).rejects.toThrow(BlockNumberNotSequentialError);
99
- await expect(store.getPublishedBlocks(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);
100
245
  });
101
246
  });
102
- describe('unwindBlocks', ()=>{
103
- it('unwinding blocks will remove blocks from the chain', async ()=>{
104
- await store.addBlocks(blocks);
105
- const blockNumber = await store.getSynchedL2BlockNumber();
106
- expectBlocksEqual(await store.getPublishedBlocks(blockNumber, 1), [
107
- blocks[blocks.length - 1]
108
- ]);
109
- await store.unwindBlocks(blockNumber, 1);
110
- expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1);
111
- 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();
112
261
  });
113
262
  it('can unwind multiple empty blocks', async ()=>{
114
- const emptyBlocks = await timesParallel(10, async (i)=>makePublished(await L2Block.random(i + 1, 0), i + 10));
115
- await store.addBlocks(emptyBlocks);
116
- expect(await store.getSynchedL2BlockNumber()).toBe(10);
117
- await store.unwindBlocks(10, 3);
118
- expect(await store.getSynchedL2BlockNumber()).toBe(7);
119
- expect((await store.getPublishedBlocks(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([
120
280
  1,
121
281
  2,
122
282
  3,
@@ -126,113 +286,1308 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
126
286
  7
127
287
  ]);
128
288
  });
129
- it('refuses to unwind blocks if the tip is not the last block', async ()=>{
130
- await store.addBlocks(blocks);
131
- await expect(store.unwindBlocks(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);
132
292
  });
133
293
  it('unwound blocks and headers cannot be retrieved by hash or archive', async ()=>{
134
- await store.addBlocks(blocks);
135
- const lastBlock = blocks[blocks.length - 1];
136
- const blockHash = await lastBlock.block.hash();
137
- 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;
138
299
  // Verify block and header exist before unwinding
139
- expect(await store.getPublishedBlockByHash(blockHash)).toBeDefined();
140
- expect(await store.getPublishedBlockByArchive(archive)).toBeDefined();
141
- expect(await store.getBlockHeaderByHash(blockHash)).toBeDefined();
142
- expect(await store.getBlockHeaderByArchive(archive)).toBeDefined();
143
- // Unwind the block
144
- 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);
145
314
  // Verify neither block nor header can be retrieved after unwinding
146
- expect(await store.getPublishedBlockByHash(blockHash)).toBeUndefined();
147
- expect(await store.getPublishedBlockByArchive(archive)).toBeUndefined();
315
+ expect(await store.getCheckpointedBlockByHash(blockHash)).toBeUndefined();
316
+ expect(await store.getCheckpointedBlockByArchive(archive)).toBeUndefined();
148
317
  expect(await store.getBlockHeaderByHash(blockHash)).toBeUndefined();
149
318
  expect(await store.getBlockHeaderByArchive(archive)).toBeUndefined();
150
319
  });
151
320
  });
152
- describe('getBlocks', ()=>{
153
- beforeEach(async ()=>{
154
- 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
+ }
155
453
  });
156
- it.each(blockTests)('retrieves previously stored blocks', async (start, limit, getExpectedBlocks)=>{
157
- expectBlocksEqual(await store.getPublishedBlocks(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
+ }
158
494
  });
159
- it('returns an empty array if no blocks are found', async ()=>{
160
- await expect(store.getPublishedBlocks(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
+ }
161
513
  });
162
- it('throws an error if limit is invalid', async ()=>{
163
- await expect(store.getPublishedBlocks(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
+ }
164
532
  });
165
- it('throws an error if `from` it is out of range', async ()=>{
166
- 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);
167
553
  });
168
- 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
+ });
169
585
  await store.addBlocks([
170
- makePublished(await L2Block.random(21), 31)
171
- ], {
172
- 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))?.equals(block3)).toBe(true);
624
+ expect((await store.getBlock(4))?.equals(block4)).toBe(true);
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.equals(block3)).toBe(true);
638
+ const retrieved4 = await store.getBlock(4);
639
+ expect(retrieved4.number).toBe(4);
640
+ expect(retrieved4.equals(block4)).toBe(true);
641
+ const retrieved5 = await store.getBlock(5);
642
+ expect(retrieved5.number).toBe(5);
643
+ expect(retrieved5.equals(block5)).toBe(true);
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
173
655
  });
174
- await expect(store.getPublishedBlocks(20, 1)).rejects.toThrow(`mismatch`);
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.equals(block1)).toBe(true);
665
+ const retrieved2 = await store.getBlockByHash(hash2);
666
+ expect(retrieved2.equals(block2)).toBe(true);
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.equals(block1)).toBe(true);
688
+ const retrieved2 = await store.getBlockByArchive(archive2);
689
+ expect(retrieved2.equals(block2)).toBe(true);
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))?.equals(block3)).toBe(true);
724
+ expect((await store.getBlock(4))?.equals(block4)).toBe(true);
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))?.equals(block1)).toBe(true);
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))?.equals(block1)).toBe(true);
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
761
+ });
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
+ ]);
175
899
  });
176
- 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))?.equals(block3)).toBe(true);
979
+ expect((await store.getBlock(4))?.equals(block4)).toBe(true);
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))?.equals(block1)).toBe(true);
998
+ expect((await store.getBlock(2))?.equals(block2)).toBe(true);
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
+ });
177
1109
  await store.addBlocks([
178
- makePublished(await L2Block.random(20), 30),
179
- makePublished(await L2Block.random(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
1146
+ ], {
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
180
1174
  ], {
181
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
182
1191
  });
183
- await expect(store.getPublishedBlocks(20, 2)).rejects.toThrow(`mismatch`);
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();
184
1323
  });
185
1324
  });
186
- describe('getPublishedBlockByHash', ()=>{
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
1393
+ });
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', ()=>{
187
1530
  beforeEach(async ()=>{
188
- await store.addBlocks(blocks);
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();
1545
+ });
1546
+ });
1547
+ describe('getCheckpointedBlockByHash', ()=>{
1548
+ beforeEach(async ()=>{
1549
+ await store.addCheckpoints(publishedCheckpoints);
189
1550
  });
190
1551
  it('retrieves a block by its hash', async ()=>{
191
- const expectedBlock = blocks[5];
192
- const blockHash = await expectedBlock.block.hash();
193
- 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);
194
1556
  expect(retrievedBlock).toBeDefined();
195
- expectBlocksEqual([
196
- retrievedBlock
197
- ], [
198
- expectedBlock
199
- ]);
1557
+ expectCheckpointedBlockEquals(retrievedBlock, expectedBlock, expectedCheckpoint);
200
1558
  });
201
1559
  it('returns undefined for non-existent block hash', async ()=>{
202
1560
  const nonExistentHash = Fr.random();
203
- await expect(store.getPublishedBlockByHash(nonExistentHash)).resolves.toBeUndefined();
1561
+ await expect(store.getCheckpointedBlockByHash(nonExistentHash)).resolves.toBeUndefined();
204
1562
  });
205
1563
  });
206
- describe('getPublishedBlockByArchive', ()=>{
1564
+ describe('getCheckpointedBlockByArchive', ()=>{
207
1565
  beforeEach(async ()=>{
208
- await store.addBlocks(blocks);
1566
+ await store.addCheckpoints(publishedCheckpoints);
209
1567
  });
210
1568
  it('retrieves a block by its archive root', async ()=>{
211
- const expectedBlock = blocks[3];
212
- const archive = expectedBlock.block.archive.root;
213
- 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);
214
1573
  expect(retrievedBlock).toBeDefined();
215
- expectBlocksEqual([
216
- retrievedBlock
217
- ], [
218
- expectedBlock
219
- ]);
1574
+ expectCheckpointedBlockEquals(retrievedBlock, expectedBlock, expectedCheckpoint);
220
1575
  });
221
1576
  it('returns undefined for non-existent archive root', async ()=>{
222
1577
  const nonExistentArchive = Fr.random();
223
- await expect(store.getPublishedBlockByArchive(nonExistentArchive)).resolves.toBeUndefined();
1578
+ await expect(store.getCheckpointedBlockByArchive(nonExistentArchive)).resolves.toBeUndefined();
224
1579
  });
225
1580
  });
226
1581
  describe('getBlockHeaderByHash', ()=>{
227
1582
  beforeEach(async ()=>{
228
- await store.addBlocks(blocks);
1583
+ await store.addCheckpoints(publishedCheckpoints);
229
1584
  });
230
1585
  it('retrieves a block header by its hash', async ()=>{
231
- const expectedBlock = blocks[7];
232
- const blockHash = await expectedBlock.block.hash();
1586
+ const expectedBlock = publishedCheckpoints[7].checkpoint.blocks[0];
1587
+ const blockHash = await expectedBlock.header.hash();
233
1588
  const retrievedHeader = await store.getBlockHeaderByHash(blockHash);
234
1589
  expect(retrievedHeader).toBeDefined();
235
- expect(retrievedHeader.equals(expectedBlock.block.getBlockHeader())).toBe(true);
1590
+ expect(retrievedHeader.equals(expectedBlock.header)).toBe(true);
236
1591
  });
237
1592
  it('returns undefined for non-existent block hash', async ()=>{
238
1593
  const nonExistentHash = Fr.random();
@@ -241,27 +1596,27 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
241
1596
  });
242
1597
  describe('getBlockHeaderByArchive', ()=>{
243
1598
  beforeEach(async ()=>{
244
- await store.addBlocks(blocks);
1599
+ await store.addCheckpoints(publishedCheckpoints);
245
1600
  });
246
1601
  it('retrieves a block header by its archive root', async ()=>{
247
- const expectedBlock = blocks[2];
248
- const archive = expectedBlock.block.archive.root;
1602
+ const expectedBlock = publishedCheckpoints[2].checkpoint.blocks[0];
1603
+ const archive = expectedBlock.archive.root;
249
1604
  const retrievedHeader = await store.getBlockHeaderByArchive(archive);
250
1605
  expect(retrievedHeader).toBeDefined();
251
- expect(retrievedHeader.equals(expectedBlock.block.getBlockHeader())).toBe(true);
1606
+ expect(retrievedHeader.equals(expectedBlock.header)).toBe(true);
252
1607
  });
253
1608
  it('returns undefined for non-existent archive root', async ()=>{
254
1609
  const nonExistentArchive = Fr.random();
255
1610
  await expect(store.getBlockHeaderByArchive(nonExistentArchive)).resolves.toBeUndefined();
256
1611
  });
257
1612
  });
258
- describe('getSyncedL2BlockNumber', ()=>{
259
- it('returns the block number before INITIAL_L2_BLOCK_NUM if no blocks have been added', async ()=>{
260
- 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);
261
1616
  });
262
- it("returns the most recently added block's number", async ()=>{
263
- await store.addBlocks(blocks);
264
- 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);
265
1620
  });
266
1621
  });
267
1622
  describe('getSynchPoint', ()=>{
@@ -272,7 +1627,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
272
1627
  });
273
1628
  });
274
1629
  it('returns the L1 block number in which the most recent L2 block was published', async ()=>{
275
- await store.addBlocks(blocks);
1630
+ await store.addCheckpoints(publishedCheckpoints);
276
1631
  await expect(store.getSynchPoint()).resolves.toEqual({
277
1632
  blocksSynchedTo: 19n,
278
1633
  messagesSynchedTo: undefined
@@ -324,77 +1679,62 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
324
1679
  });
325
1680
  describe('addLogs', ()=>{
326
1681
  it('adds private & public logs', async ()=>{
327
- const block = blocks[0].block;
328
- await expect(store.addLogs([
329
- block
330
- ])).resolves.toEqual(true);
331
- });
332
- });
333
- describe('deleteLogs', ()=>{
334
- it('deletes private & public logs', async ()=>{
335
- const block = blocks[0].block;
336
- await store.addBlocks([
337
- blocks[0]
338
- ]);
339
- await expect(store.addLogs([
340
- block
341
- ])).resolves.toEqual(true);
342
- expect((await store.getPrivateLogs(1, 1)).length).toEqual(block.body.txEffects.map((txEffect)=>txEffect.privateLogs).flat().length);
343
- expect((await store.getPublicLogs({
344
- fromBlock: 1
345
- })).logs.length).toEqual(block.body.txEffects.map((txEffect)=>txEffect.publicLogs).flat().length);
346
- // This one is a pain for memory as we would never want to just delete memory in the middle.
347
- await store.deleteLogs([
348
- block
1682
+ const checkpoint = publishedCheckpoints[0];
1683
+ await store.addCheckpoints([
1684
+ checkpoint
349
1685
  ]);
350
- expect((await store.getPrivateLogs(1, 1)).length).toEqual(0);
351
- expect((await store.getPublicLogs({
352
- fromBlock: 1
353
- })).logs.length).toEqual(0);
1686
+ await expect(store.addLogs(checkpoint.checkpoint.blocks)).resolves.toEqual(true);
354
1687
  });
355
1688
  });
356
- describe('getPrivateLogs', ()=>{
357
- it('gets added private logs', async ()=>{
358
- const block = blocks[0].block;
359
- await store.addBlocks([
360
- blocks[0]
361
- ]);
362
- await store.addLogs([
363
- block
364
- ]);
365
- const privateLogs = await store.getPrivateLogs(1, 1);
366
- expect(privateLogs).toEqual(block.body.txEffects.map((txEffect)=>txEffect.privateLogs).flat());
367
- });
1689
+ it('deleteLogs', async ()=>{
1690
+ const block = publishedCheckpoints[0].checkpoint.blocks[0];
1691
+ await store.addBlocks([
1692
+ block
1693
+ ]);
1694
+ await expect(store.addLogs([
1695
+ block
1696
+ ])).resolves.toEqual(true);
1697
+ expect((await store.getPublicLogs({
1698
+ fromBlock: BlockNumber(1)
1699
+ })).logs.length).toEqual(block.body.txEffects.map((txEffect)=>txEffect.publicLogs).flat().length);
1700
+ // This one is a pain for memory as we would never want to just delete memory in the middle.
1701
+ await store.deleteLogs([
1702
+ block
1703
+ ]);
1704
+ expect((await store.getPublicLogs({
1705
+ fromBlock: BlockNumber(1)
1706
+ })).logs.length).toEqual(0);
368
1707
  });
369
1708
  describe('getTxEffect', ()=>{
1709
+ const getBlock = (i)=>publishedCheckpoints[i].checkpoint.blocks[0];
370
1710
  beforeEach(async ()=>{
371
- await store.addLogs(blocks.map((b)=>b.block));
372
- await store.addBlocks(blocks);
1711
+ await store.addLogs(publishedCheckpoints.flatMap((x)=>x.checkpoint.blocks));
1712
+ await store.addCheckpoints(publishedCheckpoints);
373
1713
  });
374
1714
  it.each([
375
1715
  ()=>({
376
- data: blocks[0].block.body.txEffects[0],
377
- block: blocks[0].block,
1716
+ data: getBlock(0).body.txEffects[0],
1717
+ block: getBlock(0),
378
1718
  txIndexInBlock: 0
379
1719
  }),
380
1720
  ()=>({
381
- data: blocks[9].block.body.txEffects[3],
382
- block: blocks[9].block,
1721
+ data: getBlock(9).body.txEffects[3],
1722
+ block: getBlock(9),
383
1723
  txIndexInBlock: 3
384
1724
  }),
385
1725
  ()=>({
386
- data: blocks[3].block.body.txEffects[1],
387
- block: blocks[3].block,
1726
+ data: getBlock(3).body.txEffects[1],
1727
+ block: getBlock(3),
388
1728
  txIndexInBlock: 1
389
1729
  }),
390
1730
  ()=>({
391
- data: blocks[5].block.body.txEffects[2],
392
- block: blocks[5].block,
1731
+ data: getBlock(5).body.txEffects[2],
1732
+ block: getBlock(5),
393
1733
  txIndexInBlock: 2
394
1734
  }),
395
1735
  ()=>({
396
- data: blocks[1].block.body.txEffects[0],
397
- block: blocks[1].block,
1736
+ data: getBlock(1).body.txEffects[0],
1737
+ block: getBlock(1),
398
1738
  txIndexInBlock: 0
399
1739
  })
400
1740
  ])('retrieves a previously stored transaction', async (getExpectedTx)=>{
@@ -402,7 +1742,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
402
1742
  const expectedTx = {
403
1743
  data,
404
1744
  l2BlockNumber: block.number,
405
- l2BlockHash: L2BlockHash.fromField(await block.hash()),
1745
+ l2BlockHash: L2BlockHash.fromField(await block.header.hash()),
406
1746
  txIndexInBlock
407
1747
  };
408
1748
  const actualTx = await store.getTxEffect(data.txHash);
@@ -412,36 +1752,36 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
412
1752
  await expect(store.getTxEffect(TxHash.random())).resolves.toBeUndefined();
413
1753
  });
414
1754
  it.each([
415
- ()=>wrapInBlock(blocks[0].block.body.txEffects[0], blocks[0].block),
416
- ()=>wrapInBlock(blocks[9].block.body.txEffects[3], blocks[9].block),
417
- ()=>wrapInBlock(blocks[3].block.body.txEffects[1], blocks[3].block),
418
- ()=>wrapInBlock(blocks[5].block.body.txEffects[2], blocks[5].block),
419
- ()=>wrapInBlock(blocks[1].block.body.txEffects[0], blocks[1].block)
420
- ])('tries to retrieves a previously stored transaction after deleted', async (getExpectedTx)=>{
421
- await store.unwindBlocks(blocks.length, blocks.length);
422
- const expectedTx = await getExpectedTx();
423
- 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);
424
1764
  expect(actualTx).toEqual(undefined);
425
1765
  });
426
1766
  it('returns undefined if tx is not found', async ()=>{
427
1767
  await expect(store.getTxEffect(TxHash.random())).resolves.toBeUndefined();
428
1768
  });
429
1769
  it('does not fail if the block is unwound while requesting a tx', async ()=>{
430
- const expectedTx = await wrapInBlock(blocks[1].block.body.txEffects[0], blocks[1].block);
1770
+ const txEffect = getBlock(1).body.txEffects[0];
431
1771
  let done = false;
432
1772
  void (async ()=>{
433
1773
  while(!done){
434
- void store.getTxEffect(expectedTx.data.txHash);
1774
+ void store.getTxEffect(txEffect.txHash);
435
1775
  await sleep(1);
436
1776
  }
437
1777
  })();
438
- await store.unwindBlocks(blocks.length, blocks.length);
1778
+ await store.unwindCheckpoints(CheckpointNumber(publishedCheckpoints.length), publishedCheckpoints.length);
439
1779
  done = true;
440
- expect(await store.getTxEffect(expectedTx.data.txHash)).toEqual(undefined);
1780
+ expect(await store.getTxEffect(txEffect.txHash)).toEqual(undefined);
441
1781
  });
442
1782
  });
443
1783
  describe('L1 to L2 Messages', ()=>{
444
- const initialL2BlockNumber = 13;
1784
+ const initialCheckpointNumber = CheckpointNumber(13);
445
1785
  const checkMessages = async (msgs)=>{
446
1786
  expect(await store.getLastL1ToL2Message()).toEqual(msgs.at(-1));
447
1787
  expect(await toArray(store.iterateL1ToL2Messages())).toEqual(msgs);
@@ -449,11 +1789,11 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
449
1789
  };
450
1790
  const makeInboxMessagesWithFullBlocks = (blockCount, opts = {})=>makeInboxMessages(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * blockCount, {
451
1791
  overrideFn: (msg, i)=>{
452
- const l2BlockNumber = (opts.initialL2BlockNumber ?? initialL2BlockNumber) + Math.floor(i / NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
453
- const index = InboxLeaf.smallestIndexFromL2Block(l2BlockNumber) + BigInt(i % NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
1792
+ const checkpointNumber = CheckpointNumber((opts.initialCheckpointNumber ?? initialCheckpointNumber) + Math.floor(i / NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP));
1793
+ const index = InboxLeaf.smallestIndexForCheckpoint(checkpointNumber) + BigInt(i % NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
454
1794
  return {
455
1795
  ...msg,
456
- l2BlockNumber,
1796
+ checkpointNumber,
457
1797
  index
458
1798
  };
459
1799
  }
@@ -461,7 +1801,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
461
1801
  it('stores first message ever', async ()=>{
462
1802
  const msg = makeInboxMessage(Buffer16.ZERO, {
463
1803
  index: 0n,
464
- l2BlockNumber: 1
1804
+ checkpointNumber: CheckpointNumber(1)
465
1805
  });
466
1806
  await store.addL1ToL2Messages([
467
1807
  msg
@@ -469,13 +1809,13 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
469
1809
  await checkMessages([
470
1810
  msg
471
1811
  ]);
472
- expect(await store.getL1ToL2Messages(1)).toEqual([
1812
+ expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toEqual([
473
1813
  msg.leaf
474
1814
  ]);
475
1815
  });
476
1816
  it('stores single message', async ()=>{
477
1817
  const msg = makeInboxMessage(Buffer16.ZERO, {
478
- l2BlockNumber: 2
1818
+ checkpointNumber: CheckpointNumber(2)
479
1819
  });
480
1820
  await store.addL1ToL2Messages([
481
1821
  msg
@@ -483,23 +1823,23 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
483
1823
  await checkMessages([
484
1824
  msg
485
1825
  ]);
486
- expect(await store.getL1ToL2Messages(2)).toEqual([
1826
+ expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toEqual([
487
1827
  msg.leaf
488
1828
  ]);
489
1829
  });
490
1830
  it('stores and returns messages across different blocks', async ()=>{
491
1831
  const msgs = makeInboxMessages(5, {
492
- initialL2BlockNumber
1832
+ initialCheckpointNumber
493
1833
  });
494
1834
  await store.addL1ToL2Messages(msgs);
495
1835
  await checkMessages(msgs);
496
- expect(await store.getL1ToL2Messages(initialL2BlockNumber + 2)).toEqual([
1836
+ expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 2))).toEqual([
497
1837
  msgs[2]
498
1838
  ].map((m)=>m.leaf));
499
1839
  });
500
1840
  it('stores the same messages again', async ()=>{
501
1841
  const msgs = makeInboxMessages(5, {
502
- initialL2BlockNumber
1842
+ initialCheckpointNumber
503
1843
  });
504
1844
  await store.addL1ToL2Messages(msgs);
505
1845
  await store.addL1ToL2Messages(msgs.slice(2));
@@ -507,10 +1847,10 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
507
1847
  });
508
1848
  it('stores and returns messages across different blocks with gaps', async ()=>{
509
1849
  const msgs1 = makeInboxMessages(3, {
510
- initialL2BlockNumber: 1
1850
+ initialCheckpointNumber: CheckpointNumber(1)
511
1851
  });
512
1852
  const msgs2 = makeInboxMessages(3, {
513
- initialL2BlockNumber: 20,
1853
+ initialCheckpointNumber: CheckpointNumber(20),
514
1854
  initialHash: msgs1.at(-1).rollingHash
515
1855
  });
516
1856
  await store.addL1ToL2Messages(msgs1);
@@ -519,22 +1859,22 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
519
1859
  ...msgs1,
520
1860
  ...msgs2
521
1861
  ]);
522
- expect(await store.getL1ToL2Messages(1)).toEqual([
1862
+ expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toEqual([
523
1863
  msgs1[0].leaf
524
1864
  ]);
525
- expect(await store.getL1ToL2Messages(4)).toEqual([]);
526
- expect(await store.getL1ToL2Messages(20)).toEqual([
1865
+ expect(await store.getL1ToL2Messages(CheckpointNumber(4))).toEqual([]);
1866
+ expect(await store.getL1ToL2Messages(CheckpointNumber(20))).toEqual([
527
1867
  msgs2[0].leaf
528
1868
  ]);
529
- expect(await store.getL1ToL2Messages(24)).toEqual([]);
1869
+ expect(await store.getL1ToL2Messages(CheckpointNumber(24))).toEqual([]);
530
1870
  });
531
1871
  it('stores and returns messages with block numbers larger than a byte', async ()=>{
532
1872
  const msgs = makeInboxMessages(5, {
533
- initialL2BlockNumber: 1000
1873
+ initialCheckpointNumber: CheckpointNumber(1000)
534
1874
  });
535
1875
  await store.addL1ToL2Messages(msgs);
536
1876
  await checkMessages(msgs);
537
- expect(await store.getL1ToL2Messages(1002)).toEqual([
1877
+ expect(await store.getL1ToL2Messages(CheckpointNumber(1002))).toEqual([
538
1878
  msgs[2]
539
1879
  ].map((m)=>m.leaf));
540
1880
  });
@@ -542,27 +1882,27 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
542
1882
  const msgs = makeInboxMessagesWithFullBlocks(4);
543
1883
  await store.addL1ToL2Messages(msgs);
544
1884
  await checkMessages(msgs);
545
- const blockMessages = await store.getL1ToL2Messages(initialL2BlockNumber + 1);
1885
+ const blockMessages = await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 1));
546
1886
  expect(blockMessages).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
547
1887
  expect(blockMessages).toEqual(msgs.slice(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2).map((m)=>m.leaf));
548
1888
  });
549
1889
  it('stores messages in multiple operations', async ()=>{
550
1890
  const msgs = makeInboxMessages(20, {
551
- initialL2BlockNumber
1891
+ initialCheckpointNumber
552
1892
  });
553
1893
  await store.addL1ToL2Messages(msgs.slice(0, 10));
554
1894
  await store.addL1ToL2Messages(msgs.slice(10, 20));
555
- expect(await store.getL1ToL2Messages(initialL2BlockNumber + 2)).toEqual([
1895
+ expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 2))).toEqual([
556
1896
  msgs[2]
557
1897
  ].map((m)=>m.leaf));
558
- expect(await store.getL1ToL2Messages(initialL2BlockNumber + 12)).toEqual([
1898
+ expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 12))).toEqual([
559
1899
  msgs[12]
560
1900
  ].map((m)=>m.leaf));
561
1901
  await checkMessages(msgs);
562
1902
  });
563
1903
  it('iterates over messages from start index', async ()=>{
564
1904
  const msgs = makeInboxMessages(10, {
565
- initialL2BlockNumber
1905
+ initialCheckpointNumber
566
1906
  });
567
1907
  await store.addL1ToL2Messages(msgs);
568
1908
  const iterated = await toArray(store.iterateL1ToL2Messages({
@@ -572,9 +1912,10 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
572
1912
  });
573
1913
  it('iterates over messages in reverse', async ()=>{
574
1914
  const msgs = makeInboxMessages(10, {
575
- initialL2BlockNumber
1915
+ initialCheckpointNumber
576
1916
  });
577
1917
  await store.addL1ToL2Messages(msgs);
1918
+ initialCheckpointNumber;
578
1919
  const iterated = await toArray(store.iterateL1ToL2Messages({
579
1920
  reverse: true,
580
1921
  end: msgs[3].index
@@ -592,9 +1933,9 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
592
1933
  });
593
1934
  it('throws if block number for the first message is out of order', async ()=>{
594
1935
  const msgs = makeInboxMessages(4, {
595
- initialL2BlockNumber
1936
+ initialCheckpointNumber
596
1937
  });
597
- msgs[2].l2BlockNumber = initialL2BlockNumber - 1;
1938
+ msgs[2].checkpointNumber = CheckpointNumber(initialCheckpointNumber - 1);
598
1939
  await store.addL1ToL2Messages(msgs.slice(0, 2));
599
1940
  await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
600
1941
  });
@@ -606,29 +1947,29 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
606
1947
  it('throws if rolling hash for first message is not correct', async ()=>{
607
1948
  const msgs = makeInboxMessages(4);
608
1949
  msgs[2].rollingHash = Buffer16.random();
609
- await store.addL1ToL2Messages(msgs.slice(0, 2));
1950
+ await store.addL1ToL2Messages(msgs.slice(0, CheckpointNumber(2)));
610
1951
  await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
611
1952
  });
612
1953
  it('throws if index is not in the correct range', async ()=>{
613
1954
  const msgs = makeInboxMessages(5, {
614
- initialL2BlockNumber
1955
+ initialCheckpointNumber
615
1956
  });
616
1957
  msgs.at(-1).index += 100n;
617
1958
  await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
618
1959
  });
619
1960
  it('throws if first index in block has gaps', async ()=>{
620
1961
  const msgs = makeInboxMessages(4, {
621
- initialL2BlockNumber
1962
+ initialCheckpointNumber
622
1963
  });
623
1964
  msgs[2].index++;
624
1965
  await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
625
1966
  });
626
1967
  it('throws if index does not follow previous one', async ()=>{
627
1968
  const msgs = makeInboxMessages(2, {
628
- initialL2BlockNumber,
1969
+ initialCheckpointNumber,
629
1970
  overrideFn: (msg, i)=>({
630
1971
  ...msg,
631
- l2BlockNumber: 2,
1972
+ checkpointNumber: CheckpointNumber(2),
632
1973
  index: BigInt(i + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2)
633
1974
  })
634
1975
  });
@@ -637,24 +1978,24 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
637
1978
  });
638
1979
  it('removes messages up to the given block number', async ()=>{
639
1980
  const msgs = makeInboxMessagesWithFullBlocks(4, {
640
- initialL2BlockNumber: 1
1981
+ initialCheckpointNumber: CheckpointNumber(1)
641
1982
  });
642
1983
  await store.addL1ToL2Messages(msgs);
643
1984
  await checkMessages(msgs);
644
- expect(await store.getL1ToL2Messages(1)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
645
- expect(await store.getL1ToL2Messages(2)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
646
- expect(await store.getL1ToL2Messages(3)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
647
- expect(await store.getL1ToL2Messages(4)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
648
- await store.rollbackL1ToL2MessagesToL2Block(2);
649
- expect(await store.getL1ToL2Messages(1)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
650
- expect(await store.getL1ToL2Messages(2)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
651
- expect(await store.getL1ToL2Messages(3)).toHaveLength(0);
652
- expect(await store.getL1ToL2Messages(4)).toHaveLength(0);
1985
+ expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
1986
+ expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
1987
+ expect(await store.getL1ToL2Messages(CheckpointNumber(3))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
1988
+ expect(await store.getL1ToL2Messages(CheckpointNumber(4))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
1989
+ await store.rollbackL1ToL2MessagesToCheckpoint(CheckpointNumber(2));
1990
+ expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
1991
+ expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
1992
+ expect(await store.getL1ToL2Messages(CheckpointNumber(3))).toHaveLength(0);
1993
+ expect(await store.getL1ToL2Messages(CheckpointNumber(4))).toHaveLength(0);
653
1994
  await checkMessages(msgs.slice(0, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2));
654
1995
  });
655
1996
  it('removes messages starting with the given index', async ()=>{
656
1997
  const msgs = makeInboxMessagesWithFullBlocks(4, {
657
- initialL2BlockNumber: 1
1998
+ initialCheckpointNumber: CheckpointNumber(1)
658
1999
  });
659
2000
  await store.addL1ToL2Messages(msgs);
660
2001
  await store.removeL1ToL2Messages(msgs[13].index);
@@ -677,7 +2018,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
677
2018
  };
678
2019
  await store.addContractInstances([
679
2020
  contractInstance
680
- ], blockNum);
2021
+ ], BlockNumber(blockNum));
681
2022
  });
682
2023
  it('returns previously stored contract instances', async ()=>{
683
2024
  await expect(store.getContractInstance(contractInstance.address, timestamp)).resolves.toMatchObject(contractInstance);
@@ -688,7 +2029,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
688
2029
  it('returns undefined if previously stored contract instances was deleted', async ()=>{
689
2030
  await store.deleteContractInstances([
690
2031
  contractInstance
691
- ], blockNum);
2032
+ ], BlockNumber(blockNum));
692
2033
  await expect(store.getContractInstance(contractInstance.address, timestamp)).resolves.toBeUndefined();
693
2034
  });
694
2035
  });
@@ -710,7 +2051,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
710
2051
  };
711
2052
  await store.addContractInstances([
712
2053
  contractInstance
713
- ], 1);
2054
+ ], BlockNumber(1));
714
2055
  await store.addContractInstanceUpdates([
715
2056
  {
716
2057
  prevContractClassId: classId,
@@ -747,7 +2088,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
747
2088
  };
748
2089
  await store.addContractInstances([
749
2090
  otherContractInstance
750
- ], 1);
2091
+ ], BlockNumber(1));
751
2092
  const fetchedInstance = await store.getContractInstance(otherContractInstance.address, timestampOfChange + 1n);
752
2093
  expect(fetchedInstance?.originalContractClassId).toEqual(otherClassId);
753
2094
  expect(fetchedInstance?.currentContractClassId).toEqual(otherClassId);
@@ -765,7 +2106,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
765
2106
  };
766
2107
  await store.addContractInstances([
767
2108
  otherContractInstance
768
- ], 1);
2109
+ ], BlockNumber(1));
769
2110
  await store.addContractInstanceUpdates([
770
2111
  {
771
2112
  prevContractClassId: otherClassId,
@@ -788,7 +2129,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
788
2129
  contractClass
789
2130
  ], [
790
2131
  await computePublicBytecodeCommitment(contractClass.packedBytecode)
791
- ], blockNum);
2132
+ ], BlockNumber(blockNum));
792
2133
  });
793
2134
  it('returns previously stored contract class', async ()=>{
794
2135
  await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
@@ -796,7 +2137,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
796
2137
  it('returns undefined if the initial deployed contract class was deleted', async ()=>{
797
2138
  await store.deleteContractClasses([
798
2139
  contractClass
799
- ], blockNum);
2140
+ ], BlockNumber(blockNum));
800
2141
  await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined();
801
2142
  });
802
2143
  it('returns contract class if later "deployment" class was deleted', async ()=>{
@@ -804,10 +2145,10 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
804
2145
  contractClass
805
2146
  ], [
806
2147
  await computePublicBytecodeCommitment(contractClass.packedBytecode)
807
- ], blockNum + 1);
2148
+ ], BlockNumber(blockNum + 1));
808
2149
  await store.deleteContractClasses([
809
2150
  contractClass
810
- ], blockNum + 1);
2151
+ ], BlockNumber(blockNum + 1));
811
2152
  await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
812
2153
  });
813
2154
  it('returns undefined if contract class is not found', async ()=>{
@@ -840,180 +2181,268 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
840
2181
  expect(stored?.utilityFunctions).toEqual(fns);
841
2182
  });
842
2183
  });
843
- describe('getLogsByTags', ()=>{
844
- const numBlocks = 3;
2184
+ describe('getPrivateLogsByTags', ()=>{
2185
+ const numBlocksForLogs = 3;
845
2186
  const numTxsPerBlock = 4;
846
2187
  const numPrivateLogsPerTx = 3;
847
- const numPublicLogsPerTx = 2;
848
- let blocks;
849
- const makeTag = (blockNumber, txIndex, logIndex, isPublic = false)=>blockNumber === 1 && txIndex === 0 && logIndex === 0 ? Fr.ZERO // Shared tag
850
- : new Fr((blockNumber * 100 + txIndex * 10 + logIndex) * (isPublic ? 123 : 1));
2188
+ let logsCheckpoints;
2189
+ const makePrivateLogTag = (blockNumber, txIndex, logIndex)=>new SiloedTag(blockNumber === 1 && txIndex === 0 && logIndex === 0 ? Fr.ZERO // Shared tag
2190
+ : new Fr(blockNumber * 100 + txIndex * 10 + logIndex));
851
2191
  const makePrivateLog = (tag)=>PrivateLog.from({
852
- fields: makeTuple(PRIVATE_LOG_SIZE_IN_FIELDS, (i)=>!i ? tag : new Fr(tag.toNumber() + i)),
2192
+ fields: makeTuple(PRIVATE_LOG_SIZE_IN_FIELDS, (i)=>!i ? tag.value : new Fr(tag.value.toBigInt() + BigInt(i))),
853
2193
  emittedLength: PRIVATE_LOG_SIZE_IN_FIELDS
854
2194
  });
855
- const makePublicLog = (tag)=>PublicLog.from({
856
- contractAddress: AztecAddress.fromNumber(1),
857
- // Arbitrary length
858
- fields: new Array(10).fill(null).map((_, i)=>!i ? tag : new Fr(tag.toNumber() + i))
859
- });
860
2195
  const mockPrivateLogs = (blockNumber, txIndex)=>{
861
2196
  return times(numPrivateLogsPerTx, (logIndex)=>{
862
- const tag = makeTag(blockNumber, txIndex, logIndex);
2197
+ const tag = makePrivateLogTag(blockNumber, txIndex, logIndex);
863
2198
  return makePrivateLog(tag);
864
2199
  });
865
2200
  };
866
- const mockPublicLogs = (blockNumber, txIndex)=>{
867
- return times(numPublicLogsPerTx, (logIndex)=>{
868
- const tag = makeTag(blockNumber, txIndex, logIndex, /* isPublic */ true);
869
- return makePublicLog(tag);
2201
+ const mockCheckpointWithLogs = async (blockNumber, previousArchive)=>{
2202
+ const block = await L2BlockNew.random(BlockNumber(blockNumber), {
2203
+ checkpointNumber: CheckpointNumber(blockNumber),
2204
+ indexWithinCheckpoint: 0,
2205
+ state: makeStateForBlock(blockNumber, numTxsPerBlock),
2206
+ ...previousArchive ? {
2207
+ lastArchive: previousArchive
2208
+ } : {}
870
2209
  });
871
- };
872
- const mockBlockWithLogs = async (blockNumber)=>{
873
- const block = await L2Block.random(blockNumber);
874
- block.header.globalVariables.blockNumber = blockNumber;
2210
+ block.header.globalVariables.blockNumber = BlockNumber(blockNumber);
875
2211
  block.body.txEffects = await timesParallel(numTxsPerBlock, async (txIndex)=>{
876
2212
  const txEffect = await TxEffect.random();
877
2213
  txEffect.privateLogs = mockPrivateLogs(blockNumber, txIndex);
878
- txEffect.publicLogs = mockPublicLogs(blockNumber, txIndex);
2214
+ txEffect.publicLogs = []; // No public logs needed for private log tests
879
2215
  return txEffect;
880
2216
  });
881
- return PublishedL2Block.fromFields({
882
- block: block,
883
- attestations: times(3, CommitteeAttestation.random),
884
- l1: {
885
- blockNumber: BigInt(blockNumber),
886
- blockHash: makeBlockHash(blockNumber),
887
- timestamp: BigInt(blockNumber)
888
- }
889
- });
2217
+ const checkpoint = new Checkpoint(AppendOnlyTreeSnapshot.random(), CheckpointHeader.random(), [
2218
+ block
2219
+ ], CheckpointNumber(blockNumber));
2220
+ return makePublishedCheckpoint(checkpoint, blockNumber);
890
2221
  };
891
2222
  beforeEach(async ()=>{
892
- blocks = await timesParallel(numBlocks, (index)=>mockBlockWithLogs(index + 1));
893
- await store.addBlocks(blocks);
894
- await store.addLogs(blocks.map((b)=>b.block));
2223
+ // Create checkpoints sequentially to chain archive roots
2224
+ logsCheckpoints = [];
2225
+ for(let i = 0; i < numBlocksForLogs; i++){
2226
+ const previousArchive = i > 0 ? logsCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
2227
+ logsCheckpoints.push(await mockCheckpointWithLogs(i + 1, previousArchive));
2228
+ }
2229
+ await store.addCheckpoints(logsCheckpoints);
2230
+ await store.addLogs(logsCheckpoints.flatMap((p)=>p.checkpoint.blocks));
895
2231
  });
896
2232
  it('is possible to batch request private logs via tags', async ()=>{
897
2233
  const tags = [
898
- makeTag(2, 1, 2),
899
- makeTag(1, 2, 0)
2234
+ makePrivateLogTag(2, 1, 2),
2235
+ makePrivateLogTag(1, 2, 0)
900
2236
  ];
901
- const logsByTags = await store.getLogsByTags(tags);
2237
+ const logsByTags = await store.getPrivateLogsByTags(tags);
902
2238
  expect(logsByTags).toEqual([
903
2239
  [
904
2240
  expect.objectContaining({
905
2241
  blockNumber: 2,
906
- log: makePrivateLog(tags[0]),
907
- isFromPublic: false
2242
+ logData: makePrivateLog(tags[0]).getEmittedFields()
908
2243
  })
909
2244
  ],
910
2245
  [
911
2246
  expect.objectContaining({
912
2247
  blockNumber: 1,
913
- log: makePrivateLog(tags[1]),
914
- isFromPublic: false
2248
+ logData: makePrivateLog(tags[1]).getEmittedFields()
915
2249
  })
916
2250
  ]
917
2251
  ]);
918
2252
  });
919
- it('is possible to batch request all logs (private and public) via tags', async ()=>{
920
- // Tag(1, 0, 0) is shared with the first private log and the first public log.
2253
+ it('is possible to batch request logs that have the same tag but different content', async ()=>{
921
2254
  const tags = [
922
- makeTag(1, 0, 0)
2255
+ makePrivateLogTag(1, 2, 1)
923
2256
  ];
924
- const logsByTags = await store.getLogsByTags(tags);
2257
+ // Create a checkpoint containing logs that have the same tag as the checkpoints before.
2258
+ // Chain from the last checkpoint's archive
2259
+ const newBlockNumber = numBlocksForLogs + 1;
2260
+ const previousArchive = logsCheckpoints[logsCheckpoints.length - 1].checkpoint.blocks[0].archive;
2261
+ const newCheckpoint = await mockCheckpointWithLogs(newBlockNumber, previousArchive);
2262
+ const newLog = newCheckpoint.checkpoint.blocks[0].body.txEffects[1].privateLogs[1];
2263
+ newLog.fields[0] = tags[0].value;
2264
+ newCheckpoint.checkpoint.blocks[0].body.txEffects[1].privateLogs[1] = newLog;
2265
+ await store.addCheckpoints([
2266
+ newCheckpoint
2267
+ ]);
2268
+ await store.addLogs([
2269
+ newCheckpoint.checkpoint.blocks[0]
2270
+ ]);
2271
+ const logsByTags = await store.getPrivateLogsByTags(tags);
925
2272
  expect(logsByTags).toEqual([
926
2273
  [
927
2274
  expect.objectContaining({
928
2275
  blockNumber: 1,
929
- log: makePrivateLog(tags[0]),
930
- isFromPublic: false
2276
+ logData: makePrivateLog(tags[0]).getEmittedFields()
931
2277
  }),
2278
+ expect.objectContaining({
2279
+ blockNumber: newBlockNumber,
2280
+ logData: newLog.getEmittedFields()
2281
+ })
2282
+ ]
2283
+ ]);
2284
+ });
2285
+ it('is possible to request logs for non-existing tags and determine their position', async ()=>{
2286
+ const tags = [
2287
+ makePrivateLogTag(99, 88, 77),
2288
+ makePrivateLogTag(1, 1, 1)
2289
+ ];
2290
+ const logsByTags = await store.getPrivateLogsByTags(tags);
2291
+ expect(logsByTags).toEqual([
2292
+ [],
2293
+ [
932
2294
  expect.objectContaining({
933
2295
  blockNumber: 1,
934
- log: makePublicLog(tags[0]),
935
- isFromPublic: true
2296
+ logData: makePrivateLog(tags[1]).getEmittedFields()
2297
+ })
2298
+ ]
2299
+ ]);
2300
+ });
2301
+ });
2302
+ describe('getPublicLogsByTagsFromContract', ()=>{
2303
+ const numBlocksForLogs = 3;
2304
+ const numTxsPerBlock = 4;
2305
+ const numPublicLogsPerTx = 2;
2306
+ const contractAddress = AztecAddress.fromNumber(543254);
2307
+ let logsCheckpoints;
2308
+ const makePublicLogTag = (blockNumber, txIndex, logIndex)=>new Tag(blockNumber === 1 && txIndex === 0 && logIndex === 0 ? Fr.ZERO // Shared tag
2309
+ : new Fr((blockNumber * 100 + txIndex * 10 + logIndex) * 123));
2310
+ const makePublicLog = (tag)=>PublicLog.from({
2311
+ contractAddress: contractAddress,
2312
+ // Arbitrary length
2313
+ fields: new Array(10).fill(null).map((_, i)=>!i ? tag.value : new Fr(tag.value.toBigInt() + BigInt(i)))
2314
+ });
2315
+ const mockPublicLogs = (blockNumber, txIndex)=>{
2316
+ return times(numPublicLogsPerTx, (logIndex)=>{
2317
+ const tag = makePublicLogTag(blockNumber, txIndex, logIndex);
2318
+ return makePublicLog(tag);
2319
+ });
2320
+ };
2321
+ const mockCheckpointWithLogs = async (blockNumber, previousArchive)=>{
2322
+ const block = await L2BlockNew.random(BlockNumber(blockNumber), {
2323
+ checkpointNumber: CheckpointNumber(blockNumber),
2324
+ indexWithinCheckpoint: 0,
2325
+ state: makeStateForBlock(blockNumber, numTxsPerBlock),
2326
+ ...previousArchive ? {
2327
+ lastArchive: previousArchive
2328
+ } : {}
2329
+ });
2330
+ block.header.globalVariables.blockNumber = BlockNumber(blockNumber);
2331
+ block.body.txEffects = await timesParallel(numTxsPerBlock, async (txIndex)=>{
2332
+ const txEffect = await TxEffect.random();
2333
+ txEffect.privateLogs = []; // No private logs needed for public log tests
2334
+ txEffect.publicLogs = mockPublicLogs(blockNumber, txIndex);
2335
+ return txEffect;
2336
+ });
2337
+ const checkpoint = new Checkpoint(AppendOnlyTreeSnapshot.random(), CheckpointHeader.random(), [
2338
+ block
2339
+ ], CheckpointNumber(blockNumber));
2340
+ return makePublishedCheckpoint(checkpoint, blockNumber);
2341
+ };
2342
+ beforeEach(async ()=>{
2343
+ // Create checkpoints sequentially to chain archive roots
2344
+ logsCheckpoints = [];
2345
+ for(let i = 0; i < numBlocksForLogs; i++){
2346
+ const previousArchive = i > 0 ? logsCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
2347
+ logsCheckpoints.push(await mockCheckpointWithLogs(i + 1, previousArchive));
2348
+ }
2349
+ await store.addCheckpoints(logsCheckpoints);
2350
+ await store.addLogs(logsCheckpoints.flatMap((p)=>p.checkpoint.blocks));
2351
+ });
2352
+ it('is possible to batch request public logs via tags', async ()=>{
2353
+ const tags = [
2354
+ makePublicLogTag(2, 1, 1),
2355
+ makePublicLogTag(1, 2, 0)
2356
+ ];
2357
+ const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
2358
+ expect(logsByTags).toEqual([
2359
+ [
2360
+ expect.objectContaining({
2361
+ blockNumber: 2,
2362
+ logData: makePublicLog(tags[0]).getEmittedFields()
2363
+ })
2364
+ ],
2365
+ [
2366
+ expect.objectContaining({
2367
+ blockNumber: 1,
2368
+ logData: makePublicLog(tags[1]).getEmittedFields()
936
2369
  })
937
2370
  ]
938
2371
  ]);
939
2372
  });
940
2373
  it('is possible to batch request logs that have the same tag but different content', async ()=>{
941
2374
  const tags = [
942
- makeTag(1, 2, 1)
2375
+ makePublicLogTag(1, 2, 1)
943
2376
  ];
944
- // Create a block containing logs that have the same tag as the blocks before.
945
- const newBlockNumber = numBlocks;
946
- const newBlock = await mockBlockWithLogs(newBlockNumber);
947
- const newLog = newBlock.block.body.txEffects[1].privateLogs[1];
948
- newLog.fields[0] = tags[0];
949
- newBlock.block.body.txEffects[1].privateLogs[1] = newLog;
950
- await store.addBlocks([
951
- newBlock
2377
+ // Create a checkpoint containing logs that have the same tag as the checkpoints before.
2378
+ // Chain from the last checkpoint's archive
2379
+ const newBlockNumber = numBlocksForLogs + 1;
2380
+ const previousArchive = logsCheckpoints[logsCheckpoints.length - 1].checkpoint.blocks[0].archive;
2381
+ const newCheckpoint = await mockCheckpointWithLogs(newBlockNumber, previousArchive);
2382
+ const newLog = newCheckpoint.checkpoint.blocks[0].body.txEffects[1].publicLogs[1];
2383
+ newLog.fields[0] = tags[0].value;
2384
+ newCheckpoint.checkpoint.blocks[0].body.txEffects[1].publicLogs[1] = newLog;
2385
+ await store.addCheckpoints([
2386
+ newCheckpoint
952
2387
  ]);
953
2388
  await store.addLogs([
954
- newBlock.block
2389
+ newCheckpoint.checkpoint.blocks[0]
955
2390
  ]);
956
- const logsByTags = await store.getLogsByTags(tags);
2391
+ const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
957
2392
  expect(logsByTags).toEqual([
958
2393
  [
959
2394
  expect.objectContaining({
960
2395
  blockNumber: 1,
961
- log: makePrivateLog(tags[0]),
962
- isFromPublic: false
2396
+ logData: makePublicLog(tags[0]).getEmittedFields()
963
2397
  }),
964
2398
  expect.objectContaining({
965
2399
  blockNumber: newBlockNumber,
966
- log: newLog,
967
- isFromPublic: false
2400
+ logData: newLog.getEmittedFields()
968
2401
  })
969
2402
  ]
970
2403
  ]);
971
2404
  });
972
2405
  it('is possible to request logs for non-existing tags and determine their position', async ()=>{
973
2406
  const tags = [
974
- makeTag(99, 88, 77),
975
- makeTag(1, 1, 1)
2407
+ makePublicLogTag(99, 88, 77),
2408
+ makePublicLogTag(1, 1, 0)
976
2409
  ];
977
- const logsByTags = await store.getLogsByTags(tags);
2410
+ const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
978
2411
  expect(logsByTags).toEqual([
979
2412
  [],
980
2413
  [
981
2414
  expect.objectContaining({
982
2415
  blockNumber: 1,
983
- log: makePrivateLog(tags[1]),
984
- isFromPublic: false
2416
+ logData: makePublicLog(tags[1]).getEmittedFields()
985
2417
  })
986
2418
  ]
987
2419
  ]);
988
2420
  });
989
2421
  });
990
2422
  describe('getPublicLogs', ()=>{
991
- const txsPerBlock = 4;
992
- const numPublicFunctionCalls = 3;
993
- const numPublicLogs = 2;
994
- const numBlocks = 10;
995
- let blocks;
2423
+ const numBlocksForPublicLogs = 10;
2424
+ // Helper to get total public logs per tx from a block
2425
+ const getPublicLogsPerTx = (block, txIndex)=>block.body.txEffects[txIndex].publicLogs.length;
2426
+ // Helper to get number of txs in a block
2427
+ const getTxsPerBlock = (block)=>block.body.txEffects.length;
996
2428
  beforeEach(async ()=>{
997
- blocks = await timesParallel(numBlocks, async (index)=>PublishedL2Block.fromFields({
998
- block: await L2Block.random(index + 1, txsPerBlock, numPublicFunctionCalls, numPublicLogs),
999
- l1: {
1000
- blockNumber: BigInt(index),
1001
- blockHash: makeBlockHash(index),
1002
- timestamp: BigInt(index)
1003
- },
1004
- attestations: times(3, CommitteeAttestation.random)
1005
- }));
1006
- await store.addBlocks(blocks);
1007
- await store.addLogs(blocks.map((b)=>b.block));
2429
+ // Use the outer publishedCheckpoints for log tests
2430
+ for(let i = 0; i < numBlocksForPublicLogs; i++){
2431
+ await store.addCheckpoints([
2432
+ publishedCheckpoints[i]
2433
+ ]);
2434
+ await store.addLogs(publishedCheckpoints[i].checkpoint.blocks);
2435
+ }
1008
2436
  });
1009
2437
  it('no logs returned if deleted ("txHash" filter param is respected variant)', async ()=>{
1010
2438
  // get random tx
1011
- const targetBlockIndex = randomInt(numBlocks);
1012
- const targetTxIndex = randomInt(txsPerBlock);
1013
- const targetTxHash = blocks[targetBlockIndex].block.body.txEffects[targetTxIndex].txHash;
2439
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2440
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2441
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2442
+ const targetTxHash = targetBlock.body.txEffects[targetTxIndex].txHash;
1014
2443
  await Promise.all([
1015
- store.unwindBlocks(blocks.length, blocks.length),
1016
- store.deleteLogs(blocks.map((b)=>b.block))
2444
+ store.unwindCheckpoints(CheckpointNumber(numBlocksForPublicLogs), numBlocksForPublicLogs),
2445
+ store.deleteLogs(publishedCheckpoints.slice(0, numBlocksForPublicLogs).flatMap((b)=>b.checkpoint.blocks))
1017
2446
  ]);
1018
2447
  const response = await store.getPublicLogs({
1019
2448
  txHash: targetTxHash
@@ -1024,15 +2453,16 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1024
2453
  });
1025
2454
  it('"txHash" filter param is respected', async ()=>{
1026
2455
  // get random tx
1027
- const targetBlockIndex = randomInt(numBlocks);
1028
- const targetTxIndex = randomInt(txsPerBlock);
1029
- const targetTxHash = blocks[targetBlockIndex].block.body.txEffects[targetTxIndex].txHash;
2456
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2457
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2458
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2459
+ const targetTxHash = targetBlock.body.txEffects[targetTxIndex].txHash;
1030
2460
  const response = await store.getPublicLogs({
1031
2461
  txHash: targetTxHash
1032
2462
  });
1033
2463
  const logs = response.logs;
1034
2464
  expect(response.maxLogsHit).toBeFalsy();
1035
- const expectedNumLogs = numPublicFunctionCalls * numPublicLogs;
2465
+ const expectedNumLogs = getPublicLogsPerTx(targetBlock, targetTxIndex);
1036
2466
  expect(logs.length).toEqual(expectedNumLogs);
1037
2467
  const targeBlockNumber = targetBlockIndex + INITIAL_L2_BLOCK_NUM;
1038
2468
  for (const log of logs){
@@ -1040,6 +2470,16 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1040
2470
  expect(log.id.txIndex).toEqual(targetTxIndex);
1041
2471
  }
1042
2472
  });
2473
+ it('returns block hash on public log ids', async ()=>{
2474
+ const targetBlock = publishedCheckpoints[0].checkpoint.blocks[0];
2475
+ const expectedBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
2476
+ const logs = (await store.getPublicLogs({
2477
+ fromBlock: targetBlock.number,
2478
+ toBlock: targetBlock.number + 1
2479
+ })).logs;
2480
+ expect(logs.length).toBeGreaterThan(0);
2481
+ expect(logs.every((log)=>log.id.blockHash.equals(expectedBlockHash))).toBe(true);
2482
+ });
1043
2483
  it('"fromBlock" and "toBlock" filter params are respected', async ()=>{
1044
2484
  // Set "fromBlock" and "toBlock"
1045
2485
  const fromBlock = 3;
@@ -1050,7 +2490,12 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1050
2490
  });
1051
2491
  const logs = response.logs;
1052
2492
  expect(response.maxLogsHit).toBeFalsy();
1053
- const expectedNumLogs = txsPerBlock * numPublicFunctionCalls * numPublicLogs * (toBlock - fromBlock);
2493
+ // Compute expected logs from the blocks in range
2494
+ let expectedNumLogs = 0;
2495
+ for(let i = fromBlock - 1; i < toBlock - 1; i++){
2496
+ const block = publishedCheckpoints[i].checkpoint.blocks[0];
2497
+ expectedNumLogs += block.body.txEffects.reduce((sum, tx)=>sum + tx.publicLogs.length, 0);
2498
+ }
1054
2499
  expect(logs.length).toEqual(expectedNumLogs);
1055
2500
  for (const log of logs){
1056
2501
  const blockNumber = log.id.blockNumber;
@@ -1060,10 +2505,11 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1060
2505
  });
1061
2506
  it('"contractAddress" filter param is respected', async ()=>{
1062
2507
  // Get a random contract address from the logs
1063
- const targetBlockIndex = randomInt(numBlocks);
1064
- const targetTxIndex = randomInt(txsPerBlock);
1065
- const targetLogIndex = randomInt(numPublicLogs * numPublicFunctionCalls);
1066
- const targetContractAddress = blocks[targetBlockIndex].block.body.txEffects[targetTxIndex].publicLogs[targetLogIndex].contractAddress;
2508
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2509
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2510
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2511
+ const targetLogIndex = randomInt(getPublicLogsPerTx(targetBlock, targetTxIndex));
2512
+ const targetContractAddress = targetBlock.body.txEffects[targetTxIndex].publicLogs[targetLogIndex].contractAddress;
1067
2513
  const response = await store.getPublicLogs({
1068
2514
  contractAddress: targetContractAddress
1069
2515
  });
@@ -1074,10 +2520,13 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1074
2520
  });
1075
2521
  it('"afterLog" filter param is respected', async ()=>{
1076
2522
  // Get a random log as reference
1077
- const targetBlockIndex = randomInt(numBlocks);
1078
- const targetTxIndex = randomInt(txsPerBlock);
1079
- const targetLogIndex = randomInt(numPublicLogs);
1080
- const afterLog = new LogId(targetBlockIndex + INITIAL_L2_BLOCK_NUM, targetTxIndex, targetLogIndex);
2523
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2524
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2525
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2526
+ const numLogsInTx = targetBlock.body.txEffects[targetTxIndex].publicLogs.length;
2527
+ const targetLogIndex = numLogsInTx > 0 ? randomInt(numLogsInTx) : 0;
2528
+ const targetBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
2529
+ const afterLog = new LogId(BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM), targetBlockHash, targetTxIndex, targetLogIndex);
1081
2530
  const response = await store.getPublicLogs({
1082
2531
  afterLog
1083
2532
  });
@@ -1097,7 +2546,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1097
2546
  it('"txHash" filter param is ignored when "afterLog" is set', async ()=>{
1098
2547
  // Get random txHash
1099
2548
  const txHash = TxHash.random();
1100
- const afterLog = new LogId(1, 0, 0);
2549
+ const afterLog = new LogId(BlockNumber(1), L2BlockHash.random(), 0, 0);
1101
2550
  const response = await store.getPublicLogs({
1102
2551
  txHash,
1103
2552
  afterLog
@@ -1113,7 +2562,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1113
2562
  // "fromBlock" gets correctly trimmed to range and "toBlock" is exclusive
1114
2563
  logs = (await store.getPublicLogs({
1115
2564
  fromBlock: -10,
1116
- toBlock: 5
2565
+ toBlock: BlockNumber(5)
1117
2566
  })).logs;
1118
2567
  let blockNumbers = new Set(logs.map((log)=>log.id.blockNumber));
1119
2568
  expect(blockNumbers).toEqual(new Set([
@@ -1124,13 +2573,13 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1124
2573
  ]));
1125
2574
  // "toBlock" should be exclusive
1126
2575
  logs = (await store.getPublicLogs({
1127
- fromBlock: 1,
1128
- toBlock: 1
2576
+ fromBlock: BlockNumber(1),
2577
+ toBlock: BlockNumber(1)
1129
2578
  })).logs;
1130
2579
  expect(logs.length).toBe(0);
1131
2580
  logs = (await store.getPublicLogs({
1132
- fromBlock: 10,
1133
- toBlock: 5
2581
+ fromBlock: BlockNumber(10),
2582
+ toBlock: BlockNumber(5)
1134
2583
  })).logs;
1135
2584
  expect(logs.length).toBe(0);
1136
2585
  // both "fromBlock" and "toBlock" get correctly capped to range and logs from all blocks are returned
@@ -1139,35 +2588,38 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1139
2588
  toBlock: +100
1140
2589
  })).logs;
1141
2590
  blockNumbers = new Set(logs.map((log)=>log.id.blockNumber));
1142
- expect(blockNumbers.size).toBe(numBlocks);
2591
+ expect(blockNumbers.size).toBe(numBlocksForPublicLogs);
1143
2592
  // intersecting with "afterLog" works
1144
2593
  logs = (await store.getPublicLogs({
1145
- fromBlock: 2,
1146
- toBlock: 5,
1147
- afterLog: new LogId(4, 0, 0)
2594
+ fromBlock: BlockNumber(2),
2595
+ toBlock: BlockNumber(5),
2596
+ afterLog: new LogId(BlockNumber(4), L2BlockHash.random(), 0, 0)
1148
2597
  })).logs;
1149
2598
  blockNumbers = new Set(logs.map((log)=>log.id.blockNumber));
1150
2599
  expect(blockNumbers).toEqual(new Set([
1151
2600
  4
1152
2601
  ]));
1153
2602
  logs = (await store.getPublicLogs({
1154
- toBlock: 5,
1155
- afterLog: new LogId(5, 1, 0)
2603
+ toBlock: BlockNumber(5),
2604
+ afterLog: new LogId(BlockNumber(5), L2BlockHash.random(), 1, 0)
1156
2605
  })).logs;
1157
2606
  expect(logs.length).toBe(0);
1158
2607
  logs = (await store.getPublicLogs({
1159
- fromBlock: 2,
1160
- toBlock: 5,
1161
- afterLog: new LogId(100, 0, 0)
2608
+ fromBlock: BlockNumber(2),
2609
+ toBlock: BlockNumber(5),
2610
+ afterLog: new LogId(BlockNumber(100), L2BlockHash.random(), 0, 0)
1162
2611
  })).logs;
1163
2612
  expect(logs.length).toBe(0);
1164
2613
  });
1165
2614
  it('"txIndex" and "logIndex" are respected when "afterLog.blockNumber" is equal to "fromBlock"', async ()=>{
1166
2615
  // Get a random log as reference
1167
- const targetBlockIndex = randomInt(numBlocks);
1168
- const targetTxIndex = randomInt(txsPerBlock);
1169
- const targetLogIndex = randomInt(numPublicLogs);
1170
- const afterLog = new LogId(targetBlockIndex + INITIAL_L2_BLOCK_NUM, targetTxIndex, targetLogIndex);
2616
+ const targetBlockIndex = randomInt(numBlocksForPublicLogs);
2617
+ const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
2618
+ const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
2619
+ const numLogsInTx = targetBlock.body.txEffects[targetTxIndex].publicLogs.length;
2620
+ const targetLogIndex = numLogsInTx > 0 ? randomInt(numLogsInTx) : 0;
2621
+ const targetBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
2622
+ const afterLog = new LogId(BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM), targetBlockHash, targetTxIndex, targetLogIndex);
1171
2623
  const response = await store.getPublicLogs({
1172
2624
  afterLog,
1173
2625
  fromBlock: afterLog.blockNumber
@@ -1186,6 +2638,36 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1186
2638
  }
1187
2639
  });
1188
2640
  });
2641
+ describe('getContractClassLogs', ()=>{
2642
+ let targetBlock;
2643
+ let expectedContractClassLog;
2644
+ beforeEach(async ()=>{
2645
+ await store.addCheckpoints(publishedCheckpoints);
2646
+ targetBlock = publishedCheckpoints[0].checkpoint.blocks[0];
2647
+ expectedContractClassLog = await ContractClassLog.random();
2648
+ targetBlock.body.txEffects.forEach((txEffect, index)=>{
2649
+ txEffect.contractClassLogs = index === 0 ? [
2650
+ expectedContractClassLog
2651
+ ] : [];
2652
+ });
2653
+ await store.addLogs([
2654
+ targetBlock
2655
+ ]);
2656
+ });
2657
+ it('returns block hash on contract class log ids', async ()=>{
2658
+ const result = await store.getContractClassLogs({
2659
+ fromBlock: targetBlock.number,
2660
+ toBlock: targetBlock.number + 1
2661
+ });
2662
+ expect(result.maxLogsHit).toBeFalsy();
2663
+ expect(result.logs).toHaveLength(1);
2664
+ const [{ id, log }] = result.logs;
2665
+ const expectedBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
2666
+ expect(id.blockHash.equals(expectedBlockHash)).toBe(true);
2667
+ expect(id.blockNumber).toEqual(targetBlock.number);
2668
+ expect(log).toEqual(expectedContractClassLog);
2669
+ });
2670
+ });
1189
2671
  describe('pendingChainValidationStatus', ()=>{
1190
2672
  it('should return undefined when no status is set', async ()=>{
1191
2673
  const status = await store.getPendingChainValidationStatus();
@@ -1207,7 +2689,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1207
2689
  EthAddress.random(),
1208
2690
  EthAddress.random()
1209
2691
  ],
1210
- epoch: 123n,
2692
+ epoch: EpochNumber(123),
1211
2693
  seed: 456n,
1212
2694
  attestors: [
1213
2695
  EthAddress.random()
@@ -1231,7 +2713,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1231
2713
  attestors: [
1232
2714
  EthAddress.random()
1233
2715
  ],
1234
- epoch: 789n,
2716
+ epoch: EpochNumber(789),
1235
2717
  seed: 101n,
1236
2718
  attestations: [
1237
2719
  CommitteeAttestation.random()
@@ -1253,7 +2735,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1253
2735
  committee: [
1254
2736
  EthAddress.random()
1255
2737
  ],
1256
- epoch: 999n,
2738
+ epoch: EpochNumber(999),
1257
2739
  seed: 888n,
1258
2740
  attestors: [
1259
2741
  EthAddress.random()
@@ -1273,7 +2755,7 @@ import { MessageStoreError } from './kv_archiver_store/message_store.js';
1273
2755
  valid: false,
1274
2756
  block: randomBlockInfo(4),
1275
2757
  committee: [],
1276
- epoch: 0n,
2758
+ epoch: EpochNumber(0),
1277
2759
  seed: 0n,
1278
2760
  attestors: [],
1279
2761
  attestations: [],