@aztec/archiver 0.0.1-commit.9b94fc1 → 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 +96 -79
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +451 -214
- 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 +1867 -386
- package/dest/archiver/config.d.ts +5 -4
- package/dest/archiver/config.d.ts.map +1 -1
- package/dest/archiver/config.js +10 -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 +3 -1
- 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 +46 -56
- 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/{data_retrieval.js → l1/data_retrieval.js} +52 -107
- 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/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 +1 -2
- package/dest/archiver/structs/published.d.ts.map +1 -1
- 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/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 +18 -7
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +37 -23
- 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 +17 -16
- package/src/archiver/archiver.ts +595 -296
- package/src/archiver/archiver_store.ts +113 -46
- package/src/archiver/archiver_store_test_suite.ts +1932 -353
- package/src/archiver/config.ts +15 -10
- package/src/archiver/errors.ts +64 -0
- package/src/archiver/index.ts +1 -1
- package/src/archiver/instrumentation.ts +14 -0
- 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/{data_retrieval.ts → l1/data_retrieval.ts} +101 -165
- 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 +0 -1
- 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 +45 -25
- 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
|
@@ -1,46 +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';
|
|
7
|
-
import { EpochNumber } from '@aztec/foundation/branded-types';
|
|
9
|
+
import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
8
10
|
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
9
11
|
import { times, timesParallel } from '@aztec/foundation/collection';
|
|
10
|
-
import { randomInt } from '@aztec/foundation/crypto';
|
|
11
|
-
import { Fr } from '@aztec/foundation/
|
|
12
|
+
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
13
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
12
14
|
import { toArray } from '@aztec/foundation/iterable';
|
|
13
15
|
import { sleep } from '@aztec/foundation/sleep';
|
|
14
16
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
15
17
|
import {
|
|
18
|
+
CheckpointedL2Block,
|
|
16
19
|
CommitteeAttestation,
|
|
17
20
|
EthAddress,
|
|
18
|
-
L2Block,
|
|
19
21
|
L2BlockHash,
|
|
20
|
-
|
|
22
|
+
L2BlockNew,
|
|
21
23
|
type ValidateBlockResult,
|
|
22
24
|
randomBlockInfo,
|
|
23
|
-
wrapInBlock,
|
|
24
25
|
} from '@aztec/stdlib/block';
|
|
26
|
+
import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
25
27
|
import {
|
|
26
28
|
type ContractClassPublic,
|
|
27
29
|
type ContractInstanceWithAddress,
|
|
28
30
|
SerializableContractInstance,
|
|
29
31
|
computePublicBytecodeCommitment,
|
|
30
32
|
} from '@aztec/stdlib/contract';
|
|
31
|
-
import { LogId, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
|
|
33
|
+
import { ContractClassLog, LogId, PrivateLog, PublicLog, SiloedTag, Tag } from '@aztec/stdlib/logs';
|
|
32
34
|
import { InboxLeaf } from '@aztec/stdlib/messaging';
|
|
35
|
+
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
33
36
|
import {
|
|
34
37
|
makeContractClassPublic,
|
|
35
38
|
makeExecutablePrivateFunctionWithMembershipProof,
|
|
36
39
|
makeUtilityFunctionWithMembershipProof,
|
|
37
40
|
} from '@aztec/stdlib/testing';
|
|
38
41
|
import '@aztec/stdlib/testing/jest';
|
|
39
|
-
import {
|
|
42
|
+
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
43
|
+
import { type IndexedTxEffect, PartialStateReference, StateReference, TxEffect, TxHash } from '@aztec/stdlib/tx';
|
|
40
44
|
|
|
41
45
|
import { makeInboxMessage, makeInboxMessages } from '../test/mock_structs.js';
|
|
42
46
|
import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
|
|
43
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
BlockArchiveNotConsistentError,
|
|
49
|
+
BlockIndexNotSequentialError,
|
|
50
|
+
BlockNumberNotSequentialError,
|
|
51
|
+
CheckpointNumberNotConsistentError,
|
|
52
|
+
CheckpointNumberNotSequentialError,
|
|
53
|
+
InitialBlockNumberNotSequentialError,
|
|
54
|
+
InitialCheckpointNumberNotSequentialError,
|
|
55
|
+
} from './errors.js';
|
|
44
56
|
import { MessageStoreError } from './kv_archiver_store/message_store.js';
|
|
45
57
|
import type { InboxMessage } from './structs/inbox_message.js';
|
|
46
58
|
|
|
@@ -54,206 +66,1572 @@ export function describeArchiverDataStore(
|
|
|
54
66
|
) {
|
|
55
67
|
describe(testName, () => {
|
|
56
68
|
let store: ArchiverDataStore;
|
|
57
|
-
let
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
[1,
|
|
61
|
-
[10,
|
|
62
|
-
[
|
|
63
|
-
[2, 5, () => blocks.slice(1, 6)],
|
|
64
|
-
[5, 2, () => blocks.slice(4, 6)],
|
|
69
|
+
let publishedCheckpoints: PublishedCheckpoint[];
|
|
70
|
+
|
|
71
|
+
const blockNumberTests: [number, () => L2BlockNew][] = [
|
|
72
|
+
[1, () => publishedCheckpoints[0].checkpoint.blocks[0]],
|
|
73
|
+
[10, () => publishedCheckpoints[9].checkpoint.blocks[0]],
|
|
74
|
+
[5, () => publishedCheckpoints[4].checkpoint.blocks[0]],
|
|
65
75
|
];
|
|
66
76
|
|
|
67
77
|
const makeBlockHash = (blockNumber: number) => `0x${blockNumber.toString(16).padStart(64, '0')}`;
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
79
|
+
// Create a state reference with properly calculated noteHashTree.nextAvailableLeafIndex
|
|
80
|
+
// This is needed because the log store calculates dataStartIndexForBlock as:
|
|
81
|
+
// noteHashTree.nextAvailableLeafIndex - txEffects.length * MAX_NOTE_HASHES_PER_TX
|
|
82
|
+
// If nextAvailableLeafIndex is too small (random values 0-1000), this becomes negative
|
|
83
|
+
const makeStateForBlock = (blockNumber: number, txsPerBlock: number): StateReference => {
|
|
84
|
+
// Ensure nextAvailableLeafIndex is large enough for all blocks up to this point
|
|
85
|
+
const noteHashIndex = blockNumber * txsPerBlock * MAX_NOTE_HASHES_PER_TX;
|
|
86
|
+
return new StateReference(
|
|
87
|
+
AppendOnlyTreeSnapshot.random(),
|
|
88
|
+
new PartialStateReference(
|
|
89
|
+
new AppendOnlyTreeSnapshot(Fr.random(), noteHashIndex),
|
|
90
|
+
AppendOnlyTreeSnapshot.random(),
|
|
91
|
+
AppendOnlyTreeSnapshot.random(),
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const makePublishedCheckpoint = (checkpoint: Checkpoint, l1BlockNumber: number): PublishedCheckpoint => {
|
|
97
|
+
return new PublishedCheckpoint(
|
|
98
|
+
checkpoint,
|
|
99
|
+
new L1PublishedData(BigInt(l1BlockNumber), BigInt(l1BlockNumber * 1000), makeBlockHash(l1BlockNumber)),
|
|
100
|
+
times(3, CommitteeAttestation.random),
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const expectCheckpointedBlockEquals = (
|
|
105
|
+
actual: CheckpointedL2Block,
|
|
106
|
+
expectedBlock: L2BlockNew,
|
|
107
|
+
expectedCheckpoint: PublishedCheckpoint,
|
|
108
|
+
) => {
|
|
109
|
+
expect(actual.l1).toEqual(expectedCheckpoint.l1);
|
|
110
|
+
expect(actual.block.header.equals(expectedBlock.header)).toBe(true);
|
|
111
|
+
expect(actual.checkpointNumber).toEqual(expectedCheckpoint.checkpoint.number);
|
|
112
|
+
expect(actual.attestations.every((a, i) => a.equals(expectedCheckpoint.attestations[i]))).toBe(true);
|
|
89
113
|
};
|
|
90
114
|
|
|
91
115
|
beforeEach(async () => {
|
|
92
116
|
store = await getStore();
|
|
93
|
-
|
|
117
|
+
// Create checkpoints sequentially to ensure archive roots are chained properly.
|
|
118
|
+
// Each block's header.lastArchive must equal the previous block's archive.
|
|
119
|
+
publishedCheckpoints = [];
|
|
120
|
+
const txsPerBlock = 4;
|
|
121
|
+
for (let i = 0; i < 10; i++) {
|
|
122
|
+
const blockNumber = i + 1;
|
|
123
|
+
const previousArchive = i > 0 ? publishedCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
|
|
124
|
+
const checkpoint = await Checkpoint.random(CheckpointNumber(i + 1), {
|
|
125
|
+
numBlocks: 1,
|
|
126
|
+
startBlockNumber: blockNumber,
|
|
127
|
+
previousArchive,
|
|
128
|
+
txsPerBlock,
|
|
129
|
+
state: makeStateForBlock(blockNumber, txsPerBlock),
|
|
130
|
+
// Ensure each tx has public logs for getPublicLogs tests
|
|
131
|
+
txOptions: { numPublicCallsPerTx: 2, numPublicLogsPerCall: 2 },
|
|
132
|
+
});
|
|
133
|
+
publishedCheckpoints.push(makePublishedCheckpoint(checkpoint, i + 10));
|
|
134
|
+
}
|
|
94
135
|
});
|
|
95
136
|
|
|
96
|
-
describe('
|
|
97
|
-
it('returns success when adding
|
|
98
|
-
await expect(store.
|
|
137
|
+
describe('addCheckpoints', () => {
|
|
138
|
+
it('returns success when adding checkpoints', async () => {
|
|
139
|
+
await expect(store.addCheckpoints(publishedCheckpoints)).resolves.toBe(true);
|
|
99
140
|
});
|
|
100
141
|
|
|
101
|
-
it('
|
|
102
|
-
await store.
|
|
103
|
-
await expect(store.
|
|
142
|
+
it('throws on duplicate checkpoints', async () => {
|
|
143
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
144
|
+
await expect(store.addCheckpoints(publishedCheckpoints)).rejects.toThrow(
|
|
145
|
+
InitialCheckpointNumberNotSequentialError,
|
|
146
|
+
);
|
|
104
147
|
});
|
|
105
148
|
|
|
106
149
|
it('throws an error if the previous block does not exist in the store', async () => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
await expect(store.
|
|
150
|
+
const checkpoint = await Checkpoint.random(CheckpointNumber(2), { numBlocks: 1, startBlockNumber: 2 });
|
|
151
|
+
const block = makePublishedCheckpoint(checkpoint, 2);
|
|
152
|
+
await expect(store.addCheckpoints([block])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
|
|
153
|
+
await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
|
|
110
154
|
});
|
|
111
155
|
|
|
112
156
|
it('throws an error if there is a gap in the blocks being added', async () => {
|
|
113
|
-
const
|
|
114
|
-
await
|
|
115
|
-
|
|
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
|
+
);
|
|
116
313
|
});
|
|
117
314
|
});
|
|
118
315
|
|
|
119
|
-
describe('
|
|
120
|
-
it('unwinding
|
|
121
|
-
await store.
|
|
122
|
-
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;
|
|
123
322
|
|
|
124
|
-
|
|
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);
|
|
125
328
|
|
|
126
|
-
await store.
|
|
329
|
+
await store.unwindCheckpoints(checkpointNumber, 1);
|
|
127
330
|
|
|
128
|
-
expect(await store.
|
|
129
|
-
expect(
|
|
331
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(checkpointNumber - 1);
|
|
332
|
+
await expect(store.getCheckpointedBlock(lastBlockNumber)).resolves.toBeUndefined();
|
|
130
333
|
});
|
|
131
334
|
|
|
132
335
|
it('can unwind multiple empty blocks', async () => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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);
|
|
136
350
|
|
|
137
|
-
await store.
|
|
138
|
-
expect(await store.
|
|
139
|
-
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
|
+
]);
|
|
140
356
|
});
|
|
141
357
|
|
|
142
|
-
it('refuses to unwind
|
|
143
|
-
await store.
|
|
144
|
-
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
|
+
);
|
|
145
363
|
});
|
|
146
364
|
|
|
147
365
|
it('unwound blocks and headers cannot be retrieved by hash or archive', async () => {
|
|
148
|
-
await store.
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
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;
|
|
152
371
|
|
|
153
372
|
// Verify block and header exist before unwinding
|
|
154
|
-
|
|
155
|
-
expect(
|
|
156
|
-
expect(
|
|
157
|
-
|
|
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);
|
|
158
380
|
|
|
159
|
-
|
|
160
|
-
|
|
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);
|
|
161
391
|
|
|
162
392
|
// Verify neither block nor header can be retrieved after unwinding
|
|
163
|
-
expect(await store.
|
|
164
|
-
expect(await store.
|
|
393
|
+
expect(await store.getCheckpointedBlockByHash(blockHash)).toBeUndefined();
|
|
394
|
+
expect(await store.getCheckpointedBlockByArchive(archive)).toBeUndefined();
|
|
165
395
|
expect(await store.getBlockHeaderByHash(blockHash)).toBeUndefined();
|
|
166
396
|
expect(await store.getBlockHeaderByArchive(archive)).toBeUndefined();
|
|
167
397
|
});
|
|
168
398
|
});
|
|
169
399
|
|
|
170
|
-
describe('
|
|
171
|
-
|
|
172
|
-
|
|
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);
|
|
173
430
|
});
|
|
174
431
|
|
|
175
|
-
it
|
|
176
|
-
|
|
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);
|
|
177
469
|
});
|
|
178
470
|
|
|
179
|
-
it('
|
|
180
|
-
|
|
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
|
+
}
|
|
181
536
|
});
|
|
182
537
|
|
|
183
|
-
it('
|
|
184
|
-
|
|
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
|
+
}
|
|
185
578
|
});
|
|
186
579
|
|
|
187
|
-
it('
|
|
188
|
-
|
|
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
|
+
}
|
|
189
599
|
});
|
|
190
600
|
|
|
191
|
-
it('
|
|
192
|
-
|
|
193
|
-
|
|
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
|
+
}
|
|
194
620
|
});
|
|
195
621
|
|
|
196
|
-
it('
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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,
|
|
200
626
|
);
|
|
201
|
-
|
|
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);
|
|
202
645
|
});
|
|
203
646
|
});
|
|
204
647
|
|
|
205
|
-
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', () => {
|
|
206
1559
|
beforeEach(async () => {
|
|
207
|
-
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);
|
|
208
1584
|
});
|
|
209
1585
|
|
|
210
1586
|
it('retrieves a block by its hash', async () => {
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
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);
|
|
214
1591
|
|
|
215
1592
|
expect(retrievedBlock).toBeDefined();
|
|
216
|
-
|
|
1593
|
+
expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
|
|
217
1594
|
});
|
|
218
1595
|
|
|
219
1596
|
it('returns undefined for non-existent block hash', async () => {
|
|
220
1597
|
const nonExistentHash = Fr.random();
|
|
221
|
-
await expect(store.
|
|
1598
|
+
await expect(store.getCheckpointedBlockByHash(nonExistentHash)).resolves.toBeUndefined();
|
|
222
1599
|
});
|
|
223
1600
|
});
|
|
224
1601
|
|
|
225
|
-
describe('
|
|
1602
|
+
describe('getCheckpointedBlockByArchive', () => {
|
|
226
1603
|
beforeEach(async () => {
|
|
227
|
-
await store.
|
|
1604
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
228
1605
|
});
|
|
229
1606
|
|
|
230
1607
|
it('retrieves a block by its archive root', async () => {
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
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);
|
|
234
1612
|
|
|
235
1613
|
expect(retrievedBlock).toBeDefined();
|
|
236
|
-
|
|
1614
|
+
expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
|
|
237
1615
|
});
|
|
238
1616
|
|
|
239
1617
|
it('returns undefined for non-existent archive root', async () => {
|
|
240
1618
|
const nonExistentArchive = Fr.random();
|
|
241
|
-
await expect(store.
|
|
1619
|
+
await expect(store.getCheckpointedBlockByArchive(nonExistentArchive)).resolves.toBeUndefined();
|
|
242
1620
|
});
|
|
243
1621
|
});
|
|
244
1622
|
|
|
245
1623
|
describe('getBlockHeaderByHash', () => {
|
|
246
1624
|
beforeEach(async () => {
|
|
247
|
-
await store.
|
|
1625
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
248
1626
|
});
|
|
249
1627
|
|
|
250
1628
|
it('retrieves a block header by its hash', async () => {
|
|
251
|
-
const expectedBlock =
|
|
252
|
-
const blockHash = await expectedBlock.
|
|
1629
|
+
const expectedBlock = publishedCheckpoints[7].checkpoint.blocks[0];
|
|
1630
|
+
const blockHash = await expectedBlock.header.hash();
|
|
253
1631
|
const retrievedHeader = await store.getBlockHeaderByHash(blockHash);
|
|
254
1632
|
|
|
255
1633
|
expect(retrievedHeader).toBeDefined();
|
|
256
|
-
expect(retrievedHeader!.equals(expectedBlock.
|
|
1634
|
+
expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true);
|
|
257
1635
|
});
|
|
258
1636
|
|
|
259
1637
|
it('returns undefined for non-existent block hash', async () => {
|
|
@@ -264,16 +1642,16 @@ export function describeArchiverDataStore(
|
|
|
264
1642
|
|
|
265
1643
|
describe('getBlockHeaderByArchive', () => {
|
|
266
1644
|
beforeEach(async () => {
|
|
267
|
-
await store.
|
|
1645
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
268
1646
|
});
|
|
269
1647
|
|
|
270
1648
|
it('retrieves a block header by its archive root', async () => {
|
|
271
|
-
const expectedBlock =
|
|
272
|
-
const archive = expectedBlock.
|
|
1649
|
+
const expectedBlock = publishedCheckpoints[2].checkpoint.blocks[0];
|
|
1650
|
+
const archive = expectedBlock.archive.root;
|
|
273
1651
|
const retrievedHeader = await store.getBlockHeaderByArchive(archive);
|
|
274
1652
|
|
|
275
1653
|
expect(retrievedHeader).toBeDefined();
|
|
276
|
-
expect(retrievedHeader!.equals(expectedBlock.
|
|
1654
|
+
expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true);
|
|
277
1655
|
});
|
|
278
1656
|
|
|
279
1657
|
it('returns undefined for non-existent archive root', async () => {
|
|
@@ -282,14 +1660,16 @@ export function describeArchiverDataStore(
|
|
|
282
1660
|
});
|
|
283
1661
|
});
|
|
284
1662
|
|
|
285
|
-
describe('
|
|
286
|
-
it('returns the
|
|
287
|
-
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);
|
|
288
1666
|
});
|
|
289
1667
|
|
|
290
|
-
it(
|
|
291
|
-
await store.
|
|
292
|
-
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
|
+
);
|
|
293
1673
|
});
|
|
294
1674
|
});
|
|
295
1675
|
|
|
@@ -302,7 +1682,7 @@ export function describeArchiverDataStore(
|
|
|
302
1682
|
});
|
|
303
1683
|
|
|
304
1684
|
it('returns the L1 block number in which the most recent L2 block was published', async () => {
|
|
305
|
-
await store.
|
|
1685
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
306
1686
|
await expect(store.getSynchPoint()).resolves.toEqual({
|
|
307
1687
|
blocksSynchedTo: 19n,
|
|
308
1688
|
messagesSynchedTo: undefined,
|
|
@@ -335,61 +1715,47 @@ export function describeArchiverDataStore(
|
|
|
335
1715
|
|
|
336
1716
|
describe('addLogs', () => {
|
|
337
1717
|
it('adds private & public logs', async () => {
|
|
338
|
-
const
|
|
339
|
-
await
|
|
1718
|
+
const checkpoint = publishedCheckpoints[0];
|
|
1719
|
+
await store.addCheckpoints([checkpoint]);
|
|
1720
|
+
await expect(store.addLogs(checkpoint.checkpoint.blocks)).resolves.toEqual(true);
|
|
340
1721
|
});
|
|
341
1722
|
});
|
|
342
1723
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
await expect(store.addLogs([block])).resolves.toEqual(true);
|
|
348
|
-
|
|
349
|
-
expect((await store.getPrivateLogs(1, 1)).length).toEqual(
|
|
350
|
-
block.body.txEffects.map(txEffect => txEffect.privateLogs).flat().length,
|
|
351
|
-
);
|
|
352
|
-
expect((await store.getPublicLogs({ fromBlock: 1 })).logs.length).toEqual(
|
|
353
|
-
block.body.txEffects.map(txEffect => txEffect.publicLogs).flat().length,
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
// This one is a pain for memory as we would never want to just delete memory in the middle.
|
|
357
|
-
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);
|
|
358
1728
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
});
|
|
1729
|
+
expect((await store.getPublicLogs({ fromBlock: BlockNumber(1) })).logs.length).toEqual(
|
|
1730
|
+
block.body.txEffects.map(txEffect => txEffect.publicLogs).flat().length,
|
|
1731
|
+
);
|
|
363
1732
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const block = blocks[0].block;
|
|
367
|
-
await store.addBlocks([blocks[0]]);
|
|
368
|
-
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]);
|
|
369
1735
|
|
|
370
|
-
|
|
371
|
-
expect(privateLogs).toEqual(block.body.txEffects.map(txEffect => txEffect.privateLogs).flat());
|
|
372
|
-
});
|
|
1736
|
+
expect((await store.getPublicLogs({ fromBlock: BlockNumber(1) })).logs.length).toEqual(0);
|
|
373
1737
|
});
|
|
374
1738
|
|
|
375
1739
|
describe('getTxEffect', () => {
|
|
1740
|
+
const getBlock = (i: number) => publishedCheckpoints[i].checkpoint.blocks[0];
|
|
1741
|
+
|
|
376
1742
|
beforeEach(async () => {
|
|
377
|
-
await store.addLogs(
|
|
378
|
-
await store.
|
|
1743
|
+
await store.addLogs(publishedCheckpoints.flatMap(x => x.checkpoint.blocks));
|
|
1744
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
379
1745
|
});
|
|
380
1746
|
|
|
381
1747
|
it.each([
|
|
382
|
-
() => ({ data:
|
|
383
|
-
() => ({ data:
|
|
384
|
-
() => ({ data:
|
|
385
|
-
() => ({ data:
|
|
386
|
-
() => ({ 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 }),
|
|
387
1753
|
])('retrieves a previously stored transaction', async getExpectedTx => {
|
|
388
1754
|
const { data, block, txIndexInBlock } = getExpectedTx();
|
|
389
1755
|
const expectedTx: IndexedTxEffect = {
|
|
390
1756
|
data,
|
|
391
1757
|
l2BlockNumber: block.number,
|
|
392
|
-
l2BlockHash: L2BlockHash.fromField(await block.hash()),
|
|
1758
|
+
l2BlockHash: L2BlockHash.fromField(await block.header.hash()),
|
|
393
1759
|
txIndexInBlock,
|
|
394
1760
|
};
|
|
395
1761
|
const actualTx = await store.getTxEffect(data.txHash);
|
|
@@ -401,16 +1767,16 @@ export function describeArchiverDataStore(
|
|
|
401
1767
|
});
|
|
402
1768
|
|
|
403
1769
|
it.each([
|
|
404
|
-
() =>
|
|
405
|
-
() =>
|
|
406
|
-
() =>
|
|
407
|
-
() =>
|
|
408
|
-
() =>
|
|
409
|
-
])('tries to retrieves a previously stored transaction after deleted', async
|
|
410
|
-
await store.
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
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);
|
|
414
1780
|
expect(actualTx).toEqual(undefined);
|
|
415
1781
|
});
|
|
416
1782
|
|
|
@@ -419,22 +1785,22 @@ export function describeArchiverDataStore(
|
|
|
419
1785
|
});
|
|
420
1786
|
|
|
421
1787
|
it('does not fail if the block is unwound while requesting a tx', async () => {
|
|
422
|
-
const
|
|
1788
|
+
const txEffect = getBlock(1).body.txEffects[0];
|
|
423
1789
|
let done = false;
|
|
424
1790
|
void (async () => {
|
|
425
1791
|
while (!done) {
|
|
426
|
-
void store.getTxEffect(
|
|
1792
|
+
void store.getTxEffect(txEffect.txHash);
|
|
427
1793
|
await sleep(1);
|
|
428
1794
|
}
|
|
429
1795
|
})();
|
|
430
|
-
await store.
|
|
1796
|
+
await store.unwindCheckpoints(CheckpointNumber(publishedCheckpoints.length), publishedCheckpoints.length);
|
|
431
1797
|
done = true;
|
|
432
|
-
expect(await store.getTxEffect(
|
|
1798
|
+
expect(await store.getTxEffect(txEffect.txHash)).toEqual(undefined);
|
|
433
1799
|
});
|
|
434
1800
|
});
|
|
435
1801
|
|
|
436
1802
|
describe('L1 to L2 Messages', () => {
|
|
437
|
-
const
|
|
1803
|
+
const initialCheckpointNumber = CheckpointNumber(13);
|
|
438
1804
|
|
|
439
1805
|
const checkMessages = async (msgs: InboxMessage[]) => {
|
|
440
1806
|
expect(await store.getLastL1ToL2Message()).toEqual(msgs.at(-1));
|
|
@@ -442,43 +1808,50 @@ export function describeArchiverDataStore(
|
|
|
442
1808
|
expect(await store.getTotalL1ToL2MessageCount()).toEqual(BigInt(msgs.length));
|
|
443
1809
|
};
|
|
444
1810
|
|
|
445
|
-
const makeInboxMessagesWithFullBlocks = (
|
|
1811
|
+
const makeInboxMessagesWithFullBlocks = (
|
|
1812
|
+
blockCount: number,
|
|
1813
|
+
opts: { initialCheckpointNumber?: CheckpointNumber } = {},
|
|
1814
|
+
) =>
|
|
446
1815
|
makeInboxMessages(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * blockCount, {
|
|
447
1816
|
overrideFn: (msg, i) => {
|
|
448
|
-
const
|
|
449
|
-
(opts.
|
|
1817
|
+
const checkpointNumber = CheckpointNumber(
|
|
1818
|
+
(opts.initialCheckpointNumber ?? initialCheckpointNumber) +
|
|
1819
|
+
Math.floor(i / NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP),
|
|
1820
|
+
);
|
|
450
1821
|
const index =
|
|
451
|
-
InboxLeaf.
|
|
452
|
-
return { ...msg,
|
|
1822
|
+
InboxLeaf.smallestIndexForCheckpoint(checkpointNumber) + BigInt(i % NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1823
|
+
return { ...msg, checkpointNumber, index };
|
|
453
1824
|
},
|
|
454
1825
|
});
|
|
455
1826
|
|
|
456
1827
|
it('stores first message ever', async () => {
|
|
457
|
-
const msg = makeInboxMessage(Buffer16.ZERO, { index: 0n,
|
|
1828
|
+
const msg = makeInboxMessage(Buffer16.ZERO, { index: 0n, checkpointNumber: CheckpointNumber(1) });
|
|
458
1829
|
await store.addL1ToL2Messages([msg]);
|
|
459
1830
|
|
|
460
1831
|
await checkMessages([msg]);
|
|
461
|
-
expect(await store.getL1ToL2Messages(1)).toEqual([msg.leaf]);
|
|
1832
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toEqual([msg.leaf]);
|
|
462
1833
|
});
|
|
463
1834
|
|
|
464
1835
|
it('stores single message', async () => {
|
|
465
|
-
const msg = makeInboxMessage(Buffer16.ZERO, {
|
|
1836
|
+
const msg = makeInboxMessage(Buffer16.ZERO, { checkpointNumber: CheckpointNumber(2) });
|
|
466
1837
|
await store.addL1ToL2Messages([msg]);
|
|
467
1838
|
|
|
468
1839
|
await checkMessages([msg]);
|
|
469
|
-
expect(await store.getL1ToL2Messages(2)).toEqual([msg.leaf]);
|
|
1840
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toEqual([msg.leaf]);
|
|
470
1841
|
});
|
|
471
1842
|
|
|
472
1843
|
it('stores and returns messages across different blocks', async () => {
|
|
473
|
-
const msgs = makeInboxMessages(5, {
|
|
1844
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber });
|
|
474
1845
|
await store.addL1ToL2Messages(msgs);
|
|
475
1846
|
|
|
476
1847
|
await checkMessages(msgs);
|
|
477
|
-
expect(await store.getL1ToL2Messages(
|
|
1848
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 2))).toEqual(
|
|
1849
|
+
[msgs[2]].map(m => m.leaf),
|
|
1850
|
+
);
|
|
478
1851
|
});
|
|
479
1852
|
|
|
480
1853
|
it('stores the same messages again', async () => {
|
|
481
|
-
const msgs = makeInboxMessages(5, {
|
|
1854
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber });
|
|
482
1855
|
await store.addL1ToL2Messages(msgs);
|
|
483
1856
|
await store.addL1ToL2Messages(msgs.slice(2));
|
|
484
1857
|
|
|
@@ -486,26 +1859,29 @@ export function describeArchiverDataStore(
|
|
|
486
1859
|
});
|
|
487
1860
|
|
|
488
1861
|
it('stores and returns messages across different blocks with gaps', async () => {
|
|
489
|
-
const msgs1 = makeInboxMessages(3, {
|
|
490
|
-
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
|
+
});
|
|
491
1867
|
|
|
492
1868
|
await store.addL1ToL2Messages(msgs1);
|
|
493
1869
|
await store.addL1ToL2Messages(msgs2);
|
|
494
1870
|
|
|
495
1871
|
await checkMessages([...msgs1, ...msgs2]);
|
|
496
1872
|
|
|
497
|
-
expect(await store.getL1ToL2Messages(1)).toEqual([msgs1[0].leaf]);
|
|
498
|
-
expect(await store.getL1ToL2Messages(4)).toEqual([]);
|
|
499
|
-
expect(await store.getL1ToL2Messages(20)).toEqual([msgs2[0].leaf]);
|
|
500
|
-
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([]);
|
|
501
1877
|
});
|
|
502
1878
|
|
|
503
1879
|
it('stores and returns messages with block numbers larger than a byte', async () => {
|
|
504
|
-
const msgs = makeInboxMessages(5, {
|
|
1880
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber: CheckpointNumber(1000) });
|
|
505
1881
|
await store.addL1ToL2Messages(msgs);
|
|
506
1882
|
|
|
507
1883
|
await checkMessages(msgs);
|
|
508
|
-
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));
|
|
509
1885
|
});
|
|
510
1886
|
|
|
511
1887
|
it('stores and returns multiple messages per block', async () => {
|
|
@@ -513,7 +1889,7 @@ export function describeArchiverDataStore(
|
|
|
513
1889
|
await store.addL1ToL2Messages(msgs);
|
|
514
1890
|
|
|
515
1891
|
await checkMessages(msgs);
|
|
516
|
-
const blockMessages = await store.getL1ToL2Messages(
|
|
1892
|
+
const blockMessages = await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 1));
|
|
517
1893
|
expect(blockMessages).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
518
1894
|
expect(blockMessages).toEqual(
|
|
519
1895
|
msgs.slice(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2).map(m => m.leaf),
|
|
@@ -521,17 +1897,21 @@ export function describeArchiverDataStore(
|
|
|
521
1897
|
});
|
|
522
1898
|
|
|
523
1899
|
it('stores messages in multiple operations', async () => {
|
|
524
|
-
const msgs = makeInboxMessages(20, {
|
|
1900
|
+
const msgs = makeInboxMessages(20, { initialCheckpointNumber });
|
|
525
1901
|
await store.addL1ToL2Messages(msgs.slice(0, 10));
|
|
526
1902
|
await store.addL1ToL2Messages(msgs.slice(10, 20));
|
|
527
1903
|
|
|
528
|
-
expect(await store.getL1ToL2Messages(
|
|
529
|
-
|
|
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
|
+
);
|
|
530
1910
|
await checkMessages(msgs);
|
|
531
1911
|
});
|
|
532
1912
|
|
|
533
1913
|
it('iterates over messages from start index', async () => {
|
|
534
|
-
const msgs = makeInboxMessages(10, {
|
|
1914
|
+
const msgs = makeInboxMessages(10, { initialCheckpointNumber });
|
|
535
1915
|
await store.addL1ToL2Messages(msgs);
|
|
536
1916
|
|
|
537
1917
|
const iterated = await toArray(store.iterateL1ToL2Messages({ start: msgs[3].index }));
|
|
@@ -539,8 +1919,9 @@ export function describeArchiverDataStore(
|
|
|
539
1919
|
});
|
|
540
1920
|
|
|
541
1921
|
it('iterates over messages in reverse', async () => {
|
|
542
|
-
const msgs = makeInboxMessages(10, {
|
|
1922
|
+
const msgs = makeInboxMessages(10, { initialCheckpointNumber });
|
|
543
1923
|
await store.addL1ToL2Messages(msgs);
|
|
1924
|
+
initialCheckpointNumber;
|
|
544
1925
|
|
|
545
1926
|
const iterated = await toArray(store.iterateL1ToL2Messages({ reverse: true, end: msgs[3].index }));
|
|
546
1927
|
expect(iterated).toEqual(msgs.slice(0, 4).reverse());
|
|
@@ -552,8 +1933,8 @@ export function describeArchiverDataStore(
|
|
|
552
1933
|
});
|
|
553
1934
|
|
|
554
1935
|
it('throws if block number for the first message is out of order', async () => {
|
|
555
|
-
const msgs = makeInboxMessages(4, {
|
|
556
|
-
msgs[2].
|
|
1936
|
+
const msgs = makeInboxMessages(4, { initialCheckpointNumber });
|
|
1937
|
+
msgs[2].checkpointNumber = CheckpointNumber(initialCheckpointNumber - 1);
|
|
557
1938
|
await store.addL1ToL2Messages(msgs.slice(0, 2));
|
|
558
1939
|
await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
|
|
559
1940
|
});
|
|
@@ -567,28 +1948,28 @@ export function describeArchiverDataStore(
|
|
|
567
1948
|
it('throws if rolling hash for first message is not correct', async () => {
|
|
568
1949
|
const msgs = makeInboxMessages(4);
|
|
569
1950
|
msgs[2].rollingHash = Buffer16.random();
|
|
570
|
-
await store.addL1ToL2Messages(msgs.slice(0, 2));
|
|
1951
|
+
await store.addL1ToL2Messages(msgs.slice(0, CheckpointNumber(2)));
|
|
571
1952
|
await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
|
|
572
1953
|
});
|
|
573
1954
|
|
|
574
1955
|
it('throws if index is not in the correct range', async () => {
|
|
575
|
-
const msgs = makeInboxMessages(5, {
|
|
1956
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber });
|
|
576
1957
|
msgs.at(-1)!.index += 100n;
|
|
577
1958
|
await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
|
|
578
1959
|
});
|
|
579
1960
|
|
|
580
1961
|
it('throws if first index in block has gaps', async () => {
|
|
581
|
-
const msgs = makeInboxMessages(4, {
|
|
1962
|
+
const msgs = makeInboxMessages(4, { initialCheckpointNumber });
|
|
582
1963
|
msgs[2].index++;
|
|
583
1964
|
await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
|
|
584
1965
|
});
|
|
585
1966
|
|
|
586
1967
|
it('throws if index does not follow previous one', async () => {
|
|
587
1968
|
const msgs = makeInboxMessages(2, {
|
|
588
|
-
|
|
1969
|
+
initialCheckpointNumber,
|
|
589
1970
|
overrideFn: (msg, i) => ({
|
|
590
1971
|
...msg,
|
|
591
|
-
|
|
1972
|
+
checkpointNumber: CheckpointNumber(2),
|
|
592
1973
|
index: BigInt(i + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2),
|
|
593
1974
|
}),
|
|
594
1975
|
});
|
|
@@ -597,28 +1978,28 @@ export function describeArchiverDataStore(
|
|
|
597
1978
|
});
|
|
598
1979
|
|
|
599
1980
|
it('removes messages up to the given block number', async () => {
|
|
600
|
-
const msgs = makeInboxMessagesWithFullBlocks(4, {
|
|
1981
|
+
const msgs = makeInboxMessagesWithFullBlocks(4, { initialCheckpointNumber: CheckpointNumber(1) });
|
|
601
1982
|
|
|
602
1983
|
await store.addL1ToL2Messages(msgs);
|
|
603
1984
|
await checkMessages(msgs);
|
|
604
1985
|
|
|
605
|
-
expect(await store.getL1ToL2Messages(1)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
606
|
-
expect(await store.getL1ToL2Messages(2)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
607
|
-
expect(await store.getL1ToL2Messages(3)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
608
|
-
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);
|
|
609
1990
|
|
|
610
|
-
await store.
|
|
1991
|
+
await store.rollbackL1ToL2MessagesToCheckpoint(CheckpointNumber(2));
|
|
611
1992
|
|
|
612
|
-
expect(await store.getL1ToL2Messages(1)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
613
|
-
expect(await store.getL1ToL2Messages(2)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
614
|
-
expect(await store.getL1ToL2Messages(3)).toHaveLength(0);
|
|
615
|
-
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);
|
|
616
1997
|
|
|
617
1998
|
await checkMessages(msgs.slice(0, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2));
|
|
618
1999
|
});
|
|
619
2000
|
|
|
620
2001
|
it('removes messages starting with the given index', async () => {
|
|
621
|
-
const msgs = makeInboxMessagesWithFullBlocks(4, {
|
|
2002
|
+
const msgs = makeInboxMessagesWithFullBlocks(4, { initialCheckpointNumber: CheckpointNumber(1) });
|
|
622
2003
|
await store.addL1ToL2Messages(msgs);
|
|
623
2004
|
|
|
624
2005
|
await store.removeL1ToL2Messages(msgs[13].index);
|
|
@@ -638,7 +2019,7 @@ export function describeArchiverDataStore(
|
|
|
638
2019
|
originalContractClassId: classId,
|
|
639
2020
|
});
|
|
640
2021
|
contractInstance = { ...randomInstance, address: await AztecAddress.random() };
|
|
641
|
-
await store.addContractInstances([contractInstance], blockNum);
|
|
2022
|
+
await store.addContractInstances([contractInstance], BlockNumber(blockNum));
|
|
642
2023
|
});
|
|
643
2024
|
|
|
644
2025
|
it('returns previously stored contract instances', async () => {
|
|
@@ -652,7 +2033,7 @@ export function describeArchiverDataStore(
|
|
|
652
2033
|
});
|
|
653
2034
|
|
|
654
2035
|
it('returns undefined if previously stored contract instances was deleted', async () => {
|
|
655
|
-
await store.deleteContractInstances([contractInstance], blockNum);
|
|
2036
|
+
await store.deleteContractInstances([contractInstance], BlockNumber(blockNum));
|
|
656
2037
|
await expect(store.getContractInstance(contractInstance.address, timestamp)).resolves.toBeUndefined();
|
|
657
2038
|
});
|
|
658
2039
|
});
|
|
@@ -671,7 +2052,7 @@ export function describeArchiverDataStore(
|
|
|
671
2052
|
originalContractClassId: classId,
|
|
672
2053
|
});
|
|
673
2054
|
contractInstance = { ...randomInstance, address: await AztecAddress.random() };
|
|
674
|
-
await store.addContractInstances([contractInstance], 1);
|
|
2055
|
+
await store.addContractInstances([contractInstance], BlockNumber(1));
|
|
675
2056
|
await store.addContractInstanceUpdates(
|
|
676
2057
|
[
|
|
677
2058
|
{
|
|
@@ -713,7 +2094,7 @@ export function describeArchiverDataStore(
|
|
|
713
2094
|
...randomInstance,
|
|
714
2095
|
address: await AztecAddress.random(),
|
|
715
2096
|
};
|
|
716
|
-
await store.addContractInstances([otherContractInstance], 1);
|
|
2097
|
+
await store.addContractInstances([otherContractInstance], BlockNumber(1));
|
|
717
2098
|
|
|
718
2099
|
const fetchedInstance = await store.getContractInstance(otherContractInstance.address, timestampOfChange + 1n);
|
|
719
2100
|
expect(fetchedInstance?.originalContractClassId).toEqual(otherClassId);
|
|
@@ -731,7 +2112,7 @@ export function describeArchiverDataStore(
|
|
|
731
2112
|
...randomInstance,
|
|
732
2113
|
address: await AztecAddress.random(),
|
|
733
2114
|
};
|
|
734
|
-
await store.addContractInstances([otherContractInstance], 1);
|
|
2115
|
+
await store.addContractInstances([otherContractInstance], BlockNumber(1));
|
|
735
2116
|
await store.addContractInstanceUpdates(
|
|
736
2117
|
[
|
|
737
2118
|
{
|
|
@@ -759,7 +2140,7 @@ export function describeArchiverDataStore(
|
|
|
759
2140
|
await store.addContractClasses(
|
|
760
2141
|
[contractClass],
|
|
761
2142
|
[await computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
762
|
-
blockNum,
|
|
2143
|
+
BlockNumber(blockNum),
|
|
763
2144
|
);
|
|
764
2145
|
});
|
|
765
2146
|
|
|
@@ -768,7 +2149,7 @@ export function describeArchiverDataStore(
|
|
|
768
2149
|
});
|
|
769
2150
|
|
|
770
2151
|
it('returns undefined if the initial deployed contract class was deleted', async () => {
|
|
771
|
-
await store.deleteContractClasses([contractClass], blockNum);
|
|
2152
|
+
await store.deleteContractClasses([contractClass], BlockNumber(blockNum));
|
|
772
2153
|
await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined();
|
|
773
2154
|
});
|
|
774
2155
|
|
|
@@ -776,9 +2157,9 @@ export function describeArchiverDataStore(
|
|
|
776
2157
|
await store.addContractClasses(
|
|
777
2158
|
[contractClass],
|
|
778
2159
|
[await computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
779
|
-
blockNum + 1,
|
|
2160
|
+
BlockNumber(blockNum + 1),
|
|
780
2161
|
);
|
|
781
|
-
await store.deleteContractClasses([contractClass], blockNum + 1);
|
|
2162
|
+
await store.deleteContractClasses([contractClass], BlockNumber(blockNum + 1));
|
|
782
2163
|
await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
|
|
783
2164
|
});
|
|
784
2165
|
|
|
@@ -817,154 +2198,269 @@ export function describeArchiverDataStore(
|
|
|
817
2198
|
});
|
|
818
2199
|
});
|
|
819
2200
|
|
|
820
|
-
describe('
|
|
821
|
-
const
|
|
2201
|
+
describe('getPrivateLogsByTags', () => {
|
|
2202
|
+
const numBlocksForLogs = 3;
|
|
822
2203
|
const numTxsPerBlock = 4;
|
|
823
2204
|
const numPrivateLogsPerTx = 3;
|
|
824
|
-
const numPublicLogsPerTx = 2;
|
|
825
2205
|
|
|
826
|
-
let
|
|
2206
|
+
let logsCheckpoints: PublishedCheckpoint[];
|
|
827
2207
|
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
+
);
|
|
832
2214
|
|
|
833
|
-
const makePrivateLog = (tag:
|
|
2215
|
+
const makePrivateLog = (tag: SiloedTag) =>
|
|
834
2216
|
PrivateLog.from({
|
|
835
|
-
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
|
+
),
|
|
836
2220
|
emittedLength: PRIVATE_LOG_SIZE_IN_FIELDS,
|
|
837
2221
|
});
|
|
838
2222
|
|
|
839
|
-
const makePublicLog = (tag: Fr) =>
|
|
840
|
-
PublicLog.from({
|
|
841
|
-
contractAddress: AztecAddress.fromNumber(1),
|
|
842
|
-
// Arbitrary length
|
|
843
|
-
fields: new Array(10).fill(null).map((_, i) => (!i ? tag : new Fr(tag.toNumber() + i))),
|
|
844
|
-
});
|
|
845
|
-
|
|
846
2223
|
const mockPrivateLogs = (blockNumber: number, txIndex: number) => {
|
|
847
2224
|
return times(numPrivateLogsPerTx, (logIndex: number) => {
|
|
848
|
-
const tag =
|
|
2225
|
+
const tag = makePrivateLogTag(blockNumber, txIndex, logIndex);
|
|
849
2226
|
return makePrivateLog(tag);
|
|
850
2227
|
});
|
|
851
2228
|
};
|
|
852
2229
|
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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 } : {}),
|
|
857
2239
|
});
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
const mockBlockWithLogs = async (blockNumber: number): Promise<PublishedL2Block> => {
|
|
861
|
-
const block = await L2Block.random(blockNumber);
|
|
862
|
-
block.header.globalVariables.blockNumber = blockNumber;
|
|
2240
|
+
block.header.globalVariables.blockNumber = BlockNumber(blockNumber);
|
|
863
2241
|
|
|
864
2242
|
block.body.txEffects = await timesParallel(numTxsPerBlock, async (txIndex: number) => {
|
|
865
2243
|
const txEffect = await TxEffect.random();
|
|
866
2244
|
txEffect.privateLogs = mockPrivateLogs(blockNumber, txIndex);
|
|
867
|
-
txEffect.publicLogs =
|
|
2245
|
+
txEffect.publicLogs = []; // No public logs needed for private log tests
|
|
868
2246
|
return txEffect;
|
|
869
2247
|
});
|
|
870
2248
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
},
|
|
879
|
-
});
|
|
2249
|
+
const checkpoint = new Checkpoint(
|
|
2250
|
+
AppendOnlyTreeSnapshot.random(),
|
|
2251
|
+
CheckpointHeader.random(),
|
|
2252
|
+
[block],
|
|
2253
|
+
CheckpointNumber(blockNumber),
|
|
2254
|
+
);
|
|
2255
|
+
return makePublishedCheckpoint(checkpoint, blockNumber);
|
|
880
2256
|
};
|
|
881
2257
|
|
|
882
2258
|
beforeEach(async () => {
|
|
883
|
-
|
|
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
|
+
}
|
|
884
2265
|
|
|
885
|
-
await store.
|
|
886
|
-
await store.addLogs(
|
|
2266
|
+
await store.addCheckpoints(logsCheckpoints);
|
|
2267
|
+
await store.addLogs(logsCheckpoints.flatMap(p => p.checkpoint.blocks));
|
|
887
2268
|
});
|
|
888
2269
|
|
|
889
2270
|
it('is possible to batch request private logs via tags', async () => {
|
|
890
|
-
const tags = [
|
|
2271
|
+
const tags = [makePrivateLogTag(2, 1, 2), makePrivateLogTag(1, 2, 0)];
|
|
891
2272
|
|
|
892
|
-
const logsByTags = await store.
|
|
2273
|
+
const logsByTags = await store.getPrivateLogsByTags(tags);
|
|
893
2274
|
|
|
894
2275
|
expect(logsByTags).toEqual([
|
|
895
2276
|
[
|
|
896
2277
|
expect.objectContaining({
|
|
897
2278
|
blockNumber: 2,
|
|
898
|
-
|
|
899
|
-
isFromPublic: false,
|
|
2279
|
+
logData: makePrivateLog(tags[0]).getEmittedFields(),
|
|
900
2280
|
}),
|
|
901
2281
|
],
|
|
902
2282
|
[
|
|
903
2283
|
expect.objectContaining({
|
|
904
2284
|
blockNumber: 1,
|
|
905
|
-
|
|
906
|
-
isFromPublic: false,
|
|
2285
|
+
logData: makePrivateLog(tags[1]).getEmittedFields(),
|
|
907
2286
|
}),
|
|
908
2287
|
],
|
|
909
2288
|
]);
|
|
910
2289
|
});
|
|
911
2290
|
|
|
912
|
-
it('is possible to batch request
|
|
913
|
-
|
|
914
|
-
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)];
|
|
915
2293
|
|
|
916
|
-
|
|
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);
|
|
917
2306
|
|
|
918
2307
|
expect(logsByTags).toEqual([
|
|
919
2308
|
[
|
|
920
2309
|
expect.objectContaining({
|
|
921
2310
|
blockNumber: 1,
|
|
922
|
-
|
|
923
|
-
|
|
2311
|
+
logData: makePrivateLog(tags[0]).getEmittedFields(),
|
|
2312
|
+
}),
|
|
2313
|
+
expect.objectContaining({
|
|
2314
|
+
blockNumber: newBlockNumber,
|
|
2315
|
+
logData: newLog.getEmittedFields(),
|
|
924
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
|
+
[
|
|
925
2331
|
expect.objectContaining({
|
|
926
2332
|
blockNumber: 1,
|
|
927
|
-
|
|
928
|
-
|
|
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(),
|
|
929
2425
|
}),
|
|
930
2426
|
],
|
|
931
2427
|
]);
|
|
932
2428
|
});
|
|
933
2429
|
|
|
934
2430
|
it('is possible to batch request logs that have the same tag but different content', async () => {
|
|
935
|
-
const tags = [
|
|
2431
|
+
const tags = [makePublicLogTag(1, 2, 1)];
|
|
936
2432
|
|
|
937
|
-
// Create a
|
|
938
|
-
|
|
939
|
-
const
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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]]);
|
|
945
2443
|
|
|
946
|
-
const logsByTags = await store.
|
|
2444
|
+
const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
|
|
947
2445
|
|
|
948
2446
|
expect(logsByTags).toEqual([
|
|
949
2447
|
[
|
|
950
2448
|
expect.objectContaining({
|
|
951
2449
|
blockNumber: 1,
|
|
952
|
-
|
|
953
|
-
isFromPublic: false,
|
|
2450
|
+
logData: makePublicLog(tags[0]).getEmittedFields(),
|
|
954
2451
|
}),
|
|
955
2452
|
expect.objectContaining({
|
|
956
2453
|
blockNumber: newBlockNumber,
|
|
957
|
-
|
|
958
|
-
isFromPublic: false,
|
|
2454
|
+
logData: newLog.getEmittedFields(),
|
|
959
2455
|
}),
|
|
960
2456
|
],
|
|
961
2457
|
]);
|
|
962
2458
|
});
|
|
963
2459
|
|
|
964
2460
|
it('is possible to request logs for non-existing tags and determine their position', async () => {
|
|
965
|
-
const tags = [
|
|
2461
|
+
const tags = [makePublicLogTag(99, 88, 77), makePublicLogTag(1, 1, 0)];
|
|
966
2462
|
|
|
967
|
-
const logsByTags = await store.
|
|
2463
|
+
const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
|
|
968
2464
|
|
|
969
2465
|
expect(logsByTags).toEqual([
|
|
970
2466
|
[
|
|
@@ -973,8 +2469,7 @@ export function describeArchiverDataStore(
|
|
|
973
2469
|
[
|
|
974
2470
|
expect.objectContaining({
|
|
975
2471
|
blockNumber: 1,
|
|
976
|
-
|
|
977
|
-
isFromPublic: false,
|
|
2472
|
+
logData: makePublicLog(tags[1]).getEmittedFields(),
|
|
978
2473
|
}),
|
|
979
2474
|
],
|
|
980
2475
|
]);
|
|
@@ -982,34 +2477,33 @@ export function describeArchiverDataStore(
|
|
|
982
2477
|
});
|
|
983
2478
|
|
|
984
2479
|
describe('getPublicLogs', () => {
|
|
985
|
-
const
|
|
986
|
-
const numPublicFunctionCalls = 3;
|
|
987
|
-
const numPublicLogs = 2;
|
|
988
|
-
const numBlocks = 10;
|
|
989
|
-
let blocks: PublishedL2Block[];
|
|
2480
|
+
const numBlocksForPublicLogs = 10;
|
|
990
2481
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
block: await L2Block.random(index + 1, txsPerBlock, numPublicFunctionCalls, numPublicLogs),
|
|
995
|
-
l1: { blockNumber: BigInt(index), blockHash: makeBlockHash(index), timestamp: BigInt(index) },
|
|
996
|
-
attestations: times(3, CommitteeAttestation.random),
|
|
997
|
-
}),
|
|
998
|
-
);
|
|
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;
|
|
999
2485
|
|
|
1000
|
-
|
|
1001
|
-
|
|
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
|
+
}
|
|
1002
2495
|
});
|
|
1003
2496
|
|
|
1004
2497
|
it('no logs returned if deleted ("txHash" filter param is respected variant)', async () => {
|
|
1005
2498
|
// get random tx
|
|
1006
|
-
const targetBlockIndex = randomInt(
|
|
1007
|
-
const
|
|
1008
|
-
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;
|
|
1009
2503
|
|
|
1010
2504
|
await Promise.all([
|
|
1011
|
-
store.
|
|
1012
|
-
store.deleteLogs(
|
|
2505
|
+
store.unwindCheckpoints(CheckpointNumber(numBlocksForPublicLogs), numBlocksForPublicLogs),
|
|
2506
|
+
store.deleteLogs(publishedCheckpoints.slice(0, numBlocksForPublicLogs).flatMap(b => b.checkpoint.blocks)),
|
|
1013
2507
|
]);
|
|
1014
2508
|
|
|
1015
2509
|
const response = await store.getPublicLogs({ txHash: targetTxHash });
|
|
@@ -1021,16 +2515,17 @@ export function describeArchiverDataStore(
|
|
|
1021
2515
|
|
|
1022
2516
|
it('"txHash" filter param is respected', async () => {
|
|
1023
2517
|
// get random tx
|
|
1024
|
-
const targetBlockIndex = randomInt(
|
|
1025
|
-
const
|
|
1026
|
-
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;
|
|
1027
2522
|
|
|
1028
2523
|
const response = await store.getPublicLogs({ txHash: targetTxHash });
|
|
1029
2524
|
const logs = response.logs;
|
|
1030
2525
|
|
|
1031
2526
|
expect(response.maxLogsHit).toBeFalsy();
|
|
1032
2527
|
|
|
1033
|
-
const expectedNumLogs =
|
|
2528
|
+
const expectedNumLogs = getPublicLogsPerTx(targetBlock, targetTxIndex);
|
|
1034
2529
|
expect(logs.length).toEqual(expectedNumLogs);
|
|
1035
2530
|
|
|
1036
2531
|
const targeBlockNumber = targetBlockIndex + INITIAL_L2_BLOCK_NUM;
|
|
@@ -1040,6 +2535,17 @@ export function describeArchiverDataStore(
|
|
|
1040
2535
|
}
|
|
1041
2536
|
});
|
|
1042
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
|
+
|
|
1043
2549
|
it('"fromBlock" and "toBlock" filter params are respected', async () => {
|
|
1044
2550
|
// Set "fromBlock" and "toBlock"
|
|
1045
2551
|
const fromBlock = 3;
|
|
@@ -1050,7 +2556,12 @@ export function describeArchiverDataStore(
|
|
|
1050
2556
|
|
|
1051
2557
|
expect(response.maxLogsHit).toBeFalsy();
|
|
1052
2558
|
|
|
1053
|
-
|
|
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
|
+
}
|
|
1054
2565
|
expect(logs.length).toEqual(expectedNumLogs);
|
|
1055
2566
|
|
|
1056
2567
|
for (const log of logs) {
|
|
@@ -1062,11 +2573,12 @@ export function describeArchiverDataStore(
|
|
|
1062
2573
|
|
|
1063
2574
|
it('"contractAddress" filter param is respected', async () => {
|
|
1064
2575
|
// Get a random contract address from the logs
|
|
1065
|
-
const targetBlockIndex = randomInt(
|
|
1066
|
-
const
|
|
1067
|
-
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));
|
|
1068
2580
|
const targetContractAddress =
|
|
1069
|
-
|
|
2581
|
+
targetBlock.body.txEffects[targetTxIndex].publicLogs[targetLogIndex].contractAddress;
|
|
1070
2582
|
|
|
1071
2583
|
const response = await store.getPublicLogs({ contractAddress: targetContractAddress });
|
|
1072
2584
|
|
|
@@ -1079,11 +2591,19 @@ export function describeArchiverDataStore(
|
|
|
1079
2591
|
|
|
1080
2592
|
it('"afterLog" filter param is respected', async () => {
|
|
1081
2593
|
// Get a random log as reference
|
|
1082
|
-
const targetBlockIndex = randomInt(
|
|
1083
|
-
const
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
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
|
+
);
|
|
1087
2607
|
|
|
1088
2608
|
const response = await store.getPublicLogs({ afterLog });
|
|
1089
2609
|
const logs = response.logs;
|
|
@@ -1105,52 +2625,77 @@ export function describeArchiverDataStore(
|
|
|
1105
2625
|
it('"txHash" filter param is ignored when "afterLog" is set', async () => {
|
|
1106
2626
|
// Get random txHash
|
|
1107
2627
|
const txHash = TxHash.random();
|
|
1108
|
-
const afterLog = new LogId(1, 0, 0);
|
|
2628
|
+
const afterLog = new LogId(BlockNumber(1), L2BlockHash.random(), 0, 0);
|
|
1109
2629
|
|
|
1110
2630
|
const response = await store.getPublicLogs({ txHash, afterLog });
|
|
1111
2631
|
expect(response.logs.length).toBeGreaterThan(1);
|
|
1112
2632
|
});
|
|
1113
2633
|
|
|
1114
2634
|
it('intersecting works', async () => {
|
|
1115
|
-
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;
|
|
1116
2636
|
expect(logs.length).toBe(0);
|
|
1117
2637
|
|
|
1118
2638
|
// "fromBlock" gets correctly trimmed to range and "toBlock" is exclusive
|
|
1119
|
-
logs = (await store.getPublicLogs({ fromBlock: -10, toBlock: 5 })).logs;
|
|
2639
|
+
logs = (await store.getPublicLogs({ fromBlock: -10 as BlockNumber, toBlock: BlockNumber(5) })).logs;
|
|
1120
2640
|
let blockNumbers = new Set(logs.map(log => log.id.blockNumber));
|
|
1121
2641
|
expect(blockNumbers).toEqual(new Set([1, 2, 3, 4]));
|
|
1122
2642
|
|
|
1123
2643
|
// "toBlock" should be exclusive
|
|
1124
|
-
logs = (await store.getPublicLogs({ fromBlock: 1, toBlock: 1 })).logs;
|
|
2644
|
+
logs = (await store.getPublicLogs({ fromBlock: BlockNumber(1), toBlock: BlockNumber(1) })).logs;
|
|
1125
2645
|
expect(logs.length).toBe(0);
|
|
1126
2646
|
|
|
1127
|
-
logs = (await store.getPublicLogs({ fromBlock: 10, toBlock: 5 })).logs;
|
|
2647
|
+
logs = (await store.getPublicLogs({ fromBlock: BlockNumber(10), toBlock: BlockNumber(5) })).logs;
|
|
1128
2648
|
expect(logs.length).toBe(0);
|
|
1129
2649
|
|
|
1130
2650
|
// both "fromBlock" and "toBlock" get correctly capped to range and logs from all blocks are returned
|
|
1131
|
-
logs = (await store.getPublicLogs({ fromBlock: -100, toBlock: +100 })).logs;
|
|
2651
|
+
logs = (await store.getPublicLogs({ fromBlock: -100 as BlockNumber, toBlock: +100 })).logs;
|
|
1132
2652
|
blockNumbers = new Set(logs.map(log => log.id.blockNumber));
|
|
1133
|
-
expect(blockNumbers.size).toBe(
|
|
2653
|
+
expect(blockNumbers.size).toBe(numBlocksForPublicLogs);
|
|
1134
2654
|
|
|
1135
2655
|
// intersecting with "afterLog" works
|
|
1136
|
-
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;
|
|
1137
2663
|
blockNumbers = new Set(logs.map(log => log.id.blockNumber));
|
|
1138
2664
|
expect(blockNumbers).toEqual(new Set([4]));
|
|
1139
2665
|
|
|
1140
|
-
logs = (
|
|
2666
|
+
logs = (
|
|
2667
|
+
await store.getPublicLogs({
|
|
2668
|
+
toBlock: BlockNumber(5),
|
|
2669
|
+
afterLog: new LogId(BlockNumber(5), L2BlockHash.random(), 1, 0),
|
|
2670
|
+
})
|
|
2671
|
+
).logs;
|
|
1141
2672
|
expect(logs.length).toBe(0);
|
|
1142
2673
|
|
|
1143
|
-
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;
|
|
1144
2681
|
expect(logs.length).toBe(0);
|
|
1145
2682
|
});
|
|
1146
2683
|
|
|
1147
2684
|
it('"txIndex" and "logIndex" are respected when "afterLog.blockNumber" is equal to "fromBlock"', async () => {
|
|
1148
2685
|
// Get a random log as reference
|
|
1149
|
-
const targetBlockIndex = randomInt(
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
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
|
+
);
|
|
1154
2699
|
|
|
1155
2700
|
const response = await store.getPublicLogs({ afterLog, fromBlock: afterLog.blockNumber });
|
|
1156
2701
|
const logs = response.logs;
|
|
@@ -1170,6 +2715,40 @@ export function describeArchiverDataStore(
|
|
|
1170
2715
|
});
|
|
1171
2716
|
});
|
|
1172
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
|
+
|
|
1173
2752
|
describe('pendingChainValidationStatus', () => {
|
|
1174
2753
|
it('should return undefined when no status is set', async () => {
|
|
1175
2754
|
const status = await store.getPendingChainValidationStatus();
|