@aztec/archiver 0.0.1-commit.fffb133c → 0.0.1-dev
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 +9 -5
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +87 -111
- 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 +4 -3
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +32 -26
- 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 +10 -14
- 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/l1/validate_trace.d.ts +6 -3
- package/dest/l1/validate_trace.d.ts.map +1 -1
- package/dest/l1/validate_trace.js +13 -9
- package/dest/modules/data_source_base.d.ts +13 -8
- 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 +114 -40
- package/dest/modules/instrumentation.d.ts +4 -2
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +26 -12
- 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 +42 -13
- package/dest/store/block_store.d.ts +38 -29
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +294 -85
- 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 +27 -7
- 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 +17 -2
- package/dest/store/kv_archiver_store.d.ts +40 -18
- package/dest/store/kv_archiver_store.d.ts.map +1 -1
- package/dest/store/kv_archiver_store.js +44 -16
- package/dest/store/l2_tips_cache.d.ts +18 -0
- package/dest/store/l2_tips_cache.d.ts.map +1 -0
- package/dest/store/l2_tips_cache.js +20 -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 +125 -56
- 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/index.js +3 -1
- 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 +25 -9
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +133 -87
- package/dest/test/mock_structs.d.ts +6 -2
- package/dest/test/mock_structs.d.ts.map +1 -1
- package/dest/test/mock_structs.js +20 -6
- package/dest/test/noop_l1_archiver.d.ts +26 -0
- package/dest/test/noop_l1_archiver.d.ts.map +1 -0
- package/dest/test/noop_l1_archiver.js +72 -0
- package/package.json +14 -13
- package/src/archiver.ts +110 -132
- package/src/errors.ts +10 -24
- package/src/factory.ts +47 -23
- 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 +7 -17
- package/src/l1/spire_proposer.ts +7 -15
- package/src/l1/validate_trace.ts +24 -6
- package/src/modules/data_source_base.ts +59 -98
- package/src/modules/data_store_updater.ts +124 -43
- package/src/modules/instrumentation.ts +24 -12
- package/src/modules/l1_synchronizer.ts +49 -17
- package/src/store/block_store.ts +359 -115
- package/src/store/contract_class_store.ts +27 -7
- package/src/store/contract_instance_store.ts +19 -5
- package/src/store/kv_archiver_store.ts +71 -25
- package/src/store/l2_tips_cache.ts +27 -0
- package/src/store/log_store.ts +186 -57
- package/src/store/message_store.ts +1 -1
- package/src/test/fake_l1_state.ts +125 -21
- package/src/test/index.ts +3 -0
- package/src/test/mock_archiver.ts +3 -2
- package/src/test/mock_l2_block_source.ts +179 -87
- package/src/test/mock_structs.ts +42 -12
- package/src/test/noop_l1_archiver.ts +115 -0
package/src/store/block_store.ts
CHANGED
|
@@ -9,17 +9,21 @@ 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,
|
|
13
|
+
BlockHash,
|
|
12
14
|
Body,
|
|
13
15
|
CheckpointedL2Block,
|
|
14
16
|
CommitteeAttestation,
|
|
17
|
+
GENESIS_CHECKPOINT_HEADER_HASH,
|
|
15
18
|
L2Block,
|
|
16
|
-
|
|
19
|
+
type L2TipId,
|
|
20
|
+
type L2Tips,
|
|
17
21
|
type ValidateCheckpointResult,
|
|
18
22
|
deserializeValidateCheckpointResult,
|
|
19
23
|
serializeValidateCheckpointResult,
|
|
20
24
|
} from '@aztec/stdlib/block';
|
|
21
|
-
import { L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
22
|
-
import type
|
|
25
|
+
import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
26
|
+
import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
23
27
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
24
28
|
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
25
29
|
import {
|
|
@@ -34,15 +38,14 @@ import {
|
|
|
34
38
|
} from '@aztec/stdlib/tx';
|
|
35
39
|
|
|
36
40
|
import {
|
|
41
|
+
BlockAlreadyCheckpointedError,
|
|
37
42
|
BlockArchiveNotConsistentError,
|
|
38
43
|
BlockIndexNotSequentialError,
|
|
39
44
|
BlockNotFoundError,
|
|
40
45
|
BlockNumberNotSequentialError,
|
|
41
46
|
CannotOverwriteCheckpointedBlockError,
|
|
42
47
|
CheckpointNotFoundError,
|
|
43
|
-
CheckpointNumberNotConsistentError,
|
|
44
48
|
CheckpointNumberNotSequentialError,
|
|
45
|
-
InitialBlockNumberNotSequentialError,
|
|
46
49
|
InitialCheckpointNumberNotSequentialError,
|
|
47
50
|
} from '../errors.js';
|
|
48
51
|
|
|
@@ -61,23 +64,14 @@ type BlockStorage = {
|
|
|
61
64
|
type CheckpointStorage = {
|
|
62
65
|
header: Buffer;
|
|
63
66
|
archive: Buffer;
|
|
67
|
+
checkpointOutHash: Buffer;
|
|
64
68
|
checkpointNumber: number;
|
|
65
69
|
startBlock: number;
|
|
66
|
-
|
|
70
|
+
blockCount: number;
|
|
67
71
|
l1: Buffer;
|
|
68
72
|
attestations: Buffer[];
|
|
69
73
|
};
|
|
70
74
|
|
|
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
75
|
export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
|
|
82
76
|
|
|
83
77
|
/**
|
|
@@ -90,6 +84,9 @@ export class BlockStore {
|
|
|
90
84
|
/** Map checkpoint number to checkpoint data */
|
|
91
85
|
#checkpoints: AztecAsyncMap<number, CheckpointStorage>;
|
|
92
86
|
|
|
87
|
+
/** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
|
|
88
|
+
#slotToCheckpoint: AztecAsyncMap<number, number>;
|
|
89
|
+
|
|
93
90
|
/** Map block hash to list of tx hashes */
|
|
94
91
|
#blockTxs: AztecAsyncMap<string, Buffer>;
|
|
95
92
|
|
|
@@ -102,6 +99,9 @@ export class BlockStore {
|
|
|
102
99
|
/** Stores last proven checkpoint */
|
|
103
100
|
#lastProvenCheckpoint: AztecAsyncSingleton<number>;
|
|
104
101
|
|
|
102
|
+
/** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
|
|
103
|
+
#lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
|
|
104
|
+
|
|
105
105
|
/** Stores the pending chain validation status */
|
|
106
106
|
#pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
|
|
107
107
|
|
|
@@ -116,10 +116,7 @@ export class BlockStore {
|
|
|
116
116
|
|
|
117
117
|
#log = createLogger('archiver:block_store');
|
|
118
118
|
|
|
119
|
-
constructor(
|
|
120
|
-
private db: AztecAsyncKVStore,
|
|
121
|
-
private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
122
|
-
) {
|
|
119
|
+
constructor(private db: AztecAsyncKVStore) {
|
|
123
120
|
this.#blocks = db.openMap('archiver_blocks');
|
|
124
121
|
this.#blockTxs = db.openMap('archiver_block_txs');
|
|
125
122
|
this.#txEffects = db.openMap('archiver_tx_effects');
|
|
@@ -128,40 +125,42 @@ export class BlockStore {
|
|
|
128
125
|
this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
|
|
129
126
|
this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
|
|
130
127
|
this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
|
|
128
|
+
this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
|
|
131
129
|
this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
|
|
132
130
|
this.#checkpoints = db.openMap('archiver_checkpoints');
|
|
131
|
+
this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
/**
|
|
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
|
|
135
|
+
* Returns the finalized L2 block number. An L2 block is finalized when it was proven
|
|
136
|
+
* in an L1 block that has itself been finalized on Ethereum.
|
|
140
137
|
* @returns The finalized block number.
|
|
141
138
|
*/
|
|
142
139
|
async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
|
|
143
|
-
const
|
|
144
|
-
|
|
140
|
+
const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
|
|
141
|
+
if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
|
|
142
|
+
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
143
|
+
}
|
|
144
|
+
const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
|
|
145
|
+
if (!checkpointStorage) {
|
|
146
|
+
throw new CheckpointNotFoundError(finalizedCheckpointNumber);
|
|
147
|
+
}
|
|
148
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
/**
|
|
148
|
-
* Append new proposed
|
|
149
|
-
*
|
|
152
|
+
* Append a new proposed block to the store.
|
|
153
|
+
* This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
|
|
150
154
|
* For checkpointed blocks (already published to L1), use addCheckpoints() instead.
|
|
151
|
-
* @param
|
|
155
|
+
* @param block - The proposed L2 block to be added to the store.
|
|
152
156
|
* @returns True if the operation is successful.
|
|
153
157
|
*/
|
|
154
|
-
async
|
|
155
|
-
if (blocks.length === 0) {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
|
|
158
|
+
async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
|
|
159
159
|
return await this.db.transactionAsync(async () => {
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const firstBlockLastArchive = blocks[0].header.lastArchive.root;
|
|
160
|
+
const blockNumber = block.number;
|
|
161
|
+
const blockCheckpointNumber = block.checkpointNumber;
|
|
162
|
+
const blockIndex = block.indexWithinCheckpoint;
|
|
163
|
+
const blockLastArchive = block.header.lastArchive.root;
|
|
165
164
|
|
|
166
165
|
// Extract the latest block and checkpoint numbers
|
|
167
166
|
const previousBlockNumber = await this.getLatestBlockNumber();
|
|
@@ -169,71 +168,52 @@ export class BlockStore {
|
|
|
169
168
|
|
|
170
169
|
// Verify we're not overwriting checkpointed blocks
|
|
171
170
|
const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
|
|
172
|
-
if (!opts.force &&
|
|
173
|
-
|
|
171
|
+
if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
|
|
172
|
+
// Check if the proposed block matches the already-checkpointed one
|
|
173
|
+
const existingBlock = await this.getBlock(BlockNumber(blockNumber));
|
|
174
|
+
if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
|
|
175
|
+
throw new BlockAlreadyCheckpointedError(blockNumber);
|
|
176
|
+
}
|
|
177
|
+
throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
// Check that the
|
|
177
|
-
if (!opts.force && previousBlockNumber !==
|
|
178
|
-
throw new
|
|
180
|
+
// Check that the block number is the expected one
|
|
181
|
+
if (!opts.force && previousBlockNumber !== blockNumber - 1) {
|
|
182
|
+
throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
// The same check as above but for checkpoints
|
|
182
|
-
if (!opts.force && previousCheckpointNumber !==
|
|
183
|
-
throw new
|
|
186
|
+
if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
|
|
187
|
+
throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
// Extract the previous block if there is one and see if it is for the same checkpoint or not
|
|
187
191
|
const previousBlockResult = await this.getBlock(previousBlockNumber);
|
|
188
192
|
|
|
189
|
-
let
|
|
193
|
+
let expectedBlockIndex = 0;
|
|
190
194
|
let previousBlockIndex: number | undefined = undefined;
|
|
191
195
|
if (previousBlockResult !== undefined) {
|
|
192
|
-
if (previousBlockResult.checkpointNumber ===
|
|
196
|
+
if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
|
|
193
197
|
// The previous block is for the same checkpoint, therefore our index should follow it
|
|
194
198
|
previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
|
|
195
|
-
|
|
199
|
+
expectedBlockIndex = previousBlockIndex + 1;
|
|
196
200
|
}
|
|
197
|
-
if (!previousBlockResult.archive.root.equals(
|
|
201
|
+
if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
|
|
198
202
|
throw new BlockArchiveNotConsistentError(
|
|
199
|
-
|
|
203
|
+
blockNumber,
|
|
200
204
|
previousBlockResult.number,
|
|
201
|
-
|
|
205
|
+
blockLastArchive,
|
|
202
206
|
previousBlockResult.archive.root,
|
|
203
207
|
);
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
// Now check that the
|
|
208
|
-
if (!opts.force &&
|
|
209
|
-
throw new BlockIndexNotSequentialError(
|
|
211
|
+
// Now check that the block has the expected index value
|
|
212
|
+
if (!opts.force && expectedBlockIndex !== blockIndex) {
|
|
213
|
+
throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
|
|
210
214
|
}
|
|
211
215
|
|
|
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
|
-
}
|
|
216
|
+
await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
|
|
237
217
|
|
|
238
218
|
return true;
|
|
239
219
|
});
|
|
@@ -250,21 +230,34 @@ export class BlockStore {
|
|
|
250
230
|
}
|
|
251
231
|
|
|
252
232
|
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
233
|
const firstCheckpointNumber = checkpoints[0].checkpoint.number;
|
|
255
234
|
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
256
235
|
|
|
257
|
-
|
|
236
|
+
// Handle already-stored checkpoints at the start of the batch.
|
|
237
|
+
// This can happen after an L1 reorg re-includes a checkpoint in a different L1 block.
|
|
238
|
+
// We accept them if archives match (same content) and update their L1 metadata.
|
|
239
|
+
if (!opts.force && firstCheckpointNumber <= previousCheckpointNumber) {
|
|
240
|
+
checkpoints = await this.skipOrUpdateAlreadyStoredCheckpoints(checkpoints, previousCheckpointNumber);
|
|
241
|
+
if (checkpoints.length === 0) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
// Re-check sequentiality after skipping
|
|
245
|
+
const newFirstNumber = checkpoints[0].checkpoint.number;
|
|
246
|
+
if (previousCheckpointNumber !== newFirstNumber - 1) {
|
|
247
|
+
throw new InitialCheckpointNumberNotSequentialError(newFirstNumber, previousCheckpointNumber);
|
|
248
|
+
}
|
|
249
|
+
} else if (previousCheckpointNumber !== firstCheckpointNumber - 1 && !opts.force) {
|
|
258
250
|
throw new InitialCheckpointNumberNotSequentialError(firstCheckpointNumber, previousCheckpointNumber);
|
|
259
251
|
}
|
|
260
252
|
|
|
261
253
|
// Extract the previous checkpoint if there is one
|
|
254
|
+
const currentFirstCheckpointNumber = checkpoints[0].checkpoint.number;
|
|
262
255
|
let previousCheckpointData: CheckpointData | undefined = undefined;
|
|
263
|
-
if (
|
|
256
|
+
if (currentFirstCheckpointNumber - 1 !== INITIAL_CHECKPOINT_NUMBER - 1) {
|
|
264
257
|
// There should be a previous checkpoint
|
|
265
|
-
previousCheckpointData = await this.getCheckpointData(
|
|
258
|
+
previousCheckpointData = await this.getCheckpointData(CheckpointNumber(currentFirstCheckpointNumber - 1));
|
|
266
259
|
if (previousCheckpointData === undefined) {
|
|
267
|
-
throw new CheckpointNotFoundError(
|
|
260
|
+
throw new CheckpointNotFoundError(CheckpointNumber(currentFirstCheckpointNumber - 1));
|
|
268
261
|
}
|
|
269
262
|
}
|
|
270
263
|
|
|
@@ -273,7 +266,7 @@ export class BlockStore {
|
|
|
273
266
|
|
|
274
267
|
// If we have a previous checkpoint then we need to get the previous block number
|
|
275
268
|
if (previousCheckpointData !== undefined) {
|
|
276
|
-
previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.
|
|
269
|
+
previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
|
|
277
270
|
previousBlock = await this.getBlock(previousBlockNumber);
|
|
278
271
|
if (previousBlock === undefined) {
|
|
279
272
|
// We should be able to get the required previous block
|
|
@@ -337,12 +330,16 @@ export class BlockStore {
|
|
|
337
330
|
await this.#checkpoints.set(checkpoint.checkpoint.number, {
|
|
338
331
|
header: checkpoint.checkpoint.header.toBuffer(),
|
|
339
332
|
archive: checkpoint.checkpoint.archive.toBuffer(),
|
|
333
|
+
checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
|
|
340
334
|
l1: checkpoint.l1.toBuffer(),
|
|
341
335
|
attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
|
|
342
336
|
checkpointNumber: checkpoint.checkpoint.number,
|
|
343
337
|
startBlock: checkpoint.checkpoint.blocks[0].number,
|
|
344
|
-
|
|
338
|
+
blockCount: checkpoint.checkpoint.blocks.length,
|
|
345
339
|
});
|
|
340
|
+
|
|
341
|
+
// Update slot-to-checkpoint index
|
|
342
|
+
await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
|
|
346
343
|
}
|
|
347
344
|
|
|
348
345
|
await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
|
|
@@ -350,8 +347,52 @@ export class BlockStore {
|
|
|
350
347
|
});
|
|
351
348
|
}
|
|
352
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Handles checkpoints at the start of a batch that are already stored (e.g. due to L1 reorg).
|
|
352
|
+
* Verifies the archive root matches, updates L1 metadata, and returns only the new checkpoints.
|
|
353
|
+
*/
|
|
354
|
+
private async skipOrUpdateAlreadyStoredCheckpoints(
|
|
355
|
+
checkpoints: PublishedCheckpoint[],
|
|
356
|
+
latestStored: CheckpointNumber,
|
|
357
|
+
): Promise<PublishedCheckpoint[]> {
|
|
358
|
+
let i = 0;
|
|
359
|
+
for (; i < checkpoints.length && checkpoints[i].checkpoint.number <= latestStored; i++) {
|
|
360
|
+
const incoming = checkpoints[i];
|
|
361
|
+
const stored = await this.getCheckpointData(incoming.checkpoint.number);
|
|
362
|
+
if (!stored) {
|
|
363
|
+
// Should not happen if latestStored is correct, but be safe
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
// Verify the checkpoint content matches (archive root)
|
|
367
|
+
if (!stored.archive.root.equals(incoming.checkpoint.archive.root)) {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`Checkpoint ${incoming.checkpoint.number} already exists in store but with a different archive root. ` +
|
|
370
|
+
`Stored: ${stored.archive.root}, incoming: ${incoming.checkpoint.archive.root}`,
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
// Update L1 metadata and attestations for the already-stored checkpoint
|
|
374
|
+
this.#log.warn(
|
|
375
|
+
`Checkpoint ${incoming.checkpoint.number} already stored, updating L1 info ` +
|
|
376
|
+
`(L1 block ${stored.l1.blockNumber} -> ${incoming.l1.blockNumber})`,
|
|
377
|
+
);
|
|
378
|
+
await this.#checkpoints.set(incoming.checkpoint.number, {
|
|
379
|
+
header: incoming.checkpoint.header.toBuffer(),
|
|
380
|
+
archive: incoming.checkpoint.archive.toBuffer(),
|
|
381
|
+
checkpointOutHash: incoming.checkpoint.getCheckpointOutHash().toBuffer(),
|
|
382
|
+
l1: incoming.l1.toBuffer(),
|
|
383
|
+
attestations: incoming.attestations.map(a => a.toBuffer()),
|
|
384
|
+
checkpointNumber: incoming.checkpoint.number,
|
|
385
|
+
startBlock: incoming.checkpoint.blocks[0].number,
|
|
386
|
+
blockCount: incoming.checkpoint.blocks.length,
|
|
387
|
+
});
|
|
388
|
+
// Update the sync point to reflect the new L1 block
|
|
389
|
+
await this.#lastSynchedL1Block.set(incoming.l1.blockNumber);
|
|
390
|
+
}
|
|
391
|
+
return checkpoints.slice(i);
|
|
392
|
+
}
|
|
393
|
+
|
|
353
394
|
private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
|
|
354
|
-
const blockHash =
|
|
395
|
+
const blockHash = await block.hash();
|
|
355
396
|
|
|
356
397
|
await this.#blocks.set(block.number, {
|
|
357
398
|
header: block.header.toBuffer(),
|
|
@@ -425,7 +466,7 @@ export class BlockStore {
|
|
|
425
466
|
if (!targetCheckpoint) {
|
|
426
467
|
throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
|
|
427
468
|
}
|
|
428
|
-
lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.
|
|
469
|
+
lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
|
|
429
470
|
}
|
|
430
471
|
|
|
431
472
|
// Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
|
|
@@ -433,6 +474,11 @@ export class BlockStore {
|
|
|
433
474
|
|
|
434
475
|
// Remove all checkpoints after the target
|
|
435
476
|
for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
|
|
477
|
+
const checkpointStorage = await this.#checkpoints.getAsync(c);
|
|
478
|
+
if (checkpointStorage) {
|
|
479
|
+
const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
|
|
480
|
+
await this.#slotToCheckpoint.delete(slotNumber);
|
|
481
|
+
}
|
|
436
482
|
await this.#checkpoints.delete(c);
|
|
437
483
|
this.#log.debug(`Removed checkpoint ${c}`);
|
|
438
484
|
}
|
|
@@ -461,17 +507,32 @@ export class BlockStore {
|
|
|
461
507
|
return checkpoints;
|
|
462
508
|
}
|
|
463
509
|
|
|
464
|
-
|
|
465
|
-
|
|
510
|
+
/** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
|
|
511
|
+
async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
|
|
512
|
+
const result: CheckpointData[] = [];
|
|
513
|
+
for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
|
|
514
|
+
start: startSlot,
|
|
515
|
+
end: endSlot + 1,
|
|
516
|
+
})) {
|
|
517
|
+
const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
|
|
518
|
+
if (checkpointStorage) {
|
|
519
|
+
result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return result;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
|
|
526
|
+
return {
|
|
466
527
|
header: CheckpointHeader.fromBuffer(checkpointStorage.header),
|
|
467
528
|
archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
|
|
529
|
+
checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
|
|
468
530
|
checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
|
|
469
|
-
startBlock: checkpointStorage.startBlock,
|
|
470
|
-
|
|
531
|
+
startBlock: BlockNumber(checkpointStorage.startBlock),
|
|
532
|
+
blockCount: checkpointStorage.blockCount,
|
|
471
533
|
l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
|
|
472
|
-
attestations: checkpointStorage.attestations,
|
|
534
|
+
attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
|
|
473
535
|
};
|
|
474
|
-
return data;
|
|
475
536
|
}
|
|
476
537
|
|
|
477
538
|
async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
|
|
@@ -483,7 +544,7 @@ export class BlockStore {
|
|
|
483
544
|
const blocksForCheckpoint = await toArray(
|
|
484
545
|
this.#blocks.entriesAsync({
|
|
485
546
|
start: checkpoint.startBlock,
|
|
486
|
-
end: checkpoint.startBlock + checkpoint.
|
|
547
|
+
end: checkpoint.startBlock + checkpoint.blockCount,
|
|
487
548
|
}),
|
|
488
549
|
);
|
|
489
550
|
|
|
@@ -556,7 +617,7 @@ export class BlockStore {
|
|
|
556
617
|
if (!checkpointStorage) {
|
|
557
618
|
throw new CheckpointNotFoundError(provenCheckpointNumber);
|
|
558
619
|
} else {
|
|
559
|
-
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.
|
|
620
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
560
621
|
}
|
|
561
622
|
}
|
|
562
623
|
|
|
@@ -567,6 +628,138 @@ export class BlockStore {
|
|
|
567
628
|
: BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
568
629
|
}
|
|
569
630
|
|
|
631
|
+
/**
|
|
632
|
+
* Resolves all L2 chain tips in a single read transaction so the snapshot is internally consistent.
|
|
633
|
+
* The result is guaranteed to satisfy finalized <= proven <= checkpointed <= proposed by block number.
|
|
634
|
+
*/
|
|
635
|
+
async getL2TipsData(genesisBlockHash: Fr): Promise<L2Tips> {
|
|
636
|
+
return await this.db.transactionAsync(async () => {
|
|
637
|
+
const genesisBlockNumber = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
638
|
+
const genesisCheckpointNumber = CheckpointNumber(INITIAL_CHECKPOINT_NUMBER - 1);
|
|
639
|
+
const genesisBlockId = { number: genesisBlockNumber, hash: genesisBlockHash.toString() };
|
|
640
|
+
const genesisCheckpointId = {
|
|
641
|
+
number: genesisCheckpointNumber,
|
|
642
|
+
hash: GENESIS_CHECKPOINT_HEADER_HASH.toString(),
|
|
643
|
+
};
|
|
644
|
+
const genesisTip: L2TipId = { block: genesisBlockId, checkpoint: genesisCheckpointId };
|
|
645
|
+
|
|
646
|
+
const [latestBlockEntry] = await toArray(this.#blocks.entriesAsync({ reverse: true, limit: 1 }));
|
|
647
|
+
const [latestCheckpointEntry] = await toArray(this.#checkpoints.entriesAsync({ reverse: true, limit: 1 }));
|
|
648
|
+
const latestCheckpointNumber = latestCheckpointEntry
|
|
649
|
+
? CheckpointNumber(latestCheckpointEntry[0])
|
|
650
|
+
: genesisCheckpointNumber;
|
|
651
|
+
|
|
652
|
+
const [provenRaw, finalizedRaw] = await Promise.all([
|
|
653
|
+
this.#lastProvenCheckpoint.getAsync(),
|
|
654
|
+
this.#lastFinalizedCheckpoint.getAsync(),
|
|
655
|
+
]);
|
|
656
|
+
|
|
657
|
+
const provenCheckpointNumber = CheckpointNumber(Math.min(provenRaw ?? 0, latestCheckpointNumber));
|
|
658
|
+
const finalizedCheckpointNumber = CheckpointNumber(Math.min(finalizedRaw ?? 0, provenCheckpointNumber));
|
|
659
|
+
|
|
660
|
+
const checkpointStorageCache = new Map<CheckpointNumber, CheckpointStorage>();
|
|
661
|
+
if (latestCheckpointEntry) {
|
|
662
|
+
checkpointStorageCache.set(CheckpointNumber(latestCheckpointEntry[0]), latestCheckpointEntry[1]);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const loadCheckpointStorage = async (
|
|
666
|
+
checkpointNumber: CheckpointNumber,
|
|
667
|
+
): Promise<CheckpointStorage | undefined> => {
|
|
668
|
+
if (checkpointNumber === genesisCheckpointNumber) {
|
|
669
|
+
return undefined;
|
|
670
|
+
}
|
|
671
|
+
if (!checkpointStorageCache.has(checkpointNumber)) {
|
|
672
|
+
const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
|
|
673
|
+
if (!checkpointStorage) {
|
|
674
|
+
throw new CheckpointNotFoundError(checkpointNumber);
|
|
675
|
+
}
|
|
676
|
+
checkpointStorageCache.set(checkpointNumber, checkpointStorage);
|
|
677
|
+
}
|
|
678
|
+
return checkpointStorageCache.get(checkpointNumber)!;
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
const [provenCheckpoint, finalizedCheckpoint] = await Promise.all([
|
|
682
|
+
loadCheckpointStorage(provenCheckpointNumber),
|
|
683
|
+
loadCheckpointStorage(finalizedCheckpointNumber),
|
|
684
|
+
]);
|
|
685
|
+
|
|
686
|
+
const blockHashCache = new Map<BlockNumber, string>();
|
|
687
|
+
blockHashCache.set(genesisBlockNumber, genesisBlockHash.toString());
|
|
688
|
+
if (latestBlockEntry) {
|
|
689
|
+
blockHashCache.set(
|
|
690
|
+
BlockNumber(latestBlockEntry[0]),
|
|
691
|
+
BlockHash.fromBuffer(latestBlockEntry[1].blockHash).toString(),
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const loadBlockHash = async (blockNumber: BlockNumber): Promise<string> => {
|
|
696
|
+
if (!blockHashCache.has(blockNumber)) {
|
|
697
|
+
const blockStorage = await this.#blocks.getAsync(blockNumber);
|
|
698
|
+
if (!blockStorage) {
|
|
699
|
+
throw new BlockNotFoundError(blockNumber);
|
|
700
|
+
}
|
|
701
|
+
blockHashCache.set(blockNumber, BlockHash.fromBuffer(blockStorage.blockHash).toString());
|
|
702
|
+
}
|
|
703
|
+
return blockHashCache.get(blockNumber)!;
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
const proposed =
|
|
707
|
+
latestBlockEntry === undefined
|
|
708
|
+
? genesisBlockId
|
|
709
|
+
: {
|
|
710
|
+
number: BlockNumber(latestBlockEntry[0]),
|
|
711
|
+
hash: BlockHash.fromBuffer(latestBlockEntry[1].blockHash).toString(),
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
const buildTipFromCheckpoint = async (stored: CheckpointStorage | undefined): Promise<L2TipId> => {
|
|
715
|
+
if (!stored) {
|
|
716
|
+
return genesisTip;
|
|
717
|
+
}
|
|
718
|
+
const blockNumber = BlockNumber(stored.startBlock + stored.blockCount - 1);
|
|
719
|
+
return {
|
|
720
|
+
block: { number: blockNumber, hash: await loadBlockHash(blockNumber) },
|
|
721
|
+
checkpoint: {
|
|
722
|
+
number: CheckpointNumber(stored.checkpointNumber),
|
|
723
|
+
hash: CheckpointHeader.fromBuffer(stored.header).hash().toString(),
|
|
724
|
+
},
|
|
725
|
+
};
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const [checkpointed, proven, finalized] = await Promise.all([
|
|
729
|
+
buildTipFromCheckpoint(latestCheckpointEntry?.[1]),
|
|
730
|
+
buildTipFromCheckpoint(provenCheckpoint),
|
|
731
|
+
buildTipFromCheckpoint(finalizedCheckpoint),
|
|
732
|
+
]);
|
|
733
|
+
|
|
734
|
+
if (proposed.number < checkpointed.block.number) {
|
|
735
|
+
throw new Error(
|
|
736
|
+
`Inconsistent block store: latest block ${proposed.number} is behind checkpointed block ${checkpointed.block.number}`,
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (
|
|
741
|
+
finalized.checkpoint.number > proven.checkpoint.number ||
|
|
742
|
+
proven.checkpoint.number > checkpointed.checkpoint.number
|
|
743
|
+
) {
|
|
744
|
+
throw new Error(
|
|
745
|
+
`Inconsistent checkpoint numbers in chain tips: finalized=${finalized.checkpoint.number} proven=${proven.checkpoint.number} checkpointed=${checkpointed.checkpoint.number}`,
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (
|
|
750
|
+
finalized.block.number > proven.block.number ||
|
|
751
|
+
proven.block.number > checkpointed.block.number ||
|
|
752
|
+
checkpointed.block.number > proposed.number
|
|
753
|
+
) {
|
|
754
|
+
throw new Error(
|
|
755
|
+
`Inconsistent block numbers in chain tips: finalized=${finalized.block.number} proven=${proven.block.number} checkpointed=${checkpointed.block.number} proposed=${proposed.number}`,
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return { proposed, checkpointed, proven, finalized };
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
570
763
|
async getLatestCheckpointNumber(): Promise<CheckpointNumber> {
|
|
571
764
|
const [latestCheckpointNumber] = await toArray(this.#checkpoints.keysAsync({ reverse: true, limit: 1 }));
|
|
572
765
|
if (latestCheckpointNumber === undefined) {
|
|
@@ -624,7 +817,7 @@ export class BlockStore {
|
|
|
624
817
|
}
|
|
625
818
|
}
|
|
626
819
|
|
|
627
|
-
async getCheckpointedBlockByHash(blockHash:
|
|
820
|
+
async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
|
|
628
821
|
const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
|
|
629
822
|
if (blockNumber === undefined) {
|
|
630
823
|
return undefined;
|
|
@@ -655,6 +848,32 @@ export class BlockStore {
|
|
|
655
848
|
}
|
|
656
849
|
}
|
|
657
850
|
|
|
851
|
+
/**
|
|
852
|
+
* Gets block metadata (without tx data) by block number.
|
|
853
|
+
* @param blockNumber - The number of the block to return.
|
|
854
|
+
* @returns The requested block data.
|
|
855
|
+
*/
|
|
856
|
+
async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
|
|
857
|
+
const blockStorage = await this.#blocks.getAsync(blockNumber);
|
|
858
|
+
if (!blockStorage || !blockStorage.header) {
|
|
859
|
+
return undefined;
|
|
860
|
+
}
|
|
861
|
+
return this.getBlockDataFromBlockStorage(blockStorage);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Gets block metadata (without tx data) by archive root.
|
|
866
|
+
* @param archive - The archive root of the block to return.
|
|
867
|
+
* @returns The requested block data.
|
|
868
|
+
*/
|
|
869
|
+
async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
|
|
870
|
+
const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
|
|
871
|
+
if (blockNumber === undefined) {
|
|
872
|
+
return undefined;
|
|
873
|
+
}
|
|
874
|
+
return this.getBlockData(BlockNumber(blockNumber));
|
|
875
|
+
}
|
|
876
|
+
|
|
658
877
|
/**
|
|
659
878
|
* Gets an L2 block.
|
|
660
879
|
* @param blockNumber - The number of the block to return.
|
|
@@ -673,7 +892,7 @@ export class BlockStore {
|
|
|
673
892
|
* @param blockHash - The hash of the block to return.
|
|
674
893
|
* @returns The requested L2 block.
|
|
675
894
|
*/
|
|
676
|
-
async getBlockByHash(blockHash:
|
|
895
|
+
async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
|
|
677
896
|
const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
|
|
678
897
|
if (blockNumber === undefined) {
|
|
679
898
|
return undefined;
|
|
@@ -699,7 +918,7 @@ export class BlockStore {
|
|
|
699
918
|
* @param blockHash - The hash of the block to return.
|
|
700
919
|
* @returns The requested block header.
|
|
701
920
|
*/
|
|
702
|
-
async getBlockHeaderByHash(blockHash:
|
|
921
|
+
async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
|
|
703
922
|
const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
|
|
704
923
|
if (blockNumber === undefined) {
|
|
705
924
|
return undefined;
|
|
@@ -759,15 +978,24 @@ export class BlockStore {
|
|
|
759
978
|
}
|
|
760
979
|
}
|
|
761
980
|
|
|
981
|
+
private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
|
|
982
|
+
return {
|
|
983
|
+
header: BlockHeader.fromBuffer(blockStorage.header),
|
|
984
|
+
archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
|
|
985
|
+
blockHash: Fr.fromBuffer(blockStorage.blockHash),
|
|
986
|
+
checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
|
|
987
|
+
indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
|
|
762
991
|
private async getBlockFromBlockStorage(
|
|
763
992
|
blockNumber: number,
|
|
764
993
|
blockStorage: BlockStorage,
|
|
765
994
|
): Promise<L2Block | undefined> {
|
|
766
|
-
const header =
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const blockHashString = bufferToHex(blockHash);
|
|
995
|
+
const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
|
|
996
|
+
this.getBlockDataFromBlockStorage(blockStorage);
|
|
997
|
+
header.setHash(blockHash);
|
|
998
|
+
const blockHashString = bufferToHex(blockStorage.blockHash);
|
|
771
999
|
const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
|
|
772
1000
|
if (blockTxsBuffer === undefined) {
|
|
773
1001
|
this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
|
|
@@ -786,13 +1014,7 @@ export class BlockStore {
|
|
|
786
1014
|
txEffects.push(deserializeIndexedTxEffect(txEffect).data);
|
|
787
1015
|
}
|
|
788
1016
|
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
|
-
);
|
|
1017
|
+
const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
|
|
796
1018
|
|
|
797
1019
|
if (block.number !== blockNumber) {
|
|
798
1020
|
throw new Error(
|
|
@@ -822,7 +1044,10 @@ export class BlockStore {
|
|
|
822
1044
|
* @param txHash - The hash of a tx we try to get the receipt for.
|
|
823
1045
|
* @returns The requested tx receipt (or undefined if not found).
|
|
824
1046
|
*/
|
|
825
|
-
async getSettledTxReceipt(
|
|
1047
|
+
async getSettledTxReceipt(
|
|
1048
|
+
txHash: TxHash,
|
|
1049
|
+
l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
|
|
1050
|
+
): Promise<TxReceipt | undefined> {
|
|
826
1051
|
const txEffect = await this.getTxEffect(txHash);
|
|
827
1052
|
if (!txEffect) {
|
|
828
1053
|
return undefined;
|
|
@@ -831,10 +1056,11 @@ export class BlockStore {
|
|
|
831
1056
|
const blockNumber = BlockNumber(txEffect.l2BlockNumber);
|
|
832
1057
|
|
|
833
1058
|
// Use existing archiver methods to determine finalization level
|
|
834
|
-
const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
|
|
1059
|
+
const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
|
|
835
1060
|
this.getProvenBlockNumber(),
|
|
836
1061
|
this.getCheckpointedL2BlockNumber(),
|
|
837
1062
|
this.getFinalizedL2BlockNumber(),
|
|
1063
|
+
this.getBlockData(blockNumber),
|
|
838
1064
|
]);
|
|
839
1065
|
|
|
840
1066
|
let status: TxStatus;
|
|
@@ -848,6 +1074,9 @@ export class BlockStore {
|
|
|
848
1074
|
status = TxStatus.PROPOSED;
|
|
849
1075
|
}
|
|
850
1076
|
|
|
1077
|
+
const epochNumber =
|
|
1078
|
+
blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;
|
|
1079
|
+
|
|
851
1080
|
return new TxReceipt(
|
|
852
1081
|
txHash,
|
|
853
1082
|
status,
|
|
@@ -856,6 +1085,7 @@ export class BlockStore {
|
|
|
856
1085
|
txEffect.data.transactionFee.toBigInt(),
|
|
857
1086
|
txEffect.l2BlockHash,
|
|
858
1087
|
blockNumber,
|
|
1088
|
+
epochNumber,
|
|
859
1089
|
);
|
|
860
1090
|
}
|
|
861
1091
|
|
|
@@ -892,7 +1122,7 @@ export class BlockStore {
|
|
|
892
1122
|
if (!checkpoint) {
|
|
893
1123
|
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
894
1124
|
}
|
|
895
|
-
return BlockNumber(checkpoint.startBlock + checkpoint.
|
|
1125
|
+
return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
|
|
896
1126
|
}
|
|
897
1127
|
|
|
898
1128
|
async getLatestL2BlockNumber(): Promise<BlockNumber> {
|
|
@@ -927,6 +1157,20 @@ export class BlockStore {
|
|
|
927
1157
|
return result;
|
|
928
1158
|
}
|
|
929
1159
|
|
|
1160
|
+
async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
1161
|
+
const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
|
|
1162
|
+
this.getLatestCheckpointNumber(),
|
|
1163
|
+
this.#lastFinalizedCheckpoint.getAsync(),
|
|
1164
|
+
]);
|
|
1165
|
+
return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
|
|
1166
|
+
? latestCheckpointNumber
|
|
1167
|
+
: CheckpointNumber(finalizedCheckpointNumber ?? 0);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
|
|
1171
|
+
return this.#lastFinalizedCheckpoint.set(checkpointNumber);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
930
1174
|
#computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
|
|
931
1175
|
if (limit < 1) {
|
|
932
1176
|
throw new Error(`Invalid limit: ${limit}`);
|