@aztec/archiver 0.0.1-commit.a072138 → 0.0.1-commit.a89ec08
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/archiver.d.ts +7 -4
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +73 -110
- package/dest/errors.d.ts +7 -9
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +9 -14
- package/dest/factory.d.ts +3 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +31 -24
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/l1/bin/retrieve-calldata.js +36 -33
- package/dest/l1/calldata_retriever.d.ts +73 -50
- package/dest/l1/calldata_retriever.d.ts.map +1 -1
- package/dest/l1/calldata_retriever.js +190 -259
- package/dest/l1/data_retrieval.d.ts +4 -7
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +9 -13
- package/dest/l1/spire_proposer.d.ts +5 -5
- package/dest/l1/spire_proposer.d.ts.map +1 -1
- package/dest/l1/spire_proposer.js +9 -17
- package/dest/modules/data_source_base.d.ts +10 -5
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +29 -73
- package/dest/modules/data_store_updater.d.ts +22 -7
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +113 -40
- package/dest/modules/instrumentation.d.ts +4 -2
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +9 -2
- package/dest/modules/l1_synchronizer.d.ts +5 -8
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +40 -10
- package/dest/store/block_store.d.ts +30 -26
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +180 -83
- package/dest/store/contract_class_store.d.ts +1 -1
- package/dest/store/contract_class_store.d.ts.map +1 -1
- package/dest/store/contract_class_store.js +6 -2
- package/dest/store/contract_instance_store.d.ts +1 -1
- package/dest/store/contract_instance_store.d.ts.map +1 -1
- package/dest/store/contract_instance_store.js +6 -2
- package/dest/store/kv_archiver_store.d.ts +37 -15
- package/dest/store/kv_archiver_store.d.ts.map +1 -1
- package/dest/store/kv_archiver_store.js +42 -13
- package/dest/store/l2_tips_cache.d.ts +19 -0
- package/dest/store/l2_tips_cache.d.ts.map +1 -0
- package/dest/store/l2_tips_cache.js +89 -0
- package/dest/store/log_store.d.ts +1 -1
- package/dest/store/log_store.d.ts.map +1 -1
- package/dest/store/log_store.js +103 -45
- package/dest/store/message_store.js +1 -1
- package/dest/test/fake_l1_state.d.ts +20 -1
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +97 -20
- package/dest/test/mock_archiver.d.ts +1 -1
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_archiver.js +3 -2
- package/dest/test/mock_l2_block_source.d.ts +21 -5
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +132 -86
- package/dest/test/mock_structs.d.ts +4 -1
- package/dest/test/mock_structs.d.ts.map +1 -1
- package/dest/test/mock_structs.js +13 -1
- package/dest/test/noop_l1_archiver.d.ts +4 -1
- package/dest/test/noop_l1_archiver.d.ts.map +1 -1
- package/dest/test/noop_l1_archiver.js +5 -1
- package/package.json +13 -13
- package/src/archiver.ts +88 -130
- package/src/errors.ts +10 -24
- package/src/factory.ts +45 -21
- package/src/index.ts +1 -0
- package/src/l1/README.md +25 -68
- package/src/l1/bin/retrieve-calldata.ts +46 -39
- package/src/l1/calldata_retriever.ts +249 -379
- package/src/l1/data_retrieval.ts +6 -16
- package/src/l1/spire_proposer.ts +7 -15
- package/src/modules/data_source_base.ts +56 -95
- package/src/modules/data_store_updater.ts +123 -43
- package/src/modules/instrumentation.ts +9 -2
- package/src/modules/l1_synchronizer.ts +47 -14
- package/src/store/block_store.ts +219 -110
- package/src/store/contract_class_store.ts +7 -3
- package/src/store/contract_instance_store.ts +8 -5
- package/src/store/kv_archiver_store.ts +66 -20
- package/src/store/l2_tips_cache.ts +89 -0
- package/src/store/log_store.ts +159 -43
- package/src/store/message_store.ts +1 -1
- package/src/test/fake_l1_state.ts +125 -21
- package/src/test/mock_archiver.ts +3 -2
- package/src/test/mock_l2_block_source.ts +173 -81
- package/src/test/mock_structs.ts +20 -6
- package/src/test/noop_l1_archiver.ts +7 -1
package/src/store/block_store.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { isDefined } from '@aztec/foundation/types';
|
|
|
9
9
|
import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } from '@aztec/kv-store';
|
|
10
10
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
11
11
|
import {
|
|
12
|
+
type BlockData,
|
|
12
13
|
BlockHash,
|
|
13
14
|
Body,
|
|
14
15
|
CheckpointedL2Block,
|
|
@@ -18,8 +19,8 @@ import {
|
|
|
18
19
|
deserializeValidateCheckpointResult,
|
|
19
20
|
serializeValidateCheckpointResult,
|
|
20
21
|
} from '@aztec/stdlib/block';
|
|
21
|
-
import { L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
22
|
-
import type
|
|
22
|
+
import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
23
|
+
import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
23
24
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
24
25
|
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
25
26
|
import {
|
|
@@ -34,15 +35,14 @@ import {
|
|
|
34
35
|
} from '@aztec/stdlib/tx';
|
|
35
36
|
|
|
36
37
|
import {
|
|
38
|
+
BlockAlreadyCheckpointedError,
|
|
37
39
|
BlockArchiveNotConsistentError,
|
|
38
40
|
BlockIndexNotSequentialError,
|
|
39
41
|
BlockNotFoundError,
|
|
40
42
|
BlockNumberNotSequentialError,
|
|
41
43
|
CannotOverwriteCheckpointedBlockError,
|
|
42
44
|
CheckpointNotFoundError,
|
|
43
|
-
CheckpointNumberNotConsistentError,
|
|
44
45
|
CheckpointNumberNotSequentialError,
|
|
45
|
-
InitialBlockNumberNotSequentialError,
|
|
46
46
|
InitialCheckpointNumberNotSequentialError,
|
|
47
47
|
} from '../errors.js';
|
|
48
48
|
|
|
@@ -61,23 +61,14 @@ type BlockStorage = {
|
|
|
61
61
|
type CheckpointStorage = {
|
|
62
62
|
header: Buffer;
|
|
63
63
|
archive: Buffer;
|
|
64
|
+
checkpointOutHash: Buffer;
|
|
64
65
|
checkpointNumber: number;
|
|
65
66
|
startBlock: number;
|
|
66
|
-
|
|
67
|
+
blockCount: number;
|
|
67
68
|
l1: Buffer;
|
|
68
69
|
attestations: Buffer[];
|
|
69
70
|
};
|
|
70
71
|
|
|
71
|
-
export type CheckpointData = {
|
|
72
|
-
checkpointNumber: CheckpointNumber;
|
|
73
|
-
header: CheckpointHeader;
|
|
74
|
-
archive: AppendOnlyTreeSnapshot;
|
|
75
|
-
startBlock: number;
|
|
76
|
-
numBlocks: number;
|
|
77
|
-
l1: L1PublishedData;
|
|
78
|
-
attestations: Buffer[];
|
|
79
|
-
};
|
|
80
|
-
|
|
81
72
|
export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
|
|
82
73
|
|
|
83
74
|
/**
|
|
@@ -90,6 +81,9 @@ export class BlockStore {
|
|
|
90
81
|
/** Map checkpoint number to checkpoint data */
|
|
91
82
|
#checkpoints: AztecAsyncMap<number, CheckpointStorage>;
|
|
92
83
|
|
|
84
|
+
/** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
|
|
85
|
+
#slotToCheckpoint: AztecAsyncMap<number, number>;
|
|
86
|
+
|
|
93
87
|
/** Map block hash to list of tx hashes */
|
|
94
88
|
#blockTxs: AztecAsyncMap<string, Buffer>;
|
|
95
89
|
|
|
@@ -102,6 +96,9 @@ export class BlockStore {
|
|
|
102
96
|
/** Stores last proven checkpoint */
|
|
103
97
|
#lastProvenCheckpoint: AztecAsyncSingleton<number>;
|
|
104
98
|
|
|
99
|
+
/** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
|
|
100
|
+
#lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
|
|
101
|
+
|
|
105
102
|
/** Stores the pending chain validation status */
|
|
106
103
|
#pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
|
|
107
104
|
|
|
@@ -116,10 +113,7 @@ export class BlockStore {
|
|
|
116
113
|
|
|
117
114
|
#log = createLogger('archiver:block_store');
|
|
118
115
|
|
|
119
|
-
constructor(
|
|
120
|
-
private db: AztecAsyncKVStore,
|
|
121
|
-
private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
122
|
-
) {
|
|
116
|
+
constructor(private db: AztecAsyncKVStore) {
|
|
123
117
|
this.#blocks = db.openMap('archiver_blocks');
|
|
124
118
|
this.#blockTxs = db.openMap('archiver_block_txs');
|
|
125
119
|
this.#txEffects = db.openMap('archiver_tx_effects');
|
|
@@ -128,40 +122,42 @@ export class BlockStore {
|
|
|
128
122
|
this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
|
|
129
123
|
this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
|
|
130
124
|
this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
|
|
125
|
+
this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
|
|
131
126
|
this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
|
|
132
127
|
this.#checkpoints = db.openMap('archiver_checkpoints');
|
|
128
|
+
this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
|
|
133
129
|
}
|
|
134
130
|
|
|
135
131
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* TODO(#13569): Compute proper finalized block number based on L1 finalized block.
|
|
139
|
-
* TODO(palla/mbps): Even the provisional computation is wrong, since it should subtract checkpoints, not blocks
|
|
132
|
+
* Returns the finalized L2 block number. An L2 block is finalized when it was proven
|
|
133
|
+
* in an L1 block that has itself been finalized on Ethereum.
|
|
140
134
|
* @returns The finalized block number.
|
|
141
135
|
*/
|
|
142
136
|
async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
|
|
143
|
-
const
|
|
144
|
-
|
|
137
|
+
const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
|
|
138
|
+
if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
|
|
139
|
+
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
140
|
+
}
|
|
141
|
+
const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
|
|
142
|
+
if (!checkpointStorage) {
|
|
143
|
+
throw new CheckpointNotFoundError(finalizedCheckpointNumber);
|
|
144
|
+
}
|
|
145
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
/**
|
|
148
|
-
* Append new proposed
|
|
149
|
-
*
|
|
149
|
+
* Append a new proposed block to the store.
|
|
150
|
+
* This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
|
|
150
151
|
* For checkpointed blocks (already published to L1), use addCheckpoints() instead.
|
|
151
|
-
* @param
|
|
152
|
+
* @param block - The proposed L2 block to be added to the store.
|
|
152
153
|
* @returns True if the operation is successful.
|
|
153
154
|
*/
|
|
154
|
-
async
|
|
155
|
-
if (blocks.length === 0) {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
|
|
155
|
+
async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
|
|
159
156
|
return await this.db.transactionAsync(async () => {
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const firstBlockLastArchive = blocks[0].header.lastArchive.root;
|
|
157
|
+
const blockNumber = block.number;
|
|
158
|
+
const blockCheckpointNumber = block.checkpointNumber;
|
|
159
|
+
const blockIndex = block.indexWithinCheckpoint;
|
|
160
|
+
const blockLastArchive = block.header.lastArchive.root;
|
|
165
161
|
|
|
166
162
|
// Extract the latest block and checkpoint numbers
|
|
167
163
|
const previousBlockNumber = await this.getLatestBlockNumber();
|
|
@@ -169,71 +165,52 @@ export class BlockStore {
|
|
|
169
165
|
|
|
170
166
|
// Verify we're not overwriting checkpointed blocks
|
|
171
167
|
const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
|
|
172
|
-
if (!opts.force &&
|
|
173
|
-
|
|
168
|
+
if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
|
|
169
|
+
// Check if the proposed block matches the already-checkpointed one
|
|
170
|
+
const existingBlock = await this.getBlock(BlockNumber(blockNumber));
|
|
171
|
+
if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
|
|
172
|
+
throw new BlockAlreadyCheckpointedError(blockNumber);
|
|
173
|
+
}
|
|
174
|
+
throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
// Check that the
|
|
177
|
-
if (!opts.force && previousBlockNumber !==
|
|
178
|
-
throw new
|
|
177
|
+
// Check that the block number is the expected one
|
|
178
|
+
if (!opts.force && previousBlockNumber !== blockNumber - 1) {
|
|
179
|
+
throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
// The same check as above but for checkpoints
|
|
182
|
-
if (!opts.force && previousCheckpointNumber !==
|
|
183
|
-
throw new
|
|
183
|
+
if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
|
|
184
|
+
throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
// Extract the previous block if there is one and see if it is for the same checkpoint or not
|
|
187
188
|
const previousBlockResult = await this.getBlock(previousBlockNumber);
|
|
188
189
|
|
|
189
|
-
let
|
|
190
|
+
let expectedBlockIndex = 0;
|
|
190
191
|
let previousBlockIndex: number | undefined = undefined;
|
|
191
192
|
if (previousBlockResult !== undefined) {
|
|
192
|
-
if (previousBlockResult.checkpointNumber ===
|
|
193
|
+
if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
|
|
193
194
|
// The previous block is for the same checkpoint, therefore our index should follow it
|
|
194
195
|
previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
|
|
195
|
-
|
|
196
|
+
expectedBlockIndex = previousBlockIndex + 1;
|
|
196
197
|
}
|
|
197
|
-
if (!previousBlockResult.archive.root.equals(
|
|
198
|
+
if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
|
|
198
199
|
throw new BlockArchiveNotConsistentError(
|
|
199
|
-
|
|
200
|
+
blockNumber,
|
|
200
201
|
previousBlockResult.number,
|
|
201
|
-
|
|
202
|
+
blockLastArchive,
|
|
202
203
|
previousBlockResult.archive.root,
|
|
203
204
|
);
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
// Now check that the
|
|
208
|
-
if (!opts.force &&
|
|
209
|
-
throw new BlockIndexNotSequentialError(
|
|
208
|
+
// Now check that the block has the expected index value
|
|
209
|
+
if (!opts.force && expectedBlockIndex !== blockIndex) {
|
|
210
|
+
throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
|
|
213
|
-
let previousBlock: L2Block | undefined = undefined;
|
|
214
|
-
for (const block of blocks) {
|
|
215
|
-
if (!opts.force && previousBlock) {
|
|
216
|
-
if (previousBlock.number + 1 !== block.number) {
|
|
217
|
-
throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
|
|
218
|
-
}
|
|
219
|
-
if (previousBlock.indexWithinCheckpoint + 1 !== block.indexWithinCheckpoint) {
|
|
220
|
-
throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
|
|
221
|
-
}
|
|
222
|
-
if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
|
|
223
|
-
throw new BlockArchiveNotConsistentError(
|
|
224
|
-
block.number,
|
|
225
|
-
previousBlock.number,
|
|
226
|
-
block.header.lastArchive.root,
|
|
227
|
-
previousBlock.archive.root,
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (!opts.force && firstBlockCheckpointNumber !== block.checkpointNumber) {
|
|
232
|
-
throw new CheckpointNumberNotConsistentError(block.checkpointNumber, firstBlockCheckpointNumber);
|
|
233
|
-
}
|
|
234
|
-
previousBlock = block;
|
|
235
|
-
await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
|
|
236
|
-
}
|
|
213
|
+
await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
|
|
237
214
|
|
|
238
215
|
return true;
|
|
239
216
|
});
|
|
@@ -250,21 +227,34 @@ export class BlockStore {
|
|
|
250
227
|
}
|
|
251
228
|
|
|
252
229
|
return await this.db.transactionAsync(async () => {
|
|
253
|
-
// Check that the checkpoint immediately before the first block to be added is present in the store.
|
|
254
230
|
const firstCheckpointNumber = checkpoints[0].checkpoint.number;
|
|
255
231
|
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
256
232
|
|
|
257
|
-
|
|
233
|
+
// Handle already-stored checkpoints at the start of the batch.
|
|
234
|
+
// This can happen after an L1 reorg re-includes a checkpoint in a different L1 block.
|
|
235
|
+
// We accept them if archives match (same content) and update their L1 metadata.
|
|
236
|
+
if (!opts.force && firstCheckpointNumber <= previousCheckpointNumber) {
|
|
237
|
+
checkpoints = await this.skipOrUpdateAlreadyStoredCheckpoints(checkpoints, previousCheckpointNumber);
|
|
238
|
+
if (checkpoints.length === 0) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
// Re-check sequentiality after skipping
|
|
242
|
+
const newFirstNumber = checkpoints[0].checkpoint.number;
|
|
243
|
+
if (previousCheckpointNumber !== newFirstNumber - 1) {
|
|
244
|
+
throw new InitialCheckpointNumberNotSequentialError(newFirstNumber, previousCheckpointNumber);
|
|
245
|
+
}
|
|
246
|
+
} else if (previousCheckpointNumber !== firstCheckpointNumber - 1 && !opts.force) {
|
|
258
247
|
throw new InitialCheckpointNumberNotSequentialError(firstCheckpointNumber, previousCheckpointNumber);
|
|
259
248
|
}
|
|
260
249
|
|
|
261
250
|
// Extract the previous checkpoint if there is one
|
|
251
|
+
const currentFirstCheckpointNumber = checkpoints[0].checkpoint.number;
|
|
262
252
|
let previousCheckpointData: CheckpointData | undefined = undefined;
|
|
263
|
-
if (
|
|
253
|
+
if (currentFirstCheckpointNumber - 1 !== INITIAL_CHECKPOINT_NUMBER - 1) {
|
|
264
254
|
// There should be a previous checkpoint
|
|
265
|
-
previousCheckpointData = await this.getCheckpointData(
|
|
255
|
+
previousCheckpointData = await this.getCheckpointData(CheckpointNumber(currentFirstCheckpointNumber - 1));
|
|
266
256
|
if (previousCheckpointData === undefined) {
|
|
267
|
-
throw new CheckpointNotFoundError(
|
|
257
|
+
throw new CheckpointNotFoundError(CheckpointNumber(currentFirstCheckpointNumber - 1));
|
|
268
258
|
}
|
|
269
259
|
}
|
|
270
260
|
|
|
@@ -273,7 +263,7 @@ export class BlockStore {
|
|
|
273
263
|
|
|
274
264
|
// If we have a previous checkpoint then we need to get the previous block number
|
|
275
265
|
if (previousCheckpointData !== undefined) {
|
|
276
|
-
previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.
|
|
266
|
+
previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
|
|
277
267
|
previousBlock = await this.getBlock(previousBlockNumber);
|
|
278
268
|
if (previousBlock === undefined) {
|
|
279
269
|
// We should be able to get the required previous block
|
|
@@ -337,12 +327,16 @@ export class BlockStore {
|
|
|
337
327
|
await this.#checkpoints.set(checkpoint.checkpoint.number, {
|
|
338
328
|
header: checkpoint.checkpoint.header.toBuffer(),
|
|
339
329
|
archive: checkpoint.checkpoint.archive.toBuffer(),
|
|
330
|
+
checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
|
|
340
331
|
l1: checkpoint.l1.toBuffer(),
|
|
341
332
|
attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
|
|
342
333
|
checkpointNumber: checkpoint.checkpoint.number,
|
|
343
334
|
startBlock: checkpoint.checkpoint.blocks[0].number,
|
|
344
|
-
|
|
335
|
+
blockCount: checkpoint.checkpoint.blocks.length,
|
|
345
336
|
});
|
|
337
|
+
|
|
338
|
+
// Update slot-to-checkpoint index
|
|
339
|
+
await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
|
|
346
340
|
}
|
|
347
341
|
|
|
348
342
|
await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
|
|
@@ -350,6 +344,50 @@ export class BlockStore {
|
|
|
350
344
|
});
|
|
351
345
|
}
|
|
352
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Handles checkpoints at the start of a batch that are already stored (e.g. due to L1 reorg).
|
|
349
|
+
* Verifies the archive root matches, updates L1 metadata, and returns only the new checkpoints.
|
|
350
|
+
*/
|
|
351
|
+
private async skipOrUpdateAlreadyStoredCheckpoints(
|
|
352
|
+
checkpoints: PublishedCheckpoint[],
|
|
353
|
+
latestStored: CheckpointNumber,
|
|
354
|
+
): Promise<PublishedCheckpoint[]> {
|
|
355
|
+
let i = 0;
|
|
356
|
+
for (; i < checkpoints.length && checkpoints[i].checkpoint.number <= latestStored; i++) {
|
|
357
|
+
const incoming = checkpoints[i];
|
|
358
|
+
const stored = await this.getCheckpointData(incoming.checkpoint.number);
|
|
359
|
+
if (!stored) {
|
|
360
|
+
// Should not happen if latestStored is correct, but be safe
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
// Verify the checkpoint content matches (archive root)
|
|
364
|
+
if (!stored.archive.root.equals(incoming.checkpoint.archive.root)) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
`Checkpoint ${incoming.checkpoint.number} already exists in store but with a different archive root. ` +
|
|
367
|
+
`Stored: ${stored.archive.root}, incoming: ${incoming.checkpoint.archive.root}`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
// Update L1 metadata and attestations for the already-stored checkpoint
|
|
371
|
+
this.#log.warn(
|
|
372
|
+
`Checkpoint ${incoming.checkpoint.number} already stored, updating L1 info ` +
|
|
373
|
+
`(L1 block ${stored.l1.blockNumber} -> ${incoming.l1.blockNumber})`,
|
|
374
|
+
);
|
|
375
|
+
await this.#checkpoints.set(incoming.checkpoint.number, {
|
|
376
|
+
header: incoming.checkpoint.header.toBuffer(),
|
|
377
|
+
archive: incoming.checkpoint.archive.toBuffer(),
|
|
378
|
+
checkpointOutHash: incoming.checkpoint.getCheckpointOutHash().toBuffer(),
|
|
379
|
+
l1: incoming.l1.toBuffer(),
|
|
380
|
+
attestations: incoming.attestations.map(a => a.toBuffer()),
|
|
381
|
+
checkpointNumber: incoming.checkpoint.number,
|
|
382
|
+
startBlock: incoming.checkpoint.blocks[0].number,
|
|
383
|
+
blockCount: incoming.checkpoint.blocks.length,
|
|
384
|
+
});
|
|
385
|
+
// Update the sync point to reflect the new L1 block
|
|
386
|
+
await this.#lastSynchedL1Block.set(incoming.l1.blockNumber);
|
|
387
|
+
}
|
|
388
|
+
return checkpoints.slice(i);
|
|
389
|
+
}
|
|
390
|
+
|
|
353
391
|
private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
|
|
354
392
|
const blockHash = await block.hash();
|
|
355
393
|
|
|
@@ -425,7 +463,7 @@ export class BlockStore {
|
|
|
425
463
|
if (!targetCheckpoint) {
|
|
426
464
|
throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
|
|
427
465
|
}
|
|
428
|
-
lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.
|
|
466
|
+
lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
|
|
429
467
|
}
|
|
430
468
|
|
|
431
469
|
// Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
|
|
@@ -433,6 +471,11 @@ export class BlockStore {
|
|
|
433
471
|
|
|
434
472
|
// Remove all checkpoints after the target
|
|
435
473
|
for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
|
|
474
|
+
const checkpointStorage = await this.#checkpoints.getAsync(c);
|
|
475
|
+
if (checkpointStorage) {
|
|
476
|
+
const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
|
|
477
|
+
await this.#slotToCheckpoint.delete(slotNumber);
|
|
478
|
+
}
|
|
436
479
|
await this.#checkpoints.delete(c);
|
|
437
480
|
this.#log.debug(`Removed checkpoint ${c}`);
|
|
438
481
|
}
|
|
@@ -461,17 +504,32 @@ export class BlockStore {
|
|
|
461
504
|
return checkpoints;
|
|
462
505
|
}
|
|
463
506
|
|
|
464
|
-
|
|
465
|
-
|
|
507
|
+
/** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
|
|
508
|
+
async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
|
|
509
|
+
const result: CheckpointData[] = [];
|
|
510
|
+
for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
|
|
511
|
+
start: startSlot,
|
|
512
|
+
end: endSlot + 1,
|
|
513
|
+
})) {
|
|
514
|
+
const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
|
|
515
|
+
if (checkpointStorage) {
|
|
516
|
+
result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return result;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
|
|
523
|
+
return {
|
|
466
524
|
header: CheckpointHeader.fromBuffer(checkpointStorage.header),
|
|
467
525
|
archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
|
|
526
|
+
checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
|
|
468
527
|
checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
|
|
469
|
-
startBlock: checkpointStorage.startBlock,
|
|
470
|
-
|
|
528
|
+
startBlock: BlockNumber(checkpointStorage.startBlock),
|
|
529
|
+
blockCount: checkpointStorage.blockCount,
|
|
471
530
|
l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
|
|
472
|
-
attestations: checkpointStorage.attestations,
|
|
531
|
+
attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
|
|
473
532
|
};
|
|
474
|
-
return data;
|
|
475
533
|
}
|
|
476
534
|
|
|
477
535
|
async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
|
|
@@ -483,7 +541,7 @@ export class BlockStore {
|
|
|
483
541
|
const blocksForCheckpoint = await toArray(
|
|
484
542
|
this.#blocks.entriesAsync({
|
|
485
543
|
start: checkpoint.startBlock,
|
|
486
|
-
end: checkpoint.startBlock + checkpoint.
|
|
544
|
+
end: checkpoint.startBlock + checkpoint.blockCount,
|
|
487
545
|
}),
|
|
488
546
|
);
|
|
489
547
|
|
|
@@ -556,7 +614,7 @@ export class BlockStore {
|
|
|
556
614
|
if (!checkpointStorage) {
|
|
557
615
|
throw new CheckpointNotFoundError(provenCheckpointNumber);
|
|
558
616
|
} else {
|
|
559
|
-
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.
|
|
617
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
560
618
|
}
|
|
561
619
|
}
|
|
562
620
|
|
|
@@ -655,6 +713,32 @@ export class BlockStore {
|
|
|
655
713
|
}
|
|
656
714
|
}
|
|
657
715
|
|
|
716
|
+
/**
|
|
717
|
+
* Gets block metadata (without tx data) by block number.
|
|
718
|
+
* @param blockNumber - The number of the block to return.
|
|
719
|
+
* @returns The requested block data.
|
|
720
|
+
*/
|
|
721
|
+
async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
|
|
722
|
+
const blockStorage = await this.#blocks.getAsync(blockNumber);
|
|
723
|
+
if (!blockStorage || !blockStorage.header) {
|
|
724
|
+
return undefined;
|
|
725
|
+
}
|
|
726
|
+
return this.getBlockDataFromBlockStorage(blockStorage);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Gets block metadata (without tx data) by archive root.
|
|
731
|
+
* @param archive - The archive root of the block to return.
|
|
732
|
+
* @returns The requested block data.
|
|
733
|
+
*/
|
|
734
|
+
async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
|
|
735
|
+
const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
|
|
736
|
+
if (blockNumber === undefined) {
|
|
737
|
+
return undefined;
|
|
738
|
+
}
|
|
739
|
+
return this.getBlockData(BlockNumber(blockNumber));
|
|
740
|
+
}
|
|
741
|
+
|
|
658
742
|
/**
|
|
659
743
|
* Gets an L2 block.
|
|
660
744
|
* @param blockNumber - The number of the block to return.
|
|
@@ -759,15 +843,24 @@ export class BlockStore {
|
|
|
759
843
|
}
|
|
760
844
|
}
|
|
761
845
|
|
|
846
|
+
private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
|
|
847
|
+
return {
|
|
848
|
+
header: BlockHeader.fromBuffer(blockStorage.header),
|
|
849
|
+
archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
|
|
850
|
+
blockHash: Fr.fromBuffer(blockStorage.blockHash),
|
|
851
|
+
checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
|
|
852
|
+
indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
762
856
|
private async getBlockFromBlockStorage(
|
|
763
857
|
blockNumber: number,
|
|
764
858
|
blockStorage: BlockStorage,
|
|
765
859
|
): Promise<L2Block | undefined> {
|
|
766
|
-
const header =
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const blockHashString = bufferToHex(blockHash);
|
|
860
|
+
const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
|
|
861
|
+
this.getBlockDataFromBlockStorage(blockStorage);
|
|
862
|
+
header.setHash(blockHash);
|
|
863
|
+
const blockHashString = bufferToHex(blockStorage.blockHash);
|
|
771
864
|
const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
|
|
772
865
|
if (blockTxsBuffer === undefined) {
|
|
773
866
|
this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
|
|
@@ -786,13 +879,7 @@ export class BlockStore {
|
|
|
786
879
|
txEffects.push(deserializeIndexedTxEffect(txEffect).data);
|
|
787
880
|
}
|
|
788
881
|
const body = new Body(txEffects);
|
|
789
|
-
const block = new L2Block(
|
|
790
|
-
archive,
|
|
791
|
-
header,
|
|
792
|
-
body,
|
|
793
|
-
CheckpointNumber(blockStorage.checkpointNumber!),
|
|
794
|
-
IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
795
|
-
);
|
|
882
|
+
const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
|
|
796
883
|
|
|
797
884
|
if (block.number !== blockNumber) {
|
|
798
885
|
throw new Error(
|
|
@@ -822,7 +909,10 @@ export class BlockStore {
|
|
|
822
909
|
* @param txHash - The hash of a tx we try to get the receipt for.
|
|
823
910
|
* @returns The requested tx receipt (or undefined if not found).
|
|
824
911
|
*/
|
|
825
|
-
async getSettledTxReceipt(
|
|
912
|
+
async getSettledTxReceipt(
|
|
913
|
+
txHash: TxHash,
|
|
914
|
+
l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
|
|
915
|
+
): Promise<TxReceipt | undefined> {
|
|
826
916
|
const txEffect = await this.getTxEffect(txHash);
|
|
827
917
|
if (!txEffect) {
|
|
828
918
|
return undefined;
|
|
@@ -831,10 +921,11 @@ export class BlockStore {
|
|
|
831
921
|
const blockNumber = BlockNumber(txEffect.l2BlockNumber);
|
|
832
922
|
|
|
833
923
|
// Use existing archiver methods to determine finalization level
|
|
834
|
-
const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
|
|
924
|
+
const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
|
|
835
925
|
this.getProvenBlockNumber(),
|
|
836
926
|
this.getCheckpointedL2BlockNumber(),
|
|
837
927
|
this.getFinalizedL2BlockNumber(),
|
|
928
|
+
this.getBlockData(blockNumber),
|
|
838
929
|
]);
|
|
839
930
|
|
|
840
931
|
let status: TxStatus;
|
|
@@ -848,6 +939,9 @@ export class BlockStore {
|
|
|
848
939
|
status = TxStatus.PROPOSED;
|
|
849
940
|
}
|
|
850
941
|
|
|
942
|
+
const epochNumber =
|
|
943
|
+
blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;
|
|
944
|
+
|
|
851
945
|
return new TxReceipt(
|
|
852
946
|
txHash,
|
|
853
947
|
status,
|
|
@@ -856,6 +950,7 @@ export class BlockStore {
|
|
|
856
950
|
txEffect.data.transactionFee.toBigInt(),
|
|
857
951
|
txEffect.l2BlockHash,
|
|
858
952
|
blockNumber,
|
|
953
|
+
epochNumber,
|
|
859
954
|
);
|
|
860
955
|
}
|
|
861
956
|
|
|
@@ -892,7 +987,7 @@ export class BlockStore {
|
|
|
892
987
|
if (!checkpoint) {
|
|
893
988
|
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
894
989
|
}
|
|
895
|
-
return BlockNumber(checkpoint.startBlock + checkpoint.
|
|
990
|
+
return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
|
|
896
991
|
}
|
|
897
992
|
|
|
898
993
|
async getLatestL2BlockNumber(): Promise<BlockNumber> {
|
|
@@ -927,6 +1022,20 @@ export class BlockStore {
|
|
|
927
1022
|
return result;
|
|
928
1023
|
}
|
|
929
1024
|
|
|
1025
|
+
async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
1026
|
+
const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
|
|
1027
|
+
this.getLatestCheckpointNumber(),
|
|
1028
|
+
this.#lastFinalizedCheckpoint.getAsync(),
|
|
1029
|
+
]);
|
|
1030
|
+
return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
|
|
1031
|
+
? latestCheckpointNumber
|
|
1032
|
+
: CheckpointNumber(finalizedCheckpointNumber ?? 0);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
|
|
1036
|
+
return this.#lastFinalizedCheckpoint.set(checkpointNumber);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
930
1039
|
#computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
|
|
931
1040
|
if (limit < 1) {
|
|
932
1041
|
throw new Error(`Invalid limit: ${limit}`);
|
|
@@ -29,11 +29,15 @@ export class ContractClassStore {
|
|
|
29
29
|
blockNumber: number,
|
|
30
30
|
): Promise<void> {
|
|
31
31
|
await this.db.transactionAsync(async () => {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const key = contractClass.id.toString();
|
|
33
|
+
if (await this.#contractClasses.hasAsync(key)) {
|
|
34
|
+
throw new Error(`Contract class ${key} already exists, cannot add again at block ${blockNumber}`);
|
|
35
|
+
}
|
|
36
|
+
await this.#contractClasses.set(
|
|
37
|
+
key,
|
|
34
38
|
serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
|
|
35
39
|
);
|
|
36
|
-
await this.#bytecodeCommitments.
|
|
40
|
+
await this.#bytecodeCommitments.set(key, bytecodeCommitment.toBuffer());
|
|
37
41
|
});
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -27,11 +27,14 @@ export class ContractInstanceStore {
|
|
|
27
27
|
|
|
28
28
|
addContractInstance(contractInstance: ContractInstanceWithAddress, blockNumber: number): Promise<void> {
|
|
29
29
|
return this.db.transactionAsync(async () => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
new
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const key = contractInstance.address.toString();
|
|
31
|
+
if (await this.#contractInstances.hasAsync(key)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Contract instance at ${key} already exists (deployed at block ${await this.#contractInstancePublishedAt.getAsync(key)}), cannot add again at block ${blockNumber}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
await this.#contractInstances.set(key, new SerializableContractInstance(contractInstance).toBuffer());
|
|
37
|
+
await this.#contractInstancePublishedAt.set(key, blockNumber);
|
|
35
38
|
});
|
|
36
39
|
}
|
|
37
40
|
|