@aztec/archiver 0.86.0 → 0.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/archiver/archiver.d.ts +62 -5
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +362 -91
- package/dest/archiver/archiver_store.d.ts +33 -17
- package/dest/archiver/archiver_store.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.js +364 -62
- package/dest/archiver/data_retrieval.d.ts +23 -14
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +86 -38
- package/dest/archiver/errors.d.ts +8 -0
- package/dest/archiver/errors.d.ts.map +1 -1
- package/dest/archiver/errors.js +12 -0
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.d.ts +4 -1
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +43 -6
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +3 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +17 -3
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +24 -14
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +37 -11
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +1 -1
- package/dest/archiver/kv_archiver_store/message_store.d.ts +21 -15
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +150 -48
- package/dest/archiver/structs/inbox_message.d.ts +14 -0
- package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
- package/dest/archiver/structs/inbox_message.js +38 -0
- package/dest/rpc/index.d.ts +1 -1
- package/dest/test/mock_l2_block_source.d.ts +2 -0
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +8 -1
- package/dest/test/mock_structs.d.ts +9 -0
- package/dest/test/mock_structs.d.ts.map +1 -0
- package/dest/test/mock_structs.js +37 -0
- package/package.json +15 -15
- package/src/archiver/archiver.ts +431 -108
- package/src/archiver/archiver_store.ts +37 -18
- package/src/archiver/archiver_store_test_suite.ts +307 -52
- package/src/archiver/data_retrieval.ts +130 -53
- package/src/archiver/errors.ts +21 -0
- package/src/archiver/instrumentation.ts +4 -1
- package/src/archiver/kv_archiver_store/block_store.ts +46 -8
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +20 -7
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +61 -17
- package/src/archiver/kv_archiver_store/log_store.ts +6 -2
- package/src/archiver/kv_archiver_store/message_store.ts +209 -53
- package/src/archiver/structs/inbox_message.ts +40 -0
- package/src/test/mock_l2_block_source.ts +9 -1
- package/src/test/mock_structs.ts +49 -0
- 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/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { Blob, BlobDeserializationError } from '@aztec/blob-lib';
|
|
2
2
|
import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
|
|
3
|
-
import type { EpochProofPublicInputArgs, ViemPublicClient } from '@aztec/ethereum';
|
|
3
|
+
import type { EpochProofPublicInputArgs, ViemClient, ViemPublicClient } from '@aztec/ethereum';
|
|
4
4
|
import { asyncPool } from '@aztec/foundation/async-pool';
|
|
5
|
+
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
5
6
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
7
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
7
8
|
import { Fr } from '@aztec/foundation/fields';
|
|
8
9
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
9
|
-
import { numToUInt32BE } from '@aztec/foundation/serialize';
|
|
10
10
|
import { ForwarderAbi, type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
11
11
|
import { Body, L2Block } from '@aztec/stdlib/block';
|
|
12
|
-
import { InboxLeaf } from '@aztec/stdlib/messaging';
|
|
13
12
|
import { Proof } from '@aztec/stdlib/proofs';
|
|
14
13
|
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
15
|
-
import { BlockHeader } from '@aztec/stdlib/tx';
|
|
14
|
+
import { BlockHeader, GlobalVariables, ProposedBlockHeader, StateReference } from '@aztec/stdlib/tx';
|
|
16
15
|
|
|
17
16
|
import {
|
|
18
17
|
type GetContractEventsReturnType,
|
|
@@ -25,8 +24,68 @@ import {
|
|
|
25
24
|
|
|
26
25
|
import { NoBlobBodiesFoundError } from './errors.js';
|
|
27
26
|
import type { DataRetrieval } from './structs/data_retrieval.js';
|
|
27
|
+
import type { InboxMessage } from './structs/inbox_message.js';
|
|
28
28
|
import type { L1PublishedData, PublishedL2Block } from './structs/published.js';
|
|
29
29
|
|
|
30
|
+
export type RetrievedL2Block = {
|
|
31
|
+
l2BlockNumber: bigint;
|
|
32
|
+
archiveRoot: Fr;
|
|
33
|
+
stateReference: StateReference;
|
|
34
|
+
header: ProposedBlockHeader;
|
|
35
|
+
body: Body;
|
|
36
|
+
l1: L1PublishedData;
|
|
37
|
+
chainId: Fr;
|
|
38
|
+
version: Fr;
|
|
39
|
+
signatures: Signature[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function retrievedBlockToPublishedL2Block(retrievedBlock: RetrievedL2Block): PublishedL2Block {
|
|
43
|
+
const {
|
|
44
|
+
l2BlockNumber,
|
|
45
|
+
archiveRoot,
|
|
46
|
+
stateReference,
|
|
47
|
+
header: proposedHeader,
|
|
48
|
+
body,
|
|
49
|
+
l1,
|
|
50
|
+
chainId,
|
|
51
|
+
version,
|
|
52
|
+
signatures,
|
|
53
|
+
} = retrievedBlock;
|
|
54
|
+
|
|
55
|
+
const archive = new AppendOnlyTreeSnapshot(
|
|
56
|
+
archiveRoot,
|
|
57
|
+
Number(l2BlockNumber + 1n), // nextAvailableLeafIndex
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const globalVariables = GlobalVariables.from({
|
|
61
|
+
chainId,
|
|
62
|
+
version,
|
|
63
|
+
blockNumber: new Fr(l2BlockNumber),
|
|
64
|
+
slotNumber: proposedHeader.slotNumber,
|
|
65
|
+
timestamp: new Fr(proposedHeader.timestamp),
|
|
66
|
+
coinbase: proposedHeader.coinbase,
|
|
67
|
+
feeRecipient: proposedHeader.feeRecipient,
|
|
68
|
+
gasFees: proposedHeader.gasFees,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const header = BlockHeader.from({
|
|
72
|
+
lastArchive: new AppendOnlyTreeSnapshot(proposedHeader.lastArchiveRoot, Number(l2BlockNumber)),
|
|
73
|
+
contentCommitment: proposedHeader.contentCommitment,
|
|
74
|
+
state: stateReference,
|
|
75
|
+
globalVariables,
|
|
76
|
+
totalFees: body.txEffects.reduce((accum, txEffect) => accum.add(txEffect.transactionFee), Fr.ZERO),
|
|
77
|
+
totalManaUsed: proposedHeader.totalManaUsed,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const block = new L2Block(archive, header, body);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
block,
|
|
84
|
+
l1,
|
|
85
|
+
signatures,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
30
89
|
/**
|
|
31
90
|
* Fetches new L2 blocks.
|
|
32
91
|
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
@@ -43,8 +102,11 @@ export async function retrieveBlocksFromRollup(
|
|
|
43
102
|
searchStartBlock: bigint,
|
|
44
103
|
searchEndBlock: bigint,
|
|
45
104
|
logger: Logger = createLogger('archiver'),
|
|
46
|
-
): Promise<
|
|
47
|
-
const retrievedBlocks:
|
|
105
|
+
): Promise<RetrievedL2Block[]> {
|
|
106
|
+
const retrievedBlocks: RetrievedL2Block[] = [];
|
|
107
|
+
|
|
108
|
+
let rollupConstants: { chainId: Fr; version: Fr } | undefined;
|
|
109
|
+
|
|
48
110
|
do {
|
|
49
111
|
if (searchStartBlock > searchEndBlock) {
|
|
50
112
|
break;
|
|
@@ -68,11 +130,17 @@ export async function retrieveBlocksFromRollup(
|
|
|
68
130
|
`Got ${l2BlockProposedLogs.length} L2 block processed logs for L2 blocks ${l2BlockProposedLogs[0].args.blockNumber}-${lastLog.args.blockNumber} between L1 blocks ${searchStartBlock}-${searchEndBlock}`,
|
|
69
131
|
);
|
|
70
132
|
|
|
133
|
+
if (rollupConstants === undefined) {
|
|
134
|
+
const [chainId, version] = await Promise.all([publicClient.getChainId(), rollup.read.getVersion()]);
|
|
135
|
+
rollupConstants = { chainId: new Fr(chainId), version: new Fr(version) };
|
|
136
|
+
}
|
|
137
|
+
|
|
71
138
|
const newBlocks = await processL2BlockProposedLogs(
|
|
72
139
|
rollup,
|
|
73
140
|
publicClient,
|
|
74
141
|
blobSinkClient,
|
|
75
142
|
l2BlockProposedLogs,
|
|
143
|
+
rollupConstants,
|
|
76
144
|
logger,
|
|
77
145
|
);
|
|
78
146
|
retrievedBlocks.push(...newBlocks);
|
|
@@ -90,14 +158,15 @@ export async function retrieveBlocksFromRollup(
|
|
|
90
158
|
* @param logs - L2BlockProposed logs.
|
|
91
159
|
* @returns - An array blocks.
|
|
92
160
|
*/
|
|
93
|
-
|
|
161
|
+
async function processL2BlockProposedLogs(
|
|
94
162
|
rollup: GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
|
|
95
163
|
publicClient: ViemPublicClient,
|
|
96
164
|
blobSinkClient: BlobSinkClientInterface,
|
|
97
165
|
logs: GetContractEventsReturnType<typeof RollupAbi, 'L2BlockProposed'>,
|
|
166
|
+
{ chainId, version }: { chainId: Fr; version: Fr },
|
|
98
167
|
logger: Logger,
|
|
99
|
-
): Promise<
|
|
100
|
-
const retrievedBlocks:
|
|
168
|
+
): Promise<RetrievedL2Block[]> {
|
|
169
|
+
const retrievedBlocks: RetrievedL2Block[] = [];
|
|
101
170
|
await asyncPool(10, logs, async log => {
|
|
102
171
|
const l2BlockNumber = log.args.blockNumber!;
|
|
103
172
|
const archive = log.args.archive!;
|
|
@@ -122,7 +191,7 @@ export async function processL2BlockProposedLogs(
|
|
|
122
191
|
timestamp: await getL1BlockTime(publicClient, log.blockNumber),
|
|
123
192
|
};
|
|
124
193
|
|
|
125
|
-
retrievedBlocks.push({ ...block, l1 });
|
|
194
|
+
retrievedBlocks.push({ ...block, l1, chainId, version });
|
|
126
195
|
logger.trace(`Retrieved L2 block ${l2BlockNumber} from L1 tx ${log.transactionHash}`, {
|
|
127
196
|
l1BlockNumber: log.blockNumber,
|
|
128
197
|
l2BlockNumber,
|
|
@@ -187,7 +256,7 @@ function extractRollupProposeCalldata(forwarderData: Hex, rollupAddress: Hex): H
|
|
|
187
256
|
if (rollupFunctionName === 'propose') {
|
|
188
257
|
return callData;
|
|
189
258
|
}
|
|
190
|
-
} catch
|
|
259
|
+
} catch {
|
|
191
260
|
// Skip invalid function data
|
|
192
261
|
continue;
|
|
193
262
|
}
|
|
@@ -202,7 +271,7 @@ function extractRollupProposeCalldata(forwarderData: Hex, rollupAddress: Hex): H
|
|
|
202
271
|
* TODO: Add retries and error management.
|
|
203
272
|
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
204
273
|
* @param txHash - Hash of the tx that published it.
|
|
205
|
-
* @param
|
|
274
|
+
* @param l2BlockNumber - L2 block number.
|
|
206
275
|
* @returns L2 block from the calldata, deserialized
|
|
207
276
|
*/
|
|
208
277
|
async function getBlockFromRollupTx(
|
|
@@ -210,10 +279,10 @@ async function getBlockFromRollupTx(
|
|
|
210
279
|
blobSinkClient: BlobSinkClientInterface,
|
|
211
280
|
txHash: `0x${string}`,
|
|
212
281
|
blobHashes: Buffer[], // TODO(md): buffer32?
|
|
213
|
-
|
|
282
|
+
l2BlockNumber: bigint,
|
|
214
283
|
rollupAddress: Hex,
|
|
215
284
|
logger: Logger,
|
|
216
|
-
): Promise<Omit<
|
|
285
|
+
): Promise<Omit<RetrievedL2Block, 'l1' | 'chainId' | 'version'>> {
|
|
217
286
|
const { input: forwarderData, blockHash } = await publicClient.getTransaction({ hash: txHash });
|
|
218
287
|
|
|
219
288
|
const rollupData = extractRollupProposeCalldata(forwarderData, rollupAddress);
|
|
@@ -226,10 +295,11 @@ async function getBlockFromRollupTx(
|
|
|
226
295
|
throw new Error(`Unexpected rollup method called ${rollupFunctionName}`);
|
|
227
296
|
}
|
|
228
297
|
|
|
229
|
-
const [decodedArgs, signatures] = rollupArgs! as readonly [
|
|
298
|
+
const [decodedArgs, signatures, _blobInput] = rollupArgs! as readonly [
|
|
230
299
|
{
|
|
231
300
|
header: Hex;
|
|
232
301
|
archive: Hex;
|
|
302
|
+
stateReference: Hex;
|
|
233
303
|
blockHash: Hex;
|
|
234
304
|
oracleInput: {
|
|
235
305
|
feeAssetPriceModifier: bigint;
|
|
@@ -240,10 +310,10 @@ async function getBlockFromRollupTx(
|
|
|
240
310
|
Hex,
|
|
241
311
|
];
|
|
242
312
|
|
|
243
|
-
const header =
|
|
313
|
+
const header = ProposedBlockHeader.fromBuffer(Buffer.from(hexToBytes(decodedArgs.header)));
|
|
244
314
|
const blobBodies = await blobSinkClient.getBlobSidecar(blockHash, blobHashes);
|
|
245
315
|
if (blobBodies.length === 0) {
|
|
246
|
-
throw new NoBlobBodiesFoundError(Number(
|
|
316
|
+
throw new NoBlobBodiesFoundError(Number(l2BlockNumber));
|
|
247
317
|
}
|
|
248
318
|
|
|
249
319
|
let blockFields: Fr[];
|
|
@@ -259,23 +329,30 @@ async function getBlockFromRollupTx(
|
|
|
259
329
|
}
|
|
260
330
|
|
|
261
331
|
// The blob source gives us blockFields, and we must construct the body from them:
|
|
262
|
-
const
|
|
332
|
+
const body = Body.fromBlobFields(blockFields);
|
|
263
333
|
|
|
264
|
-
const
|
|
334
|
+
const archiveRoot = new Fr(Buffer.from(hexToBytes(decodedArgs.archive)));
|
|
265
335
|
|
|
266
|
-
|
|
267
|
-
throw new Error(`Block number mismatch: expected ${l2BlockNum} but got ${blockNumberFromHeader}`);
|
|
268
|
-
}
|
|
336
|
+
const stateReference = StateReference.fromBuffer(Buffer.from(hexToBytes(decodedArgs.stateReference)));
|
|
269
337
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
338
|
+
return {
|
|
339
|
+
l2BlockNumber,
|
|
340
|
+
archiveRoot,
|
|
341
|
+
stateReference,
|
|
342
|
+
header,
|
|
343
|
+
body,
|
|
344
|
+
signatures: signatures.map(Signature.fromViemSignature),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
276
347
|
|
|
277
|
-
|
|
278
|
-
|
|
348
|
+
/** Given an L1 to L2 message, retrieves its corresponding event from the Inbox. */
|
|
349
|
+
export async function retrieveL1ToL2Message(
|
|
350
|
+
inbox: GetContractReturnType<typeof InboxAbi, ViemClient>,
|
|
351
|
+
leaf: Fr,
|
|
352
|
+
fromBlock: bigint,
|
|
353
|
+
): Promise<InboxMessage | undefined> {
|
|
354
|
+
const logs = await inbox.getEvents.MessageSent({ hash: leaf.toString() }, { fromBlock });
|
|
355
|
+
return mapLogsInboxMessage(logs)[0];
|
|
279
356
|
}
|
|
280
357
|
|
|
281
358
|
/**
|
|
@@ -288,39 +365,39 @@ async function getBlockFromRollupTx(
|
|
|
288
365
|
* @returns An array of InboxLeaf and next eth block to search from.
|
|
289
366
|
*/
|
|
290
367
|
export async function retrieveL1ToL2Messages(
|
|
291
|
-
inbox: GetContractReturnType<typeof InboxAbi,
|
|
368
|
+
inbox: GetContractReturnType<typeof InboxAbi, ViemClient>,
|
|
292
369
|
searchStartBlock: bigint,
|
|
293
370
|
searchEndBlock: bigint,
|
|
294
|
-
): Promise<
|
|
295
|
-
const retrievedL1ToL2Messages:
|
|
296
|
-
|
|
297
|
-
if (searchStartBlock > searchEndBlock) {
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
|
|
371
|
+
): Promise<InboxMessage[]> {
|
|
372
|
+
const retrievedL1ToL2Messages: InboxMessage[] = [];
|
|
373
|
+
while (searchStartBlock <= searchEndBlock) {
|
|
301
374
|
const messageSentLogs = (
|
|
302
|
-
await inbox.getEvents.MessageSent(
|
|
303
|
-
{},
|
|
304
|
-
{
|
|
305
|
-
fromBlock: searchStartBlock,
|
|
306
|
-
toBlock: searchEndBlock,
|
|
307
|
-
},
|
|
308
|
-
)
|
|
375
|
+
await inbox.getEvents.MessageSent({}, { fromBlock: searchStartBlock, toBlock: searchEndBlock })
|
|
309
376
|
).filter(log => log.blockNumber! >= searchStartBlock && log.blockNumber! <= searchEndBlock);
|
|
310
377
|
|
|
311
378
|
if (messageSentLogs.length === 0) {
|
|
312
379
|
break;
|
|
313
380
|
}
|
|
314
381
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
382
|
+
retrievedL1ToL2Messages.push(...mapLogsInboxMessage(messageSentLogs));
|
|
383
|
+
searchStartBlock = messageSentLogs.at(-1)!.blockNumber + 1n;
|
|
384
|
+
}
|
|
319
385
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
386
|
+
return retrievedL1ToL2Messages;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function mapLogsInboxMessage(logs: GetContractEventsReturnType<typeof InboxAbi, 'MessageSent'>): InboxMessage[] {
|
|
390
|
+
return logs.map(log => {
|
|
391
|
+
const { index, hash, l2BlockNumber, rollingHash } = log.args;
|
|
392
|
+
return {
|
|
393
|
+
index: index!,
|
|
394
|
+
leaf: Fr.fromHexString(hash!),
|
|
395
|
+
l1BlockNumber: log.blockNumber,
|
|
396
|
+
l1BlockHash: Buffer32.fromString(log.blockHash),
|
|
397
|
+
l2BlockNumber: l2BlockNumber!,
|
|
398
|
+
rollingHash: Buffer16.fromString(rollingHash!),
|
|
399
|
+
};
|
|
400
|
+
});
|
|
324
401
|
}
|
|
325
402
|
|
|
326
403
|
/** Retrieves L2ProofVerified events from the rollup contract. */
|
package/src/archiver/errors.ts
CHANGED
|
@@ -3,3 +3,24 @@ export class NoBlobBodiesFoundError extends Error {
|
|
|
3
3
|
super(`No blob bodies found for block ${l2BlockNum}`);
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
|
+
|
|
7
|
+
export class InitialBlockNumberNotSequentialError extends Error {
|
|
8
|
+
constructor(
|
|
9
|
+
public readonly newBlockNumber: number,
|
|
10
|
+
public readonly previousBlockNumber: number | undefined,
|
|
11
|
+
) {
|
|
12
|
+
super(
|
|
13
|
+
`Cannot insert new block ${newBlockNumber} given previous block number in store is ${
|
|
14
|
+
previousBlockNumber ?? 'undefined'
|
|
15
|
+
}`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class BlockNumberNotSequentialError extends Error {
|
|
21
|
+
constructor(newBlockNumber: number, previous: number | undefined) {
|
|
22
|
+
super(
|
|
23
|
+
`Cannot insert new block ${newBlockNumber} given previous block number in batch is ${previous ?? 'undefined'}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -28,7 +28,10 @@ export class ArchiverInstrumentation {
|
|
|
28
28
|
|
|
29
29
|
private log = createLogger('archiver:instrumentation');
|
|
30
30
|
|
|
31
|
-
private constructor(
|
|
31
|
+
private constructor(
|
|
32
|
+
private telemetry: TelemetryClient,
|
|
33
|
+
lmdbStats?: LmdbStatsCallback,
|
|
34
|
+
) {
|
|
32
35
|
this.tracer = telemetry.getTracer('Archiver');
|
|
33
36
|
const meter = telemetry.getMeter('Archiver');
|
|
34
37
|
this.blockHeight = meter.createGauge(Metrics.ARCHIVER_BLOCK_HEIGHT, {
|
|
@@ -8,6 +8,7 @@ import { Body, L2Block, L2BlockHash } from '@aztec/stdlib/block';
|
|
|
8
8
|
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
9
9
|
import { BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
|
|
10
10
|
|
|
11
|
+
import { BlockNumberNotSequentialError, InitialBlockNumberNotSequentialError } from '../errors.js';
|
|
11
12
|
import type { L1PublishedData, PublishedL2Block } from '../structs/published.js';
|
|
12
13
|
|
|
13
14
|
export { TxReceipt, type TxEffect, type TxHash } from '@aztec/stdlib/tx';
|
|
@@ -37,9 +38,6 @@ export class BlockStore {
|
|
|
37
38
|
/** Stores l2 block number of the last proven block */
|
|
38
39
|
#lastProvenL2Block: AztecAsyncSingleton<number>;
|
|
39
40
|
|
|
40
|
-
/** Stores l2 epoch number of the last proven epoch */
|
|
41
|
-
#lastProvenL2Epoch: AztecAsyncSingleton<number>;
|
|
42
|
-
|
|
43
41
|
/** Index mapping transaction hash (as a string) to its location in a block */
|
|
44
42
|
#txIndex: AztecAsyncMap<string, BlockIndexValue>;
|
|
45
43
|
|
|
@@ -55,7 +53,6 @@ export class BlockStore {
|
|
|
55
53
|
this.#contractIndex = db.openMap('archiver_contract_index');
|
|
56
54
|
this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
|
|
57
55
|
this.#lastProvenL2Block = db.openSingleton('archiver_last_proven_l2_block');
|
|
58
|
-
this.#lastProvenL2Epoch = db.openSingleton('archiver_last_proven_l2_epoch');
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
/**
|
|
@@ -63,13 +60,32 @@ export class BlockStore {
|
|
|
63
60
|
* @param blocks - The L2 blocks to be added to the store.
|
|
64
61
|
* @returns True if the operation is successful.
|
|
65
62
|
*/
|
|
66
|
-
async addBlocks(blocks: PublishedL2Block[]): Promise<boolean> {
|
|
63
|
+
async addBlocks(blocks: PublishedL2Block[], opts: { force?: boolean } = {}): Promise<boolean> {
|
|
67
64
|
if (blocks.length === 0) {
|
|
68
65
|
return true;
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
return await this.db.transactionAsync(async () => {
|
|
69
|
+
// Check that the block immediately before the first block to be added is present in the store.
|
|
70
|
+
const firstBlockNumber = blocks[0].block.number;
|
|
71
|
+
const [previousBlockNumber] = await toArray(
|
|
72
|
+
this.#blocks.keysAsync({ reverse: true, limit: 1, end: firstBlockNumber - 1 }),
|
|
73
|
+
);
|
|
74
|
+
const hasPreviousBlock =
|
|
75
|
+
firstBlockNumber === INITIAL_L2_BLOCK_NUM ||
|
|
76
|
+
(previousBlockNumber !== undefined && previousBlockNumber === firstBlockNumber - 1);
|
|
77
|
+
if (!opts.force && !hasPreviousBlock) {
|
|
78
|
+
throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Iterate over blocks array and insert them, checking that the block numbers are sequential.
|
|
82
|
+
let previousBlock: PublishedL2Block | undefined = undefined;
|
|
72
83
|
for (const block of blocks) {
|
|
84
|
+
if (!opts.force && previousBlock && previousBlock.block.number + 1 !== block.block.number) {
|
|
85
|
+
throw new BlockNumberNotSequentialError(block.block.number, previousBlock.block.number);
|
|
86
|
+
}
|
|
87
|
+
previousBlock = block;
|
|
88
|
+
|
|
73
89
|
await this.#blocks.set(block.block.number, {
|
|
74
90
|
header: block.block.header.toBuffer(),
|
|
75
91
|
archive: block.block.archive.toBuffer(),
|
|
@@ -104,6 +120,11 @@ export class BlockStore {
|
|
|
104
120
|
throw new Error(`Can only unwind blocks from the tip (requested ${from} but current tip is ${last})`);
|
|
105
121
|
}
|
|
106
122
|
|
|
123
|
+
const proven = await this.getProvenL2BlockNumber();
|
|
124
|
+
if (from - blocksToUnwind < proven) {
|
|
125
|
+
await this.setProvenL2BlockNumber(from - blocksToUnwind);
|
|
126
|
+
}
|
|
127
|
+
|
|
107
128
|
for (let i = 0; i < blocksToUnwind; i++) {
|
|
108
129
|
const blockNumber = from - i;
|
|
109
130
|
const block = await this.getBlock(blockNumber);
|
|
@@ -130,7 +151,7 @@ export class BlockStore {
|
|
|
130
151
|
* @returns The requested L2 blocks
|
|
131
152
|
*/
|
|
132
153
|
async *getBlocks(start: number, limit: number): AsyncIterableIterator<PublishedL2Block> {
|
|
133
|
-
for await (const [blockNumber, blockStorage] of this
|
|
154
|
+
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
134
155
|
const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
|
|
135
156
|
if (block) {
|
|
136
157
|
yield block;
|
|
@@ -158,7 +179,7 @@ export class BlockStore {
|
|
|
158
179
|
* @returns The requested L2 block headers
|
|
159
180
|
*/
|
|
160
181
|
async *getBlockHeaders(start: number, limit: number): AsyncIterableIterator<BlockHeader> {
|
|
161
|
-
for await (const [blockNumber, blockStorage] of this
|
|
182
|
+
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
162
183
|
const header = BlockHeader.fromBuffer(blockStorage.header);
|
|
163
184
|
if (header.getBlockNumber() !== blockNumber) {
|
|
164
185
|
throw new Error(
|
|
@@ -169,6 +190,19 @@ export class BlockStore {
|
|
|
169
190
|
}
|
|
170
191
|
}
|
|
171
192
|
|
|
193
|
+
private async *getBlockStorages(start: number, limit: number) {
|
|
194
|
+
let expectedBlockNumber = start;
|
|
195
|
+
for await (const [blockNumber, blockStorage] of this.#blocks.entriesAsync(this.#computeBlockRange(start, limit))) {
|
|
196
|
+
if (blockNumber !== expectedBlockNumber) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Block number mismatch when iterating blocks from archive (expected ${expectedBlockNumber} but got ${blockNumber})`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
expectedBlockNumber++;
|
|
202
|
+
yield [blockNumber, blockStorage] as const;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
172
206
|
private async getBlockFromBlockStorage(blockNumber: number, blockStorage: BlockStorage) {
|
|
173
207
|
const header = BlockHeader.fromBuffer(blockStorage.header);
|
|
174
208
|
const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
|
|
@@ -283,7 +317,11 @@ export class BlockStore {
|
|
|
283
317
|
}
|
|
284
318
|
|
|
285
319
|
async getProvenL2BlockNumber(): Promise<number> {
|
|
286
|
-
|
|
320
|
+
const [latestBlockNumber, provenBlockNumber] = await Promise.all([
|
|
321
|
+
this.getSynchedL2BlockNumber(),
|
|
322
|
+
this.#lastProvenL2Block.getAsync(),
|
|
323
|
+
]);
|
|
324
|
+
return (provenBlockNumber ?? 0) > latestBlockNumber ? latestBlockNumber : (provenBlockNumber ?? 0);
|
|
287
325
|
}
|
|
288
326
|
|
|
289
327
|
setProvenL2BlockNumber(blockNumber: number) {
|
|
@@ -15,22 +15,30 @@ type ContractInstanceUpdateKey = [string, number] | [string, number, number];
|
|
|
15
15
|
*/
|
|
16
16
|
export class ContractInstanceStore {
|
|
17
17
|
#contractInstances: AztecAsyncMap<string, Buffer>;
|
|
18
|
+
#contractInstanceDeployedAt: AztecAsyncMap<string, number>;
|
|
18
19
|
#contractInstanceUpdates: AztecAsyncMap<ContractInstanceUpdateKey, Buffer>;
|
|
19
20
|
|
|
20
|
-
constructor(db: AztecAsyncKVStore) {
|
|
21
|
+
constructor(private db: AztecAsyncKVStore) {
|
|
21
22
|
this.#contractInstances = db.openMap('archiver_contract_instances');
|
|
23
|
+
this.#contractInstanceDeployedAt = db.openMap('archiver_contract_instances_deployment_block_number');
|
|
22
24
|
this.#contractInstanceUpdates = db.openMap('archiver_contract_instance_updates');
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
addContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
|
|
26
|
-
return this
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
addContractInstance(contractInstance: ContractInstanceWithAddress, blockNumber: number): Promise<void> {
|
|
28
|
+
return this.db.transactionAsync(async () => {
|
|
29
|
+
await this.#contractInstances.set(
|
|
30
|
+
contractInstance.address.toString(),
|
|
31
|
+
new SerializableContractInstance(contractInstance).toBuffer(),
|
|
32
|
+
);
|
|
33
|
+
await this.#contractInstanceDeployedAt.set(contractInstance.address.toString(), blockNumber);
|
|
34
|
+
});
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
deleteContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
|
|
33
|
-
return this
|
|
38
|
+
return this.db.transactionAsync(async () => {
|
|
39
|
+
await this.#contractInstances.delete(contractInstance.address.toString());
|
|
40
|
+
await this.#contractInstanceDeployedAt.delete(contractInstance.address.toString());
|
|
41
|
+
});
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
getUpdateKey(contractAddress: AztecAddress, blockNumber: number, logIndex?: number): ContractInstanceUpdateKey {
|
|
@@ -71,6 +79,7 @@ export class ContractInstanceStore {
|
|
|
71
79
|
const queryResult = await this.#contractInstanceUpdates
|
|
72
80
|
.valuesAsync({
|
|
73
81
|
reverse: true,
|
|
82
|
+
start: this.getUpdateKey(address, 0), // Make sure we only look at updates for this contract
|
|
74
83
|
end: this.getUpdateKey(address, blockNumber + 1), // No update can match this key since it doesn't have a log index. We want the highest key <= blockNumber
|
|
75
84
|
limit: 1,
|
|
76
85
|
})
|
|
@@ -104,4 +113,8 @@ export class ContractInstanceStore {
|
|
|
104
113
|
);
|
|
105
114
|
return instance;
|
|
106
115
|
}
|
|
116
|
+
|
|
117
|
+
getContractInstanceDeploymentBlockNumber(address: AztecAddress): Promise<number | undefined> {
|
|
118
|
+
return this.#contractInstanceDeployedAt.getAsync(address.toString());
|
|
119
|
+
}
|
|
107
120
|
}
|