@aztec/archiver 0.0.0-test.1 → 0.0.1-commit.1142ef1
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/README.md +27 -6
- package/dest/archiver/archiver.d.ts +204 -94
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +1616 -414
- package/dest/archiver/archiver_store.d.ts +178 -83
- 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 +2373 -397
- package/dest/archiver/config.d.ts +7 -22
- package/dest/archiver/config.d.ts.map +1 -1
- package/dest/archiver/config.js +30 -14
- package/dest/archiver/errors.d.ts +33 -1
- package/dest/archiver/errors.d.ts.map +1 -1
- package/dest/archiver/errors.js +49 -0
- package/dest/archiver/index.d.ts +3 -4
- package/dest/archiver/index.d.ts.map +1 -1
- package/dest/archiver/index.js +1 -2
- package/dest/archiver/instrumentation.d.ts +14 -6
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/instrumentation.js +45 -41
- package/dest/archiver/kv_archiver_store/block_store.d.ts +98 -21
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +495 -86
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +4 -4
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +13 -19
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +12 -9
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +30 -16
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +81 -75
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +145 -83
- 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 +151 -114
- package/dest/archiver/kv_archiver_store/message_store.d.ts +25 -18
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +152 -49
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts +3 -0
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts.map +1 -0
- package/dest/archiver/l1/bin/retrieve-calldata.js +149 -0
- package/dest/archiver/l1/calldata_retriever.d.ts +112 -0
- package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -0
- package/dest/archiver/l1/calldata_retriever.js +471 -0
- package/dest/archiver/l1/data_retrieval.d.ts +90 -0
- package/dest/archiver/l1/data_retrieval.d.ts.map +1 -0
- package/dest/archiver/l1/data_retrieval.js +331 -0
- package/dest/archiver/l1/debug_tx.d.ts +19 -0
- package/dest/archiver/l1/debug_tx.d.ts.map +1 -0
- package/dest/archiver/l1/debug_tx.js +73 -0
- package/dest/archiver/l1/spire_proposer.d.ts +70 -0
- package/dest/archiver/l1/spire_proposer.d.ts.map +1 -0
- package/dest/archiver/l1/spire_proposer.js +157 -0
- package/dest/archiver/l1/trace_tx.d.ts +97 -0
- package/dest/archiver/l1/trace_tx.d.ts.map +1 -0
- package/dest/archiver/l1/trace_tx.js +91 -0
- package/dest/archiver/l1/types.d.ts +12 -0
- package/dest/archiver/l1/types.d.ts.map +1 -0
- package/dest/archiver/l1/types.js +3 -0
- package/dest/archiver/l1/validate_trace.d.ts +29 -0
- package/dest/archiver/l1/validate_trace.d.ts.map +1 -0
- package/dest/archiver/l1/validate_trace.js +150 -0
- package/dest/archiver/structs/data_retrieval.d.ts +1 -1
- package/dest/archiver/structs/inbox_message.d.ts +15 -0
- package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
- package/dest/archiver/structs/inbox_message.js +39 -0
- package/dest/archiver/structs/published.d.ts +2 -11
- package/dest/archiver/structs/published.d.ts.map +1 -1
- package/dest/archiver/structs/published.js +1 -1
- package/dest/archiver/validation.d.ts +17 -0
- package/dest/archiver/validation.d.ts.map +1 -0
- package/dest/archiver/validation.js +98 -0
- package/dest/factory.d.ts +9 -14
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +22 -52
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/rpc/index.d.ts +2 -3
- package/dest/rpc/index.d.ts.map +1 -1
- package/dest/rpc/index.js +1 -4
- package/dest/test/index.d.ts +1 -1
- package/dest/test/mock_archiver.d.ts +16 -8
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_archiver.js +19 -14
- package/dest/test/mock_l1_to_l2_message_source.d.ts +9 -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 +30 -7
- package/dest/test/mock_l2_block_source.d.ts +56 -13
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +196 -25
- package/dest/test/mock_structs.d.ts +10 -0
- package/dest/test/mock_structs.d.ts.map +1 -0
- package/dest/test/mock_structs.js +38 -0
- package/package.json +29 -30
- package/src/archiver/archiver.ts +1596 -512
- package/src/archiver/archiver_store.ts +205 -88
- package/src/archiver/archiver_store_test_suite.ts +2386 -354
- package/src/archiver/config.ts +38 -46
- package/src/archiver/errors.ts +85 -0
- package/src/archiver/index.ts +2 -3
- package/src/archiver/instrumentation.ts +65 -45
- package/src/archiver/kv_archiver_store/block_store.ts +668 -101
- package/src/archiver/kv_archiver_store/contract_class_store.ts +14 -24
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +36 -28
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +197 -113
- package/src/archiver/kv_archiver_store/log_store.ts +204 -132
- package/src/archiver/kv_archiver_store/message_store.ts +213 -54
- package/src/archiver/l1/README.md +98 -0
- package/src/archiver/l1/bin/retrieve-calldata.ts +182 -0
- package/src/archiver/l1/calldata_retriever.ts +641 -0
- package/src/archiver/l1/data_retrieval.ts +512 -0
- package/src/archiver/l1/debug_tx.ts +99 -0
- package/src/archiver/l1/spire_proposer.ts +160 -0
- package/src/archiver/l1/trace_tx.ts +128 -0
- package/src/archiver/l1/types.ts +13 -0
- package/src/archiver/l1/validate_trace.ts +211 -0
- package/src/archiver/structs/inbox_message.ts +41 -0
- package/src/archiver/structs/published.ts +1 -11
- package/src/archiver/validation.ts +124 -0
- package/src/factory.ts +28 -69
- package/src/index.ts +1 -1
- package/src/rpc/index.ts +1 -5
- 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 +26 -8
- package/src/test/mock_l2_block_source.ts +254 -31
- package/src/test/mock_structs.ts +50 -0
- package/dest/archiver/data_retrieval.d.ts +0 -74
- package/dest/archiver/data_retrieval.d.ts.map +0 -1
- package/dest/archiver/data_retrieval.js +0 -283
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +0 -12
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +0 -1
- package/dest/archiver/kv_archiver_store/nullifier_store.js +0 -73
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +0 -175
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +0 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.js +0 -636
- package/src/archiver/data_retrieval.ts +0 -422
- package/src/archiver/kv_archiver_store/nullifier_store.ts +0 -97
- package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
- package/src/archiver/memory_archiver_store/memory_archiver_store.ts +0 -801
|
@@ -1,33 +1,59 @@
|
|
|
1
1
|
import {
|
|
2
|
+
INITIAL_CHECKPOINT_NUMBER,
|
|
2
3
|
INITIAL_L2_BLOCK_NUM,
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
MAX_NOTE_HASHES_PER_TX,
|
|
5
|
+
NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
|
|
5
6
|
PRIVATE_LOG_SIZE_IN_FIELDS,
|
|
6
|
-
PUBLIC_LOG_DATA_SIZE_IN_FIELDS,
|
|
7
7
|
} from '@aztec/constants';
|
|
8
|
+
import { makeTuple } from '@aztec/foundation/array';
|
|
9
|
+
import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
10
|
+
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
8
11
|
import { times, timesParallel } from '@aztec/foundation/collection';
|
|
9
|
-
import { randomInt } from '@aztec/foundation/crypto';
|
|
10
|
-
import { Fr } from '@aztec/foundation/
|
|
12
|
+
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
13
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
14
|
+
import { toArray } from '@aztec/foundation/iterable';
|
|
15
|
+
import { sleep } from '@aztec/foundation/sleep';
|
|
11
16
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
12
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
CheckpointedL2Block,
|
|
19
|
+
CommitteeAttestation,
|
|
20
|
+
EthAddress,
|
|
21
|
+
L2BlockHash,
|
|
22
|
+
L2BlockNew,
|
|
23
|
+
type ValidateCheckpointResult,
|
|
24
|
+
} from '@aztec/stdlib/block';
|
|
25
|
+
import { Checkpoint, L1PublishedData, PublishedCheckpoint, randomCheckpointInfo } from '@aztec/stdlib/checkpoint';
|
|
13
26
|
import {
|
|
14
27
|
type ContractClassPublic,
|
|
15
28
|
type ContractInstanceWithAddress,
|
|
16
29
|
SerializableContractInstance,
|
|
17
30
|
computePublicBytecodeCommitment,
|
|
18
31
|
} from '@aztec/stdlib/contract';
|
|
19
|
-
import { LogId, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
|
|
32
|
+
import { ContractClassLog, LogId, PrivateLog, PublicLog, SiloedTag, Tag } from '@aztec/stdlib/logs';
|
|
20
33
|
import { InboxLeaf } from '@aztec/stdlib/messaging';
|
|
34
|
+
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
21
35
|
import {
|
|
22
36
|
makeContractClassPublic,
|
|
23
37
|
makeExecutablePrivateFunctionWithMembershipProof,
|
|
24
|
-
|
|
38
|
+
makeUtilityFunctionWithMembershipProof,
|
|
25
39
|
} from '@aztec/stdlib/testing';
|
|
26
40
|
import '@aztec/stdlib/testing/jest';
|
|
27
|
-
import {
|
|
41
|
+
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
42
|
+
import { type IndexedTxEffect, PartialStateReference, StateReference, TxEffect, TxHash } from '@aztec/stdlib/tx';
|
|
28
43
|
|
|
44
|
+
import { makeInboxMessage, makeInboxMessages } from '../test/mock_structs.js';
|
|
29
45
|
import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
|
|
30
|
-
import
|
|
46
|
+
import {
|
|
47
|
+
BlockArchiveNotConsistentError,
|
|
48
|
+
BlockIndexNotSequentialError,
|
|
49
|
+
BlockNumberNotSequentialError,
|
|
50
|
+
CheckpointNumberNotConsistentError,
|
|
51
|
+
CheckpointNumberNotSequentialError,
|
|
52
|
+
InitialBlockNumberNotSequentialError,
|
|
53
|
+
InitialCheckpointNumberNotSequentialError,
|
|
54
|
+
} from './errors.js';
|
|
55
|
+
import { MessageStoreError } from './kv_archiver_store/message_store.js';
|
|
56
|
+
import type { InboxMessage } from './structs/inbox_message.js';
|
|
31
57
|
|
|
32
58
|
/**
|
|
33
59
|
* @param testName - The name of the test suite.
|
|
@@ -39,99 +65,1610 @@ export function describeArchiverDataStore(
|
|
|
39
65
|
) {
|
|
40
66
|
describe(testName, () => {
|
|
41
67
|
let store: ArchiverDataStore;
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
[
|
|
46
|
-
[
|
|
47
|
-
[
|
|
48
|
-
[5, 2, () => blocks.slice(4, 6)],
|
|
68
|
+
let publishedCheckpoints: PublishedCheckpoint[];
|
|
69
|
+
|
|
70
|
+
const blockNumberTests: [number, () => L2BlockNew][] = [
|
|
71
|
+
[1, () => publishedCheckpoints[0].checkpoint.blocks[0]],
|
|
72
|
+
[10, () => publishedCheckpoints[9].checkpoint.blocks[0]],
|
|
73
|
+
[5, () => publishedCheckpoints[4].checkpoint.blocks[0]],
|
|
49
74
|
];
|
|
50
75
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
76
|
+
const makeBlockHash = (blockNumber: number) => `0x${blockNumber.toString(16).padStart(64, '0')}`;
|
|
77
|
+
|
|
78
|
+
// Create a state reference with properly calculated noteHashTree.nextAvailableLeafIndex
|
|
79
|
+
// This is needed because the log store calculates dataStartIndexForBlock as:
|
|
80
|
+
// noteHashTree.nextAvailableLeafIndex - txEffects.length * MAX_NOTE_HASHES_PER_TX
|
|
81
|
+
// If nextAvailableLeafIndex is too small (random values 0-1000), this becomes negative
|
|
82
|
+
const makeStateForBlock = (blockNumber: number, txsPerBlock: number): StateReference => {
|
|
83
|
+
// Ensure nextAvailableLeafIndex is large enough for all blocks up to this point
|
|
84
|
+
const noteHashIndex = blockNumber * txsPerBlock * MAX_NOTE_HASHES_PER_TX;
|
|
85
|
+
return new StateReference(
|
|
86
|
+
AppendOnlyTreeSnapshot.random(),
|
|
87
|
+
new PartialStateReference(
|
|
88
|
+
new AppendOnlyTreeSnapshot(Fr.random(), noteHashIndex),
|
|
89
|
+
AppendOnlyTreeSnapshot.random(),
|
|
90
|
+
AppendOnlyTreeSnapshot.random(),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const makePublishedCheckpoint = (checkpoint: Checkpoint, l1BlockNumber: number): PublishedCheckpoint => {
|
|
96
|
+
return new PublishedCheckpoint(
|
|
97
|
+
checkpoint,
|
|
98
|
+
new L1PublishedData(BigInt(l1BlockNumber), BigInt(l1BlockNumber * 1000), makeBlockHash(l1BlockNumber)),
|
|
99
|
+
times(3, CommitteeAttestation.random),
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const expectCheckpointedBlockEquals = (
|
|
104
|
+
actual: CheckpointedL2Block,
|
|
105
|
+
expectedBlock: L2BlockNew,
|
|
106
|
+
expectedCheckpoint: PublishedCheckpoint,
|
|
107
|
+
) => {
|
|
108
|
+
expect(actual.l1).toEqual(expectedCheckpoint.l1);
|
|
109
|
+
expect(actual.block.header.equals(expectedBlock.header)).toBe(true);
|
|
110
|
+
expect(actual.checkpointNumber).toEqual(expectedCheckpoint.checkpoint.number);
|
|
111
|
+
expect(actual.attestations.every((a, i) => a.equals(expectedCheckpoint.attestations[i]))).toBe(true);
|
|
112
|
+
};
|
|
59
113
|
|
|
60
114
|
beforeEach(async () => {
|
|
61
115
|
store = await getStore();
|
|
62
|
-
|
|
116
|
+
// Create checkpoints sequentially to ensure archive roots are chained properly.
|
|
117
|
+
// Each block's header.lastArchive must equal the previous block's archive.
|
|
118
|
+
publishedCheckpoints = [];
|
|
119
|
+
const txsPerBlock = 4;
|
|
120
|
+
for (let i = 0; i < 10; i++) {
|
|
121
|
+
const blockNumber = i + 1;
|
|
122
|
+
const previousArchive = i > 0 ? publishedCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
|
|
123
|
+
const checkpoint = await Checkpoint.random(CheckpointNumber(i + 1), {
|
|
124
|
+
numBlocks: 1,
|
|
125
|
+
startBlockNumber: blockNumber,
|
|
126
|
+
previousArchive,
|
|
127
|
+
txsPerBlock,
|
|
128
|
+
state: makeStateForBlock(blockNumber, txsPerBlock),
|
|
129
|
+
// Ensure each tx has public logs for getPublicLogs tests
|
|
130
|
+
txOptions: { numPublicCallsPerTx: 2, numPublicLogsPerCall: 2 },
|
|
131
|
+
});
|
|
132
|
+
publishedCheckpoints.push(makePublishedCheckpoint(checkpoint, i + 10));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('addCheckpoints', () => {
|
|
137
|
+
it('returns success when adding checkpoints', async () => {
|
|
138
|
+
await expect(store.addCheckpoints(publishedCheckpoints)).resolves.toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('throws on duplicate checkpoints', async () => {
|
|
142
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
143
|
+
await expect(store.addCheckpoints(publishedCheckpoints)).rejects.toThrow(
|
|
144
|
+
InitialCheckpointNumberNotSequentialError,
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('throws an error if the previous block does not exist in the store', async () => {
|
|
149
|
+
const checkpoint = await Checkpoint.random(CheckpointNumber(2), { numBlocks: 1, startBlockNumber: 2 });
|
|
150
|
+
const block = makePublishedCheckpoint(checkpoint, 2);
|
|
151
|
+
await expect(store.addCheckpoints([block])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
|
|
152
|
+
await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('throws an error if there is a gap in the blocks being added', async () => {
|
|
156
|
+
const checkpoint1 = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 });
|
|
157
|
+
const checkpoint3 = await Checkpoint.random(CheckpointNumber(3), { numBlocks: 1, startBlockNumber: 3 });
|
|
158
|
+
const checkpoints = [makePublishedCheckpoint(checkpoint1, 1), makePublishedCheckpoint(checkpoint3, 3)];
|
|
159
|
+
await expect(store.addCheckpoints(checkpoints)).rejects.toThrow(CheckpointNumberNotSequentialError);
|
|
160
|
+
await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('throws an error if blocks within a checkpoint are not sequential', async () => {
|
|
164
|
+
// Create a checkpoint with non-sequential block numbers (block 1 and block 3, skipping block 2)
|
|
165
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), { checkpointNumber: CheckpointNumber(1) });
|
|
166
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), { checkpointNumber: CheckpointNumber(1) });
|
|
167
|
+
|
|
168
|
+
const checkpoint = new Checkpoint(
|
|
169
|
+
AppendOnlyTreeSnapshot.random(),
|
|
170
|
+
CheckpointHeader.random(),
|
|
171
|
+
[block1, block3],
|
|
172
|
+
CheckpointNumber(1),
|
|
173
|
+
);
|
|
174
|
+
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
|
|
175
|
+
|
|
176
|
+
await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockNumberNotSequentialError);
|
|
177
|
+
await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('throws an error if blocks within a checkpoint do not have sequential indexes', async () => {
|
|
181
|
+
// Create a checkpoint with non-sequential indexes
|
|
182
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
183
|
+
checkpointNumber: CheckpointNumber(1),
|
|
184
|
+
indexWithinCheckpoint: 0,
|
|
185
|
+
});
|
|
186
|
+
const block3 = await L2BlockNew.random(BlockNumber(2), {
|
|
187
|
+
checkpointNumber: CheckpointNumber(1),
|
|
188
|
+
indexWithinCheckpoint: 2,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const checkpoint = new Checkpoint(
|
|
192
|
+
AppendOnlyTreeSnapshot.random(),
|
|
193
|
+
CheckpointHeader.random(),
|
|
194
|
+
[block1, block3],
|
|
195
|
+
CheckpointNumber(1),
|
|
196
|
+
);
|
|
197
|
+
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
|
|
198
|
+
|
|
199
|
+
await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockIndexNotSequentialError);
|
|
200
|
+
await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('throws an error if blocks within a checkpoint do not start from index 0', async () => {
|
|
204
|
+
// Create a checkpoint with non-sequential indexes
|
|
205
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
206
|
+
checkpointNumber: CheckpointNumber(1),
|
|
207
|
+
indexWithinCheckpoint: 1,
|
|
208
|
+
});
|
|
209
|
+
const block3 = await L2BlockNew.random(BlockNumber(2), {
|
|
210
|
+
checkpointNumber: CheckpointNumber(1),
|
|
211
|
+
indexWithinCheckpoint: 2,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const checkpoint = new Checkpoint(
|
|
215
|
+
AppendOnlyTreeSnapshot.random(),
|
|
216
|
+
CheckpointHeader.random(),
|
|
217
|
+
[block1, block3],
|
|
218
|
+
CheckpointNumber(1),
|
|
219
|
+
);
|
|
220
|
+
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
|
|
221
|
+
|
|
222
|
+
await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockIndexNotSequentialError);
|
|
223
|
+
await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('throws an error if block has invalid checkpoint index', async () => {
|
|
227
|
+
// Create a block wit an invalid checkpoint index
|
|
228
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
229
|
+
checkpointNumber: CheckpointNumber(1),
|
|
230
|
+
indexWithinCheckpoint: -1,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const checkpoint = new Checkpoint(
|
|
234
|
+
AppendOnlyTreeSnapshot.random(),
|
|
235
|
+
CheckpointHeader.random(),
|
|
236
|
+
[block1],
|
|
237
|
+
CheckpointNumber(1),
|
|
238
|
+
);
|
|
239
|
+
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
|
|
240
|
+
|
|
241
|
+
await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockIndexNotSequentialError);
|
|
242
|
+
await expect(store.getCheckpointedBlock(1)).resolves.toBeUndefined();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('throws an error if checkpoint has invalid initial number', async () => {
|
|
246
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
247
|
+
checkpointNumber: CheckpointNumber(2),
|
|
248
|
+
indexWithinCheckpoint: 0,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const checkpoint = new Checkpoint(
|
|
252
|
+
AppendOnlyTreeSnapshot.random(),
|
|
253
|
+
CheckpointHeader.random(),
|
|
254
|
+
[block1],
|
|
255
|
+
CheckpointNumber(2),
|
|
256
|
+
);
|
|
257
|
+
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
|
|
258
|
+
|
|
259
|
+
await expect(store.addCheckpoints([publishedCheckpoint])).rejects.toThrow(
|
|
260
|
+
InitialCheckpointNumberNotSequentialError,
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('allows the correct initial checkpoint', async () => {
|
|
265
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
266
|
+
checkpointNumber: CheckpointNumber(1),
|
|
267
|
+
indexWithinCheckpoint: 0,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const checkpoint = new Checkpoint(
|
|
271
|
+
AppendOnlyTreeSnapshot.random(),
|
|
272
|
+
CheckpointHeader.random(),
|
|
273
|
+
[block1],
|
|
274
|
+
CheckpointNumber(1),
|
|
275
|
+
);
|
|
276
|
+
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
|
|
277
|
+
|
|
278
|
+
await expect(store.addCheckpoints([publishedCheckpoint])).resolves.toBe(true);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('throws on duplicate initial checkpoint', async () => {
|
|
282
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
283
|
+
checkpointNumber: CheckpointNumber(1),
|
|
284
|
+
indexWithinCheckpoint: 0,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const block2 = await L2BlockNew.random(BlockNumber(1), {
|
|
288
|
+
checkpointNumber: CheckpointNumber(1),
|
|
289
|
+
indexWithinCheckpoint: 0,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const checkpoint = new Checkpoint(
|
|
293
|
+
AppendOnlyTreeSnapshot.random(),
|
|
294
|
+
CheckpointHeader.random(),
|
|
295
|
+
[block1],
|
|
296
|
+
CheckpointNumber(1),
|
|
297
|
+
);
|
|
298
|
+
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
|
|
299
|
+
|
|
300
|
+
const checkpoint2 = new Checkpoint(
|
|
301
|
+
AppendOnlyTreeSnapshot.random(),
|
|
302
|
+
CheckpointHeader.random(),
|
|
303
|
+
[block2],
|
|
304
|
+
CheckpointNumber(1),
|
|
305
|
+
);
|
|
306
|
+
const publishedCheckpoint2 = makePublishedCheckpoint(checkpoint2, 10);
|
|
307
|
+
|
|
308
|
+
await expect(store.addCheckpoints([publishedCheckpoint])).resolves.toBe(true);
|
|
309
|
+
await expect(store.addCheckpoints([publishedCheckpoint2])).rejects.toThrow(
|
|
310
|
+
InitialCheckpointNumberNotSequentialError,
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('unwindcheckpoints', () => {
|
|
316
|
+
it('unwinding checkpoints will remove checkpoints from the chain', async () => {
|
|
317
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
318
|
+
const checkpointNumber = await store.getSynchedCheckpointNumber();
|
|
319
|
+
const lastCheckpoint = publishedCheckpoints.at(-1)!;
|
|
320
|
+
const lastBlockNumber = lastCheckpoint.checkpoint.blocks[0].number;
|
|
321
|
+
|
|
322
|
+
// Verify block exists before unwinding
|
|
323
|
+
const retrievedBlock = await store.getCheckpointedBlock(lastBlockNumber);
|
|
324
|
+
expect(retrievedBlock).toBeDefined();
|
|
325
|
+
expect(retrievedBlock!.block.header.equals(lastCheckpoint.checkpoint.blocks[0].header)).toBe(true);
|
|
326
|
+
expect(retrievedBlock!.checkpointNumber).toEqual(checkpointNumber);
|
|
327
|
+
|
|
328
|
+
await store.unwindCheckpoints(checkpointNumber, 1);
|
|
329
|
+
|
|
330
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(checkpointNumber - 1);
|
|
331
|
+
await expect(store.getCheckpointedBlock(lastBlockNumber)).resolves.toBeUndefined();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('can unwind multiple empty blocks', async () => {
|
|
335
|
+
// Create checkpoints sequentially to chain archive roots
|
|
336
|
+
const emptyCheckpoints: PublishedCheckpoint[] = [];
|
|
337
|
+
for (let i = 0; i < 10; i++) {
|
|
338
|
+
const previousArchive = i > 0 ? emptyCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
|
|
339
|
+
const checkpoint = await Checkpoint.random(CheckpointNumber(i + 1), {
|
|
340
|
+
numBlocks: 1,
|
|
341
|
+
startBlockNumber: i + 1,
|
|
342
|
+
txsPerBlock: 0,
|
|
343
|
+
previousArchive,
|
|
344
|
+
});
|
|
345
|
+
emptyCheckpoints.push(makePublishedCheckpoint(checkpoint, i + 10));
|
|
346
|
+
}
|
|
347
|
+
await store.addCheckpoints(emptyCheckpoints);
|
|
348
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(10);
|
|
349
|
+
|
|
350
|
+
await store.unwindCheckpoints(CheckpointNumber(10), 3);
|
|
351
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(7);
|
|
352
|
+
expect((await store.getRangeOfCheckpoints(CheckpointNumber(1), 10)).map(b => b.checkpointNumber)).toEqual([
|
|
353
|
+
1, 2, 3, 4, 5, 6, 7,
|
|
354
|
+
]);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('refuses to unwind checkpoints if the tip is not the last checkpoint', async () => {
|
|
358
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
359
|
+
await expect(store.unwindCheckpoints(CheckpointNumber(5), 1)).rejects.toThrow(
|
|
360
|
+
/can only unwind checkpoints from the tip/i,
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('unwound blocks and headers cannot be retrieved by hash or archive', async () => {
|
|
365
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
366
|
+
const lastCheckpoint = publishedCheckpoints[publishedCheckpoints.length - 1];
|
|
367
|
+
const lastBlock = lastCheckpoint.checkpoint.blocks[0];
|
|
368
|
+
const blockHash = await lastBlock.header.hash();
|
|
369
|
+
const archive = lastBlock.archive.root;
|
|
370
|
+
|
|
371
|
+
// Verify block and header exist before unwinding
|
|
372
|
+
const retrievedByHash = await store.getCheckpointedBlockByHash(blockHash);
|
|
373
|
+
expect(retrievedByHash).toBeDefined();
|
|
374
|
+
expect(retrievedByHash!.block.header.equals(lastBlock.header)).toBe(true);
|
|
375
|
+
|
|
376
|
+
const retrievedByArchive = await store.getCheckpointedBlockByArchive(archive);
|
|
377
|
+
expect(retrievedByArchive).toBeDefined();
|
|
378
|
+
expect(retrievedByArchive!.block.header.equals(lastBlock.header)).toBe(true);
|
|
379
|
+
|
|
380
|
+
const headerByHash = await store.getBlockHeaderByHash(blockHash);
|
|
381
|
+
expect(headerByHash).toBeDefined();
|
|
382
|
+
expect(headerByHash!.equals(lastBlock.header)).toBe(true);
|
|
383
|
+
|
|
384
|
+
const headerByArchive = await store.getBlockHeaderByArchive(archive);
|
|
385
|
+
expect(headerByArchive).toBeDefined();
|
|
386
|
+
expect(headerByArchive!.equals(lastBlock.header)).toBe(true);
|
|
387
|
+
|
|
388
|
+
// Unwind the checkpoint
|
|
389
|
+
await store.unwindCheckpoints(lastCheckpoint.checkpoint.number, 1);
|
|
390
|
+
|
|
391
|
+
// Verify neither block nor header can be retrieved after unwinding
|
|
392
|
+
expect(await store.getCheckpointedBlockByHash(blockHash)).toBeUndefined();
|
|
393
|
+
expect(await store.getCheckpointedBlockByArchive(archive)).toBeUndefined();
|
|
394
|
+
expect(await store.getBlockHeaderByHash(blockHash)).toBeUndefined();
|
|
395
|
+
expect(await store.getBlockHeaderByArchive(archive)).toBeUndefined();
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('multi-block checkpoints', () => {
|
|
400
|
+
it('block number increases correctly when adding checkpoints with multiple blocks', async () => {
|
|
401
|
+
// Create 3 checkpoints: first with 2 blocks, second with 3 blocks, third with 1 block
|
|
402
|
+
// Total blocks: 6, spanning block numbers 1-6
|
|
403
|
+
// Chain archive roots across checkpoints
|
|
404
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
405
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
406
|
+
|
|
407
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
408
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
409
|
+
numBlocks: 3,
|
|
410
|
+
startBlockNumber: 3,
|
|
411
|
+
previousArchive: previousArchive1,
|
|
412
|
+
});
|
|
413
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
414
|
+
|
|
415
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
416
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
417
|
+
numBlocks: 1,
|
|
418
|
+
startBlockNumber: 6,
|
|
419
|
+
previousArchive: previousArchive2,
|
|
420
|
+
});
|
|
421
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
422
|
+
|
|
423
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
424
|
+
|
|
425
|
+
// Checkpoint number should be 3 (the last checkpoint number)
|
|
426
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(3);
|
|
427
|
+
// Block number should be 6 (the last block number across all checkpoints)
|
|
428
|
+
expect(await store.getLatestBlockNumber()).toBe(6);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('block number decreases correctly when unwinding checkpoints with multiple blocks', async () => {
|
|
432
|
+
// Create 3 checkpoints with varying block counts, chaining archive roots
|
|
433
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
434
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
435
|
+
|
|
436
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
437
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
438
|
+
numBlocks: 3,
|
|
439
|
+
startBlockNumber: 3,
|
|
440
|
+
previousArchive: previousArchive1,
|
|
441
|
+
});
|
|
442
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
443
|
+
|
|
444
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
445
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
446
|
+
numBlocks: 2,
|
|
447
|
+
startBlockNumber: 6,
|
|
448
|
+
previousArchive: previousArchive2,
|
|
449
|
+
});
|
|
450
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
451
|
+
|
|
452
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
453
|
+
|
|
454
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(3);
|
|
455
|
+
expect(await store.getLatestBlockNumber()).toBe(7);
|
|
456
|
+
|
|
457
|
+
// Unwind the last checkpoint (which has 2 blocks)
|
|
458
|
+
await store.unwindCheckpoints(CheckpointNumber(3), 1);
|
|
459
|
+
|
|
460
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(2);
|
|
461
|
+
expect(await store.getLatestBlockNumber()).toBe(5);
|
|
462
|
+
|
|
463
|
+
// Unwind another checkpoint (which has 3 blocks)
|
|
464
|
+
await store.unwindCheckpoints(CheckpointNumber(2), 1);
|
|
465
|
+
|
|
466
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(1);
|
|
467
|
+
expect(await store.getLatestBlockNumber()).toBe(2);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('unwinding multiple checkpoints with multiple blocks in one go', async () => {
|
|
471
|
+
// Create 4 checkpoints with varying block counts, chaining archive roots
|
|
472
|
+
// Checkpoint 1: blocks 1-2 (2 blocks)
|
|
473
|
+
// Checkpoint 2: blocks 3-5 (3 blocks)
|
|
474
|
+
// Checkpoint 3: blocks 6-7 (2 blocks)
|
|
475
|
+
// Checkpoint 4: blocks 8-10 (3 blocks)
|
|
476
|
+
// Total: 10 blocks across 4 checkpoints
|
|
477
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
478
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
479
|
+
|
|
480
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
481
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
482
|
+
numBlocks: 3,
|
|
483
|
+
startBlockNumber: 3,
|
|
484
|
+
previousArchive: previousArchive1,
|
|
485
|
+
});
|
|
486
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
487
|
+
|
|
488
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
489
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
490
|
+
numBlocks: 2,
|
|
491
|
+
startBlockNumber: 6,
|
|
492
|
+
previousArchive: previousArchive2,
|
|
493
|
+
});
|
|
494
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
495
|
+
|
|
496
|
+
const previousArchive3 = checkpoint3Cp.blocks.at(-1)!.archive;
|
|
497
|
+
const checkpoint4Cp = await Checkpoint.random(CheckpointNumber(4), {
|
|
498
|
+
numBlocks: 3,
|
|
499
|
+
startBlockNumber: 8,
|
|
500
|
+
previousArchive: previousArchive3,
|
|
501
|
+
});
|
|
502
|
+
const checkpoint4 = makePublishedCheckpoint(checkpoint4Cp, 13);
|
|
503
|
+
|
|
504
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3, checkpoint4]);
|
|
505
|
+
|
|
506
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(4);
|
|
507
|
+
expect(await store.getLatestBlockNumber()).toBe(10);
|
|
508
|
+
|
|
509
|
+
// Unwind 2 checkpoints at once (checkpoints 3 and 4, which together have 5 blocks)
|
|
510
|
+
await store.unwindCheckpoints(CheckpointNumber(4), 2);
|
|
511
|
+
|
|
512
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(2);
|
|
513
|
+
expect(await store.getLatestBlockNumber()).toBe(5);
|
|
514
|
+
|
|
515
|
+
// Verify blocks 1-5 still exist (from checkpoints 1 and 2)
|
|
516
|
+
for (let blockNumber = 1; blockNumber <= 5; blockNumber++) {
|
|
517
|
+
expect(await store.getCheckpointedBlock(blockNumber)).toBeDefined();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Verify blocks 6-10 are gone (from checkpoints 3 and 4)
|
|
521
|
+
for (let blockNumber = 6; blockNumber <= 10; blockNumber++) {
|
|
522
|
+
expect(await store.getCheckpointedBlock(blockNumber)).toBeUndefined();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Unwind remaining 2 checkpoints at once (checkpoints 1 and 2, which together have 5 blocks)
|
|
526
|
+
await store.unwindCheckpoints(CheckpointNumber(2), 2);
|
|
527
|
+
|
|
528
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(0);
|
|
529
|
+
expect(await store.getLatestBlockNumber()).toBe(0);
|
|
530
|
+
|
|
531
|
+
// Verify all blocks are gone
|
|
532
|
+
for (let blockNumber = 1; blockNumber <= 10; blockNumber++) {
|
|
533
|
+
expect(await store.getCheckpointedBlock(blockNumber)).toBeUndefined();
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('getCheckpointedBlock returns correct checkpoint info for blocks within multi-block checkpoints', async () => {
|
|
538
|
+
// Create checkpoints with chained archive roots
|
|
539
|
+
// Create a checkpoint with 3 blocks
|
|
540
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 });
|
|
541
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
542
|
+
|
|
543
|
+
// Create another checkpoint with 2 blocks
|
|
544
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
545
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
546
|
+
numBlocks: 2,
|
|
547
|
+
startBlockNumber: 4,
|
|
548
|
+
previousArchive: previousArchive1,
|
|
549
|
+
});
|
|
550
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
551
|
+
|
|
552
|
+
await store.addCheckpoints([checkpoint1, checkpoint2]);
|
|
553
|
+
|
|
554
|
+
// Check blocks from the first checkpoint (blocks 1, 2, 3)
|
|
555
|
+
for (let i = 0; i < 3; i++) {
|
|
556
|
+
const blockNumber = i + 1;
|
|
557
|
+
const retrievedBlock = await store.getCheckpointedBlock(blockNumber);
|
|
558
|
+
|
|
559
|
+
expect(retrievedBlock).toBeDefined();
|
|
560
|
+
expect(retrievedBlock!.checkpointNumber).toBe(1);
|
|
561
|
+
expect(retrievedBlock!.block.number).toBe(blockNumber);
|
|
562
|
+
expect(retrievedBlock!.l1).toEqual(checkpoint1.l1);
|
|
563
|
+
expect(retrievedBlock!.attestations.every((a, j) => a.equals(checkpoint1.attestations[j]))).toBe(true);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Check blocks from the second checkpoint (blocks 4, 5)
|
|
567
|
+
for (let i = 0; i < 2; i++) {
|
|
568
|
+
const blockNumber = i + 4;
|
|
569
|
+
const retrievedBlock = await store.getCheckpointedBlock(blockNumber);
|
|
570
|
+
|
|
571
|
+
expect(retrievedBlock).toBeDefined();
|
|
572
|
+
expect(retrievedBlock!.checkpointNumber).toBe(2);
|
|
573
|
+
expect(retrievedBlock!.block.number).toBe(blockNumber);
|
|
574
|
+
expect(retrievedBlock!.l1).toEqual(checkpoint2.l1);
|
|
575
|
+
expect(retrievedBlock!.attestations.every((a, j) => a.equals(checkpoint2.attestations[j]))).toBe(true);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('getCheckpointedBlockByHash returns correct checkpoint info for blocks within multi-block checkpoints', async () => {
|
|
580
|
+
const checkpoint = makePublishedCheckpoint(
|
|
581
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
|
|
582
|
+
10,
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
await store.addCheckpoints([checkpoint]);
|
|
586
|
+
|
|
587
|
+
// Check each block by its hash
|
|
588
|
+
for (let i = 0; i < checkpoint.checkpoint.blocks.length; i++) {
|
|
589
|
+
const block = checkpoint.checkpoint.blocks[i];
|
|
590
|
+
const blockHash = await block.header.hash();
|
|
591
|
+
const retrievedBlock = await store.getCheckpointedBlockByHash(blockHash);
|
|
592
|
+
|
|
593
|
+
expect(retrievedBlock).toBeDefined();
|
|
594
|
+
expect(retrievedBlock!.checkpointNumber).toBe(1);
|
|
595
|
+
expect(retrievedBlock!.block.number).toBe(i + 1);
|
|
596
|
+
expect(retrievedBlock!.l1).toEqual(checkpoint.l1);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it('getCheckpointedBlockByArchive returns correct checkpoint info for blocks within multi-block checkpoints', async () => {
|
|
601
|
+
const checkpoint = makePublishedCheckpoint(
|
|
602
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
|
|
603
|
+
10,
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
await store.addCheckpoints([checkpoint]);
|
|
607
|
+
|
|
608
|
+
// Check each block by its archive root
|
|
609
|
+
for (let i = 0; i < checkpoint.checkpoint.blocks.length; i++) {
|
|
610
|
+
const block = checkpoint.checkpoint.blocks[i];
|
|
611
|
+
const archive = block.archive.root;
|
|
612
|
+
const retrievedBlock = await store.getCheckpointedBlockByArchive(archive);
|
|
613
|
+
|
|
614
|
+
expect(retrievedBlock).toBeDefined();
|
|
615
|
+
expect(retrievedBlock!.checkpointNumber).toBe(1);
|
|
616
|
+
expect(retrievedBlock!.block.number).toBe(i + 1);
|
|
617
|
+
expect(retrievedBlock!.l1).toEqual(checkpoint.l1);
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('unwinding a multi-block checkpoint removes all its blocks', async () => {
|
|
622
|
+
const checkpoint = makePublishedCheckpoint(
|
|
623
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
|
|
624
|
+
10,
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
await store.addCheckpoints([checkpoint]);
|
|
628
|
+
|
|
629
|
+
// Verify all 3 blocks exist
|
|
630
|
+
for (let blockNumber = 1; blockNumber <= 3; blockNumber++) {
|
|
631
|
+
expect(await store.getCheckpointedBlock(blockNumber)).toBeDefined();
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Unwind the checkpoint
|
|
635
|
+
await store.unwindCheckpoints(CheckpointNumber(1), 1);
|
|
636
|
+
|
|
637
|
+
// Verify all 3 blocks are removed
|
|
638
|
+
for (let blockNumber = 1; blockNumber <= 3; blockNumber++) {
|
|
639
|
+
expect(await store.getCheckpointedBlock(blockNumber)).toBeUndefined();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(0);
|
|
643
|
+
expect(await store.getLatestBlockNumber()).toBe(0);
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
describe('uncheckpointed blocks', () => {
|
|
648
|
+
it('can add blocks independently before a checkpoint arrives', async () => {
|
|
649
|
+
// First, establish some checkpointed blocks (checkpoint 1 with blocks 1-3)
|
|
650
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
651
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
|
|
652
|
+
10,
|
|
653
|
+
);
|
|
654
|
+
await store.addCheckpoints([checkpoint1]);
|
|
655
|
+
|
|
656
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(1);
|
|
657
|
+
expect(await store.getLatestBlockNumber()).toBe(3);
|
|
658
|
+
|
|
659
|
+
// Now add blocks 4, 5, 6 independently (without a checkpoint) for upcoming checkpoint 2
|
|
660
|
+
// Chain archive roots from the last block of checkpoint 1
|
|
661
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
662
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
663
|
+
checkpointNumber: CheckpointNumber(2),
|
|
664
|
+
indexWithinCheckpoint: 0,
|
|
665
|
+
lastArchive: lastBlockArchive,
|
|
666
|
+
});
|
|
667
|
+
const block5 = await L2BlockNew.random(BlockNumber(5), {
|
|
668
|
+
checkpointNumber: CheckpointNumber(2),
|
|
669
|
+
indexWithinCheckpoint: 1,
|
|
670
|
+
lastArchive: block4.archive,
|
|
671
|
+
});
|
|
672
|
+
const block6 = await L2BlockNew.random(BlockNumber(6), {
|
|
673
|
+
checkpointNumber: CheckpointNumber(2),
|
|
674
|
+
indexWithinCheckpoint: 2,
|
|
675
|
+
lastArchive: block5.archive,
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
await store.addBlocks([block4, block5, block6]);
|
|
679
|
+
|
|
680
|
+
// Checkpoint number should still be 1 (no new checkpoint added)
|
|
681
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(1);
|
|
682
|
+
// But latest block number should be 6
|
|
683
|
+
expect(await store.getLatestBlockNumber()).toBe(6);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it('getBlock retrieves uncheckpointed blocks', async () => {
|
|
687
|
+
// First, establish some checkpointed blocks
|
|
688
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
689
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
690
|
+
10,
|
|
691
|
+
);
|
|
692
|
+
await store.addCheckpoints([checkpoint1]);
|
|
693
|
+
|
|
694
|
+
// Add uncheckpointed blocks for upcoming checkpoint 2, chaining archive roots
|
|
695
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
696
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
697
|
+
checkpointNumber: CheckpointNumber(2),
|
|
698
|
+
indexWithinCheckpoint: 0,
|
|
699
|
+
lastArchive: lastBlockArchive,
|
|
700
|
+
});
|
|
701
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
702
|
+
checkpointNumber: CheckpointNumber(2),
|
|
703
|
+
indexWithinCheckpoint: 1,
|
|
704
|
+
lastArchive: block3.archive,
|
|
705
|
+
});
|
|
706
|
+
await store.addBlocks([block3, block4]);
|
|
707
|
+
|
|
708
|
+
// getBlock should work for both checkpointed and uncheckpointed blocks
|
|
709
|
+
expect((await store.getBlock(1))?.number).toBe(1);
|
|
710
|
+
expect((await store.getBlock(2))?.number).toBe(2);
|
|
711
|
+
expect((await store.getBlock(3))?.equals(block3)).toBe(true);
|
|
712
|
+
expect((await store.getBlock(4))?.equals(block4)).toBe(true);
|
|
713
|
+
expect(await store.getBlock(5)).toBeUndefined();
|
|
714
|
+
|
|
715
|
+
const block5 = await L2BlockNew.random(BlockNumber(5), {
|
|
716
|
+
checkpointNumber: CheckpointNumber(2),
|
|
717
|
+
indexWithinCheckpoint: 2,
|
|
718
|
+
lastArchive: block4.archive,
|
|
719
|
+
});
|
|
720
|
+
await store.addBlocks([block5]);
|
|
721
|
+
|
|
722
|
+
// Verify the uncheckpointed blocks have correct data
|
|
723
|
+
const retrieved3 = await store.getBlock(3);
|
|
724
|
+
expect(retrieved3!.number).toBe(3);
|
|
725
|
+
expect(retrieved3!.equals(block3)).toBe(true);
|
|
726
|
+
const retrieved4 = await store.getBlock(4);
|
|
727
|
+
expect(retrieved4!.number).toBe(4);
|
|
728
|
+
expect(retrieved4!.equals(block4)).toBe(true);
|
|
729
|
+
const retrieved5 = await store.getBlock(5);
|
|
730
|
+
expect(retrieved5!.number).toBe(5);
|
|
731
|
+
expect(retrieved5!.equals(block5)).toBe(true);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('getBlockByHash retrieves uncheckpointed blocks', async () => {
|
|
735
|
+
// Add uncheckpointed blocks (no checkpoints at all) for initial checkpoint 1, chaining archive roots
|
|
736
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
737
|
+
checkpointNumber: CheckpointNumber(1),
|
|
738
|
+
indexWithinCheckpoint: 0,
|
|
739
|
+
});
|
|
740
|
+
const block2 = await L2BlockNew.random(BlockNumber(2), {
|
|
741
|
+
checkpointNumber: CheckpointNumber(1),
|
|
742
|
+
indexWithinCheckpoint: 1,
|
|
743
|
+
lastArchive: block1.archive,
|
|
744
|
+
});
|
|
745
|
+
await store.addBlocks([block1, block2]);
|
|
746
|
+
|
|
747
|
+
// getBlockByHash should work for uncheckpointed blocks
|
|
748
|
+
const hash1 = await block1.header.hash();
|
|
749
|
+
const hash2 = await block2.header.hash();
|
|
750
|
+
|
|
751
|
+
const retrieved1 = await store.getBlockByHash(hash1);
|
|
752
|
+
expect(retrieved1!.equals(block1)).toBe(true);
|
|
753
|
+
|
|
754
|
+
const retrieved2 = await store.getBlockByHash(hash2);
|
|
755
|
+
expect(retrieved2!.equals(block2)).toBe(true);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
it('getBlockByArchive retrieves uncheckpointed blocks', async () => {
|
|
759
|
+
// Add uncheckpointed blocks for initial checkpoint 1, chaining archive roots
|
|
760
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
761
|
+
checkpointNumber: CheckpointNumber(1),
|
|
762
|
+
indexWithinCheckpoint: 0,
|
|
763
|
+
});
|
|
764
|
+
const block2 = await L2BlockNew.random(BlockNumber(2), {
|
|
765
|
+
checkpointNumber: CheckpointNumber(1),
|
|
766
|
+
indexWithinCheckpoint: 1,
|
|
767
|
+
lastArchive: block1.archive,
|
|
768
|
+
});
|
|
769
|
+
await store.addBlocks([block1, block2]);
|
|
770
|
+
|
|
771
|
+
// getBlockByArchive should work for uncheckpointed blocks
|
|
772
|
+
const archive1 = block1.archive.root;
|
|
773
|
+
const archive2 = block2.archive.root;
|
|
774
|
+
|
|
775
|
+
const retrieved1 = await store.getBlockByArchive(archive1);
|
|
776
|
+
expect(retrieved1!.equals(block1)).toBe(true);
|
|
777
|
+
|
|
778
|
+
const retrieved2 = await store.getBlockByArchive(archive2);
|
|
779
|
+
expect(retrieved2!.equals(block2)).toBe(true);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
it('getCheckpointedBlock returns undefined for uncheckpointed blocks', async () => {
|
|
783
|
+
// Add a checkpoint with blocks 1-2
|
|
784
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
785
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
786
|
+
10,
|
|
787
|
+
);
|
|
788
|
+
await store.addCheckpoints([checkpoint1]);
|
|
789
|
+
|
|
790
|
+
// Add uncheckpointed blocks 3-4 for upcoming checkpoint 2, chaining archive roots
|
|
791
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
792
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
793
|
+
checkpointNumber: CheckpointNumber(2),
|
|
794
|
+
indexWithinCheckpoint: 0,
|
|
795
|
+
lastArchive: lastBlockArchive,
|
|
796
|
+
});
|
|
797
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
798
|
+
checkpointNumber: CheckpointNumber(2),
|
|
799
|
+
indexWithinCheckpoint: 1,
|
|
800
|
+
lastArchive: block3.archive,
|
|
801
|
+
});
|
|
802
|
+
await store.addBlocks([block3, block4]);
|
|
803
|
+
|
|
804
|
+
// getCheckpointedBlock should work for checkpointed blocks
|
|
805
|
+
expect((await store.getCheckpointedBlock(1))?.block.number).toBe(1);
|
|
806
|
+
expect((await store.getCheckpointedBlock(2))?.block.number).toBe(2);
|
|
807
|
+
|
|
808
|
+
// getCheckpointedBlock should return undefined for uncheckpointed blocks
|
|
809
|
+
expect(await store.getCheckpointedBlock(3)).toBeUndefined();
|
|
810
|
+
expect(await store.getCheckpointedBlock(4)).toBeUndefined();
|
|
811
|
+
|
|
812
|
+
// But getBlock should work for all blocks
|
|
813
|
+
expect((await store.getBlock(3))?.equals(block3)).toBe(true);
|
|
814
|
+
expect((await store.getBlock(4))?.equals(block4)).toBe(true);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('getCheckpointedBlockByHash returns undefined for uncheckpointed blocks', async () => {
|
|
818
|
+
// Add uncheckpointed blocks for initial checkpoint 1
|
|
819
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
820
|
+
checkpointNumber: CheckpointNumber(1),
|
|
821
|
+
indexWithinCheckpoint: 0,
|
|
822
|
+
});
|
|
823
|
+
await store.addBlocks([block1]);
|
|
824
|
+
|
|
825
|
+
const hash = await block1.header.hash();
|
|
826
|
+
|
|
827
|
+
// getCheckpointedBlockByHash should return undefined
|
|
828
|
+
expect(await store.getCheckpointedBlockByHash(hash)).toBeUndefined();
|
|
829
|
+
|
|
830
|
+
// But getBlockByHash should work
|
|
831
|
+
expect((await store.getBlockByHash(hash))?.equals(block1)).toBe(true);
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
it('getCheckpointedBlockByArchive returns undefined for uncheckpointed blocks', async () => {
|
|
835
|
+
// Add uncheckpointed blocks for initial checkpoint 1
|
|
836
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
837
|
+
checkpointNumber: CheckpointNumber(1),
|
|
838
|
+
indexWithinCheckpoint: 0,
|
|
839
|
+
});
|
|
840
|
+
await store.addBlocks([block1]);
|
|
841
|
+
|
|
842
|
+
const archive = block1.archive.root;
|
|
843
|
+
|
|
844
|
+
// getCheckpointedBlockByArchive should return undefined
|
|
845
|
+
expect(await store.getCheckpointedBlockByArchive(archive)).toBeUndefined();
|
|
846
|
+
|
|
847
|
+
// But getBlockByArchive should work
|
|
848
|
+
expect((await store.getBlockByArchive(archive))?.equals(block1)).toBe(true);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it('checkpoint adopts previously added uncheckpointed blocks', async () => {
|
|
852
|
+
// Add blocks 1-3 without a checkpoint (for initial checkpoint 1), chaining archive roots
|
|
853
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
854
|
+
checkpointNumber: CheckpointNumber(1),
|
|
855
|
+
indexWithinCheckpoint: 0,
|
|
856
|
+
});
|
|
857
|
+
const block2 = await L2BlockNew.random(BlockNumber(2), {
|
|
858
|
+
checkpointNumber: CheckpointNumber(1),
|
|
859
|
+
indexWithinCheckpoint: 1,
|
|
860
|
+
lastArchive: block1.archive,
|
|
861
|
+
});
|
|
862
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
863
|
+
checkpointNumber: CheckpointNumber(1),
|
|
864
|
+
indexWithinCheckpoint: 2,
|
|
865
|
+
lastArchive: block2.archive,
|
|
866
|
+
});
|
|
867
|
+
await store.addBlocks([block1, block2, block3]);
|
|
868
|
+
|
|
869
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(0);
|
|
870
|
+
expect(await store.getLatestBlockNumber()).toBe(3);
|
|
871
|
+
|
|
872
|
+
// getCheckpointedBlock should return undefined for all
|
|
873
|
+
expect(await store.getCheckpointedBlock(1)).toBeUndefined();
|
|
874
|
+
expect(await store.getCheckpointedBlock(2)).toBeUndefined();
|
|
875
|
+
expect(await store.getCheckpointedBlock(3)).toBeUndefined();
|
|
876
|
+
|
|
877
|
+
// Now add a checkpoint that covers blocks 1-3
|
|
878
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
879
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
|
|
880
|
+
10,
|
|
881
|
+
);
|
|
882
|
+
await store.addCheckpoints([checkpoint1]);
|
|
883
|
+
|
|
884
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(1);
|
|
885
|
+
expect(await store.getLatestBlockNumber()).toBe(3);
|
|
886
|
+
|
|
887
|
+
// Now getCheckpointedBlock should work for all blocks
|
|
888
|
+
const checkpointed1 = await store.getCheckpointedBlock(1);
|
|
889
|
+
expect(checkpointed1).toBeDefined();
|
|
890
|
+
expect(checkpointed1!.checkpointNumber).toBe(1);
|
|
891
|
+
expect(checkpointed1!.l1).toEqual(checkpoint1.l1);
|
|
892
|
+
|
|
893
|
+
const checkpointed2 = await store.getCheckpointedBlock(2);
|
|
894
|
+
expect(checkpointed2).toBeDefined();
|
|
895
|
+
expect(checkpointed2!.checkpointNumber).toBe(1);
|
|
896
|
+
|
|
897
|
+
const checkpointed3 = await store.getCheckpointedBlock(3);
|
|
898
|
+
expect(checkpointed3).toBeDefined();
|
|
899
|
+
expect(checkpointed3!.checkpointNumber).toBe(1);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
it('can add more uncheckpointed blocks after a checkpoint and then checkpoint them', async () => {
|
|
903
|
+
// Start with checkpoint 1 covering blocks 1-2
|
|
904
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
905
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
906
|
+
10,
|
|
907
|
+
);
|
|
908
|
+
await store.addCheckpoints([checkpoint1]);
|
|
909
|
+
|
|
910
|
+
// Add uncheckpointed blocks 3-5 for the upcoming checkpoint 2, chaining archive roots
|
|
911
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
912
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
913
|
+
checkpointNumber: CheckpointNumber(2),
|
|
914
|
+
indexWithinCheckpoint: 0,
|
|
915
|
+
lastArchive: lastBlockArchive,
|
|
916
|
+
});
|
|
917
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
918
|
+
checkpointNumber: CheckpointNumber(2),
|
|
919
|
+
indexWithinCheckpoint: 1,
|
|
920
|
+
lastArchive: block3.archive,
|
|
921
|
+
});
|
|
922
|
+
const block5 = await L2BlockNew.random(BlockNumber(5), {
|
|
923
|
+
checkpointNumber: CheckpointNumber(2),
|
|
924
|
+
indexWithinCheckpoint: 2,
|
|
925
|
+
lastArchive: block4.archive,
|
|
926
|
+
});
|
|
927
|
+
await store.addBlocks([block3, block4, block5]);
|
|
928
|
+
|
|
929
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(1);
|
|
930
|
+
expect(await store.getLatestBlockNumber()).toBe(5);
|
|
931
|
+
|
|
932
|
+
// Blocks 3-5 are not checkpointed yet
|
|
933
|
+
expect(await store.getCheckpointedBlock(3)).toBeUndefined();
|
|
934
|
+
expect(await store.getCheckpointedBlock(4)).toBeUndefined();
|
|
935
|
+
expect(await store.getCheckpointedBlock(5)).toBeUndefined();
|
|
936
|
+
|
|
937
|
+
// Add checkpoint 2 covering blocks 3-5, chaining from checkpoint1
|
|
938
|
+
const checkpoint2 = makePublishedCheckpoint(
|
|
939
|
+
await Checkpoint.random(CheckpointNumber(2), {
|
|
940
|
+
numBlocks: 3,
|
|
941
|
+
startBlockNumber: 3,
|
|
942
|
+
previousArchive: lastBlockArchive,
|
|
943
|
+
}),
|
|
944
|
+
11,
|
|
945
|
+
);
|
|
946
|
+
await store.addCheckpoints([checkpoint2]);
|
|
947
|
+
|
|
948
|
+
expect(await store.getSynchedCheckpointNumber()).toBe(2);
|
|
949
|
+
expect(await store.getLatestBlockNumber()).toBe(5);
|
|
950
|
+
|
|
951
|
+
// Now blocks 3-5 should be checkpointed with checkpoint 2's info
|
|
952
|
+
const checkpointed3 = await store.getCheckpointedBlock(3);
|
|
953
|
+
expect(checkpointed3).toBeDefined();
|
|
954
|
+
expect(checkpointed3!.checkpointNumber).toBe(2);
|
|
955
|
+
expect(checkpointed3!.l1).toEqual(checkpoint2.l1);
|
|
956
|
+
|
|
957
|
+
const checkpointed4 = await store.getCheckpointedBlock(4);
|
|
958
|
+
expect(checkpointed4).toBeDefined();
|
|
959
|
+
expect(checkpointed4!.checkpointNumber).toBe(2);
|
|
960
|
+
|
|
961
|
+
const checkpointed5 = await store.getCheckpointedBlock(5);
|
|
962
|
+
expect(checkpointed5).toBeDefined();
|
|
963
|
+
expect(checkpointed5!.checkpointNumber).toBe(2);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it('getBlocks retrieves both checkpointed and uncheckpointed blocks', async () => {
|
|
967
|
+
// Add checkpoint with blocks 1-2
|
|
968
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
969
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
970
|
+
10,
|
|
971
|
+
);
|
|
972
|
+
await store.addCheckpoints([checkpoint1]);
|
|
973
|
+
|
|
974
|
+
// Add uncheckpointed blocks 3-4 for the upcoming checkpoint 2, chaining archive roots
|
|
975
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
976
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
977
|
+
checkpointNumber: CheckpointNumber(2),
|
|
978
|
+
indexWithinCheckpoint: 0,
|
|
979
|
+
lastArchive: lastBlockArchive,
|
|
980
|
+
});
|
|
981
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
982
|
+
checkpointNumber: CheckpointNumber(2),
|
|
983
|
+
indexWithinCheckpoint: 1,
|
|
984
|
+
lastArchive: block3.archive,
|
|
985
|
+
});
|
|
986
|
+
await store.addBlocks([block3, block4]);
|
|
987
|
+
|
|
988
|
+
// getBlocks should retrieve all blocks
|
|
989
|
+
const allBlocks = await store.getBlocks(1, 10);
|
|
990
|
+
expect(allBlocks.length).toBe(4);
|
|
991
|
+
expect(allBlocks.map(b => b.number)).toEqual([1, 2, 3, 4]);
|
|
992
|
+
});
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
describe('addBlocks validation', () => {
|
|
996
|
+
it('throws if blocks have different checkpoint numbers', async () => {
|
|
997
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
998
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
999
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1000
|
+
10,
|
|
1001
|
+
);
|
|
1002
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1003
|
+
|
|
1004
|
+
// Try to add blocks 3 and 4 with different checkpoint numbers
|
|
1005
|
+
// Chain archives correctly to test the checkpoint number validation
|
|
1006
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1007
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1008
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1009
|
+
indexWithinCheckpoint: 0,
|
|
1010
|
+
lastArchive: lastBlockArchive,
|
|
1011
|
+
});
|
|
1012
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1013
|
+
checkpointNumber: CheckpointNumber(3),
|
|
1014
|
+
indexWithinCheckpoint: 1,
|
|
1015
|
+
lastArchive: block3.archive,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
await expect(store.addBlocks([block3, block4])).rejects.toThrow(CheckpointNumberNotConsistentError);
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
it('throws if checkpoint number is not the current checkpoint', async () => {
|
|
1022
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1023
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1024
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1025
|
+
10,
|
|
1026
|
+
);
|
|
1027
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1028
|
+
|
|
1029
|
+
// Try to add blocks for checkpoint 3 (skipping checkpoint 2)
|
|
1030
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1031
|
+
checkpointNumber: CheckpointNumber(3),
|
|
1032
|
+
indexWithinCheckpoint: 0,
|
|
1033
|
+
});
|
|
1034
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1035
|
+
checkpointNumber: CheckpointNumber(3),
|
|
1036
|
+
indexWithinCheckpoint: 1,
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
await expect(store.addBlocks([block3, block4])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it('allows blocks with the same checkpoint number for the current checkpoint', async () => {
|
|
1043
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1044
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1045
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1046
|
+
10,
|
|
1047
|
+
);
|
|
1048
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1049
|
+
|
|
1050
|
+
// Add blocks 3 and 4 with consistent checkpoint number (2), chaining archive roots
|
|
1051
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1052
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1053
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1054
|
+
indexWithinCheckpoint: 0,
|
|
1055
|
+
lastArchive: lastBlockArchive,
|
|
1056
|
+
});
|
|
1057
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1058
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1059
|
+
indexWithinCheckpoint: 1,
|
|
1060
|
+
lastArchive: block3.archive,
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
await expect(store.addBlocks([block3, block4])).resolves.toBe(true);
|
|
1064
|
+
|
|
1065
|
+
// Verify blocks were added
|
|
1066
|
+
expect((await store.getBlock(3))?.equals(block3)).toBe(true);
|
|
1067
|
+
expect((await store.getBlock(4))?.equals(block4)).toBe(true);
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
it('allows blocks for the initial checkpoint when store is empty', async () => {
|
|
1071
|
+
// Add blocks for the initial checkpoint (1), chaining archive roots
|
|
1072
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
1073
|
+
checkpointNumber: CheckpointNumber(1),
|
|
1074
|
+
indexWithinCheckpoint: 0,
|
|
1075
|
+
});
|
|
1076
|
+
const block2 = await L2BlockNew.random(BlockNumber(2), {
|
|
1077
|
+
checkpointNumber: CheckpointNumber(1),
|
|
1078
|
+
indexWithinCheckpoint: 1,
|
|
1079
|
+
lastArchive: block1.archive,
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
await expect(store.addBlocks([block1, block2])).resolves.toBe(true);
|
|
1083
|
+
|
|
1084
|
+
// Verify blocks were added
|
|
1085
|
+
expect((await store.getBlock(1))?.equals(block1)).toBe(true);
|
|
1086
|
+
expect((await store.getBlock(2))?.equals(block2)).toBe(true);
|
|
1087
|
+
expect(await store.getLatestBlockNumber()).toBe(2);
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
it('throws if initial block is duplicated across calls', async () => {
|
|
1091
|
+
// Add blocks for the initial checkpoint (1)
|
|
1092
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
1093
|
+
checkpointNumber: CheckpointNumber(1),
|
|
1094
|
+
indexWithinCheckpoint: 0,
|
|
1095
|
+
});
|
|
1096
|
+
const block2 = await L2BlockNew.random(BlockNumber(1), {
|
|
1097
|
+
checkpointNumber: CheckpointNumber(1),
|
|
1098
|
+
indexWithinCheckpoint: 0,
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
await expect(store.addBlocks([block1])).resolves.toBe(true);
|
|
1102
|
+
await expect(store.addBlocks([block2])).rejects.toThrow(InitialBlockNumberNotSequentialError);
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
it('throws if first block has wrong checkpoint number when store is empty', async () => {
|
|
1106
|
+
// Try to add blocks for checkpoint 2 when store is empty (should start at 1)
|
|
1107
|
+
const block1 = await L2BlockNew.random(BlockNumber(1), {
|
|
1108
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1109
|
+
indexWithinCheckpoint: 0,
|
|
1110
|
+
});
|
|
1111
|
+
const block2 = await L2BlockNew.random(BlockNumber(2), {
|
|
1112
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1113
|
+
indexWithinCheckpoint: 1,
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
await expect(store.addBlocks([block1, block2])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
it('allows adding more blocks to the same checkpoint in separate calls', async () => {
|
|
1120
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1121
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1122
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1123
|
+
10,
|
|
1124
|
+
);
|
|
1125
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1126
|
+
|
|
1127
|
+
// Add block 3 for checkpoint 2, chaining archive roots
|
|
1128
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1129
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1130
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1131
|
+
indexWithinCheckpoint: 0,
|
|
1132
|
+
lastArchive: lastBlockArchive,
|
|
1133
|
+
});
|
|
1134
|
+
await expect(store.addBlocks([block3])).resolves.toBe(true);
|
|
1135
|
+
|
|
1136
|
+
// Add block 4 for the same checkpoint 2 in a separate call
|
|
1137
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1138
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1139
|
+
indexWithinCheckpoint: 1,
|
|
1140
|
+
lastArchive: block3.archive,
|
|
1141
|
+
});
|
|
1142
|
+
await expect(store.addBlocks([block4])).resolves.toBe(true);
|
|
1143
|
+
|
|
1144
|
+
expect(await store.getLatestBlockNumber()).toBe(4);
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
it('throws if adding blocks in separate calls with non-consecutive indexes', async () => {
|
|
1148
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1149
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1150
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1151
|
+
10,
|
|
1152
|
+
);
|
|
1153
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1154
|
+
|
|
1155
|
+
// Add block 3 for checkpoint 2, chaining archive roots
|
|
1156
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1157
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1158
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1159
|
+
indexWithinCheckpoint: 0,
|
|
1160
|
+
lastArchive: lastBlockArchive,
|
|
1161
|
+
});
|
|
1162
|
+
await expect(store.addBlocks([block3])).resolves.toBe(true);
|
|
1163
|
+
|
|
1164
|
+
// Add block 4 for the same checkpoint 2 in a separate call but with a missing index
|
|
1165
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1166
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1167
|
+
indexWithinCheckpoint: 2,
|
|
1168
|
+
lastArchive: block3.archive,
|
|
1169
|
+
});
|
|
1170
|
+
await expect(store.addBlocks([block4])).rejects.toThrow(BlockIndexNotSequentialError);
|
|
1171
|
+
|
|
1172
|
+
expect(await store.getLatestBlockNumber()).toBe(3);
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
it('throws if second batch of blocks has different checkpoint number than first batch', async () => {
|
|
1176
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1177
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1178
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1179
|
+
10,
|
|
1180
|
+
);
|
|
1181
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1182
|
+
|
|
1183
|
+
// Add block 3 for checkpoint 2, chaining archive roots
|
|
1184
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1185
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1186
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1187
|
+
indexWithinCheckpoint: 0,
|
|
1188
|
+
lastArchive: lastBlockArchive,
|
|
1189
|
+
});
|
|
1190
|
+
await store.addBlocks([block3]);
|
|
1191
|
+
|
|
1192
|
+
// Try to add block 4 for checkpoint 3 (should fail because current checkpoint is still 2)
|
|
1193
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1194
|
+
checkpointNumber: CheckpointNumber(3),
|
|
1195
|
+
indexWithinCheckpoint: 0,
|
|
1196
|
+
lastArchive: block3.archive,
|
|
1197
|
+
});
|
|
1198
|
+
await expect(store.addBlocks([block4])).rejects.toThrow(InitialCheckpointNumberNotSequentialError);
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
it('force option bypasses checkpoint number validation', async () => {
|
|
1202
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1203
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1204
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1205
|
+
10,
|
|
1206
|
+
);
|
|
1207
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1208
|
+
|
|
1209
|
+
// Add blocks with different checkpoint numbers using force option, chaining archive roots
|
|
1210
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1211
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1212
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1213
|
+
indexWithinCheckpoint: 0,
|
|
1214
|
+
lastArchive: lastBlockArchive,
|
|
1215
|
+
});
|
|
1216
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1217
|
+
checkpointNumber: CheckpointNumber(5),
|
|
1218
|
+
indexWithinCheckpoint: 0,
|
|
1219
|
+
lastArchive: block3.archive,
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
await expect(store.addBlocks([block3, block4], { force: true })).resolves.toBe(true);
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
it('force option bypasses blockindex number validation', async () => {
|
|
1226
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1227
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1228
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1229
|
+
10,
|
|
1230
|
+
);
|
|
1231
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1232
|
+
|
|
1233
|
+
// Add blocks with different checkpoint numbers using force option, chaining archive roots
|
|
1234
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1235
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1236
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1237
|
+
indexWithinCheckpoint: 0,
|
|
1238
|
+
lastArchive: lastBlockArchive,
|
|
1239
|
+
});
|
|
1240
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1241
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1242
|
+
indexWithinCheckpoint: 2,
|
|
1243
|
+
lastArchive: block3.archive,
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
await expect(store.addBlocks([block3, block4], { force: true })).resolves.toBe(true);
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
it('throws if adding blocks with non-consecutive archives', async () => {
|
|
1250
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1251
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1252
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1253
|
+
10,
|
|
1254
|
+
);
|
|
1255
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1256
|
+
|
|
1257
|
+
// Add block 3 for checkpoint 2 with incorrect archive
|
|
1258
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1259
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1260
|
+
indexWithinCheckpoint: 0,
|
|
1261
|
+
});
|
|
1262
|
+
await expect(store.addBlocks([block3])).rejects.toThrow(BlockArchiveNotConsistentError);
|
|
1263
|
+
|
|
1264
|
+
expect(await store.getLatestBlockNumber()).toBe(2);
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
it('throws if adding blocks with non-consecutive archives across calls', async () => {
|
|
1268
|
+
// First, establish checkpoint 1 with blocks 1-2
|
|
1269
|
+
const checkpoint1 = makePublishedCheckpoint(
|
|
1270
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1271
|
+
10,
|
|
1272
|
+
);
|
|
1273
|
+
await store.addCheckpoints([checkpoint1]);
|
|
1274
|
+
|
|
1275
|
+
// Add block 3 for checkpoint 2 with correct archive
|
|
1276
|
+
const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive;
|
|
1277
|
+
const block3 = await L2BlockNew.random(BlockNumber(3), {
|
|
1278
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1279
|
+
indexWithinCheckpoint: 0,
|
|
1280
|
+
lastArchive: lastBlockArchive,
|
|
1281
|
+
});
|
|
1282
|
+
await expect(store.addBlocks([block3])).resolves.toBe(true);
|
|
1283
|
+
|
|
1284
|
+
// Add block 4 with incorrect archive (should fail)
|
|
1285
|
+
const block4 = await L2BlockNew.random(BlockNumber(4), {
|
|
1286
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1287
|
+
indexWithinCheckpoint: 1,
|
|
1288
|
+
lastArchive: AppendOnlyTreeSnapshot.random(),
|
|
1289
|
+
});
|
|
1290
|
+
await expect(store.addBlocks([block4])).rejects.toThrow(BlockArchiveNotConsistentError);
|
|
1291
|
+
|
|
1292
|
+
expect(await store.getLatestBlockNumber()).toBe(3);
|
|
1293
|
+
});
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
describe('getBlocksForCheckpoint', () => {
|
|
1297
|
+
it('returns blocks for a single-block checkpoint', async () => {
|
|
1298
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1299
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }),
|
|
1300
|
+
10,
|
|
1301
|
+
);
|
|
1302
|
+
await store.addCheckpoints([checkpoint]);
|
|
1303
|
+
|
|
1304
|
+
const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(1));
|
|
1305
|
+
expect(blocks).toBeDefined();
|
|
1306
|
+
expect(blocks!.length).toBe(1);
|
|
1307
|
+
expect(blocks![0].number).toBe(1);
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
it('returns all blocks for a multi-block checkpoint', async () => {
|
|
1311
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1312
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 4, startBlockNumber: 1 }),
|
|
1313
|
+
10,
|
|
1314
|
+
);
|
|
1315
|
+
await store.addCheckpoints([checkpoint]);
|
|
1316
|
+
|
|
1317
|
+
const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(1));
|
|
1318
|
+
expect(blocks).toBeDefined();
|
|
1319
|
+
expect(blocks!.length).toBe(4);
|
|
1320
|
+
expect(blocks!.map(b => b.number)).toEqual([1, 2, 3, 4]);
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
it('returns correct blocks for different checkpoints', async () => {
|
|
1324
|
+
// Create checkpoints with chained archive roots
|
|
1325
|
+
// Checkpoint 1: blocks 1-2
|
|
1326
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
1327
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1328
|
+
|
|
1329
|
+
// Checkpoint 2: blocks 3-5
|
|
1330
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1331
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1332
|
+
numBlocks: 3,
|
|
1333
|
+
startBlockNumber: 3,
|
|
1334
|
+
previousArchive: previousArchive1,
|
|
1335
|
+
});
|
|
1336
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1337
|
+
|
|
1338
|
+
// Checkpoint 3: blocks 6-7
|
|
1339
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1340
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1341
|
+
numBlocks: 2,
|
|
1342
|
+
startBlockNumber: 6,
|
|
1343
|
+
previousArchive: previousArchive2,
|
|
1344
|
+
});
|
|
1345
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1346
|
+
|
|
1347
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
1348
|
+
|
|
1349
|
+
const blocks1 = await store.getBlocksForCheckpoint(CheckpointNumber(1));
|
|
1350
|
+
expect(blocks1).toBeDefined();
|
|
1351
|
+
expect(blocks1!.map(b => b.number)).toEqual([1, 2]);
|
|
1352
|
+
|
|
1353
|
+
const blocks2 = await store.getBlocksForCheckpoint(CheckpointNumber(2));
|
|
1354
|
+
expect(blocks2).toBeDefined();
|
|
1355
|
+
expect(blocks2!.map(b => b.number)).toEqual([3, 4, 5]);
|
|
1356
|
+
|
|
1357
|
+
const blocks3 = await store.getBlocksForCheckpoint(CheckpointNumber(3));
|
|
1358
|
+
expect(blocks3).toBeDefined();
|
|
1359
|
+
expect(blocks3!.map(b => b.number)).toEqual([6, 7]);
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
it('returns undefined for non-existent checkpoint', async () => {
|
|
1363
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1364
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1365
|
+
10,
|
|
1366
|
+
);
|
|
1367
|
+
await store.addCheckpoints([checkpoint]);
|
|
1368
|
+
|
|
1369
|
+
const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(5));
|
|
1370
|
+
expect(blocks).toBeUndefined();
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
it('returns undefined when no checkpoints exist', async () => {
|
|
1374
|
+
const blocks = await store.getBlocksForCheckpoint(CheckpointNumber(1));
|
|
1375
|
+
expect(blocks).toBeUndefined();
|
|
1376
|
+
});
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
describe('getRangeOfCheckpoints', () => {
|
|
1380
|
+
it('returns empty array when no checkpoints exist', async () => {
|
|
1381
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
|
|
1382
|
+
expect(checkpoints).toEqual([]);
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
it('returns single checkpoint', async () => {
|
|
1386
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1387
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1388
|
+
10,
|
|
1389
|
+
);
|
|
1390
|
+
await store.addCheckpoints([checkpoint]);
|
|
1391
|
+
|
|
1392
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
|
|
1393
|
+
expect(checkpoints.length).toBe(1);
|
|
1394
|
+
expect(checkpoints[0].checkpointNumber).toBe(1);
|
|
1395
|
+
expect(checkpoints[0].startBlock).toBe(1);
|
|
1396
|
+
expect(checkpoints[0].numBlocks).toBe(2);
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
it('returns multiple checkpoints in order', async () => {
|
|
1400
|
+
// Create checkpoints with chained archive roots
|
|
1401
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
1402
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1403
|
+
|
|
1404
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1405
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1406
|
+
numBlocks: 3,
|
|
1407
|
+
startBlockNumber: 3,
|
|
1408
|
+
previousArchive: previousArchive1,
|
|
1409
|
+
});
|
|
1410
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1411
|
+
|
|
1412
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1413
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1414
|
+
numBlocks: 1,
|
|
1415
|
+
startBlockNumber: 6,
|
|
1416
|
+
previousArchive: previousArchive2,
|
|
1417
|
+
});
|
|
1418
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1419
|
+
|
|
1420
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
1421
|
+
|
|
1422
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
|
|
1423
|
+
expect(checkpoints.length).toBe(3);
|
|
1424
|
+
expect(checkpoints.map(c => c.checkpointNumber)).toEqual([1, 2, 3]);
|
|
1425
|
+
expect(checkpoints.map(c => c.startBlock)).toEqual([1, 3, 6]);
|
|
1426
|
+
expect(checkpoints.map(c => c.numBlocks)).toEqual([2, 3, 1]);
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
it('respects the from parameter', async () => {
|
|
1430
|
+
// Create checkpoints with chained archive roots
|
|
1431
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
1432
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1433
|
+
|
|
1434
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1435
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1436
|
+
numBlocks: 2,
|
|
1437
|
+
startBlockNumber: 3,
|
|
1438
|
+
previousArchive: previousArchive1,
|
|
1439
|
+
});
|
|
1440
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1441
|
+
|
|
1442
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1443
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1444
|
+
numBlocks: 2,
|
|
1445
|
+
startBlockNumber: 5,
|
|
1446
|
+
previousArchive: previousArchive2,
|
|
1447
|
+
});
|
|
1448
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1449
|
+
|
|
1450
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
1451
|
+
|
|
1452
|
+
// Start from checkpoint 2
|
|
1453
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(2), 10);
|
|
1454
|
+
expect(checkpoints.length).toBe(2);
|
|
1455
|
+
expect(checkpoints.map(c => c.checkpointNumber)).toEqual([2, 3]);
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
it('respects the limit parameter', async () => {
|
|
1459
|
+
// Create checkpoints with chained archive roots
|
|
1460
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 });
|
|
1461
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1462
|
+
|
|
1463
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1464
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1465
|
+
numBlocks: 1,
|
|
1466
|
+
startBlockNumber: 2,
|
|
1467
|
+
previousArchive: previousArchive1,
|
|
1468
|
+
});
|
|
1469
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1470
|
+
|
|
1471
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1472
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1473
|
+
numBlocks: 1,
|
|
1474
|
+
startBlockNumber: 3,
|
|
1475
|
+
previousArchive: previousArchive2,
|
|
1476
|
+
});
|
|
1477
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1478
|
+
|
|
1479
|
+
const previousArchive3 = checkpoint3Cp.blocks.at(-1)!.archive;
|
|
1480
|
+
const checkpoint4Cp = await Checkpoint.random(CheckpointNumber(4), {
|
|
1481
|
+
numBlocks: 1,
|
|
1482
|
+
startBlockNumber: 4,
|
|
1483
|
+
previousArchive: previousArchive3,
|
|
1484
|
+
});
|
|
1485
|
+
const checkpoint4 = makePublishedCheckpoint(checkpoint4Cp, 13);
|
|
1486
|
+
|
|
1487
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3, checkpoint4]);
|
|
1488
|
+
|
|
1489
|
+
// Only get 2 checkpoints
|
|
1490
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 2);
|
|
1491
|
+
expect(checkpoints.length).toBe(2);
|
|
1492
|
+
expect(checkpoints.map(c => c.checkpointNumber)).toEqual([1, 2]);
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
it('returns correct checkpoint data including L1 info', async () => {
|
|
1496
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1497
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }),
|
|
1498
|
+
42,
|
|
1499
|
+
);
|
|
1500
|
+
await store.addCheckpoints([checkpoint]);
|
|
1501
|
+
|
|
1502
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 1);
|
|
1503
|
+
expect(checkpoints.length).toBe(1);
|
|
1504
|
+
|
|
1505
|
+
const data = checkpoints[0];
|
|
1506
|
+
expect(data.checkpointNumber).toBe(1);
|
|
1507
|
+
expect(data.startBlock).toBe(1);
|
|
1508
|
+
expect(data.numBlocks).toBe(3);
|
|
1509
|
+
expect(data.l1.blockNumber).toBe(42n);
|
|
1510
|
+
expect(data.header.equals(checkpoint.checkpoint.header)).toBe(true);
|
|
1511
|
+
expect(data.archive.equals(checkpoint.checkpoint.archive)).toBe(true);
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
it('returns empty array when from is beyond available checkpoints', async () => {
|
|
1515
|
+
const checkpoint = makePublishedCheckpoint(
|
|
1516
|
+
await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }),
|
|
1517
|
+
10,
|
|
1518
|
+
);
|
|
1519
|
+
await store.addCheckpoints([checkpoint]);
|
|
1520
|
+
|
|
1521
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(5), 10);
|
|
1522
|
+
expect(checkpoints).toEqual([]);
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
it('works correctly after unwinding checkpoints', async () => {
|
|
1526
|
+
// Create checkpoints with chained archive roots
|
|
1527
|
+
const checkpoint1Cp = await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 });
|
|
1528
|
+
const checkpoint1 = makePublishedCheckpoint(checkpoint1Cp, 10);
|
|
1529
|
+
|
|
1530
|
+
const previousArchive1 = checkpoint1Cp.blocks.at(-1)!.archive;
|
|
1531
|
+
const checkpoint2Cp = await Checkpoint.random(CheckpointNumber(2), {
|
|
1532
|
+
numBlocks: 2,
|
|
1533
|
+
startBlockNumber: 3,
|
|
1534
|
+
previousArchive: previousArchive1,
|
|
1535
|
+
});
|
|
1536
|
+
const checkpoint2 = makePublishedCheckpoint(checkpoint2Cp, 11);
|
|
1537
|
+
|
|
1538
|
+
const previousArchive2 = checkpoint2Cp.blocks.at(-1)!.archive;
|
|
1539
|
+
const checkpoint3Cp = await Checkpoint.random(CheckpointNumber(3), {
|
|
1540
|
+
numBlocks: 2,
|
|
1541
|
+
startBlockNumber: 5,
|
|
1542
|
+
previousArchive: previousArchive2,
|
|
1543
|
+
});
|
|
1544
|
+
const checkpoint3 = makePublishedCheckpoint(checkpoint3Cp, 12);
|
|
1545
|
+
|
|
1546
|
+
await store.addCheckpoints([checkpoint1, checkpoint2, checkpoint3]);
|
|
1547
|
+
|
|
1548
|
+
// Unwind checkpoint 3
|
|
1549
|
+
await store.unwindCheckpoints(CheckpointNumber(3), 1);
|
|
1550
|
+
|
|
1551
|
+
const checkpoints = await store.getRangeOfCheckpoints(CheckpointNumber(1), 10);
|
|
1552
|
+
expect(checkpoints.length).toBe(2);
|
|
1553
|
+
expect(checkpoints.map(c => c.checkpointNumber)).toEqual([1, 2]);
|
|
1554
|
+
});
|
|
63
1555
|
});
|
|
64
1556
|
|
|
65
|
-
describe('
|
|
66
|
-
|
|
67
|
-
await
|
|
1557
|
+
describe('getCheckpointedBlock', () => {
|
|
1558
|
+
beforeEach(async () => {
|
|
1559
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
it.each(blockNumberTests)('retrieves previously stored block %i', async (blockNumber, getExpectedBlock) => {
|
|
1563
|
+
const retrievedBlock = await store.getCheckpointedBlock(blockNumber);
|
|
1564
|
+
const expectedBlock = getExpectedBlock();
|
|
1565
|
+
const expectedCheckpoint = publishedCheckpoints[blockNumber - 1];
|
|
1566
|
+
|
|
1567
|
+
expect(retrievedBlock).toBeDefined();
|
|
1568
|
+
expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
|
|
68
1569
|
});
|
|
69
1570
|
|
|
70
|
-
it('
|
|
71
|
-
await store.
|
|
72
|
-
|
|
1571
|
+
it('returns undefined if block is not found', async () => {
|
|
1572
|
+
await expect(store.getCheckpointedBlock(12)).resolves.toBeUndefined();
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
it('returns undefined for block number 0', async () => {
|
|
1576
|
+
await expect(store.getCheckpointedBlock(0)).resolves.toBeUndefined();
|
|
73
1577
|
});
|
|
74
1578
|
});
|
|
75
1579
|
|
|
76
|
-
describe('
|
|
77
|
-
|
|
78
|
-
await store.
|
|
79
|
-
|
|
1580
|
+
describe('getCheckpointedBlockByHash', () => {
|
|
1581
|
+
beforeEach(async () => {
|
|
1582
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
1583
|
+
});
|
|
80
1584
|
|
|
81
|
-
|
|
1585
|
+
it('retrieves a block by its hash', async () => {
|
|
1586
|
+
const expectedCheckpoint = publishedCheckpoints[5];
|
|
1587
|
+
const expectedBlock = expectedCheckpoint.checkpoint.blocks[0];
|
|
1588
|
+
const blockHash = await expectedBlock.header.hash();
|
|
1589
|
+
const retrievedBlock = await store.getCheckpointedBlockByHash(blockHash);
|
|
82
1590
|
|
|
83
|
-
|
|
1591
|
+
expect(retrievedBlock).toBeDefined();
|
|
1592
|
+
expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
|
|
1593
|
+
});
|
|
84
1594
|
|
|
85
|
-
|
|
86
|
-
|
|
1595
|
+
it('returns undefined for non-existent block hash', async () => {
|
|
1596
|
+
const nonExistentHash = Fr.random();
|
|
1597
|
+
await expect(store.getCheckpointedBlockByHash(nonExistentHash)).resolves.toBeUndefined();
|
|
87
1598
|
});
|
|
1599
|
+
});
|
|
88
1600
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
await store.
|
|
92
|
-
|
|
1601
|
+
describe('getCheckpointedBlockByArchive', () => {
|
|
1602
|
+
beforeEach(async () => {
|
|
1603
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
it('retrieves a block by its archive root', async () => {
|
|
1607
|
+
const expectedCheckpoint = publishedCheckpoints[3];
|
|
1608
|
+
const expectedBlock = expectedCheckpoint.checkpoint.blocks[0];
|
|
1609
|
+
const archive = expectedBlock.archive.root;
|
|
1610
|
+
const retrievedBlock = await store.getCheckpointedBlockByArchive(archive);
|
|
93
1611
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
expect((await store.getBlocks(1, 10)).map(b => b.data.number)).toEqual([1, 2, 3, 4, 5, 6, 7]);
|
|
1612
|
+
expect(retrievedBlock).toBeDefined();
|
|
1613
|
+
expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint);
|
|
97
1614
|
});
|
|
98
1615
|
|
|
99
|
-
it('
|
|
100
|
-
|
|
101
|
-
await expect(store.
|
|
1616
|
+
it('returns undefined for non-existent archive root', async () => {
|
|
1617
|
+
const nonExistentArchive = Fr.random();
|
|
1618
|
+
await expect(store.getCheckpointedBlockByArchive(nonExistentArchive)).resolves.toBeUndefined();
|
|
102
1619
|
});
|
|
103
1620
|
});
|
|
104
1621
|
|
|
105
|
-
describe('
|
|
1622
|
+
describe('getBlockHeaderByHash', () => {
|
|
106
1623
|
beforeEach(async () => {
|
|
107
|
-
await store.
|
|
1624
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
it('retrieves a block header by its hash', async () => {
|
|
1628
|
+
const expectedBlock = publishedCheckpoints[7].checkpoint.blocks[0];
|
|
1629
|
+
const blockHash = await expectedBlock.header.hash();
|
|
1630
|
+
const retrievedHeader = await store.getBlockHeaderByHash(blockHash);
|
|
1631
|
+
|
|
1632
|
+
expect(retrievedHeader).toBeDefined();
|
|
1633
|
+
expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true);
|
|
108
1634
|
});
|
|
109
1635
|
|
|
110
|
-
it
|
|
111
|
-
|
|
1636
|
+
it('returns undefined for non-existent block hash', async () => {
|
|
1637
|
+
const nonExistentHash = Fr.random();
|
|
1638
|
+
await expect(store.getBlockHeaderByHash(nonExistentHash)).resolves.toBeUndefined();
|
|
112
1639
|
});
|
|
1640
|
+
});
|
|
113
1641
|
|
|
114
|
-
|
|
115
|
-
|
|
1642
|
+
describe('getBlockHeaderByArchive', () => {
|
|
1643
|
+
beforeEach(async () => {
|
|
1644
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
116
1645
|
});
|
|
117
1646
|
|
|
118
|
-
it('
|
|
119
|
-
|
|
1647
|
+
it('retrieves a block header by its archive root', async () => {
|
|
1648
|
+
const expectedBlock = publishedCheckpoints[2].checkpoint.blocks[0];
|
|
1649
|
+
const archive = expectedBlock.archive.root;
|
|
1650
|
+
const retrievedHeader = await store.getBlockHeaderByArchive(archive);
|
|
1651
|
+
|
|
1652
|
+
expect(retrievedHeader).toBeDefined();
|
|
1653
|
+
expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true);
|
|
120
1654
|
});
|
|
121
1655
|
|
|
122
|
-
it('
|
|
123
|
-
|
|
1656
|
+
it('returns undefined for non-existent archive root', async () => {
|
|
1657
|
+
const nonExistentArchive = Fr.random();
|
|
1658
|
+
await expect(store.getBlockHeaderByArchive(nonExistentArchive)).resolves.toBeUndefined();
|
|
124
1659
|
});
|
|
125
1660
|
});
|
|
126
1661
|
|
|
127
|
-
describe('
|
|
128
|
-
it('returns the
|
|
129
|
-
await expect(store.
|
|
1662
|
+
describe('getSynchedCheckpointNumber', () => {
|
|
1663
|
+
it('returns the checkpoint number before INITIAL_CHECKPOINT_NUMBER if no checkpoints have been added', async () => {
|
|
1664
|
+
await expect(store.getSynchedCheckpointNumber()).resolves.toEqual(INITIAL_CHECKPOINT_NUMBER - 1);
|
|
130
1665
|
});
|
|
131
1666
|
|
|
132
|
-
it(
|
|
133
|
-
await store.
|
|
134
|
-
await expect(store.
|
|
1667
|
+
it('returns the most recently added checkpoint number', async () => {
|
|
1668
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
1669
|
+
await expect(store.getSynchedCheckpointNumber()).resolves.toEqual(
|
|
1670
|
+
publishedCheckpoints.at(-1)!.checkpoint.number,
|
|
1671
|
+
);
|
|
135
1672
|
});
|
|
136
1673
|
});
|
|
137
1674
|
|
|
@@ -144,7 +1681,7 @@ export function describeArchiverDataStore(
|
|
|
144
1681
|
});
|
|
145
1682
|
|
|
146
1683
|
it('returns the L1 block number in which the most recent L2 block was published', async () => {
|
|
147
|
-
await store.
|
|
1684
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
148
1685
|
await expect(store.getSynchPoint()).resolves.toEqual({
|
|
149
1686
|
blocksSynchedTo: 19n,
|
|
150
1687
|
messagesSynchedTo: undefined,
|
|
@@ -152,71 +1689,75 @@ export function describeArchiverDataStore(
|
|
|
152
1689
|
});
|
|
153
1690
|
|
|
154
1691
|
it('returns the L1 block number that most recently added messages from inbox', async () => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
});
|
|
1692
|
+
const l1BlockHash = Buffer32.random();
|
|
1693
|
+
const l1BlockNumber = 10n;
|
|
1694
|
+
await store.setMessageSynchedL1Block({ l1BlockNumber: 5n, l1BlockHash: Buffer32.random() });
|
|
1695
|
+
await store.addL1ToL2Messages([makeInboxMessage(Buffer16.ZERO, { l1BlockNumber, l1BlockHash })]);
|
|
1696
|
+
await expect(store.getSynchPoint()).resolves.toEqual({
|
|
1697
|
+
blocksSynchedTo: undefined,
|
|
1698
|
+
messagesSynchedTo: { l1BlockHash, l1BlockNumber },
|
|
1699
|
+
} satisfies ArchiverL1SynchPoint);
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
it('returns the latest syncpoint if latest message is behind', async () => {
|
|
1703
|
+
const l1BlockHash = Buffer32.random();
|
|
1704
|
+
const l1BlockNumber = 10n;
|
|
1705
|
+
await store.setMessageSynchedL1Block({ l1BlockNumber, l1BlockHash });
|
|
1706
|
+
const msg = makeInboxMessage(Buffer16.ZERO, { l1BlockNumber: 5n, l1BlockHash: Buffer32.random() });
|
|
1707
|
+
await store.addL1ToL2Messages([msg]);
|
|
159
1708
|
await expect(store.getSynchPoint()).resolves.toEqual({
|
|
160
1709
|
blocksSynchedTo: undefined,
|
|
161
|
-
messagesSynchedTo:
|
|
1710
|
+
messagesSynchedTo: { l1BlockHash, l1BlockNumber },
|
|
162
1711
|
} satisfies ArchiverL1SynchPoint);
|
|
163
1712
|
});
|
|
164
1713
|
});
|
|
165
1714
|
|
|
166
1715
|
describe('addLogs', () => {
|
|
167
1716
|
it('adds private & public logs', async () => {
|
|
168
|
-
const
|
|
169
|
-
await
|
|
1717
|
+
const checkpoint = publishedCheckpoints[0];
|
|
1718
|
+
await store.addCheckpoints([checkpoint]);
|
|
1719
|
+
await expect(store.addLogs(checkpoint.checkpoint.blocks)).resolves.toEqual(true);
|
|
170
1720
|
});
|
|
171
1721
|
});
|
|
172
1722
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
await expect(store.addLogs([block])).resolves.toEqual(true);
|
|
178
|
-
|
|
179
|
-
expect((await store.getPrivateLogs(1, 1)).length).toEqual(
|
|
180
|
-
block.body.txEffects.map(txEffect => txEffect.privateLogs).flat().length,
|
|
181
|
-
);
|
|
182
|
-
expect((await store.getPublicLogs({ fromBlock: 1 })).logs.length).toEqual(
|
|
183
|
-
block.body.txEffects.map(txEffect => txEffect.publicLogs).flat().length,
|
|
184
|
-
);
|
|
1723
|
+
it('deleteLogs', async () => {
|
|
1724
|
+
const block = publishedCheckpoints[0].checkpoint.blocks[0];
|
|
1725
|
+
await store.addBlocks([block]);
|
|
1726
|
+
await expect(store.addLogs([block])).resolves.toEqual(true);
|
|
185
1727
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
expect((await store.getPrivateLogs(1, 1)).length).toEqual(0);
|
|
190
|
-
expect((await store.getPublicLogs({ fromBlock: 1 })).logs.length).toEqual(0);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
1728
|
+
expect((await store.getPublicLogs({ fromBlock: BlockNumber(1) })).logs.length).toEqual(
|
|
1729
|
+
block.body.txEffects.map(txEffect => txEffect.publicLogs).flat().length,
|
|
1730
|
+
);
|
|
193
1731
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const block = blocks[0].data;
|
|
197
|
-
await store.addBlocks([blocks[0]]);
|
|
198
|
-
await store.addLogs([block]);
|
|
1732
|
+
// This one is a pain for memory as we would never want to just delete memory in the middle.
|
|
1733
|
+
await store.deleteLogs([block]);
|
|
199
1734
|
|
|
200
|
-
|
|
201
|
-
expect(privateLogs).toEqual(block.body.txEffects.map(txEffect => txEffect.privateLogs).flat());
|
|
202
|
-
});
|
|
1735
|
+
expect((await store.getPublicLogs({ fromBlock: BlockNumber(1) })).logs.length).toEqual(0);
|
|
203
1736
|
});
|
|
204
1737
|
|
|
205
1738
|
describe('getTxEffect', () => {
|
|
1739
|
+
const getBlock = (i: number) => publishedCheckpoints[i].checkpoint.blocks[0];
|
|
1740
|
+
|
|
206
1741
|
beforeEach(async () => {
|
|
207
|
-
await store.addLogs(
|
|
208
|
-
await store.
|
|
1742
|
+
await store.addLogs(publishedCheckpoints.flatMap(x => x.checkpoint.blocks));
|
|
1743
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
209
1744
|
});
|
|
210
1745
|
|
|
211
1746
|
it.each([
|
|
212
|
-
() =>
|
|
213
|
-
() =>
|
|
214
|
-
() =>
|
|
215
|
-
() =>
|
|
216
|
-
() =>
|
|
1747
|
+
() => ({ data: getBlock(0).body.txEffects[0], block: getBlock(0), txIndexInBlock: 0 }),
|
|
1748
|
+
() => ({ data: getBlock(9).body.txEffects[3], block: getBlock(9), txIndexInBlock: 3 }),
|
|
1749
|
+
() => ({ data: getBlock(3).body.txEffects[1], block: getBlock(3), txIndexInBlock: 1 }),
|
|
1750
|
+
() => ({ data: getBlock(5).body.txEffects[2], block: getBlock(5), txIndexInBlock: 2 }),
|
|
1751
|
+
() => ({ data: getBlock(1).body.txEffects[0], block: getBlock(1), txIndexInBlock: 0 }),
|
|
217
1752
|
])('retrieves a previously stored transaction', async getExpectedTx => {
|
|
218
|
-
const
|
|
219
|
-
const
|
|
1753
|
+
const { data, block, txIndexInBlock } = getExpectedTx();
|
|
1754
|
+
const expectedTx: IndexedTxEffect = {
|
|
1755
|
+
data,
|
|
1756
|
+
l2BlockNumber: block.number,
|
|
1757
|
+
l2BlockHash: L2BlockHash.fromField(await block.header.hash()),
|
|
1758
|
+
txIndexInBlock,
|
|
1759
|
+
};
|
|
1760
|
+
const actualTx = await store.getTxEffect(data.txHash);
|
|
220
1761
|
expect(actualTx).toEqual(expectedTx);
|
|
221
1762
|
});
|
|
222
1763
|
|
|
@@ -225,61 +1766,250 @@ export function describeArchiverDataStore(
|
|
|
225
1766
|
});
|
|
226
1767
|
|
|
227
1768
|
it.each([
|
|
228
|
-
() =>
|
|
229
|
-
() =>
|
|
230
|
-
() =>
|
|
231
|
-
() =>
|
|
232
|
-
() =>
|
|
233
|
-
])('tries to retrieves a previously stored transaction after deleted', async
|
|
234
|
-
await store.
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
const actualTx = await store.getTxEffect(
|
|
1769
|
+
() => getBlock(0).body.txEffects[0],
|
|
1770
|
+
() => getBlock(9).body.txEffects[3],
|
|
1771
|
+
() => getBlock(3).body.txEffects[1],
|
|
1772
|
+
() => getBlock(5).body.txEffects[2],
|
|
1773
|
+
() => getBlock(1).body.txEffects[0],
|
|
1774
|
+
])('tries to retrieves a previously stored transaction after deleted', async getTxEffect => {
|
|
1775
|
+
await store.unwindCheckpoints(CheckpointNumber(publishedCheckpoints.length), publishedCheckpoints.length);
|
|
1776
|
+
|
|
1777
|
+
const txEffect = getTxEffect();
|
|
1778
|
+
const actualTx = await store.getTxEffect(txEffect.txHash);
|
|
238
1779
|
expect(actualTx).toEqual(undefined);
|
|
239
1780
|
});
|
|
240
1781
|
|
|
241
1782
|
it('returns undefined if tx is not found', async () => {
|
|
242
1783
|
await expect(store.getTxEffect(TxHash.random())).resolves.toBeUndefined();
|
|
243
1784
|
});
|
|
1785
|
+
|
|
1786
|
+
it('does not fail if the block is unwound while requesting a tx', async () => {
|
|
1787
|
+
const txEffect = getBlock(1).body.txEffects[0];
|
|
1788
|
+
let done = false;
|
|
1789
|
+
void (async () => {
|
|
1790
|
+
while (!done) {
|
|
1791
|
+
void store.getTxEffect(txEffect.txHash);
|
|
1792
|
+
await sleep(1);
|
|
1793
|
+
}
|
|
1794
|
+
})();
|
|
1795
|
+
await store.unwindCheckpoints(CheckpointNumber(publishedCheckpoints.length), publishedCheckpoints.length);
|
|
1796
|
+
done = true;
|
|
1797
|
+
expect(await store.getTxEffect(txEffect.txHash)).toEqual(undefined);
|
|
1798
|
+
});
|
|
244
1799
|
});
|
|
245
1800
|
|
|
246
1801
|
describe('L1 to L2 Messages', () => {
|
|
247
|
-
const
|
|
248
|
-
|
|
1802
|
+
const initialCheckpointNumber = CheckpointNumber(13);
|
|
1803
|
+
|
|
1804
|
+
const checkMessages = async (msgs: InboxMessage[]) => {
|
|
1805
|
+
expect(await store.getLastL1ToL2Message()).toEqual(msgs.at(-1));
|
|
1806
|
+
expect(await toArray(store.iterateL1ToL2Messages())).toEqual(msgs);
|
|
1807
|
+
expect(await store.getTotalL1ToL2MessageCount()).toEqual(BigInt(msgs.length));
|
|
1808
|
+
};
|
|
1809
|
+
|
|
1810
|
+
const makeInboxMessagesWithFullBlocks = (
|
|
1811
|
+
blockCount: number,
|
|
1812
|
+
opts: { initialCheckpointNumber?: CheckpointNumber } = {},
|
|
1813
|
+
) =>
|
|
1814
|
+
makeInboxMessages(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * blockCount, {
|
|
1815
|
+
overrideFn: (msg, i) => {
|
|
1816
|
+
const checkpointNumber = CheckpointNumber(
|
|
1817
|
+
(opts.initialCheckpointNumber ?? initialCheckpointNumber) +
|
|
1818
|
+
Math.floor(i / NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP),
|
|
1819
|
+
);
|
|
1820
|
+
const index =
|
|
1821
|
+
InboxLeaf.smallestIndexForCheckpoint(checkpointNumber) + BigInt(i % NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1822
|
+
return { ...msg, checkpointNumber, index };
|
|
1823
|
+
},
|
|
1824
|
+
});
|
|
1825
|
+
|
|
1826
|
+
it('stores first message ever', async () => {
|
|
1827
|
+
const msg = makeInboxMessage(Buffer16.ZERO, { index: 0n, checkpointNumber: CheckpointNumber(1) });
|
|
1828
|
+
await store.addL1ToL2Messages([msg]);
|
|
1829
|
+
|
|
1830
|
+
await checkMessages([msg]);
|
|
1831
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toEqual([msg.leaf]);
|
|
1832
|
+
});
|
|
1833
|
+
|
|
1834
|
+
it('stores single message', async () => {
|
|
1835
|
+
const msg = makeInboxMessage(Buffer16.ZERO, { checkpointNumber: CheckpointNumber(2) });
|
|
1836
|
+
await store.addL1ToL2Messages([msg]);
|
|
1837
|
+
|
|
1838
|
+
await checkMessages([msg]);
|
|
1839
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toEqual([msg.leaf]);
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
it('stores and returns messages across different blocks', async () => {
|
|
1843
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber });
|
|
1844
|
+
await store.addL1ToL2Messages(msgs);
|
|
1845
|
+
|
|
1846
|
+
await checkMessages(msgs);
|
|
1847
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 2))).toEqual(
|
|
1848
|
+
[msgs[2]].map(m => m.leaf),
|
|
1849
|
+
);
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
it('stores the same messages again', async () => {
|
|
1853
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber });
|
|
1854
|
+
await store.addL1ToL2Messages(msgs);
|
|
1855
|
+
await store.addL1ToL2Messages(msgs.slice(2));
|
|
1856
|
+
|
|
1857
|
+
await checkMessages(msgs);
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1860
|
+
it('stores and returns messages across different blocks with gaps', async () => {
|
|
1861
|
+
const msgs1 = makeInboxMessages(3, { initialCheckpointNumber: CheckpointNumber(1) });
|
|
1862
|
+
const msgs2 = makeInboxMessages(3, {
|
|
1863
|
+
initialCheckpointNumber: CheckpointNumber(20),
|
|
1864
|
+
initialHash: msgs1.at(-1)!.rollingHash,
|
|
1865
|
+
});
|
|
1866
|
+
|
|
1867
|
+
await store.addL1ToL2Messages(msgs1);
|
|
1868
|
+
await store.addL1ToL2Messages(msgs2);
|
|
1869
|
+
|
|
1870
|
+
await checkMessages([...msgs1, ...msgs2]);
|
|
1871
|
+
|
|
1872
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toEqual([msgs1[0].leaf]);
|
|
1873
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(4))).toEqual([]);
|
|
1874
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(20))).toEqual([msgs2[0].leaf]);
|
|
1875
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(24))).toEqual([]);
|
|
1876
|
+
});
|
|
1877
|
+
|
|
1878
|
+
it('stores and returns messages with block numbers larger than a byte', async () => {
|
|
1879
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber: CheckpointNumber(1000) });
|
|
1880
|
+
await store.addL1ToL2Messages(msgs);
|
|
1881
|
+
|
|
1882
|
+
await checkMessages(msgs);
|
|
1883
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1002))).toEqual([msgs[2]].map(m => m.leaf));
|
|
1884
|
+
});
|
|
1885
|
+
|
|
1886
|
+
it('stores and returns multiple messages per block', async () => {
|
|
1887
|
+
const msgs = makeInboxMessagesWithFullBlocks(4);
|
|
1888
|
+
await store.addL1ToL2Messages(msgs);
|
|
1889
|
+
|
|
1890
|
+
await checkMessages(msgs);
|
|
1891
|
+
const blockMessages = await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 1));
|
|
1892
|
+
expect(blockMessages).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1893
|
+
expect(blockMessages).toEqual(
|
|
1894
|
+
msgs.slice(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2).map(m => m.leaf),
|
|
1895
|
+
);
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1898
|
+
it('stores messages in multiple operations', async () => {
|
|
1899
|
+
const msgs = makeInboxMessages(20, { initialCheckpointNumber });
|
|
1900
|
+
await store.addL1ToL2Messages(msgs.slice(0, 10));
|
|
1901
|
+
await store.addL1ToL2Messages(msgs.slice(10, 20));
|
|
249
1902
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
{ length: numMessages },
|
|
253
|
-
(_, i) => new InboxLeaf(InboxLeaf.smallestIndexFromL2Block(blockNumber) + BigInt(i), Fr.random()),
|
|
1903
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 2))).toEqual(
|
|
1904
|
+
[msgs[2]].map(m => m.leaf),
|
|
254
1905
|
);
|
|
1906
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(initialCheckpointNumber + 12))).toEqual(
|
|
1907
|
+
[msgs[12]].map(m => m.leaf),
|
|
1908
|
+
);
|
|
1909
|
+
await checkMessages(msgs);
|
|
1910
|
+
});
|
|
1911
|
+
|
|
1912
|
+
it('iterates over messages from start index', async () => {
|
|
1913
|
+
const msgs = makeInboxMessages(10, { initialCheckpointNumber });
|
|
1914
|
+
await store.addL1ToL2Messages(msgs);
|
|
1915
|
+
|
|
1916
|
+
const iterated = await toArray(store.iterateL1ToL2Messages({ start: msgs[3].index }));
|
|
1917
|
+
expect(iterated).toEqual(msgs.slice(3));
|
|
1918
|
+
});
|
|
1919
|
+
|
|
1920
|
+
it('iterates over messages in reverse', async () => {
|
|
1921
|
+
const msgs = makeInboxMessages(10, { initialCheckpointNumber });
|
|
1922
|
+
await store.addL1ToL2Messages(msgs);
|
|
1923
|
+
initialCheckpointNumber;
|
|
1924
|
+
|
|
1925
|
+
const iterated = await toArray(store.iterateL1ToL2Messages({ reverse: true, end: msgs[3].index }));
|
|
1926
|
+
expect(iterated).toEqual(msgs.slice(0, 4).reverse());
|
|
1927
|
+
});
|
|
1928
|
+
|
|
1929
|
+
it('throws if messages are added out of order', async () => {
|
|
1930
|
+
const msgs = makeInboxMessages(5, { overrideFn: (msg, i) => ({ ...msg, index: BigInt(10 - i) }) });
|
|
1931
|
+
await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
|
|
1932
|
+
});
|
|
1933
|
+
|
|
1934
|
+
it('throws if block number for the first message is out of order', async () => {
|
|
1935
|
+
const msgs = makeInboxMessages(4, { initialCheckpointNumber });
|
|
1936
|
+
msgs[2].checkpointNumber = CheckpointNumber(initialCheckpointNumber - 1);
|
|
1937
|
+
await store.addL1ToL2Messages(msgs.slice(0, 2));
|
|
1938
|
+
await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
|
|
1939
|
+
});
|
|
1940
|
+
|
|
1941
|
+
it('throws if rolling hash is not correct', async () => {
|
|
1942
|
+
const msgs = makeInboxMessages(5);
|
|
1943
|
+
msgs[1].rollingHash = Buffer16.random();
|
|
1944
|
+
await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
|
|
1945
|
+
});
|
|
255
1946
|
|
|
256
|
-
it('
|
|
257
|
-
const msgs =
|
|
258
|
-
|
|
259
|
-
await store.addL1ToL2Messages(
|
|
260
|
-
|
|
1947
|
+
it('throws if rolling hash for first message is not correct', async () => {
|
|
1948
|
+
const msgs = makeInboxMessages(4);
|
|
1949
|
+
msgs[2].rollingHash = Buffer16.random();
|
|
1950
|
+
await store.addL1ToL2Messages(msgs.slice(0, CheckpointNumber(2)));
|
|
1951
|
+
await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
|
|
1952
|
+
});
|
|
1953
|
+
|
|
1954
|
+
it('throws if index is not in the correct range', async () => {
|
|
1955
|
+
const msgs = makeInboxMessages(5, { initialCheckpointNumber });
|
|
1956
|
+
msgs.at(-1)!.index += 100n;
|
|
1957
|
+
await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
|
|
1958
|
+
});
|
|
1959
|
+
|
|
1960
|
+
it('throws if first index in block has gaps', async () => {
|
|
1961
|
+
const msgs = makeInboxMessages(4, { initialCheckpointNumber });
|
|
1962
|
+
msgs[2].index++;
|
|
1963
|
+
await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
it('throws if index does not follow previous one', async () => {
|
|
1967
|
+
const msgs = makeInboxMessages(2, {
|
|
1968
|
+
initialCheckpointNumber,
|
|
1969
|
+
overrideFn: (msg, i) => ({
|
|
1970
|
+
...msg,
|
|
1971
|
+
checkpointNumber: CheckpointNumber(2),
|
|
1972
|
+
index: BigInt(i + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2),
|
|
1973
|
+
}),
|
|
1974
|
+
});
|
|
1975
|
+
msgs[1].index++;
|
|
1976
|
+
await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
|
|
1977
|
+
});
|
|
261
1978
|
|
|
262
|
-
|
|
263
|
-
|
|
1979
|
+
it('removes messages up to the given block number', async () => {
|
|
1980
|
+
const msgs = makeInboxMessagesWithFullBlocks(4, { initialCheckpointNumber: CheckpointNumber(1) });
|
|
1981
|
+
|
|
1982
|
+
await store.addL1ToL2Messages(msgs);
|
|
1983
|
+
await checkMessages(msgs);
|
|
1984
|
+
|
|
1985
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1986
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1987
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(3))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1988
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(4))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1989
|
+
|
|
1990
|
+
await store.rollbackL1ToL2MessagesToCheckpoint(CheckpointNumber(2));
|
|
1991
|
+
|
|
1992
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(1))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1993
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(2))).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
|
|
1994
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(3))).toHaveLength(0);
|
|
1995
|
+
expect(await store.getL1ToL2Messages(CheckpointNumber(4))).toHaveLength(0);
|
|
1996
|
+
|
|
1997
|
+
await checkMessages(msgs.slice(0, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2));
|
|
264
1998
|
});
|
|
265
1999
|
|
|
266
|
-
it('
|
|
267
|
-
const msgs =
|
|
268
|
-
|
|
269
|
-
// --> with that there will be a gap and it will be impossible to sequence the
|
|
270
|
-
// end of tree = start of next tree/block - 1
|
|
271
|
-
msgs[4] = new InboxLeaf(InboxLeaf.smallestIndexFromL2Block(l2BlockNumber + 1n) - 1n, Fr.random());
|
|
2000
|
+
it('removes messages starting with the given index', async () => {
|
|
2001
|
+
const msgs = makeInboxMessagesWithFullBlocks(4, { initialCheckpointNumber: CheckpointNumber(1) });
|
|
2002
|
+
await store.addL1ToL2Messages(msgs);
|
|
272
2003
|
|
|
273
|
-
await store.
|
|
274
|
-
await
|
|
275
|
-
await store.getL1ToL2Messages(l2BlockNumber);
|
|
276
|
-
}).rejects.toThrow(`L1 to L2 message gap found in block ${l2BlockNumber}`);
|
|
2004
|
+
await store.removeL1ToL2Messages(msgs[13].index);
|
|
2005
|
+
await checkMessages(msgs.slice(0, 13));
|
|
277
2006
|
});
|
|
278
2007
|
});
|
|
279
2008
|
|
|
280
2009
|
describe('contractInstances', () => {
|
|
281
2010
|
let contractInstance: ContractInstanceWithAddress;
|
|
282
2011
|
const blockNum = 10;
|
|
2012
|
+
const timestamp = 3600n;
|
|
283
2013
|
|
|
284
2014
|
beforeEach(async () => {
|
|
285
2015
|
const classId = Fr.random();
|
|
@@ -288,20 +2018,115 @@ export function describeArchiverDataStore(
|
|
|
288
2018
|
originalContractClassId: classId,
|
|
289
2019
|
});
|
|
290
2020
|
contractInstance = { ...randomInstance, address: await AztecAddress.random() };
|
|
291
|
-
await store.addContractInstances([contractInstance], blockNum);
|
|
2021
|
+
await store.addContractInstances([contractInstance], BlockNumber(blockNum));
|
|
292
2022
|
});
|
|
293
2023
|
|
|
294
2024
|
it('returns previously stored contract instances', async () => {
|
|
295
|
-
await expect(store.getContractInstance(contractInstance.address)).resolves.toMatchObject(
|
|
2025
|
+
await expect(store.getContractInstance(contractInstance.address, timestamp)).resolves.toMatchObject(
|
|
2026
|
+
contractInstance,
|
|
2027
|
+
);
|
|
296
2028
|
});
|
|
297
2029
|
|
|
298
2030
|
it('returns undefined if contract instance is not found', async () => {
|
|
299
|
-
await expect(store.getContractInstance(await AztecAddress.random())).resolves.toBeUndefined();
|
|
2031
|
+
await expect(store.getContractInstance(await AztecAddress.random(), timestamp)).resolves.toBeUndefined();
|
|
300
2032
|
});
|
|
301
2033
|
|
|
302
2034
|
it('returns undefined if previously stored contract instances was deleted', async () => {
|
|
303
|
-
await store.deleteContractInstances([contractInstance], blockNum);
|
|
304
|
-
await expect(store.getContractInstance(contractInstance.address)).resolves.toBeUndefined();
|
|
2035
|
+
await store.deleteContractInstances([contractInstance], BlockNumber(blockNum));
|
|
2036
|
+
await expect(store.getContractInstance(contractInstance.address, timestamp)).resolves.toBeUndefined();
|
|
2037
|
+
});
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
describe('contractInstanceUpdates', () => {
|
|
2041
|
+
let contractInstance: ContractInstanceWithAddress;
|
|
2042
|
+
let classId: Fr;
|
|
2043
|
+
let nextClassId: Fr;
|
|
2044
|
+
const timestampOfChange = 3600n;
|
|
2045
|
+
|
|
2046
|
+
beforeEach(async () => {
|
|
2047
|
+
classId = Fr.random();
|
|
2048
|
+
nextClassId = Fr.random();
|
|
2049
|
+
const randomInstance = await SerializableContractInstance.random({
|
|
2050
|
+
currentContractClassId: classId,
|
|
2051
|
+
originalContractClassId: classId,
|
|
2052
|
+
});
|
|
2053
|
+
contractInstance = { ...randomInstance, address: await AztecAddress.random() };
|
|
2054
|
+
await store.addContractInstances([contractInstance], BlockNumber(1));
|
|
2055
|
+
await store.addContractInstanceUpdates(
|
|
2056
|
+
[
|
|
2057
|
+
{
|
|
2058
|
+
prevContractClassId: classId,
|
|
2059
|
+
newContractClassId: nextClassId,
|
|
2060
|
+
timestampOfChange,
|
|
2061
|
+
address: contractInstance.address,
|
|
2062
|
+
},
|
|
2063
|
+
],
|
|
2064
|
+
timestampOfChange - 1n,
|
|
2065
|
+
);
|
|
2066
|
+
});
|
|
2067
|
+
|
|
2068
|
+
it('gets the correct current class id for a contract not updated yet', async () => {
|
|
2069
|
+
const fetchedInstance = await store.getContractInstance(contractInstance.address, timestampOfChange - 1n);
|
|
2070
|
+
expect(fetchedInstance?.originalContractClassId).toEqual(classId);
|
|
2071
|
+
expect(fetchedInstance?.currentContractClassId).toEqual(classId);
|
|
2072
|
+
});
|
|
2073
|
+
|
|
2074
|
+
it('gets the correct current class id for a contract that has just been updated', async () => {
|
|
2075
|
+
const fetchedInstance = await store.getContractInstance(contractInstance.address, timestampOfChange);
|
|
2076
|
+
expect(fetchedInstance?.originalContractClassId).toEqual(classId);
|
|
2077
|
+
expect(fetchedInstance?.currentContractClassId).toEqual(nextClassId);
|
|
2078
|
+
});
|
|
2079
|
+
|
|
2080
|
+
it('gets the correct current class id for a contract that was updated in the past', async () => {
|
|
2081
|
+
const fetchedInstance = await store.getContractInstance(contractInstance.address, timestampOfChange + 1n);
|
|
2082
|
+
expect(fetchedInstance?.originalContractClassId).toEqual(classId);
|
|
2083
|
+
expect(fetchedInstance?.currentContractClassId).toEqual(nextClassId);
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
it('ignores updates for the wrong contract', async () => {
|
|
2087
|
+
const otherClassId = Fr.random();
|
|
2088
|
+
const randomInstance = await SerializableContractInstance.random({
|
|
2089
|
+
currentContractClassId: otherClassId,
|
|
2090
|
+
originalContractClassId: otherClassId,
|
|
2091
|
+
});
|
|
2092
|
+
const otherContractInstance = {
|
|
2093
|
+
...randomInstance,
|
|
2094
|
+
address: await AztecAddress.random(),
|
|
2095
|
+
};
|
|
2096
|
+
await store.addContractInstances([otherContractInstance], BlockNumber(1));
|
|
2097
|
+
|
|
2098
|
+
const fetchedInstance = await store.getContractInstance(otherContractInstance.address, timestampOfChange + 1n);
|
|
2099
|
+
expect(fetchedInstance?.originalContractClassId).toEqual(otherClassId);
|
|
2100
|
+
expect(fetchedInstance?.currentContractClassId).toEqual(otherClassId);
|
|
2101
|
+
});
|
|
2102
|
+
|
|
2103
|
+
it('bounds its search to the right contract if more than than one update exists', async () => {
|
|
2104
|
+
const otherClassId = Fr.random();
|
|
2105
|
+
const otherNextClassId = Fr.random();
|
|
2106
|
+
const randomInstance = await SerializableContractInstance.random({
|
|
2107
|
+
currentContractClassId: otherClassId,
|
|
2108
|
+
originalContractClassId: otherNextClassId,
|
|
2109
|
+
});
|
|
2110
|
+
const otherContractInstance = {
|
|
2111
|
+
...randomInstance,
|
|
2112
|
+
address: await AztecAddress.random(),
|
|
2113
|
+
};
|
|
2114
|
+
await store.addContractInstances([otherContractInstance], BlockNumber(1));
|
|
2115
|
+
await store.addContractInstanceUpdates(
|
|
2116
|
+
[
|
|
2117
|
+
{
|
|
2118
|
+
prevContractClassId: otherClassId,
|
|
2119
|
+
newContractClassId: otherNextClassId,
|
|
2120
|
+
timestampOfChange,
|
|
2121
|
+
address: otherContractInstance.address,
|
|
2122
|
+
},
|
|
2123
|
+
],
|
|
2124
|
+
timestampOfChange - 1n,
|
|
2125
|
+
);
|
|
2126
|
+
|
|
2127
|
+
const fetchedInstance = await store.getContractInstance(contractInstance.address, timestampOfChange + 1n);
|
|
2128
|
+
expect(fetchedInstance?.originalContractClassId).toEqual(classId);
|
|
2129
|
+
expect(fetchedInstance?.currentContractClassId).toEqual(nextClassId);
|
|
305
2130
|
});
|
|
306
2131
|
});
|
|
307
2132
|
|
|
@@ -314,7 +2139,7 @@ export function describeArchiverDataStore(
|
|
|
314
2139
|
await store.addContractClasses(
|
|
315
2140
|
[contractClass],
|
|
316
2141
|
[await computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
317
|
-
blockNum,
|
|
2142
|
+
BlockNumber(blockNum),
|
|
318
2143
|
);
|
|
319
2144
|
});
|
|
320
2145
|
|
|
@@ -323,7 +2148,7 @@ export function describeArchiverDataStore(
|
|
|
323
2148
|
});
|
|
324
2149
|
|
|
325
2150
|
it('returns undefined if the initial deployed contract class was deleted', async () => {
|
|
326
|
-
await store.deleteContractClasses([contractClass], blockNum);
|
|
2151
|
+
await store.deleteContractClasses([contractClass], BlockNumber(blockNum));
|
|
327
2152
|
await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined();
|
|
328
2153
|
});
|
|
329
2154
|
|
|
@@ -331,9 +2156,9 @@ export function describeArchiverDataStore(
|
|
|
331
2156
|
await store.addContractClasses(
|
|
332
2157
|
[contractClass],
|
|
333
2158
|
[await computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
334
|
-
blockNum + 1,
|
|
2159
|
+
BlockNumber(blockNum + 1),
|
|
335
2160
|
);
|
|
336
|
-
await store.deleteContractClasses([contractClass], blockNum + 1);
|
|
2161
|
+
await store.deleteContractClasses([contractClass], BlockNumber(blockNum + 1));
|
|
337
2162
|
await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
|
|
338
2163
|
});
|
|
339
2164
|
|
|
@@ -356,172 +2181,285 @@ export function describeArchiverDataStore(
|
|
|
356
2181
|
expect(stored?.privateFunctions).toEqual(fns);
|
|
357
2182
|
});
|
|
358
2183
|
|
|
359
|
-
it('adds new
|
|
360
|
-
const fns = times(3,
|
|
2184
|
+
it('adds new utility functions', async () => {
|
|
2185
|
+
const fns = times(3, makeUtilityFunctionWithMembershipProof);
|
|
361
2186
|
await store.addFunctions(contractClass.id, [], fns);
|
|
362
2187
|
const stored = await store.getContractClass(contractClass.id);
|
|
363
|
-
expect(stored?.
|
|
2188
|
+
expect(stored?.utilityFunctions).toEqual(fns);
|
|
364
2189
|
});
|
|
365
2190
|
|
|
366
|
-
it('does not duplicate
|
|
367
|
-
const fns = times(3,
|
|
2191
|
+
it('does not duplicate utility functions', async () => {
|
|
2192
|
+
const fns = times(3, makeUtilityFunctionWithMembershipProof);
|
|
368
2193
|
await store.addFunctions(contractClass.id, [], fns.slice(0, 1));
|
|
369
2194
|
await store.addFunctions(contractClass.id, [], fns);
|
|
370
2195
|
const stored = await store.getContractClass(contractClass.id);
|
|
371
|
-
expect(stored?.
|
|
2196
|
+
expect(stored?.utilityFunctions).toEqual(fns);
|
|
372
2197
|
});
|
|
373
2198
|
});
|
|
374
2199
|
|
|
375
|
-
describe('
|
|
376
|
-
const
|
|
2200
|
+
describe('getPrivateLogsByTags', () => {
|
|
2201
|
+
const numBlocksForLogs = 3;
|
|
377
2202
|
const numTxsPerBlock = 4;
|
|
378
2203
|
const numPrivateLogsPerTx = 3;
|
|
379
|
-
const numPublicLogsPerTx = 2;
|
|
380
2204
|
|
|
381
|
-
let
|
|
2205
|
+
let logsCheckpoints: PublishedCheckpoint[];
|
|
382
2206
|
|
|
383
|
-
const
|
|
384
|
-
new
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const buf = Buffer.alloc(32);
|
|
390
|
-
buf.writeUint16BE(publicValuesLen, 27);
|
|
391
|
-
buf.writeUint16BE(privateValuesLen, 30);
|
|
392
|
-
return Fr.fromBuffer(buf);
|
|
393
|
-
};
|
|
2207
|
+
const makePrivateLogTag = (blockNumber: number, txIndex: number, logIndex: number): SiloedTag =>
|
|
2208
|
+
new SiloedTag(
|
|
2209
|
+
blockNumber === 1 && txIndex === 0 && logIndex === 0
|
|
2210
|
+
? Fr.ZERO // Shared tag
|
|
2211
|
+
: new Fr(blockNumber * 100 + txIndex * 10 + logIndex),
|
|
2212
|
+
);
|
|
394
2213
|
|
|
395
|
-
const makePrivateLog = (tag:
|
|
396
|
-
PrivateLog.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
AztecAddress.fromNumber(1).toField(), // log address
|
|
403
|
-
makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 3), // field 0
|
|
404
|
-
tag, // field 1
|
|
405
|
-
...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)), // fields 2 to end
|
|
406
|
-
]);
|
|
2214
|
+
const makePrivateLog = (tag: SiloedTag) =>
|
|
2215
|
+
PrivateLog.from({
|
|
2216
|
+
fields: makeTuple(PRIVATE_LOG_SIZE_IN_FIELDS, i =>
|
|
2217
|
+
!i ? tag.value : new Fr(tag.value.toBigInt() + BigInt(i)),
|
|
2218
|
+
),
|
|
2219
|
+
emittedLength: PRIVATE_LOG_SIZE_IN_FIELDS,
|
|
2220
|
+
});
|
|
407
2221
|
|
|
408
2222
|
const mockPrivateLogs = (blockNumber: number, txIndex: number) => {
|
|
409
2223
|
return times(numPrivateLogsPerTx, (logIndex: number) => {
|
|
410
|
-
const tag =
|
|
2224
|
+
const tag = makePrivateLogTag(blockNumber, txIndex, logIndex);
|
|
411
2225
|
return makePrivateLog(tag);
|
|
412
2226
|
});
|
|
413
2227
|
};
|
|
414
2228
|
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
2229
|
+
const mockCheckpointWithLogs = async (
|
|
2230
|
+
blockNumber: number,
|
|
2231
|
+
previousArchive?: AppendOnlyTreeSnapshot,
|
|
2232
|
+
): Promise<PublishedCheckpoint> => {
|
|
2233
|
+
const block = await L2BlockNew.random(BlockNumber(blockNumber), {
|
|
2234
|
+
checkpointNumber: CheckpointNumber(blockNumber),
|
|
2235
|
+
indexWithinCheckpoint: 0,
|
|
2236
|
+
state: makeStateForBlock(blockNumber, numTxsPerBlock),
|
|
2237
|
+
...(previousArchive ? { lastArchive: previousArchive } : {}),
|
|
419
2238
|
});
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const mockBlockWithLogs = async (blockNumber: number): Promise<L1Published<L2Block>> => {
|
|
423
|
-
const block = await L2Block.random(blockNumber);
|
|
424
|
-
block.header.globalVariables.blockNumber = new Fr(blockNumber);
|
|
2239
|
+
block.header.globalVariables.blockNumber = BlockNumber(blockNumber);
|
|
425
2240
|
|
|
426
2241
|
block.body.txEffects = await timesParallel(numTxsPerBlock, async (txIndex: number) => {
|
|
427
2242
|
const txEffect = await TxEffect.random();
|
|
428
2243
|
txEffect.privateLogs = mockPrivateLogs(blockNumber, txIndex);
|
|
429
|
-
txEffect.publicLogs =
|
|
2244
|
+
txEffect.publicLogs = []; // No public logs needed for private log tests
|
|
430
2245
|
return txEffect;
|
|
431
2246
|
});
|
|
432
2247
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
2248
|
+
const checkpoint = new Checkpoint(
|
|
2249
|
+
AppendOnlyTreeSnapshot.random(),
|
|
2250
|
+
CheckpointHeader.random(),
|
|
2251
|
+
[block],
|
|
2252
|
+
CheckpointNumber(blockNumber),
|
|
2253
|
+
);
|
|
2254
|
+
return makePublishedCheckpoint(checkpoint, blockNumber);
|
|
437
2255
|
};
|
|
438
2256
|
|
|
439
2257
|
beforeEach(async () => {
|
|
440
|
-
|
|
2258
|
+
// Create checkpoints sequentially to chain archive roots
|
|
2259
|
+
logsCheckpoints = [];
|
|
2260
|
+
for (let i = 0; i < numBlocksForLogs; i++) {
|
|
2261
|
+
const previousArchive = i > 0 ? logsCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
|
|
2262
|
+
logsCheckpoints.push(await mockCheckpointWithLogs(i + 1, previousArchive));
|
|
2263
|
+
}
|
|
441
2264
|
|
|
442
|
-
await store.
|
|
443
|
-
await store.addLogs(
|
|
2265
|
+
await store.addCheckpoints(logsCheckpoints);
|
|
2266
|
+
await store.addLogs(logsCheckpoints.flatMap(p => p.checkpoint.blocks));
|
|
444
2267
|
});
|
|
445
2268
|
|
|
446
2269
|
it('is possible to batch request private logs via tags', async () => {
|
|
447
|
-
const tags = [
|
|
2270
|
+
const tags = [makePrivateLogTag(2, 1, 2), makePrivateLogTag(1, 2, 0)];
|
|
2271
|
+
|
|
2272
|
+
const logsByTags = await store.getPrivateLogsByTags(tags);
|
|
2273
|
+
|
|
2274
|
+
expect(logsByTags).toEqual([
|
|
2275
|
+
[
|
|
2276
|
+
expect.objectContaining({
|
|
2277
|
+
blockNumber: 2,
|
|
2278
|
+
logData: makePrivateLog(tags[0]).getEmittedFields(),
|
|
2279
|
+
}),
|
|
2280
|
+
],
|
|
2281
|
+
[
|
|
2282
|
+
expect.objectContaining({
|
|
2283
|
+
blockNumber: 1,
|
|
2284
|
+
logData: makePrivateLog(tags[1]).getEmittedFields(),
|
|
2285
|
+
}),
|
|
2286
|
+
],
|
|
2287
|
+
]);
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
it('is possible to batch request logs that have the same tag but different content', async () => {
|
|
2291
|
+
const tags = [makePrivateLogTag(1, 2, 1)];
|
|
448
2292
|
|
|
449
|
-
|
|
2293
|
+
// Create a checkpoint containing logs that have the same tag as the checkpoints before.
|
|
2294
|
+
// Chain from the last checkpoint's archive
|
|
2295
|
+
const newBlockNumber = numBlocksForLogs + 1;
|
|
2296
|
+
const previousArchive = logsCheckpoints[logsCheckpoints.length - 1].checkpoint.blocks[0].archive;
|
|
2297
|
+
const newCheckpoint = await mockCheckpointWithLogs(newBlockNumber, previousArchive);
|
|
2298
|
+
const newLog = newCheckpoint.checkpoint.blocks[0].body.txEffects[1].privateLogs[1];
|
|
2299
|
+
newLog.fields[0] = tags[0].value;
|
|
2300
|
+
newCheckpoint.checkpoint.blocks[0].body.txEffects[1].privateLogs[1] = newLog;
|
|
2301
|
+
await store.addCheckpoints([newCheckpoint]);
|
|
2302
|
+
await store.addLogs([newCheckpoint.checkpoint.blocks[0]]);
|
|
2303
|
+
|
|
2304
|
+
const logsByTags = await store.getPrivateLogsByTags(tags);
|
|
450
2305
|
|
|
451
2306
|
expect(logsByTags).toEqual([
|
|
452
2307
|
[
|
|
453
2308
|
expect.objectContaining({
|
|
454
2309
|
blockNumber: 1,
|
|
455
|
-
logData: makePrivateLog(tags[0]).
|
|
456
|
-
|
|
2310
|
+
logData: makePrivateLog(tags[0]).getEmittedFields(),
|
|
2311
|
+
}),
|
|
2312
|
+
expect.objectContaining({
|
|
2313
|
+
blockNumber: newBlockNumber,
|
|
2314
|
+
logData: newLog.getEmittedFields(),
|
|
457
2315
|
}),
|
|
458
2316
|
],
|
|
2317
|
+
]);
|
|
2318
|
+
});
|
|
2319
|
+
|
|
2320
|
+
it('is possible to request logs for non-existing tags and determine their position', async () => {
|
|
2321
|
+
const tags = [makePrivateLogTag(99, 88, 77), makePrivateLogTag(1, 1, 1)];
|
|
2322
|
+
|
|
2323
|
+
const logsByTags = await store.getPrivateLogsByTags(tags);
|
|
2324
|
+
|
|
2325
|
+
expect(logsByTags).toEqual([
|
|
2326
|
+
[
|
|
2327
|
+
// No logs for the first tag.
|
|
2328
|
+
],
|
|
459
2329
|
[
|
|
460
2330
|
expect.objectContaining({
|
|
461
|
-
blockNumber:
|
|
462
|
-
logData: makePrivateLog(tags[1]).
|
|
463
|
-
isFromPublic: false,
|
|
2331
|
+
blockNumber: 1,
|
|
2332
|
+
logData: makePrivateLog(tags[1]).getEmittedFields(),
|
|
464
2333
|
}),
|
|
465
2334
|
],
|
|
466
2335
|
]);
|
|
467
2336
|
});
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2339
|
+
describe('getPublicLogsByTagsFromContract', () => {
|
|
2340
|
+
const numBlocksForLogs = 3;
|
|
2341
|
+
const numTxsPerBlock = 4;
|
|
2342
|
+
const numPublicLogsPerTx = 2;
|
|
2343
|
+
const contractAddress = AztecAddress.fromNumber(543254);
|
|
2344
|
+
|
|
2345
|
+
let logsCheckpoints: PublishedCheckpoint[];
|
|
2346
|
+
|
|
2347
|
+
const makePublicLogTag = (blockNumber: number, txIndex: number, logIndex: number): Tag =>
|
|
2348
|
+
new Tag(
|
|
2349
|
+
blockNumber === 1 && txIndex === 0 && logIndex === 0
|
|
2350
|
+
? Fr.ZERO // Shared tag
|
|
2351
|
+
: new Fr((blockNumber * 100 + txIndex * 10 + logIndex) * 123),
|
|
2352
|
+
);
|
|
2353
|
+
|
|
2354
|
+
const makePublicLog = (tag: Tag) =>
|
|
2355
|
+
PublicLog.from({
|
|
2356
|
+
contractAddress: contractAddress,
|
|
2357
|
+
// Arbitrary length
|
|
2358
|
+
fields: new Array(10).fill(null).map((_, i) => (!i ? tag.value : new Fr(tag.value.toBigInt() + BigInt(i)))),
|
|
2359
|
+
});
|
|
2360
|
+
|
|
2361
|
+
const mockPublicLogs = (blockNumber: number, txIndex: number) => {
|
|
2362
|
+
return times(numPublicLogsPerTx, (logIndex: number) => {
|
|
2363
|
+
const tag = makePublicLogTag(blockNumber, txIndex, logIndex);
|
|
2364
|
+
return makePublicLog(tag);
|
|
2365
|
+
});
|
|
2366
|
+
};
|
|
2367
|
+
|
|
2368
|
+
const mockCheckpointWithLogs = async (
|
|
2369
|
+
blockNumber: number,
|
|
2370
|
+
previousArchive?: AppendOnlyTreeSnapshot,
|
|
2371
|
+
): Promise<PublishedCheckpoint> => {
|
|
2372
|
+
const block = await L2BlockNew.random(BlockNumber(blockNumber), {
|
|
2373
|
+
checkpointNumber: CheckpointNumber(blockNumber),
|
|
2374
|
+
indexWithinCheckpoint: 0,
|
|
2375
|
+
state: makeStateForBlock(blockNumber, numTxsPerBlock),
|
|
2376
|
+
...(previousArchive ? { lastArchive: previousArchive } : {}),
|
|
2377
|
+
});
|
|
2378
|
+
block.header.globalVariables.blockNumber = BlockNumber(blockNumber);
|
|
2379
|
+
|
|
2380
|
+
block.body.txEffects = await timesParallel(numTxsPerBlock, async (txIndex: number) => {
|
|
2381
|
+
const txEffect = await TxEffect.random();
|
|
2382
|
+
txEffect.privateLogs = []; // No private logs needed for public log tests
|
|
2383
|
+
txEffect.publicLogs = mockPublicLogs(blockNumber, txIndex);
|
|
2384
|
+
return txEffect;
|
|
2385
|
+
});
|
|
2386
|
+
|
|
2387
|
+
const checkpoint = new Checkpoint(
|
|
2388
|
+
AppendOnlyTreeSnapshot.random(),
|
|
2389
|
+
CheckpointHeader.random(),
|
|
2390
|
+
[block],
|
|
2391
|
+
CheckpointNumber(blockNumber),
|
|
2392
|
+
);
|
|
2393
|
+
return makePublishedCheckpoint(checkpoint, blockNumber);
|
|
2394
|
+
};
|
|
2395
|
+
|
|
2396
|
+
beforeEach(async () => {
|
|
2397
|
+
// Create checkpoints sequentially to chain archive roots
|
|
2398
|
+
logsCheckpoints = [];
|
|
2399
|
+
for (let i = 0; i < numBlocksForLogs; i++) {
|
|
2400
|
+
const previousArchive = i > 0 ? logsCheckpoints[i - 1].checkpoint.blocks[0].archive : undefined;
|
|
2401
|
+
logsCheckpoints.push(await mockCheckpointWithLogs(i + 1, previousArchive));
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
await store.addCheckpoints(logsCheckpoints);
|
|
2405
|
+
await store.addLogs(logsCheckpoints.flatMap(p => p.checkpoint.blocks));
|
|
2406
|
+
});
|
|
468
2407
|
|
|
469
|
-
it('is possible to batch request
|
|
470
|
-
|
|
471
|
-
const tags = [makeTag(0, 0, 0)];
|
|
2408
|
+
it('is possible to batch request public logs via tags', async () => {
|
|
2409
|
+
const tags = [makePublicLogTag(2, 1, 1), makePublicLogTag(1, 2, 0)];
|
|
472
2410
|
|
|
473
|
-
const logsByTags = await store.
|
|
2411
|
+
const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
|
|
474
2412
|
|
|
475
2413
|
expect(logsByTags).toEqual([
|
|
476
2414
|
[
|
|
477
2415
|
expect.objectContaining({
|
|
478
|
-
blockNumber:
|
|
479
|
-
logData:
|
|
480
|
-
isFromPublic: false,
|
|
2416
|
+
blockNumber: 2,
|
|
2417
|
+
logData: makePublicLog(tags[0]).getEmittedFields(),
|
|
481
2418
|
}),
|
|
2419
|
+
],
|
|
2420
|
+
[
|
|
482
2421
|
expect.objectContaining({
|
|
483
|
-
blockNumber:
|
|
484
|
-
logData: makePublicLog(tags[
|
|
485
|
-
isFromPublic: true,
|
|
2422
|
+
blockNumber: 1,
|
|
2423
|
+
logData: makePublicLog(tags[1]).getEmittedFields(),
|
|
486
2424
|
}),
|
|
487
2425
|
],
|
|
488
2426
|
]);
|
|
489
2427
|
});
|
|
490
2428
|
|
|
491
2429
|
it('is possible to batch request logs that have the same tag but different content', async () => {
|
|
492
|
-
const tags = [
|
|
2430
|
+
const tags = [makePublicLogTag(1, 2, 1)];
|
|
493
2431
|
|
|
494
|
-
// Create a
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
2432
|
+
// Create a checkpoint containing logs that have the same tag as the checkpoints before.
|
|
2433
|
+
// Chain from the last checkpoint's archive
|
|
2434
|
+
const newBlockNumber = numBlocksForLogs + 1;
|
|
2435
|
+
const previousArchive = logsCheckpoints[logsCheckpoints.length - 1].checkpoint.blocks[0].archive;
|
|
2436
|
+
const newCheckpoint = await mockCheckpointWithLogs(newBlockNumber, previousArchive);
|
|
2437
|
+
const newLog = newCheckpoint.checkpoint.blocks[0].body.txEffects[1].publicLogs[1];
|
|
2438
|
+
newLog.fields[0] = tags[0].value;
|
|
2439
|
+
newCheckpoint.checkpoint.blocks[0].body.txEffects[1].publicLogs[1] = newLog;
|
|
2440
|
+
await store.addCheckpoints([newCheckpoint]);
|
|
2441
|
+
await store.addLogs([newCheckpoint.checkpoint.blocks[0]]);
|
|
502
2442
|
|
|
503
|
-
const logsByTags = await store.
|
|
2443
|
+
const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
|
|
504
2444
|
|
|
505
2445
|
expect(logsByTags).toEqual([
|
|
506
2446
|
[
|
|
507
2447
|
expect.objectContaining({
|
|
508
2448
|
blockNumber: 1,
|
|
509
|
-
logData:
|
|
510
|
-
isFromPublic: false,
|
|
2449
|
+
logData: makePublicLog(tags[0]).getEmittedFields(),
|
|
511
2450
|
}),
|
|
512
2451
|
expect.objectContaining({
|
|
513
2452
|
blockNumber: newBlockNumber,
|
|
514
|
-
logData: newLog.
|
|
515
|
-
isFromPublic: false,
|
|
2453
|
+
logData: newLog.getEmittedFields(),
|
|
516
2454
|
}),
|
|
517
2455
|
],
|
|
518
2456
|
]);
|
|
519
2457
|
});
|
|
520
2458
|
|
|
521
2459
|
it('is possible to request logs for non-existing tags and determine their position', async () => {
|
|
522
|
-
const tags = [
|
|
2460
|
+
const tags = [makePublicLogTag(99, 88, 77), makePublicLogTag(1, 1, 0)];
|
|
523
2461
|
|
|
524
|
-
const logsByTags = await store.
|
|
2462
|
+
const logsByTags = await store.getPublicLogsByTagsFromContract(contractAddress, tags);
|
|
525
2463
|
|
|
526
2464
|
expect(logsByTags).toEqual([
|
|
527
2465
|
[
|
|
@@ -530,70 +2468,41 @@ export function describeArchiverDataStore(
|
|
|
530
2468
|
[
|
|
531
2469
|
expect.objectContaining({
|
|
532
2470
|
blockNumber: 1,
|
|
533
|
-
logData:
|
|
534
|
-
isFromPublic: false,
|
|
2471
|
+
logData: makePublicLog(tags[1]).getEmittedFields(),
|
|
535
2472
|
}),
|
|
536
2473
|
],
|
|
537
2474
|
]);
|
|
538
2475
|
});
|
|
539
|
-
|
|
540
|
-
it('is not possible to add public logs by tag if they are invalid', async () => {
|
|
541
|
-
const tag = makeTag(99, 88, 77);
|
|
542
|
-
const invalidLogs = [
|
|
543
|
-
PublicLog.fromFields([
|
|
544
|
-
AztecAddress.fromNumber(1).toField(),
|
|
545
|
-
makeLengthsField(2, 3), // This field claims we have 5 items, but we actually have more
|
|
546
|
-
tag,
|
|
547
|
-
...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)),
|
|
548
|
-
]),
|
|
549
|
-
PublicLog.fromFields([
|
|
550
|
-
AztecAddress.fromNumber(1).toField(),
|
|
551
|
-
makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS), // This field claims we have more than the max items
|
|
552
|
-
tag,
|
|
553
|
-
...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)),
|
|
554
|
-
]),
|
|
555
|
-
];
|
|
556
|
-
|
|
557
|
-
// Create a block containing these invalid logs
|
|
558
|
-
const newBlockNumber = numBlocks;
|
|
559
|
-
const newBlock = await mockBlockWithLogs(newBlockNumber);
|
|
560
|
-
newBlock.data.body.txEffects[0].publicLogs = invalidLogs;
|
|
561
|
-
await store.addBlocks([newBlock]);
|
|
562
|
-
await store.addLogs([newBlock.data]);
|
|
563
|
-
|
|
564
|
-
const logsByTags = await store.getLogsByTags([tag]);
|
|
565
|
-
|
|
566
|
-
// Neither of the logs should have been added:
|
|
567
|
-
expect(logsByTags).toEqual([[]]);
|
|
568
|
-
});
|
|
569
2476
|
});
|
|
570
2477
|
|
|
571
2478
|
describe('getPublicLogs', () => {
|
|
572
|
-
const
|
|
573
|
-
const numPublicFunctionCalls = 3;
|
|
574
|
-
const numPublicLogs = 2;
|
|
575
|
-
const numBlocks = 10;
|
|
576
|
-
let blocks: L1Published<L2Block>[];
|
|
2479
|
+
const numBlocksForPublicLogs = 10;
|
|
577
2480
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
2481
|
+
// Helper to get total public logs per tx from a block
|
|
2482
|
+
const getPublicLogsPerTx = (block: L2BlockNew, txIndex: number) =>
|
|
2483
|
+
block.body.txEffects[txIndex].publicLogs.length;
|
|
2484
|
+
|
|
2485
|
+
// Helper to get number of txs in a block
|
|
2486
|
+
const getTxsPerBlock = (block: L2BlockNew) => block.body.txEffects.length;
|
|
583
2487
|
|
|
584
|
-
|
|
585
|
-
|
|
2488
|
+
beforeEach(async () => {
|
|
2489
|
+
// Use the outer publishedCheckpoints for log tests
|
|
2490
|
+
for (let i = 0; i < numBlocksForPublicLogs; i++) {
|
|
2491
|
+
await store.addCheckpoints([publishedCheckpoints[i]]);
|
|
2492
|
+
await store.addLogs(publishedCheckpoints[i].checkpoint.blocks);
|
|
2493
|
+
}
|
|
586
2494
|
});
|
|
587
2495
|
|
|
588
2496
|
it('no logs returned if deleted ("txHash" filter param is respected variant)', async () => {
|
|
589
2497
|
// get random tx
|
|
590
|
-
const targetBlockIndex = randomInt(
|
|
591
|
-
const
|
|
592
|
-
const
|
|
2498
|
+
const targetBlockIndex = randomInt(numBlocksForPublicLogs);
|
|
2499
|
+
const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
|
|
2500
|
+
const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
|
|
2501
|
+
const targetTxHash = targetBlock.body.txEffects[targetTxIndex].txHash;
|
|
593
2502
|
|
|
594
2503
|
await Promise.all([
|
|
595
|
-
store.
|
|
596
|
-
store.deleteLogs(
|
|
2504
|
+
store.unwindCheckpoints(CheckpointNumber(numBlocksForPublicLogs), numBlocksForPublicLogs),
|
|
2505
|
+
store.deleteLogs(publishedCheckpoints.slice(0, numBlocksForPublicLogs).flatMap(b => b.checkpoint.blocks)),
|
|
597
2506
|
]);
|
|
598
2507
|
|
|
599
2508
|
const response = await store.getPublicLogs({ txHash: targetTxHash });
|
|
@@ -605,16 +2514,17 @@ export function describeArchiverDataStore(
|
|
|
605
2514
|
|
|
606
2515
|
it('"txHash" filter param is respected', async () => {
|
|
607
2516
|
// get random tx
|
|
608
|
-
const targetBlockIndex = randomInt(
|
|
609
|
-
const
|
|
610
|
-
const
|
|
2517
|
+
const targetBlockIndex = randomInt(numBlocksForPublicLogs);
|
|
2518
|
+
const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
|
|
2519
|
+
const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
|
|
2520
|
+
const targetTxHash = targetBlock.body.txEffects[targetTxIndex].txHash;
|
|
611
2521
|
|
|
612
2522
|
const response = await store.getPublicLogs({ txHash: targetTxHash });
|
|
613
2523
|
const logs = response.logs;
|
|
614
2524
|
|
|
615
2525
|
expect(response.maxLogsHit).toBeFalsy();
|
|
616
2526
|
|
|
617
|
-
const expectedNumLogs =
|
|
2527
|
+
const expectedNumLogs = getPublicLogsPerTx(targetBlock, targetTxIndex);
|
|
618
2528
|
expect(logs.length).toEqual(expectedNumLogs);
|
|
619
2529
|
|
|
620
2530
|
const targeBlockNumber = targetBlockIndex + INITIAL_L2_BLOCK_NUM;
|
|
@@ -624,6 +2534,17 @@ export function describeArchiverDataStore(
|
|
|
624
2534
|
}
|
|
625
2535
|
});
|
|
626
2536
|
|
|
2537
|
+
it('returns block hash on public log ids', async () => {
|
|
2538
|
+
const targetBlock = publishedCheckpoints[0].checkpoint.blocks[0];
|
|
2539
|
+
const expectedBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
|
|
2540
|
+
|
|
2541
|
+
const logs = (await store.getPublicLogs({ fromBlock: targetBlock.number, toBlock: targetBlock.number + 1 }))
|
|
2542
|
+
.logs;
|
|
2543
|
+
|
|
2544
|
+
expect(logs.length).toBeGreaterThan(0);
|
|
2545
|
+
expect(logs.every(log => log.id.blockHash.equals(expectedBlockHash))).toBe(true);
|
|
2546
|
+
});
|
|
2547
|
+
|
|
627
2548
|
it('"fromBlock" and "toBlock" filter params are respected', async () => {
|
|
628
2549
|
// Set "fromBlock" and "toBlock"
|
|
629
2550
|
const fromBlock = 3;
|
|
@@ -634,7 +2555,12 @@ export function describeArchiverDataStore(
|
|
|
634
2555
|
|
|
635
2556
|
expect(response.maxLogsHit).toBeFalsy();
|
|
636
2557
|
|
|
637
|
-
|
|
2558
|
+
// Compute expected logs from the blocks in range
|
|
2559
|
+
let expectedNumLogs = 0;
|
|
2560
|
+
for (let i = fromBlock - 1; i < toBlock - 1; i++) {
|
|
2561
|
+
const block = publishedCheckpoints[i].checkpoint.blocks[0];
|
|
2562
|
+
expectedNumLogs += block.body.txEffects.reduce((sum, tx) => sum + tx.publicLogs.length, 0);
|
|
2563
|
+
}
|
|
638
2564
|
expect(logs.length).toEqual(expectedNumLogs);
|
|
639
2565
|
|
|
640
2566
|
for (const log of logs) {
|
|
@@ -646,11 +2572,12 @@ export function describeArchiverDataStore(
|
|
|
646
2572
|
|
|
647
2573
|
it('"contractAddress" filter param is respected', async () => {
|
|
648
2574
|
// Get a random contract address from the logs
|
|
649
|
-
const targetBlockIndex = randomInt(
|
|
650
|
-
const
|
|
651
|
-
const
|
|
2575
|
+
const targetBlockIndex = randomInt(numBlocksForPublicLogs);
|
|
2576
|
+
const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
|
|
2577
|
+
const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
|
|
2578
|
+
const targetLogIndex = randomInt(getPublicLogsPerTx(targetBlock, targetTxIndex));
|
|
652
2579
|
const targetContractAddress =
|
|
653
|
-
|
|
2580
|
+
targetBlock.body.txEffects[targetTxIndex].publicLogs[targetLogIndex].contractAddress;
|
|
654
2581
|
|
|
655
2582
|
const response = await store.getPublicLogs({ contractAddress: targetContractAddress });
|
|
656
2583
|
|
|
@@ -663,11 +2590,19 @@ export function describeArchiverDataStore(
|
|
|
663
2590
|
|
|
664
2591
|
it('"afterLog" filter param is respected', async () => {
|
|
665
2592
|
// Get a random log as reference
|
|
666
|
-
const targetBlockIndex = randomInt(
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
const
|
|
2593
|
+
const targetBlockIndex = randomInt(numBlocksForPublicLogs);
|
|
2594
|
+
const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
|
|
2595
|
+
const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
|
|
2596
|
+
const numLogsInTx = targetBlock.body.txEffects[targetTxIndex].publicLogs.length;
|
|
2597
|
+
const targetLogIndex = numLogsInTx > 0 ? randomInt(numLogsInTx) : 0;
|
|
2598
|
+
const targetBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
|
|
2599
|
+
|
|
2600
|
+
const afterLog = new LogId(
|
|
2601
|
+
BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM),
|
|
2602
|
+
targetBlockHash,
|
|
2603
|
+
targetTxIndex,
|
|
2604
|
+
targetLogIndex,
|
|
2605
|
+
);
|
|
671
2606
|
|
|
672
2607
|
const response = await store.getPublicLogs({ afterLog });
|
|
673
2608
|
const logs = response.logs;
|
|
@@ -689,52 +2624,77 @@ export function describeArchiverDataStore(
|
|
|
689
2624
|
it('"txHash" filter param is ignored when "afterLog" is set', async () => {
|
|
690
2625
|
// Get random txHash
|
|
691
2626
|
const txHash = TxHash.random();
|
|
692
|
-
const afterLog = new LogId(1, 0, 0);
|
|
2627
|
+
const afterLog = new LogId(BlockNumber(1), L2BlockHash.random(), 0, 0);
|
|
693
2628
|
|
|
694
2629
|
const response = await store.getPublicLogs({ txHash, afterLog });
|
|
695
2630
|
expect(response.logs.length).toBeGreaterThan(1);
|
|
696
2631
|
});
|
|
697
2632
|
|
|
698
2633
|
it('intersecting works', async () => {
|
|
699
|
-
let logs = (await store.getPublicLogs({ fromBlock: -10, toBlock: -5 })).logs;
|
|
2634
|
+
let logs = (await store.getPublicLogs({ fromBlock: -10 as BlockNumber, toBlock: -5 as BlockNumber })).logs;
|
|
700
2635
|
expect(logs.length).toBe(0);
|
|
701
2636
|
|
|
702
2637
|
// "fromBlock" gets correctly trimmed to range and "toBlock" is exclusive
|
|
703
|
-
logs = (await store.getPublicLogs({ fromBlock: -10, toBlock: 5 })).logs;
|
|
2638
|
+
logs = (await store.getPublicLogs({ fromBlock: -10 as BlockNumber, toBlock: BlockNumber(5) })).logs;
|
|
704
2639
|
let blockNumbers = new Set(logs.map(log => log.id.blockNumber));
|
|
705
2640
|
expect(blockNumbers).toEqual(new Set([1, 2, 3, 4]));
|
|
706
2641
|
|
|
707
2642
|
// "toBlock" should be exclusive
|
|
708
|
-
logs = (await store.getPublicLogs({ fromBlock: 1, toBlock: 1 })).logs;
|
|
2643
|
+
logs = (await store.getPublicLogs({ fromBlock: BlockNumber(1), toBlock: BlockNumber(1) })).logs;
|
|
709
2644
|
expect(logs.length).toBe(0);
|
|
710
2645
|
|
|
711
|
-
logs = (await store.getPublicLogs({ fromBlock: 10, toBlock: 5 })).logs;
|
|
2646
|
+
logs = (await store.getPublicLogs({ fromBlock: BlockNumber(10), toBlock: BlockNumber(5) })).logs;
|
|
712
2647
|
expect(logs.length).toBe(0);
|
|
713
2648
|
|
|
714
2649
|
// both "fromBlock" and "toBlock" get correctly capped to range and logs from all blocks are returned
|
|
715
|
-
logs = (await store.getPublicLogs({ fromBlock: -100, toBlock: +100 })).logs;
|
|
2650
|
+
logs = (await store.getPublicLogs({ fromBlock: -100 as BlockNumber, toBlock: +100 })).logs;
|
|
716
2651
|
blockNumbers = new Set(logs.map(log => log.id.blockNumber));
|
|
717
|
-
expect(blockNumbers.size).toBe(
|
|
2652
|
+
expect(blockNumbers.size).toBe(numBlocksForPublicLogs);
|
|
718
2653
|
|
|
719
2654
|
// intersecting with "afterLog" works
|
|
720
|
-
logs = (
|
|
2655
|
+
logs = (
|
|
2656
|
+
await store.getPublicLogs({
|
|
2657
|
+
fromBlock: BlockNumber(2),
|
|
2658
|
+
toBlock: BlockNumber(5),
|
|
2659
|
+
afterLog: new LogId(BlockNumber(4), L2BlockHash.random(), 0, 0),
|
|
2660
|
+
})
|
|
2661
|
+
).logs;
|
|
721
2662
|
blockNumbers = new Set(logs.map(log => log.id.blockNumber));
|
|
722
2663
|
expect(blockNumbers).toEqual(new Set([4]));
|
|
723
2664
|
|
|
724
|
-
logs = (
|
|
2665
|
+
logs = (
|
|
2666
|
+
await store.getPublicLogs({
|
|
2667
|
+
toBlock: BlockNumber(5),
|
|
2668
|
+
afterLog: new LogId(BlockNumber(5), L2BlockHash.random(), 1, 0),
|
|
2669
|
+
})
|
|
2670
|
+
).logs;
|
|
725
2671
|
expect(logs.length).toBe(0);
|
|
726
2672
|
|
|
727
|
-
logs = (
|
|
2673
|
+
logs = (
|
|
2674
|
+
await store.getPublicLogs({
|
|
2675
|
+
fromBlock: BlockNumber(2),
|
|
2676
|
+
toBlock: BlockNumber(5),
|
|
2677
|
+
afterLog: new LogId(BlockNumber(100), L2BlockHash.random(), 0, 0),
|
|
2678
|
+
})
|
|
2679
|
+
).logs;
|
|
728
2680
|
expect(logs.length).toBe(0);
|
|
729
2681
|
});
|
|
730
2682
|
|
|
731
2683
|
it('"txIndex" and "logIndex" are respected when "afterLog.blockNumber" is equal to "fromBlock"', async () => {
|
|
732
2684
|
// Get a random log as reference
|
|
733
|
-
const targetBlockIndex = randomInt(
|
|
734
|
-
const
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
const
|
|
2685
|
+
const targetBlockIndex = randomInt(numBlocksForPublicLogs);
|
|
2686
|
+
const targetBlock = publishedCheckpoints[targetBlockIndex].checkpoint.blocks[0];
|
|
2687
|
+
const targetTxIndex = randomInt(getTxsPerBlock(targetBlock));
|
|
2688
|
+
const numLogsInTx = targetBlock.body.txEffects[targetTxIndex].publicLogs.length;
|
|
2689
|
+
const targetLogIndex = numLogsInTx > 0 ? randomInt(numLogsInTx) : 0;
|
|
2690
|
+
const targetBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
|
|
2691
|
+
|
|
2692
|
+
const afterLog = new LogId(
|
|
2693
|
+
BlockNumber(targetBlockIndex + INITIAL_L2_BLOCK_NUM),
|
|
2694
|
+
targetBlockHash,
|
|
2695
|
+
targetTxIndex,
|
|
2696
|
+
targetLogIndex,
|
|
2697
|
+
);
|
|
738
2698
|
|
|
739
2699
|
const response = await store.getPublicLogs({ afterLog, fromBlock: afterLog.blockNumber });
|
|
740
2700
|
const logs = response.logs;
|
|
@@ -754,56 +2714,128 @@ export function describeArchiverDataStore(
|
|
|
754
2714
|
});
|
|
755
2715
|
});
|
|
756
2716
|
|
|
757
|
-
describe('
|
|
758
|
-
let
|
|
759
|
-
|
|
760
|
-
const nullifiersPerBlock = new Map<number, Fr[]>();
|
|
2717
|
+
describe('getContractClassLogs', () => {
|
|
2718
|
+
let targetBlock: L2BlockNew;
|
|
2719
|
+
let expectedContractClassLog: ContractClassLog;
|
|
761
2720
|
|
|
762
2721
|
beforeEach(async () => {
|
|
763
|
-
|
|
2722
|
+
await store.addCheckpoints(publishedCheckpoints);
|
|
764
2723
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
);
|
|
2724
|
+
targetBlock = publishedCheckpoints[0].checkpoint.blocks[0];
|
|
2725
|
+
expectedContractClassLog = await ContractClassLog.random();
|
|
2726
|
+
targetBlock.body.txEffects.forEach((txEffect, index) => {
|
|
2727
|
+
txEffect.contractClassLogs = index === 0 ? [expectedContractClassLog] : [];
|
|
770
2728
|
});
|
|
771
|
-
});
|
|
772
2729
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!, Fr.random()];
|
|
776
|
-
const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(10, nullifiersToRetrieve);
|
|
2730
|
+
await store.addLogs([targetBlock]);
|
|
2731
|
+
});
|
|
777
2732
|
|
|
778
|
-
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
expect(blockScopedNullifier).not.toBeUndefined();
|
|
783
|
-
const { data, l2BlockNumber } = blockScopedNullifier!;
|
|
784
|
-
expect(data).toEqual(expect.any(BigInt));
|
|
785
|
-
expect(l2BlockNumber).toEqual(index < MAX_NULLIFIERS_PER_TX ? 1 : 6);
|
|
2733
|
+
it('returns block hash on contract class log ids', async () => {
|
|
2734
|
+
const result = await store.getContractClassLogs({
|
|
2735
|
+
fromBlock: targetBlock.number,
|
|
2736
|
+
toBlock: targetBlock.number + 1,
|
|
786
2737
|
});
|
|
787
|
-
|
|
2738
|
+
|
|
2739
|
+
expect(result.maxLogsHit).toBeFalsy();
|
|
2740
|
+
expect(result.logs).toHaveLength(1);
|
|
2741
|
+
|
|
2742
|
+
const [{ id, log }] = result.logs;
|
|
2743
|
+
const expectedBlockHash = L2BlockHash.fromField(await targetBlock.header.hash());
|
|
2744
|
+
|
|
2745
|
+
expect(id.blockHash.equals(expectedBlockHash)).toBe(true);
|
|
2746
|
+
expect(id.blockNumber).toEqual(targetBlock.number);
|
|
2747
|
+
expect(log).toEqual(expectedContractClassLog);
|
|
2748
|
+
});
|
|
2749
|
+
});
|
|
2750
|
+
|
|
2751
|
+
describe('pendingChainValidationStatus', () => {
|
|
2752
|
+
it('should return undefined when no status is set', async () => {
|
|
2753
|
+
const status = await store.getPendingChainValidationStatus();
|
|
2754
|
+
expect(status).toBeUndefined();
|
|
788
2755
|
});
|
|
789
2756
|
|
|
790
|
-
it('
|
|
791
|
-
|
|
792
|
-
const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!];
|
|
793
|
-
const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(5, nullifiersToRetrieve);
|
|
2757
|
+
it('should store and retrieve a valid validation status', async () => {
|
|
2758
|
+
const validStatus: ValidateCheckpointResult = { valid: true };
|
|
794
2759
|
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
2760
|
+
await store.setPendingChainValidationStatus(validStatus);
|
|
2761
|
+
const retrievedStatus = await store.getPendingChainValidationStatus();
|
|
2762
|
+
|
|
2763
|
+
expect(retrievedStatus).toEqual(validStatus);
|
|
2764
|
+
});
|
|
2765
|
+
|
|
2766
|
+
it('should store and retrieve an invalid validation status with insufficient attestations', async () => {
|
|
2767
|
+
const invalidStatus: ValidateCheckpointResult = {
|
|
2768
|
+
valid: false,
|
|
2769
|
+
checkpoint: randomCheckpointInfo(1),
|
|
2770
|
+
committee: [EthAddress.random(), EthAddress.random()],
|
|
2771
|
+
epoch: EpochNumber(123),
|
|
2772
|
+
seed: 456n,
|
|
2773
|
+
attestors: [EthAddress.random()],
|
|
2774
|
+
attestations: [CommitteeAttestation.random()],
|
|
2775
|
+
reason: 'insufficient-attestations',
|
|
2776
|
+
};
|
|
2777
|
+
|
|
2778
|
+
await store.setPendingChainValidationStatus(invalidStatus);
|
|
2779
|
+
const retrievedStatus = await store.getPendingChainValidationStatus();
|
|
2780
|
+
|
|
2781
|
+
expect(retrievedStatus).toEqual(invalidStatus);
|
|
2782
|
+
});
|
|
2783
|
+
|
|
2784
|
+
it('should store and retrieve an invalid validation status with invalid attestation', async () => {
|
|
2785
|
+
const invalidStatus: ValidateCheckpointResult = {
|
|
2786
|
+
valid: false,
|
|
2787
|
+
checkpoint: randomCheckpointInfo(2),
|
|
2788
|
+
committee: [EthAddress.random()],
|
|
2789
|
+
attestors: [EthAddress.random()],
|
|
2790
|
+
epoch: EpochNumber(789),
|
|
2791
|
+
seed: 101n,
|
|
2792
|
+
attestations: [CommitteeAttestation.random()],
|
|
2793
|
+
reason: 'invalid-attestation',
|
|
2794
|
+
invalidIndex: 5,
|
|
2795
|
+
};
|
|
2796
|
+
|
|
2797
|
+
await store.setPendingChainValidationStatus(invalidStatus);
|
|
2798
|
+
const retrievedStatus = await store.getPendingChainValidationStatus();
|
|
2799
|
+
|
|
2800
|
+
expect(retrievedStatus).toEqual(invalidStatus);
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
it('should overwrite existing status when setting a new one', async () => {
|
|
2804
|
+
const firstStatus: ValidateCheckpointResult = { valid: true };
|
|
2805
|
+
const secondStatus: ValidateCheckpointResult = {
|
|
2806
|
+
valid: false,
|
|
2807
|
+
checkpoint: randomCheckpointInfo(3),
|
|
2808
|
+
committee: [EthAddress.random()],
|
|
2809
|
+
epoch: EpochNumber(999),
|
|
2810
|
+
seed: 888n,
|
|
2811
|
+
attestors: [EthAddress.random()],
|
|
2812
|
+
attestations: [CommitteeAttestation.random()],
|
|
2813
|
+
reason: 'insufficient-attestations',
|
|
2814
|
+
};
|
|
2815
|
+
|
|
2816
|
+
await store.setPendingChainValidationStatus(firstStatus);
|
|
2817
|
+
await store.setPendingChainValidationStatus(secondStatus);
|
|
2818
|
+
const retrievedStatus = await store.getPendingChainValidationStatus();
|
|
2819
|
+
|
|
2820
|
+
expect(retrievedStatus).toEqual(secondStatus);
|
|
2821
|
+
});
|
|
2822
|
+
|
|
2823
|
+
it('should handle empty committee and attestations arrays', async () => {
|
|
2824
|
+
const statusWithEmptyArrays: ValidateCheckpointResult = {
|
|
2825
|
+
valid: false,
|
|
2826
|
+
checkpoint: randomCheckpointInfo(4),
|
|
2827
|
+
committee: [],
|
|
2828
|
+
epoch: EpochNumber(0),
|
|
2829
|
+
seed: 0n,
|
|
2830
|
+
attestors: [],
|
|
2831
|
+
attestations: [],
|
|
2832
|
+
reason: 'insufficient-attestations',
|
|
2833
|
+
};
|
|
2834
|
+
|
|
2835
|
+
await store.setPendingChainValidationStatus(statusWithEmptyArrays);
|
|
2836
|
+
const retrievedStatus = await store.getPendingChainValidationStatus();
|
|
2837
|
+
|
|
2838
|
+
expect(retrievedStatus).toEqual(statusWithEmptyArrays);
|
|
807
2839
|
});
|
|
808
2840
|
});
|
|
809
2841
|
});
|