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