@aztec/archiver 0.0.1-commit.b655e406 → 0.0.1-commit.c7c42ec
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/archiver/archiver.d.ts +110 -83
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +672 -349
- package/dest/archiver/archiver_store.d.ts +100 -47
- 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 +1871 -389
- package/dest/archiver/config.d.ts +5 -4
- package/dest/archiver/config.d.ts.map +1 -1
- package/dest/archiver/config.js +15 -3
- 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/instrumentation.d.ts +5 -3
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/instrumentation.js +11 -0
- package/dest/archiver/kv_archiver_store/block_store.d.ts +51 -18
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +324 -87
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -2
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +2 -2
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +47 -57
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +65 -48
- package/dest/archiver/kv_archiver_store/log_store.d.ts +12 -16
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +149 -84
- package/dest/archiver/kv_archiver_store/message_store.d.ts +6 -5
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +15 -14
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts +3 -0
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts.map +1 -0
- package/dest/archiver/l1/bin/retrieve-calldata.js +149 -0
- package/dest/archiver/l1/calldata_retriever.d.ts +112 -0
- package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -0
- package/dest/archiver/l1/calldata_retriever.js +471 -0
- package/dest/archiver/l1/data_retrieval.d.ts +90 -0
- package/dest/archiver/l1/data_retrieval.d.ts.map +1 -0
- package/dest/archiver/l1/data_retrieval.js +331 -0
- package/dest/archiver/l1/debug_tx.d.ts +19 -0
- package/dest/archiver/l1/debug_tx.d.ts.map +1 -0
- package/dest/archiver/l1/debug_tx.js +73 -0
- package/dest/archiver/l1/spire_proposer.d.ts +70 -0
- package/dest/archiver/l1/spire_proposer.d.ts.map +1 -0
- package/dest/archiver/l1/spire_proposer.js +157 -0
- package/dest/archiver/l1/trace_tx.d.ts +97 -0
- package/dest/archiver/l1/trace_tx.d.ts.map +1 -0
- package/dest/archiver/l1/trace_tx.js +91 -0
- package/dest/archiver/l1/types.d.ts +12 -0
- package/dest/archiver/l1/types.d.ts.map +1 -0
- package/dest/archiver/l1/types.js +3 -0
- package/dest/archiver/l1/validate_trace.d.ts +29 -0
- package/dest/archiver/l1/validate_trace.d.ts.map +1 -0
- package/dest/archiver/l1/validate_trace.js +150 -0
- package/dest/archiver/structs/data_retrieval.d.ts +1 -1
- package/dest/archiver/structs/inbox_message.d.ts +4 -4
- package/dest/archiver/structs/inbox_message.d.ts.map +1 -1
- package/dest/archiver/structs/inbox_message.js +6 -5
- package/dest/archiver/structs/published.d.ts +2 -2
- package/dest/archiver/structs/published.d.ts.map +1 -1
- package/dest/archiver/validation.d.ts +10 -4
- package/dest/archiver/validation.d.ts.map +1 -1
- package/dest/archiver/validation.js +29 -21
- package/dest/factory.d.ts +2 -2
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +4 -3
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/rpc/index.d.ts +2 -2
- package/dest/test/index.d.ts +1 -1
- package/dest/test/mock_archiver.d.ts +16 -8
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_archiver.js +19 -14
- package/dest/test/mock_l1_to_l2_message_source.d.ts +7 -6
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
- package/dest/test/mock_l1_to_l2_message_source.js +10 -9
- package/dest/test/mock_l2_block_source.d.ts +23 -11
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +38 -24
- package/dest/test/mock_structs.d.ts +3 -2
- package/dest/test/mock_structs.d.ts.map +1 -1
- package/dest/test/mock_structs.js +9 -8
- package/package.json +18 -17
- package/src/archiver/archiver.ts +884 -449
- package/src/archiver/archiver_store.ts +113 -46
- package/src/archiver/archiver_store_test_suite.ts +1936 -356
- package/src/archiver/config.ts +20 -10
- package/src/archiver/errors.ts +64 -0
- package/src/archiver/index.ts +1 -1
- package/src/archiver/instrumentation.ts +16 -2
- package/src/archiver/kv_archiver_store/block_store.ts +442 -101
- package/src/archiver/kv_archiver_store/contract_class_store.ts +1 -1
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +1 -1
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +87 -71
- package/src/archiver/kv_archiver_store/log_store.ts +209 -99
- package/src/archiver/kv_archiver_store/message_store.ts +21 -18
- package/src/archiver/l1/README.md +98 -0
- package/src/archiver/l1/bin/retrieve-calldata.ts +182 -0
- package/src/archiver/l1/calldata_retriever.ts +641 -0
- package/src/archiver/l1/data_retrieval.ts +512 -0
- package/src/archiver/l1/debug_tx.ts +99 -0
- package/src/archiver/l1/spire_proposer.ts +160 -0
- package/src/archiver/l1/trace_tx.ts +128 -0
- package/src/archiver/l1/types.ts +13 -0
- package/src/archiver/l1/validate_trace.ts +211 -0
- package/src/archiver/structs/inbox_message.ts +8 -8
- package/src/archiver/structs/published.ts +1 -1
- package/src/archiver/validation.ts +52 -27
- package/src/factory.ts +4 -3
- package/src/index.ts +1 -1
- package/src/test/fixtures/debug_traceTransaction-multicall3.json +88 -0
- package/src/test/fixtures/debug_traceTransaction-multiplePropose.json +153 -0
- package/src/test/fixtures/debug_traceTransaction-proxied.json +122 -0
- package/src/test/fixtures/trace_transaction-multicall3.json +65 -0
- package/src/test/fixtures/trace_transaction-multiplePropose.json +319 -0
- package/src/test/fixtures/trace_transaction-proxied.json +128 -0
- package/src/test/fixtures/trace_transaction-randomRevert.json +216 -0
- package/src/test/mock_archiver.ts +22 -16
- package/src/test/mock_l1_to_l2_message_source.ts +10 -9
- package/src/test/mock_l2_block_source.ts +51 -30
- package/src/test/mock_structs.ts +10 -9
- package/dest/archiver/data_retrieval.d.ts +0 -79
- package/dest/archiver/data_retrieval.d.ts.map +0 -1
- package/dest/archiver/data_retrieval.js +0 -362
- package/src/archiver/data_retrieval.ts +0 -545
|
@@ -1,45 +1,58 @@
|
|
|
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';
|
|
6
8
|
import { makeTuple } from '@aztec/foundation/array';
|
|
9
|
+
import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
7
10
|
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
8
11
|
import { times, timesParallel } from '@aztec/foundation/collection';
|
|
9
|
-
import { randomInt } from '@aztec/foundation/crypto';
|
|
10
|
-
import { Fr } from '@aztec/foundation/
|
|
12
|
+
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
13
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
11
14
|
import { toArray } from '@aztec/foundation/iterable';
|
|
12
15
|
import { sleep } from '@aztec/foundation/sleep';
|
|
13
16
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
14
17
|
import {
|
|
18
|
+
CheckpointedL2Block,
|
|
15
19
|
CommitteeAttestation,
|
|
16
20
|
EthAddress,
|
|
17
|
-
L2Block,
|
|
18
21
|
L2BlockHash,
|
|
19
|
-
|
|
22
|
+
L2BlockNew,
|
|
20
23
|
type ValidateBlockResult,
|
|
21
24
|
randomBlockInfo,
|
|
22
|
-
wrapInBlock,
|
|
23
25
|
} from '@aztec/stdlib/block';
|
|
26
|
+
import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
24
27
|
import {
|
|
25
28
|
type ContractClassPublic,
|
|
26
29
|
type ContractInstanceWithAddress,
|
|
27
30
|
SerializableContractInstance,
|
|
28
31
|
computePublicBytecodeCommitment,
|
|
29
32
|
} from '@aztec/stdlib/contract';
|
|
30
|
-
import { LogId, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
|
|
33
|
+
import { ContractClassLog, LogId, PrivateLog, PublicLog, SiloedTag, Tag } from '@aztec/stdlib/logs';
|
|
31
34
|
import { InboxLeaf } from '@aztec/stdlib/messaging';
|
|
35
|
+
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
32
36
|
import {
|
|
33
37
|
makeContractClassPublic,
|
|
34
38
|
makeExecutablePrivateFunctionWithMembershipProof,
|
|
35
39
|
makeUtilityFunctionWithMembershipProof,
|
|
36
40
|
} from '@aztec/stdlib/testing';
|
|
37
41
|
import '@aztec/stdlib/testing/jest';
|
|
38
|
-
import {
|
|
42
|
+
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
43
|
+
import { type IndexedTxEffect, PartialStateReference, StateReference, TxEffect, TxHash } from '@aztec/stdlib/tx';
|
|
39
44
|
|
|
40
45
|
import { makeInboxMessage, makeInboxMessages } from '../test/mock_structs.js';
|
|
41
46
|
import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
|
|
42
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
BlockArchiveNotConsistentError,
|
|
49
|
+
BlockIndexNotSequentialError,
|
|
50
|
+
BlockNumberNotSequentialError,
|
|
51
|
+
CheckpointNumberNotConsistentError,
|
|
52
|
+
CheckpointNumberNotSequentialError,
|
|
53
|
+
InitialBlockNumberNotSequentialError,
|
|
54
|
+
InitialCheckpointNumberNotSequentialError,
|
|
55
|
+
} from './errors.js';
|
|
43
56
|
import { MessageStoreError } from './kv_archiver_store/message_store.js';
|
|
44
57
|
import type { InboxMessage } from './structs/inbox_message.js';
|
|
45
58
|
|
|
@@ -53,206 +66,1572 @@ export function describeArchiverDataStore(
|
|
|
53
66
|
) {
|
|
54
67
|
describe(testName, () => {
|
|
55
68
|
let store: ArchiverDataStore;
|
|
56
|
-
let
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
[1,
|
|
60
|
-
[10,
|
|
61
|
-
[
|
|
62
|
-
[2, 5, () => blocks.slice(1, 6)],
|
|
63
|
-
[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]],
|
|
64
75
|
];
|
|
65
76
|
|
|
66
77
|
const makeBlockHash = (blockNumber: number) => `0x${blockNumber.toString(16).padStart(64, '0')}`;
|
|
67
78
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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);
|
|
88
113
|
};
|
|
89
114
|
|
|
90
115
|
beforeEach(async () => {
|
|
91
116
|
store = await getStore();
|
|
92
|
-
|
|
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
|
+
}
|
|
93
135
|
});
|
|
94
136
|
|
|
95
|
-
describe('
|
|
96
|
-
it('returns success when adding
|
|
97
|
-
await expect(store.
|
|
137
|
+
describe('addCheckpoints', () => {
|
|
138
|
+
it('returns success when adding checkpoints', async () => {
|
|
139
|
+
await expect(store.addCheckpoints(publishedCheckpoints)).resolves.toBe(true);
|
|
98
140
|
});
|
|
99
141
|
|
|
100
|
-
it('
|
|
101
|
-
await store.
|
|
102
|
-
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
|
+
);
|
|
103
147
|
});
|
|
104
148
|
|
|
105
149
|
it('throws an error if the previous block does not exist in the store', async () => {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
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();
|
|
109
154
|
});
|
|
110
155
|
|
|
111
156
|
it('throws an error if there is a gap in the blocks being added', async () => {
|
|
112
|
-
const
|
|
113
|
-
await
|
|
114
|
-
|
|
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
|
+
);
|
|
115
313
|
});
|
|
116
314
|
});
|
|
117
315
|
|
|
118
|
-
describe('
|
|
119
|
-
it('unwinding
|
|
120
|
-
await store.
|
|
121
|
-
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;
|
|
122
322
|
|
|
123
|
-
|
|
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);
|
|
124
328
|
|
|
125
|
-
await store.
|
|
329
|
+
await store.unwindCheckpoints(checkpointNumber, 1);
|
|
126
330
|
|
|
127
|
-
expect(await store.
|
|
128
|
-
expect(
|
|
331
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(checkpointNumber - 1);
|
|
332
|
+
await expect(store.getCheckpointedBlock(lastBlockNumber)).resolves.toBeUndefined();
|
|
129
333
|
});
|
|
130
334
|
|
|
131
335
|
it('can unwind multiple empty blocks', async () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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);
|
|
135
350
|
|
|
136
|
-
await store.
|
|
137
|
-
expect(await store.
|
|
138
|
-
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([
|
|
354
|
+
1, 2, 3, 4, 5, 6, 7,
|
|
355
|
+
]);
|
|
139
356
|
});
|
|
140
357
|
|
|
141
|
-
it('refuses to unwind
|
|
142
|
-
await store.
|
|
143
|
-
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
|
+
);
|
|
144
363
|
});
|
|
145
364
|
|
|
146
365
|
it('unwound blocks and headers cannot be retrieved by hash or archive', async () => {
|
|
147
|
-
await store.
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
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;
|
|
151
371
|
|
|
152
372
|
// Verify block and header exist before unwinding
|
|
153
|
-
|
|
154
|
-
expect(
|
|
155
|
-
expect(
|
|
156
|
-
|
|
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);
|
|
157
380
|
|
|
158
|
-
|
|
159
|
-
|
|
381
|
+
const headerByHash = await store.getBlockHeaderByHash(blockHash);
|
|
382
|
+
expect(headerByHash).toBeDefined();
|
|
383
|
+
expect(headerByHash!.equals(lastBlock.header)).toBe(true);
|
|
384
|
+
|
|
385
|
+
const headerByArchive = await store.getBlockHeaderByArchive(archive);
|
|
386
|
+
expect(headerByArchive).toBeDefined();
|
|
387
|
+
expect(headerByArchive!.equals(lastBlock.header)).toBe(true);
|
|
388
|
+
|
|
389
|
+
// Unwind the checkpoint
|
|
390
|
+
await store.unwindCheckpoints(lastCheckpoint.checkpoint.number, 1);
|
|
160
391
|
|
|
161
392
|
// Verify neither block nor header can be retrieved after unwinding
|
|
162
|
-
expect(await store.
|
|
163
|
-
expect(await store.
|
|
393
|
+
expect(await store.getCheckpointedBlockByHash(blockHash)).toBeUndefined();
|
|
394
|
+
expect(await store.getCheckpointedBlockByArchive(archive)).toBeUndefined();
|
|
164
395
|
expect(await store.getBlockHeaderByHash(blockHash)).toBeUndefined();
|
|
165
396
|
expect(await store.getBlockHeaderByArchive(archive)).toBeUndefined();
|
|
166
397
|
});
|
|
167
398
|
});
|
|
168
399
|
|
|
169
|
-
describe('
|
|
170
|
-
|
|
171
|
-
|
|
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);
|
|
172
430
|
});
|
|
173
431
|
|
|
174
|
-
it
|
|
175
|
-
|
|
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);
|
|
176
469
|
});
|
|
177
470
|
|
|
178
|
-
it('
|
|
179
|
-
|
|
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
|
+
}
|
|
180
536
|
});
|
|
181
537
|
|
|
182
|
-
it('
|
|
183
|
-
|
|
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
|
+
}
|
|
184
578
|
});
|
|
185
579
|
|
|
186
|
-
it('
|
|
187
|
-
|
|
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,
|
|
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
|
+
}
|
|
188
599
|
});
|
|
189
600
|
|
|
190
|
-
it('
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
}
|
|
193
620
|
});
|
|
194
621
|
|
|
195
|
-
it('
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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,
|
|
199
626
|
);
|
|
200
|
-
|
|
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);
|
|
201
645
|
});
|
|
202
646
|
});
|
|
203
647
|
|
|
204
|
-
describe('
|
|
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,
|
|
1125
|
+
);
|
|
1126
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1127
|
+
|
|
1128
|
+
// Add block 3 for checkpoint 2, chaining archive roots
|
|
1129
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1130
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1131
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1132
|
+
indexWithinCheckpoint: 0,
|
|
1133
|
+
lastArchive: lastBlockArchive,
|
|
1134
|
+
});
|
|
1135
|
+
await expect(store.addBlocks([block3])).resolves.toBe(true);
|
|
1136
|
+
|
|
1137
|
+
// Add block 4 for the same checkpoint 2 in a separate call
|
|
1138
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1139
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1140
|
+
indexWithinCheckpoint: 1,
|
|
1141
|
+
lastArchive: block3.archive,
|
|
1142
|
+
});
|
|
1143
|
+
await expect(store.addBlocks([block4])).resolves.toBe(true);
|
|
1144
|
+
|
|
1145
|
+
expect(await store.getLatestBlockNumber()).toBe(4);
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
it('throws if adding blocks in separate calls with non-consecutive indexes', async () => {
|
|
1149
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1150
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1151
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1152
|
+
10,
|
|
1153
|
+
);
|
|
1154
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1155
|
+
|
|
1156
|
+
// Add block 3 for checkpoint 2, chaining archive roots
|
|
1157
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1158
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1159
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1160
|
+
indexWithinCheckpoint: 0,
|
|
1161
|
+
lastArchive: lastBlockArchive,
|
|
1162
|
+
});
|
|
1163
|
+
await expect(store.addBlocks([block3])).resolves.toBe(true);
|
|
1164
|
+
|
|
1165
|
+
// Add block 4 for the same checkpoint 2 in a separate call but with a missing index
|
|
1166
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1167
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1168
|
+
indexWithinCheckpoint: 2,
|
|
1169
|
+
lastArchive: block3.archive,
|
|
1170
|
+
});
|
|
1171
|
+
await expect(store.addBlocks([block4])).rejects.toThrow(BlockIndexNotSequentialError);
|
|
1172
|
+
|
|
1173
|
+
expect(await store.getLatestBlockNumber()).toBe(3);
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
it('throws if second batch of blocks has different checkpoint number than first batch', async () => {
|
|
1177
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1178
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1179
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1180
|
+
10,
|
|
1181
|
+
);
|
|
1182
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1183
|
+
|
|
1184
|
+
// Add block 3 for checkpoint 2, chaining archive roots
|
|
1185
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1186
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1187
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1188
|
+
indexWithinCheckpoint: 0,
|
|
1189
|
+
lastArchive: lastBlockArchive,
|
|
1190
|
+
});
|
|
1191
|
+
await store.addBlocks([block3]);
|
|
1192
|
+
|
|
1193
|
+
// Try to add block 4 for checkpoint 3 (should fail because current checkpoint is still 2)
|
|
1194
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1195
|
+
checkpointNumber: CheckpointNumber(3),
|
|
1196
|
+
indexWithinCheckpoint: 0,
|
|
1197
|
+
lastArchive: block3.archive,
|
|
1198
|
+
});
|
|
1199
|
+
await expect(store.addBlocks([block4])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
it('force option bypasses checkpoint number validation', async () => {
|
|
1203
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1204
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1205
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1206
|
+
10,
|
|
1207
|
+
);
|
|
1208
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1209
|
+
|
|
1210
|
+
// Add blocks with different checkpoint numbers using force option, chaining archive roots
|
|
1211
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1212
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1213
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1214
|
+
indexWithinCheckpoint: 0,
|
|
1215
|
+
lastArchive: lastBlockArchive,
|
|
1216
|
+
});
|
|
1217
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1218
|
+
checkpointNumber: CheckpointNumber(5),
|
|
1219
|
+
indexWithinCheckpoint: 0,
|
|
1220
|
+
lastArchive: block3.archive,
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
await expect(store.addBlocks([block3, block4], { force: true })).resolves.toBe(true);
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
it('force option bypasses blockindex number validation', async () => {
|
|
1227
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1228
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1229
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1230
|
+
10,
|
|
1231
|
+
);
|
|
1232
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1233
|
+
|
|
1234
|
+
// Add blocks with different checkpoint numbers using force option, chaining archive roots
|
|
1235
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1236
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1237
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1238
|
+
indexWithinCheckpoint: 0,
|
|
1239
|
+
lastArchive: lastBlockArchive,
|
|
1240
|
+
});
|
|
1241
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1242
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1243
|
+
indexWithinCheckpoint: 2,
|
|
1244
|
+
lastArchive: block3.archive,
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
await expect(store.addBlocks([block3, block4], { force: true })).resolves.toBe(true);
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
it('throws if adding blocks with non-consecutive archives', async () => {
|
|
1251
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1252
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1253
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1254
|
+
10,
|
|
1255
|
+
);
|
|
1256
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1257
|
+
|
|
1258
|
+
// Add block 3 for checkpoint 2 with incorrect archive
|
|
1259
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1260
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1261
|
+
indexWithinCheckpoint: 0,
|
|
1262
|
+
});
|
|
1263
|
+
await expect(store.addBlocks([block3])).rejects.toThrow(BlockArchiveNotConsistentError);
|
|
1264
|
+
|
|
1265
|
+
expect(await store.getLatestBlockNumber()).toBe(2);
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
it('throws if adding blocks with non-consecutive archives across calls', async () => {
|
|
1269
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1270
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1271
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1272
|
+
10,
|
|
1273
|
+
);
|
|
1274
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1275
|
+
|
|
1276
|
+
// Add block 3 for checkpoint 2 with correct archive
|
|
1277
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1278
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1279
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1280
|
+
indexWithinCheckpoint: 0,
|
|
1281
|
+
lastArchive: lastBlockArchive,
|
|
1282
|
+
});
|
|
1283
|
+
await expect(store.addBlocks([block3])).resolves.toBe(true);
|
|
1284
|
+
|
|
1285
|
+
// Add block 4 with incorrect archive (should fail)
|
|
1286
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1287
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1288
|
+
indexWithinCheckpoint: 1,
|
|
1289
|
+
lastArchive: AppendOnlyTreeSnapshot.random(),
|
|
1290
|
+
});
|
|
1291
|
+
await expect(store.addBlocks([block4])).rejects.toThrow(BlockArchiveNotConsistentError);
|
|
1292
|
+
|
|
1293
|
+
expect(await store.getLatestBlockNumber()).toBe(3);
|
|
1294
|
+
});
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
describe('getBlocksForCheckpoint', () => {
|
|
1298
|
+
it('returns blocks for a single-block checkpoint', async () => {
|
|
1299
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1300
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }),
|
|
1301
|
+
10,
|
|
1302
|
+
);
|
|
1303
|
+
await store.addCheckpoints([checkpoint]);
|
|
1304
|
+
|
|
1305
|
+
const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(1));
|
|
1306
|
+
expect(blocks).toBeDefined();
|
|
1307
|
+
expect(blocks!.length).toBe(1);
|
|
1308
|
+
expect(blocks![0].number).toBe(1);
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
it('returns all blocks for a multi-block checkpoint', async () => {
|
|
1312
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1313
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 4, startBlockNumber: 1 }),
|
|
1314
|
+
10,
|
|
1315
|
+
);
|
|
1316
|
+
await store.addCheckpoints([checkpoint]);
|
|
1317
|
+
|
|
1318
|
+
const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(1));
|
|
1319
|
+
expect(blocks).toBeDefined();
|
|
1320
|
+
expect(blocks!.length).toBe(4);
|
|
1321
|
+
expect(blocks!.map(b => b.number)).toEqual([1, 2, 3, 4]);
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
it('returns correct blocks for different checkpoints', async () => {
|
|
1325
|
+
// Create checkpoints with chained archive roots
|
|
1326
|
+
// Checkpoint 1: blocks 1-2
|
|
1327
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
1328
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1329
|
+
|
|
1330
|
+
// Checkpoint 2: blocks 3-5
|
|
1331
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1332
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1333
|
+
numBlocks: 3,
|
|
1334
|
+
startBlockNumber: 3,
|
|
1335
|
+
previousArchive: previousArchive1,
|
|
1336
|
+
});
|
|
1337
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1338
|
+
|
|
1339
|
+
// Checkpoint 3: blocks 6-7
|
|
1340
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1341
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1342
|
+
numBlocks: 2,
|
|
1343
|
+
startBlockNumber: 6,
|
|
1344
|
+
previousArchive: previousArchive2,
|
|
1345
|
+
});
|
|
1346
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1347
|
+
|
|
1348
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
1349
|
+
|
|
1350
|
+
const blocks1 = await store.getBlocksForCheckpoint(CheckpointNumber(1));
|
|
1351
|
+
expect(blocks1).toBeDefined();
|
|
1352
|
+
expect(blocks1!.map(b => b.number)).toEqual([1, 2]);
|
|
1353
|
+
|
|
1354
|
+
const blocks2 = await store.getBlocksForCheckpoint(CheckpointNumber(2));
|
|
1355
|
+
expect(blocks2).toBeDefined();
|
|
1356
|
+
expect(blocks2!.map(b => b.number)).toEqual([3, 4, 5]);
|
|
1357
|
+
|
|
1358
|
+
const blocks3 = await store.getBlocksForCheckpoint(CheckpointNumber(3));
|
|
1359
|
+
expect(blocks3).toBeDefined();
|
|
1360
|
+
expect(blocks3!.map(b => b.number)).toEqual([6, 7]);
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
it('returns undefined for non-existent checkpoint', async () => {
|
|
1364
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1365
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1366
|
+
10,
|
|
1367
|
+
);
|
|
1368
|
+
await store.addCheckpoints([checkpoint]);
|
|
1369
|
+
|
|
1370
|
+
const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(5));
|
|
1371
|
+
expect(blocks).toBeUndefined();
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
it('returns undefined when no checkpoints exist', async () => {
|
|
1375
|
+
const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(1));
|
|
1376
|
+
expect(blocks).toBeUndefined();
|
|
1377
|
+
});
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
describe('getRangeOfCheckpoints', () => {
|
|
1381
|
+
it('returns empty array when no checkpoints exist', async () => {
|
|
1382
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
|
|
1383
|
+
expect(checkpoints).toEqual([]);
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
it('returns single checkpoint', async () => {
|
|
1387
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1388
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1389
|
+
10,
|
|
1390
|
+
);
|
|
1391
|
+
await store.addCheckpoints([checkpoint]);
|
|
1392
|
+
|
|
1393
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
|
|
1394
|
+
expect(checkpoints.length).toBe(1);
|
|
1395
|
+
expect(checkpoints[0].checkpointNumber).toBe(1);
|
|
1396
|
+
expect(checkpoints[0].startBlock).toBe(1);
|
|
1397
|
+
expect(checkpoints[0].numBlocks).toBe(2);
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
it('returns multiple checkpoints in order', async () => {
|
|
1401
|
+
// Create checkpoints with chained archive roots
|
|
1402
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
1403
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1404
|
+
|
|
1405
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1406
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1407
|
+
numBlocks: 3,
|
|
1408
|
+
startBlockNumber: 3,
|
|
1409
|
+
previousArchive: previousArchive1,
|
|
1410
|
+
});
|
|
1411
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1412
|
+
|
|
1413
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1414
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1415
|
+
numBlocks: 1,
|
|
1416
|
+
startBlockNumber: 6,
|
|
1417
|
+
previousArchive: previousArchive2,
|
|
1418
|
+
});
|
|
1419
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1420
|
+
|
|
1421
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
1422
|
+
|
|
1423
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
|
|
1424
|
+
expect(checkpoints.length).toBe(3);
|
|
1425
|
+
expect(checkpoints.map(c => c.checkpointNumber)).toEqual([1, 2, 3]);
|
|
1426
|
+
expect(checkpoints.map(c => c.startBlock)).toEqual([1, 3, 6]);
|
|
1427
|
+
expect(checkpoints.map(c => c.numBlocks)).toEqual([2, 3, 1]);
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
it('respects the from parameter', async () => {
|
|
1431
|
+
// Create checkpoints with chained archive roots
|
|
1432
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
1433
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1434
|
+
|
|
1435
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1436
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1437
|
+
numBlocks: 2,
|
|
1438
|
+
startBlockNumber: 3,
|
|
1439
|
+
previousArchive: previousArchive1,
|
|
1440
|
+
});
|
|
1441
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1442
|
+
|
|
1443
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1444
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1445
|
+
numBlocks: 2,
|
|
1446
|
+
startBlockNumber: 5,
|
|
1447
|
+
previousArchive: previousArchive2,
|
|
1448
|
+
});
|
|
1449
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1450
|
+
|
|
1451
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
1452
|
+
|
|
1453
|
+
// Start from checkpoint 2
|
|
1454
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(2), 10);
|
|
1455
|
+
expect(checkpoints.length).toBe(2);
|
|
1456
|
+
expect(checkpoints.map(c => c.checkpointNumber)).toEqual([2, 3]);
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
it('respects the limit parameter', async () => {
|
|
1460
|
+
// Create checkpoints with chained archive roots
|
|
1461
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 });
|
|
1462
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1463
|
+
|
|
1464
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1465
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1466
|
+
numBlocks: 1,
|
|
1467
|
+
startBlockNumber: 2,
|
|
1468
|
+
previousArchive: previousArchive1,
|
|
1469
|
+
});
|
|
1470
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1471
|
+
|
|
1472
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1473
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1474
|
+
numBlocks: 1,
|
|
1475
|
+
startBlockNumber: 3,
|
|
1476
|
+
previousArchive: previousArchive2,
|
|
1477
|
+
});
|
|
1478
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1479
|
+
|
|
1480
|
+
const previousArchive3 = checkpoint3Cp.blocks.at(-1)!.archive;
|
|
1481
|
+
const checkpoint4Cp = await Checkpoint.random(CheckpointNumber(4), {
|
|
1482
|
+
numBlocks: 1,
|
|
1483
|
+
startBlockNumber: 4,
|
|
1484
|
+
previousArchive: previousArchive3,
|
|
1485
|
+
});
|
|
1486
|
+
const checkpoint4 = makePublishedCheckpoint(checkpoint4Cp, 13);
|
|
1487
|
+
|
|
1488
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3, checkpoint4]);
|
|
1489
|
+
|
|
1490
|
+
// Only get 2 checkpoints
|
|
1491
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 2);
|
|
1492
|
+
expect(checkpoints.length).toBe(2);
|
|
1493
|
+
expect(checkpoints.map(c => c.checkpointNumber)).toEqual([1, 2]);
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
it('returns correct checkpoint data including L1 info', async () => {
|
|
1497
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1498
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
|
|
1499
|
+
42,
|
|
1500
|
+
);
|
|
1501
|
+
await store.addCheckpoints([checkpoint]);
|
|
1502
|
+
|
|
1503
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 1);
|
|
1504
|
+
expect(checkpoints.length).toBe(1);
|
|
1505
|
+
|
|
1506
|
+
const data = checkpoints[0];
|
|
1507
|
+
expect(data.checkpointNumber).toBe(1);
|
|
1508
|
+
expect(data.startBlock).toBe(1);
|
|
1509
|
+
expect(data.numBlocks).toBe(3);
|
|
1510
|
+
expect(data.l1.blockNumber).toBe(42n);
|
|
1511
|
+
expect(data.header.equals(checkpoint.checkpoint.header)).toBe(true);
|
|
1512
|
+
expect(data.archive.equals(checkpoint.checkpoint.archive)).toBe(true);
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
it('returns empty array when from is beyond available checkpoints', async () => {
|
|
1516
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1517
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1518
|
+
10,
|
|
1519
|
+
);
|
|
1520
|
+
await store.addCheckpoints([checkpoint]);
|
|
1521
|
+
|
|
1522
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(5), 10);
|
|
1523
|
+
expect(checkpoints).toEqual([]);
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
it('works correctly after unwinding checkpoints', async () => {
|
|
1527
|
+
// Create checkpoints with chained archive roots
|
|
1528
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
1529
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1530
|
+
|
|
1531
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1532
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1533
|
+
numBlocks: 2,
|
|
1534
|
+
startBlockNumber: 3,
|
|
1535
|
+
previousArchive: previousArchive1,
|
|
1536
|
+
});
|
|
1537
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1538
|
+
|
|
1539
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1540
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1541
|
+
numBlocks: 2,
|
|
1542
|
+
startBlockNumber: 5,
|
|
1543
|
+
previousArchive: previousArchive2,
|
|
1544
|
+
});
|
|
1545
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1546
|
+
|
|
1547
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
1548
|
+
|
|
1549
|
+
// Unwind checkpoint 3
|
|
1550
|
+
await store.unwindCheckpoints(CheckpointNumber(3), 1);
|
|
1551
|
+
|
|
1552
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
|
|
1553
|
+
expect(checkpoints.length).toBe(2);
|
|
1554
|
+
expect(checkpoints.map(c => c.checkpointNumber)).toEqual([1, 2]);
|
|
1555
|
+
});
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
describe('getCheckpointedBlock', () => {
|
|
205
1559
|
beforeEach(async () => {
|
|
206
|
-
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);
|
|
207
1584
|
});
|
|
208
1585
|
|
|
209
1586
|
it('retrieves a block by its hash', async () => {
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
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);
|
|
213
1591
|
|
|
214
1592
|
expect(retrievedBlock).toBeDefined();
|
|
215
|
-
|
|
1593
|
+
expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
|
|
216
1594
|
});
|
|
217
1595
|
|
|
218
1596
|
it('returns undefined for non-existent block hash', async () => {
|
|
219
1597
|
const nonExistentHash = Fr.random();
|
|
220
|
-
await expect(store.
|
|
1598
|
+
await expect(store.getCheckpointedBlockByHash(nonExistentHash)).resolves.toBeUndefined();
|
|
221
1599
|
});
|
|
222
1600
|
});
|
|
223
1601
|
|
|
224
|
-
describe('
|
|
1602
|
+
describe('getCheckpointedBlockByArchive', () => {
|
|
225
1603
|
beforeEach(async () => {
|
|
226
|
-
await store.
|
|
1604
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
227
1605
|
});
|
|
228
1606
|
|
|
229
1607
|
it('retrieves a block by its archive root', async () => {
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
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);
|
|
233
1612
|
|
|
234
1613
|
expect(retrievedBlock).toBeDefined();
|
|
235
|
-
|
|
1614
|
+
expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
|
|
236
1615
|
});
|
|
237
1616
|
|
|
238
1617
|
it('returns undefined for non-existent archive root', async () => {
|
|
239
1618
|
const nonExistentArchive = Fr.random();
|
|
240
|
-
await expect(store.
|
|
1619
|
+
await expect(store.getCheckpointedBlockByArchive(nonExistentArchive)).resolves.toBeUndefined();
|
|
241
1620
|
});
|
|
242
1621
|
});
|
|
243
1622
|
|
|
244
1623
|
describe('getBlockHeaderByHash', () => {
|
|
245
1624
|
beforeEach(async () => {
|
|
246
|
-
await store.
|
|
1625
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
247
1626
|
});
|
|
248
1627
|
|
|
249
1628
|
it('retrieves a block header by its hash', async () => {
|
|
250
|
-
const expectedBlock =
|
|
251
|
-
const blockHash = await expectedBlock.
|
|
1629
|
+
const expectedBlock = publishedCheckpoints[7].checkpoint.blocks[0];
|
|
1630
|
+
const blockHash = await expectedBlock.header.hash();
|
|
252
1631
|
const retrievedHeader = await store.getBlockHeaderByHash(blockHash);
|
|
253
1632
|
|
|
254
1633
|
expect(retrievedHeader).toBeDefined();
|
|
255
|
-
expect(retrievedHeader!.equals(expectedBlock.
|
|
1634
|
+
expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true);
|
|
256
1635
|
});
|
|
257
1636
|
|
|
258
1637
|
it('returns undefined for non-existent block hash', async () => {
|
|
@@ -263,16 +1642,16 @@ export function describeArchiverDataStore(
|
|
|
263
1642
|
|
|
264
1643
|
describe('getBlockHeaderByArchive', () => {
|
|
265
1644
|
beforeEach(async () => {
|
|
266
|
-
await store.
|
|
1645
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
267
1646
|
});
|
|
268
1647
|
|
|
269
1648
|
it('retrieves a block header by its archive root', async () => {
|
|
270
|
-
const expectedBlock =
|
|
271
|
-
const archive = expectedBlock.
|
|
1649
|
+
const expectedBlock = publishedCheckpoints[2].checkpoint.blocks[0];
|
|
1650
|
+
const archive = expectedBlock.archive.root;
|
|
272
1651
|
const retrievedHeader = await store.getBlockHeaderByArchive(archive);
|
|
273
1652
|
|
|
274
1653
|
expect(retrievedHeader).toBeDefined();
|
|
275
|
-
expect(retrievedHeader!.equals(expectedBlock.
|
|
1654
|
+
expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true);
|
|
276
1655
|
});
|
|
277
1656
|
|
|
278
1657
|
it('returns undefined for non-existent archive root', async () => {
|
|
@@ -281,14 +1660,16 @@ export function describeArchiverDataStore(
|
|
|
281
1660
|
});
|
|
282
1661
|
});
|
|
283
1662
|
|
|
284
|
-
describe('
|
|
285
|
-
it('returns the
|
|
286
|
-
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);
|
|
287
1666
|
});
|
|
288
1667
|
|
|
289
|
-
it(
|
|
290
|
-
await store.
|
|
291
|
-
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
|
+
);
|
|
292
1673
|
});
|
|
293
1674
|
});
|
|
294
1675
|
|
|
@@ -301,7 +1682,7 @@ export function describeArchiverDataStore(
|
|
|
301
1682
|
});
|
|
302
1683
|
|
|
303
1684
|
it('returns the L1 block number in which the most recent L2 block was published', async () => {
|
|
304
|
-
await store.
|
|
1685
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
305
1686
|
await expect(store.getSynchPoint()).resolves.toEqual({
|
|
306
1687
|
blocksSynchedTo: 19n,
|
|
307
1688
|
messagesSynchedTo: undefined,
|
|
@@ -334,61 +1715,47 @@ export function describeArchiverDataStore(
|
|
|
334
1715
|
|
|
335
1716
|
describe('addLogs', () => {
|
|
336
1717
|
it('adds private & public logs', async () => {
|
|
337
|
-
const
|
|
338
|
-
await
|
|
1718
|
+
const checkpoint = publishedCheckpoints[0];
|
|
1719
|
+
await store.addCheckpoints([checkpoint]);
|
|
1720
|
+
await expect(store.addLogs(checkpoint.checkpoint.blocks)).resolves.toEqual(true);
|
|
339
1721
|
});
|
|
340
1722
|
});
|
|
341
1723
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
await expect(store.addLogs([block])).resolves.toEqual(true);
|
|
347
|
-
|
|
348
|
-
expect((await store.getPrivateLogs(1, 1)).length).toEqual(
|
|
349
|
-
block.body.txEffects.map(txEffect => txEffect.privateLogs).flat().length,
|
|
350
|
-
);
|
|
351
|
-
expect((await store.getPublicLogs({ fromBlock: 1 })).logs.length).toEqual(
|
|
352
|
-
block.body.txEffects.map(txEffect => txEffect.publicLogs).flat().length,
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
// This one is a pain for memory as we would never want to just delete memory in the middle.
|
|
356
|
-
await store.deleteLogs([block]);
|
|
1724
|
+
it('deleteLogs', async () => {
|
|
1725
|
+
const block = publishedCheckpoints[0].checkpoint.blocks[0];
|
|
1726
|
+
await store.addBlocks([block]);
|
|
1727
|
+
await expect(store.addLogs([block])).resolves.toEqual(true);
|
|
357
1728
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
});
|
|
1729
|
+
expect((await store.getPublicLogs({ fromBlock: BlockNumber(1) })).logs.length).toEqual(
|
|
1730
|
+
block.body.txEffects.map(txEffect => txEffect.publicLogs).flat().length,
|
|
1731
|
+
);
|
|
362
1732
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const block = blocks[0].block;
|
|
366
|
-
await store.addBlocks([blocks[0]]);
|
|
367
|
-
await store.addLogs([block]);
|
|
1733
|
+
// This one is a pain for memory as we would never want to just delete memory in the middle.
|
|
1734
|
+
await store.deleteLogs([block]);
|
|
368
1735
|
|
|
369
|
-
|
|
370
|
-
expect(privateLogs).toEqual(block.body.txEffects.map(txEffect => txEffect.privateLogs).flat());
|
|
371
|
-
});
|
|
1736
|
+
expect((await store.getPublicLogs({ fromBlock: BlockNumber(1) })).logs.length).toEqual(0);
|
|
372
1737
|
});
|
|
373
1738
|
|
|
374
1739
|
describe('getTxEffect', () => {
|
|
1740
|
+
const getBlock = (i: number) => publishedCheckpoints[i].checkpoint.blocks[0];
|
|
1741
|
+
|
|
375
1742
|
beforeEach(async () => {
|
|
376
|
-
await store.addLogs(
|
|
377
|
-
await store.
|
|
1743
|
+
await store.addLogs(publishedCheckpoints.flatMap(x => x.checkpoint.blocks));
|
|
1744
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
378
1745
|
});
|
|
379
1746
|
|
|
380
1747
|
it.each([
|
|
381
|
-
() => ({ data:
|
|
382
|
-
() => ({ data:
|
|
383
|
-
() => ({ data:
|
|
384
|
-
() => ({ data:
|
|
385
|
-
() => ({ 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 }),
|
|
386
1753
|
])('retrieves a previously stored transaction', async getExpectedTx => {
|
|
387
1754
|
const { data, block, txIndexInBlock } = getExpectedTx();
|
|
388
1755
|
const expectedTx: IndexedTxEffect = {
|
|
389
1756
|
data,
|
|
390
1757
|
l2BlockNumber: block.number,
|
|
391
|
-
l2BlockHash: L2BlockHash.fromField(await block.hash()),
|
|
1758
|
+
l2BlockHash: L2BlockHash.fromField(await block.header.hash()),
|
|
392
1759
|
txIndexInBlock,
|
|
393
1760
|
};
|
|
394
1761
|
const actualTx = await store.getTxEffect(data.txHash);
|
|
@@ -400,16 +1767,16 @@ export function describeArchiverDataStore(
|
|
|
400
1767
|
});
|
|
401
1768
|
|
|
402
1769
|
it.each([
|
|
403
|
-
() =>
|
|
404
|
-
() =>
|
|
405
|
-
() =>
|
|
406
|
-
() =>
|
|
407
|
-
() =>
|
|
408
|
-
])('tries to retrieves a previously stored transaction after deleted', async
|
|
409
|
-
await store.
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
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);
|
|
413
1780
|
expect(actualTx).toEqual(undefined);
|
|
414
1781
|
});
|
|
415
1782
|
|
|
@@ -418,22 +1785,22 @@ export function describeArchiverDataStore(
|
|
|
418
1785
|
});
|
|
419
1786
|
|
|
420
1787
|
it('does not fail if the block is unwound while requesting a tx', async () => {
|
|
421
|
-
const
|
|
1788
|
+
const txEffect = getBlock(1).body.txEffects[0];
|
|
422
1789
|
let done = false;
|
|
423
1790
|
void (async () => {
|
|
424
1791
|
while (!done) {
|
|
425
|
-
void store.getTxEffect(
|
|
1792
|
+
void store.getTxEffect(txEffect.txHash);
|
|
426
1793
|
await sleep(1);
|
|
427
1794
|
}
|
|
428
1795
|
})();
|
|
429
|
-
await store.
|
|
1796
|
+
await store.unwindCheckpoints(CheckpointNumber(publishedCheckpoints.length), publishedCheckpoints.length);
|
|
430
1797
|
done = true;
|
|
431
|
-
expect(await store.getTxEffect(
|
|
1798
|
+
expect(await store.getTxEffect(txEffect.txHash)).toEqual(undefined);
|
|
432
1799
|
});
|
|
433
1800
|
});
|
|
434
1801
|
|
|
435
1802
|
describe('L1 to L2 Messages', () => {
|
|
436
|
-
const
|
|
1803
|
+
const initialCheckpointNumber = CheckpointNumber(13);
|
|
437
1804
|
|
|
438
1805
|
const checkMessages = async (msgs: InboxMessage[]) => {
|
|
439
1806
|
expect(await store.getLastL1ToL2Message()).toEqual(msgs.at(-1));
|
|
@@ -441,43 +1808,50 @@ export function describeArchiverDataStore(
|
|
|
441
1808
|
expect(await store.getTotalL1ToL2MessageCount()).toEqual(BigInt(msgs.length));
|
|
442
1809
|
};
|
|
443
1810
|
|
|
444
|
-
const makeInboxMessagesWithFullBlocks = (
|
|
1811
|
+
const makeInboxMessagesWithFullBlocks = (
|
|
1812
|
+
blockCount: number,
|
|
1813
|
+
opts: { initialCheckpointNumber?: CheckpointNumber } = {},
|
|
1814
|
+
) =>
|
|
445
1815
|
makeInboxMessages(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * blockCount, {
|
|
446
1816
|
overrideFn: (msg, i) => {
|
|
447
|
-
const
|
|
448
|
-
(opts.
|
|
1817
|
+
const checkpointNumber = CheckpointNumber(
|
|
1818
|
+
(opts.initialCheckpointNumber ?? initialCheckpointNumber) +
|
|
1819
|
+
Math.floor(i / NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP),
|
|
1820
|
+
);
|
|
449
1821
|
const index =
|
|
450
|
-
InboxLeaf.
|
|
451
|
-
return { ...msg,
|
|
1822
|
+
InboxLeaf.smallestIndexForCheckpoint(checkpointNumber) + BigInt(i % NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1823
|
+
return { ...msg, checkpointNumber, index };
|
|
452
1824
|
},
|
|
453
1825
|
});
|
|
454
1826
|
|
|
455
1827
|
it('stores first message ever', async () => {
|
|
456
|
-
const msg = makeInboxMessage(Buffer16.ZERO, { index: 0n,
|
|
1828
|
+
const msg = makeInboxMessage(Buffer16.ZERO, { index: 0n, checkpointNumber: CheckpointNumber(1) });
|
|
457
1829
|
await store.addL1ToL2Messages([msg]);
|
|
458
1830
|
|
|
459
1831
|
await checkMessages([msg]);
|
|
460
|
-
expect(await store.getL1ToL2Messages(1)).toEqual([msg.leaf]);
|
|
1832
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toEqual([msg.leaf]);
|
|
461
1833
|
});
|
|
462
1834
|
|
|
463
1835
|
it('stores single message', async () => {
|
|
464
|
-
const msg = makeInboxMessage(Buffer16.ZERO, {
|
|
1836
|
+
const msg = makeInboxMessage(Buffer16.ZERO, { checkpointNumber: CheckpointNumber(2) });
|
|
465
1837
|
await store.addL1ToL2Messages([msg]);
|
|
466
1838
|
|
|
467
1839
|
await checkMessages([msg]);
|
|
468
|
-
expect(await store.getL1ToL2Messages(2)).toEqual([msg.leaf]);
|
|
1840
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toEqual([msg.leaf]);
|
|
469
1841
|
});
|
|
470
1842
|
|
|
471
1843
|
it('stores and returns messages across different blocks', async () => {
|
|
472
|
-
const msgs = makeInboxMessages(5, {
|
|
1844
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber });
|
|
473
1845
|
await store.addL1ToL2Messages(msgs);
|
|
474
1846
|
|
|
475
1847
|
await checkMessages(msgs);
|
|
476
|
-
expect(await store.getL1ToL2Messages(
|
|
1848
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 2))).toEqual(
|
|
1849
|
+
[msgs[2]].map(m => m.leaf),
|
|
1850
|
+
);
|
|
477
1851
|
});
|
|
478
1852
|
|
|
479
1853
|
it('stores the same messages again', async () => {
|
|
480
|
-
const msgs = makeInboxMessages(5, {
|
|
1854
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber });
|
|
481
1855
|
await store.addL1ToL2Messages(msgs);
|
|
482
1856
|
await store.addL1ToL2Messages(msgs.slice(2));
|
|
483
1857
|
|
|
@@ -485,26 +1859,29 @@ export function describeArchiverDataStore(
|
|
|
485
1859
|
});
|
|
486
1860
|
|
|
487
1861
|
it('stores and returns messages across different blocks with gaps', async () => {
|
|
488
|
-
const msgs1 = makeInboxMessages(3, {
|
|
489
|
-
const msgs2 = makeInboxMessages(3, {
|
|
1862
|
+
const msgs1 = makeInboxMessages(3, { initialCheckpointNumber: CheckpointNumber(1) });
|
|
1863
|
+
const msgs2 = makeInboxMessages(3, {
|
|
1864
|
+
initialCheckpointNumber: CheckpointNumber(20),
|
|
1865
|
+
initialHash: msgs1.at(-1)!.rollingHash,
|
|
1866
|
+
});
|
|
490
1867
|
|
|
491
1868
|
await store.addL1ToL2Messages(msgs1);
|
|
492
1869
|
await store.addL1ToL2Messages(msgs2);
|
|
493
1870
|
|
|
494
1871
|
await checkMessages([...msgs1, ...msgs2]);
|
|
495
1872
|
|
|
496
|
-
expect(await store.getL1ToL2Messages(1)).toEqual([msgs1[0].leaf]);
|
|
497
|
-
expect(await store.getL1ToL2Messages(4)).toEqual([]);
|
|
498
|
-
expect(await store.getL1ToL2Messages(20)).toEqual([msgs2[0].leaf]);
|
|
499
|
-
expect(await store.getL1ToL2Messages(24)).toEqual([]);
|
|
1873
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toEqual([msgs1[0].leaf]);
|
|
1874
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(4))).toEqual([]);
|
|
1875
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(20))).toEqual([msgs2[0].leaf]);
|
|
1876
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(24))).toEqual([]);
|
|
500
1877
|
});
|
|
501
1878
|
|
|
502
1879
|
it('stores and returns messages with block numbers larger than a byte', async () => {
|
|
503
|
-
const msgs = makeInboxMessages(5, {
|
|
1880
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber: CheckpointNumber(1000) });
|
|
504
1881
|
await store.addL1ToL2Messages(msgs);
|
|
505
1882
|
|
|
506
1883
|
await checkMessages(msgs);
|
|
507
|
-
expect(await store.getL1ToL2Messages(1002)).toEqual([msgs[2]].map(m => m.leaf));
|
|
1884
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1002))).toEqual([msgs[2]].map(m => m.leaf));
|
|
508
1885
|
});
|
|
509
1886
|
|
|
510
1887
|
it('stores and returns multiple messages per block', async () => {
|
|
@@ -512,7 +1889,7 @@ export function describeArchiverDataStore(
|
|
|
512
1889
|
await store.addL1ToL2Messages(msgs);
|
|
513
1890
|
|
|
514
1891
|
await checkMessages(msgs);
|
|
515
|
-
const blockMessages = await store.getL1ToL2Messages(
|
|
1892
|
+
const blockMessages = await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 1));
|
|
516
1893
|
expect(blockMessages).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
517
1894
|
expect(blockMessages).toEqual(
|
|
518
1895
|
msgs.slice(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2).map(m => m.leaf),
|
|
@@ -520,17 +1897,21 @@ export function describeArchiverDataStore(
|
|
|
520
1897
|
});
|
|
521
1898
|
|
|
522
1899
|
it('stores messages in multiple operations', async () => {
|
|
523
|
-
const msgs = makeInboxMessages(20, {
|
|
1900
|
+
const msgs = makeInboxMessages(20, { initialCheckpointNumber });
|
|
524
1901
|
await store.addL1ToL2Messages(msgs.slice(0, 10));
|
|
525
1902
|
await store.addL1ToL2Messages(msgs.slice(10, 20));
|
|
526
1903
|
|
|
527
|
-
expect(await store.getL1ToL2Messages(
|
|
528
|
-
|
|
1904
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 2))).toEqual(
|
|
1905
|
+
[msgs[2]].map(m => m.leaf),
|
|
1906
|
+
);
|
|
1907
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 12))).toEqual(
|
|
1908
|
+
[msgs[12]].map(m => m.leaf),
|
|
1909
|
+
);
|
|
529
1910
|
await checkMessages(msgs);
|
|
530
1911
|
});
|
|
531
1912
|
|
|
532
1913
|
it('iterates over messages from start index', async () => {
|
|
533
|
-
const msgs = makeInboxMessages(10, {
|
|
1914
|
+
const msgs = makeInboxMessages(10, { initialCheckpointNumber });
|
|
534
1915
|
await store.addL1ToL2Messages(msgs);
|
|
535
1916
|
|
|
536
1917
|
const iterated = await toArray(store.iterateL1ToL2Messages({ start: msgs[3].index }));
|
|
@@ -538,8 +1919,9 @@ export function describeArchiverDataStore(
|
|
|
538
1919
|
});
|
|
539
1920
|
|
|
540
1921
|
it('iterates over messages in reverse', async () => {
|
|
541
|
-
const msgs = makeInboxMessages(10, {
|
|
1922
|
+
const msgs = makeInboxMessages(10, { initialCheckpointNumber });
|
|
542
1923
|
await store.addL1ToL2Messages(msgs);
|
|
1924
|
+
initialCheckpointNumber;
|
|
543
1925
|
|
|
544
1926
|
const iterated = await toArray(store.iterateL1ToL2Messages({ reverse: true, end: msgs[3].index }));
|
|
545
1927
|
expect(iterated).toEqual(msgs.slice(0, 4).reverse());
|
|
@@ -551,8 +1933,8 @@ export function describeArchiverDataStore(
|
|
|
551
1933
|
});
|
|
552
1934
|
|
|
553
1935
|
it('throws if block number for the first message is out of order', async () => {
|
|
554
|
-
const msgs = makeInboxMessages(4, {
|
|
555
|
-
msgs[2].
|
|
1936
|
+
const msgs = makeInboxMessages(4, { initialCheckpointNumber });
|
|
1937
|
+
msgs[2].checkpointNumber = CheckpointNumber(initialCheckpointNumber - 1);
|
|
556
1938
|
await store.addL1ToL2Messages(msgs.slice(0, 2));
|
|
557
1939
|
await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
|
|
558
1940
|
});
|
|
@@ -566,28 +1948,28 @@ export function describeArchiverDataStore(
|
|
|
566
1948
|
it('throws if rolling hash for first message is not correct', async () => {
|
|
567
1949
|
const msgs = makeInboxMessages(4);
|
|
568
1950
|
msgs[2].rollingHash = Buffer16.random();
|
|
569
|
-
await store.addL1ToL2Messages(msgs.slice(0, 2));
|
|
1951
|
+
await store.addL1ToL2Messages(msgs.slice(0, CheckpointNumber(2)));
|
|
570
1952
|
await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
|
|
571
1953
|
});
|
|
572
1954
|
|
|
573
1955
|
it('throws if index is not in the correct range', async () => {
|
|
574
|
-
const msgs = makeInboxMessages(5, {
|
|
1956
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber });
|
|
575
1957
|
msgs.at(-1)!.index += 100n;
|
|
576
1958
|
await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
|
|
577
1959
|
});
|
|
578
1960
|
|
|
579
1961
|
it('throws if first index in block has gaps', async () => {
|
|
580
|
-
const msgs = makeInboxMessages(4, {
|
|
1962
|
+
const msgs = makeInboxMessages(4, { initialCheckpointNumber });
|
|
581
1963
|
msgs[2].index++;
|
|
582
1964
|
await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
|
|
583
1965
|
});
|
|
584
1966
|
|
|
585
1967
|
it('throws if index does not follow previous one', async () => {
|
|
586
1968
|
const msgs = makeInboxMessages(2, {
|
|
587
|
-
|
|
1969
|
+
initialCheckpointNumber,
|
|
588
1970
|
overrideFn: (msg, i) => ({
|
|
589
1971
|
...msg,
|
|
590
|
-
|
|
1972
|
+
checkpointNumber: CheckpointNumber(2),
|
|
591
1973
|
index: BigInt(i + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2),
|
|
592
1974
|
}),
|
|
593
1975
|
});
|
|
@@ -596,28 +1978,28 @@ export function describeArchiverDataStore(
|
|
|
596
1978
|
});
|
|
597
1979
|
|
|
598
1980
|
it('removes messages up to the given block number', async () => {
|
|
599
|
-
const msgs = makeInboxMessagesWithFullBlocks(4, {
|
|
1981
|
+
const msgs = makeInboxMessagesWithFullBlocks(4, { initialCheckpointNumber: CheckpointNumber(1) });
|
|
600
1982
|
|
|
601
1983
|
await store.addL1ToL2Messages(msgs);
|
|
602
1984
|
await checkMessages(msgs);
|
|
603
1985
|
|
|
604
|
-
expect(await store.getL1ToL2Messages(1)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
605
|
-
expect(await store.getL1ToL2Messages(2)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
606
|
-
expect(await store.getL1ToL2Messages(3)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
607
|
-
expect(await store.getL1ToL2Messages(4)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1986
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1987
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1988
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(3))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1989
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(4))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
608
1990
|
|
|
609
|
-
await store.
|
|
1991
|
+
await store.rollbackL1ToL2MessagesToCheckpoint(CheckpointNumber(2));
|
|
610
1992
|
|
|
611
|
-
expect(await store.getL1ToL2Messages(1)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
612
|
-
expect(await store.getL1ToL2Messages(2)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
613
|
-
expect(await store.getL1ToL2Messages(3)).toHaveLength(0);
|
|
614
|
-
expect(await store.getL1ToL2Messages(4)).toHaveLength(0);
|
|
1993
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1994
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1995
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(3))).toHaveLength(0);
|
|
1996
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(4))).toHaveLength(0);
|
|
615
1997
|
|
|
616
1998
|
await checkMessages(msgs.slice(0, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2));
|
|
617
1999
|
});
|
|
618
2000
|
|
|
619
2001
|
it('removes messages starting with the given index', async () => {
|
|
620
|
-
const msgs = makeInboxMessagesWithFullBlocks(4, {
|
|
2002
|
+
const msgs = makeInboxMessagesWithFullBlocks(4, { initialCheckpointNumber: CheckpointNumber(1) });
|
|
621
2003
|
await store.addL1ToL2Messages(msgs);
|
|
622
2004
|
|
|
623
2005
|
await store.removeL1ToL2Messages(msgs[13].index);
|
|
@@ -637,7 +2019,7 @@ export function describeArchiverDataStore(
|
|
|
637
2019
|
originalContractClassId: classId,
|
|
638
2020
|
});
|
|
639
2021
|
contractInstance = { ...randomInstance, address: await AztecAddress.random() };
|
|
640
|
-
await store.addContractInstances([contractInstance], blockNum);
|
|
2022
|
+
await store.addContractInstances([contractInstance], BlockNumber(blockNum));
|
|
641
2023
|
});
|
|
642
2024
|
|
|
643
2025
|
it('returns previously stored contract instances', async () => {
|
|
@@ -651,7 +2033,7 @@ export function describeArchiverDataStore(
|
|
|
651
2033
|
});
|
|
652
2034
|
|
|
653
2035
|
it('returns undefined if previously stored contract instances was deleted', async () => {
|
|
654
|
-
await store.deleteContractInstances([contractInstance], blockNum);
|
|
2036
|
+
await store.deleteContractInstances([contractInstance], BlockNumber(blockNum));
|
|
655
2037
|
await expect(store.getContractInstance(contractInstance.address, timestamp)).resolves.toBeUndefined();
|
|
656
2038
|
});
|
|
657
2039
|
});
|
|
@@ -670,7 +2052,7 @@ export function describeArchiverDataStore(
|
|
|
670
2052
|
originalContractClassId: classId,
|
|
671
2053
|
});
|
|
672
2054
|
contractInstance = { ...randomInstance, address: await AztecAddress.random() };
|
|
673
|
-
await store.addContractInstances([contractInstance], 1);
|
|
2055
|
+
await store.addContractInstances([contractInstance], BlockNumber(1));
|
|
674
2056
|
await store.addContractInstanceUpdates(
|
|
675
2057
|
[
|
|
676
2058
|
{
|
|
@@ -712,7 +2094,7 @@ export function describeArchiverDataStore(
|
|
|
712
2094
|
...randomInstance,
|
|
713
2095
|
address: await AztecAddress.random(),
|
|
714
2096
|
};
|
|
715
|
-
await store.addContractInstances([otherContractInstance], 1);
|
|
2097
|
+
await store.addContractInstances([otherContractInstance], BlockNumber(1));
|
|
716
2098
|
|
|
717
2099
|
const fetchedInstance = await store.getContractInstance(otherContractInstance.address, timestampOfChange + 1n);
|
|
718
2100
|
expect(fetchedInstance?.originalContractClassId).toEqual(otherClassId);
|
|
@@ -730,7 +2112,7 @@ export function describeArchiverDataStore(
|
|
|
730
2112
|
...randomInstance,
|
|
731
2113
|
address: await AztecAddress.random(),
|
|
732
2114
|
};
|
|
733
|
-
await store.addContractInstances([otherContractInstance], 1);
|
|
2115
|
+
await store.addContractInstances([otherContractInstance], BlockNumber(1));
|
|
734
2116
|
await store.addContractInstanceUpdates(
|
|
735
2117
|
[
|
|
736
2118
|
{
|
|
@@ -758,7 +2140,7 @@ export function describeArchiverDataStore(
|
|
|
758
2140
|
await store.addContractClasses(
|
|
759
2141
|
[contractClass],
|
|
760
2142
|
[await computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
761
|
-
blockNum,
|
|
2143
|
+
BlockNumber(blockNum),
|
|
762
2144
|
);
|
|
763
2145
|
});
|
|
764
2146
|
|
|
@@ -767,7 +2149,7 @@ export function describeArchiverDataStore(
|
|
|
767
2149
|
});
|
|
768
2150
|
|
|
769
2151
|
it('returns undefined if the initial deployed contract class was deleted', async () => {
|
|
770
|
-
await store.deleteContractClasses([contractClass], blockNum);
|
|
2152
|
+
await store.deleteContractClasses([contractClass], BlockNumber(blockNum));
|
|
771
2153
|
await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined();
|
|
772
2154
|
});
|
|
773
2155
|
|
|
@@ -775,9 +2157,9 @@ export function describeArchiverDataStore(
|
|
|
775
2157
|
await store.addContractClasses(
|
|
776
2158
|
[contractClass],
|
|
777
2159
|
[await computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
778
|
-
blockNum + 1,
|
|
2160
|
+
BlockNumber(blockNum + 1),
|
|
779
2161
|
);
|
|
780
|
-
await store.deleteContractClasses([contractClass], blockNum + 1);
|
|
2162
|
+
await store.deleteContractClasses([contractClass], BlockNumber(blockNum + 1));
|
|
781
2163
|
await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
|
|
782
2164
|
});
|
|
783
2165
|
|
|
@@ -816,154 +2198,269 @@ export function describeArchiverDataStore(
|
|
|
816
2198
|
});
|
|
817
2199
|
});
|
|
818
2200
|
|
|
819
|
-
describe('
|
|
820
|
-
const
|
|
2201
|
+
describe('getPrivateLogsByTags', () => {
|
|
2202
|
+
const numBlocksForLogs = 3;
|
|
821
2203
|
const numTxsPerBlock = 4;
|
|
822
2204
|
const numPrivateLogsPerTx = 3;
|
|
823
|
-
const numPublicLogsPerTx = 2;
|
|
824
2205
|
|
|
825
|
-
let
|
|
2206
|
+
let logsCheckpoints: PublishedCheckpoint[];
|
|
826
2207
|
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
+
);
|
|
831
2214
|
|
|
832
|
-
const makePrivateLog = (tag:
|
|
2215
|
+
const makePrivateLog = (tag: SiloedTag) =>
|
|
833
2216
|
PrivateLog.from({
|
|
834
|
-
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
|
+
),
|
|
835
2220
|
emittedLength: PRIVATE_LOG_SIZE_IN_FIELDS,
|
|
836
2221
|
});
|
|
837
2222
|
|
|
838
|
-
const makePublicLog = (tag: Fr) =>
|
|
839
|
-
PublicLog.from({
|
|
840
|
-
contractAddress: AztecAddress.fromNumber(1),
|
|
841
|
-
// Arbitrary length
|
|
842
|
-
fields: new Array(10).fill(null).map((_, i) => (!i ? tag : new Fr(tag.toNumber() + i))),
|
|
843
|
-
});
|
|
844
|
-
|
|
845
2223
|
const mockPrivateLogs = (blockNumber: number, txIndex: number) => {
|
|
846
2224
|
return times(numPrivateLogsPerTx, (logIndex: number) => {
|
|
847
|
-
const tag =
|
|
2225
|
+
const tag = makePrivateLogTag(blockNumber, txIndex, logIndex);
|
|
848
2226
|
return makePrivateLog(tag);
|
|
849
2227
|
});
|
|
850
2228
|
};
|
|
851
2229
|
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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 } : {}),
|
|
856
2239
|
});
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
const mockBlockWithLogs = async (blockNumber: number): Promise<PublishedL2Block> => {
|
|
860
|
-
const block = await L2Block.random(blockNumber);
|
|
861
|
-
block.header.globalVariables.blockNumber = blockNumber;
|
|
2240
|
+
block.header.globalVariables.blockNumber = BlockNumber(blockNumber);
|
|
862
2241
|
|
|
863
2242
|
block.body.txEffects = await timesParallel(numTxsPerBlock, async (txIndex: number) => {
|
|
864
2243
|
const txEffect = await TxEffect.random();
|
|
865
2244
|
txEffect.privateLogs = mockPrivateLogs(blockNumber, txIndex);
|
|
866
|
-
txEffect.publicLogs =
|
|
2245
|
+
txEffect.publicLogs = []; // No public logs needed for private log tests
|
|
867
2246
|
return txEffect;
|
|
868
2247
|
});
|
|
869
2248
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
},
|
|
878
|
-
});
|
|
2249
|
+
const checkpoint = new Checkpoint(
|
|
2250
|
+
AppendOnlyTreeSnapshot.random(),
|
|
2251
|
+
CheckpointHeader.random(),
|
|
2252
|
+
[block],
|
|
2253
|
+
CheckpointNumber(blockNumber),
|
|
2254
|
+
);
|
|
2255
|
+
return makePublishedCheckpoint(checkpoint, blockNumber);
|
|
879
2256
|
};
|
|
880
2257
|
|
|
881
2258
|
beforeEach(async () => {
|
|
882
|
-
|
|
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
|
+
}
|
|
883
2265
|
|
|
884
|
-
await store.
|
|
885
|
-
await store.addLogs(
|
|
2266
|
+
await store.addCheckpoints(logsCheckpoints);
|
|
2267
|
+
await store.addLogs(logsCheckpoints.flatMap(p => p.checkpoint.blocks));
|
|
886
2268
|
});
|
|
887
2269
|
|
|
888
2270
|
it('is possible to batch request private logs via tags', async () => {
|
|
889
|
-
const tags = [
|
|
2271
|
+
const tags = [makePrivateLogTag(2, 1, 2), makePrivateLogTag(1, 2, 0)];
|
|
890
2272
|
|
|
891
|
-
const logsByTags = await store.
|
|
2273
|
+
const logsByTags = await store.getPrivateLogsByTags(tags);
|
|
892
2274
|
|
|
893
2275
|
expect(logsByTags).toEqual([
|
|
894
2276
|
[
|
|
895
2277
|
expect.objectContaining({
|
|
896
2278
|
blockNumber: 2,
|
|
897
|
-
|
|
898
|
-
isFromPublic: false,
|
|
2279
|
+
logData: makePrivateLog(tags[0]).getEmittedFields(),
|
|
899
2280
|
}),
|
|
900
2281
|
],
|
|
901
2282
|
[
|
|
902
2283
|
expect.objectContaining({
|
|
903
2284
|
blockNumber: 1,
|
|
904
|
-
|
|
905
|
-
isFromPublic: false,
|
|
2285
|
+
logData: makePrivateLog(tags[1]).getEmittedFields(),
|
|
906
2286
|
}),
|
|
907
2287
|
],
|
|
908
2288
|
]);
|
|
909
2289
|
});
|
|
910
2290
|
|
|
911
|
-
it('is possible to batch request
|
|
912
|
-
|
|
913
|
-
const tags = [makeTag(1, 0, 0)];
|
|
2291
|
+
it('is possible to batch request logs that have the same tag but different content', async () => {
|
|
2292
|
+
const tags = [makePrivateLogTag(1, 2, 1)];
|
|
914
2293
|
|
|
915
|
-
|
|
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]]);
|
|
2304
|
+
|
|
2305
|
+
const logsByTags = await store.getPrivateLogsByTags(tags);
|
|
916
2306
|
|
|
917
2307
|
expect(logsByTags).toEqual([
|
|
918
2308
|
[
|
|
919
2309
|
expect.objectContaining({
|
|
920
2310
|
blockNumber: 1,
|
|
921
|
-
|
|
922
|
-
|
|
2311
|
+
logData: makePrivateLog(tags[0]).getEmittedFields(),
|
|
2312
|
+
}),
|
|
2313
|
+
expect.objectContaining({
|
|
2314
|
+
blockNumber: newBlockNumber,
|
|
2315
|
+
logData: newLog.getEmittedFields(),
|
|
923
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
|
+
[
|
|
924
2331
|
expect.objectContaining({
|
|
925
2332
|
blockNumber: 1,
|
|
926
|
-
|
|
927
|
-
|
|
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(),
|
|
2419
|
+
}),
|
|
2420
|
+
],
|
|
2421
|
+
[
|
|
2422
|
+
expect.objectContaining({
|
|
2423
|
+
blockNumber: 1,
|
|
2424
|
+
logData: makePublicLog(tags[1]).getEmittedFields(),
|
|
928
2425
|
}),
|
|
929
2426
|
],
|
|
930
2427
|
]);
|
|
931
2428
|
});
|
|
932
2429
|
|
|
933
2430
|
it('is possible to batch request logs that have the same tag but different content', async () => {
|
|
934
|
-
const tags = [
|
|
2431
|
+
const tags = [makePublicLogTag(1, 2, 1)];
|
|
935
2432
|
|
|
936
|
-
// Create a
|
|
937
|
-
|
|
938
|
-
const
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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]]);
|
|
944
2443
|
|
|
945
|
-
const logsByTags = await store.
|
|
2444
|
+
const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
|
|
946
2445
|
|
|
947
2446
|
expect(logsByTags).toEqual([
|
|
948
2447
|
[
|
|
949
2448
|
expect.objectContaining({
|
|
950
2449
|
blockNumber: 1,
|
|
951
|
-
|
|
952
|
-
isFromPublic: false,
|
|
2450
|
+
logData: makePublicLog(tags[0]).getEmittedFields(),
|
|
953
2451
|
}),
|
|
954
2452
|
expect.objectContaining({
|
|
955
2453
|
blockNumber: newBlockNumber,
|
|
956
|
-
|
|
957
|
-
isFromPublic: false,
|
|
2454
|
+
logData: newLog.getEmittedFields(),
|
|
958
2455
|
}),
|
|
959
2456
|
],
|
|
960
2457
|
]);
|
|
961
2458
|
});
|
|
962
2459
|
|
|
963
2460
|
it('is possible to request logs for non-existing tags and determine their position', async () => {
|
|
964
|
-
const tags = [
|
|
2461
|
+
const tags = [makePublicLogTag(99, 88, 77), makePublicLogTag(1, 1, 0)];
|
|
965
2462
|
|
|
966
|
-
const logsByTags = await store.
|
|
2463
|
+
const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
|
|
967
2464
|
|
|
968
2465
|
expect(logsByTags).toEqual([
|
|
969
2466
|
[
|
|
@@ -972,8 +2469,7 @@ export function describeArchiverDataStore(
|
|
|
972
2469
|
[
|
|
973
2470
|
expect.objectContaining({
|
|
974
2471
|
blockNumber: 1,
|
|
975
|
-
|
|
976
|
-
isFromPublic: false,
|
|
2472
|
+
logData: makePublicLog(tags[1]).getEmittedFields(),
|
|
977
2473
|
}),
|
|
978
2474
|
],
|
|
979
2475
|
]);
|
|
@@ -981,34 +2477,33 @@ export function describeArchiverDataStore(
|
|
|
981
2477
|
});
|
|
982
2478
|
|
|
983
2479
|
describe('getPublicLogs', () => {
|
|
984
|
-
const
|
|
985
|
-
const numPublicFunctionCalls = 3;
|
|
986
|
-
const numPublicLogs = 2;
|
|
987
|
-
const numBlocks = 10;
|
|
988
|
-
let blocks: PublishedL2Block[];
|
|
2480
|
+
const numBlocksForPublicLogs = 10;
|
|
989
2481
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
block: await L2Block.random(index + 1, txsPerBlock, numPublicFunctionCalls, numPublicLogs),
|
|
994
|
-
l1: { blockNumber: BigInt(index), blockHash: makeBlockHash(index), timestamp: BigInt(index) },
|
|
995
|
-
attestations: times(3, CommitteeAttestation.random),
|
|
996
|
-
}),
|
|
997
|
-
);
|
|
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;
|
|
998
2485
|
|
|
999
|
-
|
|
1000
|
-
|
|
2486
|
+
// Helper to get number of txs in a block
|
|
2487
|
+
const getTxsPerBlock = (block: L2BlockNew) => block.body.txEffects.length;
|
|
2488
|
+
|
|
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
|
+
}
|
|
1001
2495
|
});
|
|
1002
2496
|
|
|
1003
2497
|
it('no logs returned if deleted ("txHash" filter param is respected variant)', async () => {
|
|
1004
2498
|
// get random tx
|
|
1005
|
-
const targetBlockIndex = randomInt(
|
|
1006
|
-
const
|
|
1007
|
-
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;
|
|
1008
2503
|
|
|
1009
2504
|
await Promise.all([
|
|
1010
|
-
store.
|
|
1011
|
-
store.deleteLogs(
|
|
2505
|
+
store.unwindCheckpoints(CheckpointNumber(numBlocksForPublicLogs), numBlocksForPublicLogs),
|
|
2506
|
+
store.deleteLogs(publishedCheckpoints.slice(0, numBlocksForPublicLogs).flatMap(b => b.checkpoint.blocks)),
|
|
1012
2507
|
]);
|
|
1013
2508
|
|
|
1014
2509
|
const response = await store.getPublicLogs({ txHash: targetTxHash });
|
|
@@ -1020,16 +2515,17 @@ export function describeArchiverDataStore(
|
|
|
1020
2515
|
|
|
1021
2516
|
it('"txHash" filter param is respected', async () => {
|
|
1022
2517
|
// get random tx
|
|
1023
|
-
const targetBlockIndex = randomInt(
|
|
1024
|
-
const
|
|
1025
|
-
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;
|
|
1026
2522
|
|
|
1027
2523
|
const response = await store.getPublicLogs({ txHash: targetTxHash });
|
|
1028
2524
|
const logs = response.logs;
|
|
1029
2525
|
|
|
1030
2526
|
expect(response.maxLogsHit).toBeFalsy();
|
|
1031
2527
|
|
|
1032
|
-
const expectedNumLogs =
|
|
2528
|
+
const expectedNumLogs = getPublicLogsPerTx(targetBlock, targetTxIndex);
|
|
1033
2529
|
expect(logs.length).toEqual(expectedNumLogs);
|
|
1034
2530
|
|
|
1035
2531
|
const targeBlockNumber = targetBlockIndex + INITIAL_L2_BLOCK_NUM;
|
|
@@ -1039,6 +2535,17 @@ export function describeArchiverDataStore(
|
|
|
1039
2535
|
}
|
|
1040
2536
|
});
|
|
1041
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
|
+
|
|
1042
2549
|
it('"fromBlock" and "toBlock" filter params are respected', async () => {
|
|
1043
2550
|
// Set "fromBlock" and "toBlock"
|
|
1044
2551
|
const fromBlock = 3;
|
|
@@ -1049,7 +2556,12 @@ export function describeArchiverDataStore(
|
|
|
1049
2556
|
|
|
1050
2557
|
expect(response.maxLogsHit).toBeFalsy();
|
|
1051
2558
|
|
|
1052
|
-
|
|
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
|
+
}
|
|
1053
2565
|
expect(logs.length).toEqual(expectedNumLogs);
|
|
1054
2566
|
|
|
1055
2567
|
for (const log of logs) {
|
|
@@ -1061,11 +2573,12 @@ export function describeArchiverDataStore(
|
|
|
1061
2573
|
|
|
1062
2574
|
it('"contractAddress" filter param is respected', async () => {
|
|
1063
2575
|
// Get a random contract address from the logs
|
|
1064
|
-
const targetBlockIndex = randomInt(
|
|
1065
|
-
const
|
|
1066
|
-
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));
|
|
1067
2580
|
const targetContractAddress =
|
|
1068
|
-
|
|
2581
|
+
targetBlock.body.txEffects[targetTxIndex].publicLogs[targetLogIndex].contractAddress;
|
|
1069
2582
|
|
|
1070
2583
|
const response = await store.getPublicLogs({ contractAddress: targetContractAddress });
|
|
1071
2584
|
|
|
@@ -1078,11 +2591,19 @@ export function describeArchiverDataStore(
|
|
|
1078
2591
|
|
|
1079
2592
|
it('"afterLog" filter param is respected', async () => {
|
|
1080
2593
|
// Get a random log as reference
|
|
1081
|
-
const targetBlockIndex = randomInt(
|
|
1082
|
-
const
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
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
|
+
);
|
|
1086
2607
|
|
|
1087
2608
|
const response = await store.getPublicLogs({ afterLog });
|
|
1088
2609
|
const logs = response.logs;
|
|
@@ -1104,52 +2625,77 @@ export function describeArchiverDataStore(
|
|
|
1104
2625
|
it('"txHash" filter param is ignored when "afterLog" is set', async () => {
|
|
1105
2626
|
// Get random txHash
|
|
1106
2627
|
const txHash = TxHash.random();
|
|
1107
|
-
const afterLog = new LogId(1, 0, 0);
|
|
2628
|
+
const afterLog = new LogId(BlockNumber(1), L2BlockHash.random(), 0, 0);
|
|
1108
2629
|
|
|
1109
2630
|
const response = await store.getPublicLogs({ txHash, afterLog });
|
|
1110
2631
|
expect(response.logs.length).toBeGreaterThan(1);
|
|
1111
2632
|
});
|
|
1112
2633
|
|
|
1113
2634
|
it('intersecting works', async () => {
|
|
1114
|
-
let logs = (await store.getPublicLogs({ fromBlock: -10, toBlock: -5 })).logs;
|
|
2635
|
+
let logs = (await store.getPublicLogs({ fromBlock: -10 as BlockNumber, toBlock: -5 as BlockNumber })).logs;
|
|
1115
2636
|
expect(logs.length).toBe(0);
|
|
1116
2637
|
|
|
1117
2638
|
// "fromBlock" gets correctly trimmed to range and "toBlock" is exclusive
|
|
1118
|
-
logs = (await store.getPublicLogs({ fromBlock: -10, toBlock: 5 })).logs;
|
|
2639
|
+
logs = (await store.getPublicLogs({ fromBlock: -10 as BlockNumber, toBlock: BlockNumber(5) })).logs;
|
|
1119
2640
|
let blockNumbers = new Set(logs.map(log => log.id.blockNumber));
|
|
1120
2641
|
expect(blockNumbers).toEqual(new Set([1, 2, 3, 4]));
|
|
1121
2642
|
|
|
1122
2643
|
// "toBlock" should be exclusive
|
|
1123
|
-
logs = (await store.getPublicLogs({ fromBlock: 1, toBlock: 1 })).logs;
|
|
2644
|
+
logs = (await store.getPublicLogs({ fromBlock: BlockNumber(1), toBlock: BlockNumber(1) })).logs;
|
|
1124
2645
|
expect(logs.length).toBe(0);
|
|
1125
2646
|
|
|
1126
|
-
logs = (await store.getPublicLogs({ fromBlock: 10, toBlock: 5 })).logs;
|
|
2647
|
+
logs = (await store.getPublicLogs({ fromBlock: BlockNumber(10), toBlock: BlockNumber(5) })).logs;
|
|
1127
2648
|
expect(logs.length).toBe(0);
|
|
1128
2649
|
|
|
1129
2650
|
// both "fromBlock" and "toBlock" get correctly capped to range and logs from all blocks are returned
|
|
1130
|
-
logs = (await store.getPublicLogs({ fromBlock: -100, toBlock: +100 })).logs;
|
|
2651
|
+
logs = (await store.getPublicLogs({ fromBlock: -100 as BlockNumber, toBlock: +100 })).logs;
|
|
1131
2652
|
blockNumbers = new Set(logs.map(log => log.id.blockNumber));
|
|
1132
|
-
expect(blockNumbers.size).toBe(
|
|
2653
|
+
expect(blockNumbers.size).toBe(numBlocksForPublicLogs);
|
|
1133
2654
|
|
|
1134
2655
|
// intersecting with "afterLog" works
|
|
1135
|
-
logs = (
|
|
2656
|
+
logs = (
|
|
2657
|
+
await store.getPublicLogs({
|
|
2658
|
+
fromBlock: BlockNumber(2),
|
|
2659
|
+
toBlock: BlockNumber(5),
|
|
2660
|
+
afterLog: new LogId(BlockNumber(4), L2BlockHash.random(), 0, 0),
|
|
2661
|
+
})
|
|
2662
|
+
).logs;
|
|
1136
2663
|
blockNumbers = new Set(logs.map(log => log.id.blockNumber));
|
|
1137
2664
|
expect(blockNumbers).toEqual(new Set([4]));
|
|
1138
2665
|
|
|
1139
|
-
logs = (
|
|
2666
|
+
logs = (
|
|
2667
|
+
await store.getPublicLogs({
|
|
2668
|
+
toBlock: BlockNumber(5),
|
|
2669
|
+
afterLog: new LogId(BlockNumber(5), L2BlockHash.random(), 1, 0),
|
|
2670
|
+
})
|
|
2671
|
+
).logs;
|
|
1140
2672
|
expect(logs.length).toBe(0);
|
|
1141
2673
|
|
|
1142
|
-
logs = (
|
|
2674
|
+
logs = (
|
|
2675
|
+
await store.getPublicLogs({
|
|
2676
|
+
fromBlock: BlockNumber(2),
|
|
2677
|
+
toBlock: BlockNumber(5),
|
|
2678
|
+
afterLog: new LogId(BlockNumber(100), L2BlockHash.random(), 0, 0),
|
|
2679
|
+
})
|
|
2680
|
+
).logs;
|
|
1143
2681
|
expect(logs.length).toBe(0);
|
|
1144
2682
|
});
|
|
1145
2683
|
|
|
1146
2684
|
it('"txIndex" and "logIndex" are respected when "afterLog.blockNumber" is equal to "fromBlock"', async () => {
|
|
1147
2685
|
// Get a random log as reference
|
|
1148
|
-
const targetBlockIndex = randomInt(
|
|
1149
|
-
const
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
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
|
+
);
|
|
1153
2699
|
|
|
1154
2700
|
const response = await store.getPublicLogs({ afterLog, fromBlock: afterLog.blockNumber });
|
|
1155
2701
|
const logs = response.logs;
|
|
@@ -1169,6 +2715,40 @@ export function describeArchiverDataStore(
|
|
|
1169
2715
|
});
|
|
1170
2716
|
});
|
|
1171
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
|
+
|
|
1172
2752
|
describe('pendingChainValidationStatus', () => {
|
|
1173
2753
|
it('should return undefined when no status is set', async () => {
|
|
1174
2754
|
const status = await store.getPendingChainValidationStatus();
|
|
@@ -1189,7 +2769,7 @@ export function describeArchiverDataStore(
|
|
|
1189
2769
|
valid: false,
|
|
1190
2770
|
block: randomBlockInfo(1),
|
|
1191
2771
|
committee: [EthAddress.random(), EthAddress.random()],
|
|
1192
|
-
epoch:
|
|
2772
|
+
epoch: EpochNumber(123),
|
|
1193
2773
|
seed: 456n,
|
|
1194
2774
|
attestors: [EthAddress.random()],
|
|
1195
2775
|
attestations: [CommitteeAttestation.random()],
|
|
@@ -1208,7 +2788,7 @@ export function describeArchiverDataStore(
|
|
|
1208
2788
|
block: randomBlockInfo(2),
|
|
1209
2789
|
committee: [EthAddress.random()],
|
|
1210
2790
|
attestors: [EthAddress.random()],
|
|
1211
|
-
epoch:
|
|
2791
|
+
epoch: EpochNumber(789),
|
|
1212
2792
|
seed: 101n,
|
|
1213
2793
|
attestations: [CommitteeAttestation.random()],
|
|
1214
2794
|
reason: 'invalid-attestation',
|
|
@@ -1227,7 +2807,7 @@ export function describeArchiverDataStore(
|
|
|
1227
2807
|
valid: false,
|
|
1228
2808
|
block: randomBlockInfo(3),
|
|
1229
2809
|
committee: [EthAddress.random()],
|
|
1230
|
-
epoch:
|
|
2810
|
+
epoch: EpochNumber(999),
|
|
1231
2811
|
seed: 888n,
|
|
1232
2812
|
attestors: [EthAddress.random()],
|
|
1233
2813
|
attestations: [CommitteeAttestation.random()],
|
|
@@ -1246,7 +2826,7 @@ export function describeArchiverDataStore(
|
|
|
1246
2826
|
valid: false,
|
|
1247
2827
|
block: randomBlockInfo(4),
|
|
1248
2828
|
committee: [],
|
|
1249
|
-
epoch:
|
|
2829
|
+
epoch: EpochNumber(0),
|
|
1250
2830
|
seed: 0n,
|
|
1251
2831
|
attestors: [],
|
|
1252
2832
|
attestations: [],
|