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