@aztec/archiver 0.0.1-commit.88e6f9396 → 0.0.1-commit.8c0b8ff
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 +2 -1
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +22 -10
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +1 -2
- package/dest/errors.d.ts +1 -15
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +0 -18
- package/dest/factory.d.ts +3 -3
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +7 -4
- package/dest/l1/data_retrieval.d.ts +3 -6
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +6 -12
- package/dest/modules/data_source_base.d.ts +3 -3
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +4 -4
- package/dest/modules/data_store_updater.d.ts +1 -1
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +43 -10
- package/dest/modules/instrumentation.d.ts +1 -12
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +0 -10
- package/dest/modules/l1_synchronizer.d.ts +1 -1
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +13 -18
- package/dest/store/block_store.d.ts +2 -1
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +50 -5
- 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 +6 -11
- package/dest/store/kv_archiver_store.d.ts.map +1 -1
- package/dest/store/kv_archiver_store.js +7 -13
- package/dest/store/log_store.d.ts +3 -6
- package/dest/store/log_store.d.ts.map +1 -1
- package/dest/store/log_store.js +6 -45
- package/dest/store/message_store.d.ts +1 -5
- package/dest/store/message_store.d.ts.map +1 -1
- package/dest/store/message_store.js +0 -13
- package/dest/test/fake_l1_state.d.ts +8 -1
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +16 -11
- package/package.json +13 -13
- package/src/archiver.ts +25 -10
- package/src/config.ts +1 -8
- package/src/errors.ts +0 -30
- package/src/factory.ts +13 -6
- package/src/l1/data_retrieval.ts +9 -17
- package/src/modules/data_source_base.ts +3 -8
- package/src/modules/data_store_updater.ts +51 -12
- package/src/modules/instrumentation.ts +0 -20
- package/src/modules/l1_synchronizer.ts +25 -29
- package/src/store/block_store.ts +62 -5
- 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 +7 -19
- package/src/store/log_store.ts +15 -60
- package/src/store/message_store.ts +0 -19
- package/src/test/fake_l1_state.ts +20 -15
package/src/l1/data_retrieval.ts
CHANGED
|
@@ -265,9 +265,6 @@ async function processCheckpointProposedLogs(
|
|
|
265
265
|
checkpointNumber,
|
|
266
266
|
expectedHashes,
|
|
267
267
|
);
|
|
268
|
-
const { timestamp, parentBeaconBlockRoot } = await getL1Block(publicClient, log.l1BlockNumber);
|
|
269
|
-
const l1 = new L1PublishedData(log.l1BlockNumber, timestamp, log.l1BlockHash.toString());
|
|
270
|
-
|
|
271
268
|
const checkpointBlobData = await getCheckpointBlobDataFromBlobs(
|
|
272
269
|
blobClient,
|
|
273
270
|
checkpoint.blockHash,
|
|
@@ -275,8 +272,12 @@ async function processCheckpointProposedLogs(
|
|
|
275
272
|
checkpointNumber,
|
|
276
273
|
logger,
|
|
277
274
|
isHistoricalSync,
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const l1 = new L1PublishedData(
|
|
278
|
+
log.l1BlockNumber,
|
|
279
|
+
await getL1BlockTime(publicClient, log.l1BlockNumber),
|
|
280
|
+
log.l1BlockHash.toString(),
|
|
280
281
|
);
|
|
281
282
|
|
|
282
283
|
retrievedCheckpoints.push({ ...checkpoint, checkpointBlobData, l1, chainId, version });
|
|
@@ -297,12 +298,9 @@ async function processCheckpointProposedLogs(
|
|
|
297
298
|
return retrievedCheckpoints;
|
|
298
299
|
}
|
|
299
300
|
|
|
300
|
-
export async function
|
|
301
|
-
publicClient: ViemPublicClient,
|
|
302
|
-
blockNumber: bigint,
|
|
303
|
-
): Promise<{ timestamp: bigint; parentBeaconBlockRoot: string | undefined }> {
|
|
301
|
+
export async function getL1BlockTime(publicClient: ViemPublicClient, blockNumber: bigint): Promise<bigint> {
|
|
304
302
|
const block = await publicClient.getBlock({ blockNumber, includeTransactions: false });
|
|
305
|
-
return
|
|
303
|
+
return block.timestamp;
|
|
306
304
|
}
|
|
307
305
|
|
|
308
306
|
export async function getCheckpointBlobDataFromBlobs(
|
|
@@ -312,14 +310,8 @@ export async function getCheckpointBlobDataFromBlobs(
|
|
|
312
310
|
checkpointNumber: CheckpointNumber,
|
|
313
311
|
logger: Logger,
|
|
314
312
|
isHistoricalSync: boolean,
|
|
315
|
-
parentBeaconBlockRoot?: string,
|
|
316
|
-
l1BlockTimestamp?: bigint,
|
|
317
313
|
): Promise<CheckpointBlobData> {
|
|
318
|
-
const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, {
|
|
319
|
-
isHistoricalSync,
|
|
320
|
-
parentBeaconBlockRoot,
|
|
321
|
-
l1BlockTimestamp,
|
|
322
|
-
});
|
|
314
|
+
const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, { isHistoricalSync });
|
|
323
315
|
if (blobBodies.length === 0) {
|
|
324
316
|
throw new NoBlobBodiesFoundError(checkpointNumber);
|
|
325
317
|
}
|
|
@@ -165,21 +165,16 @@ export abstract class ArchiverDataSourceBase
|
|
|
165
165
|
return (await this.store.getPendingChainValidationStatus()) ?? { valid: true };
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
public getPrivateLogsByTags(
|
|
169
|
-
tags
|
|
170
|
-
page?: number,
|
|
171
|
-
upToBlockNumber?: BlockNumber,
|
|
172
|
-
): Promise<TxScopedL2Log[][]> {
|
|
173
|
-
return this.store.getPrivateLogsByTags(tags, page, upToBlockNumber);
|
|
168
|
+
public getPrivateLogsByTags(tags: SiloedTag[], page?: number): Promise<TxScopedL2Log[][]> {
|
|
169
|
+
return this.store.getPrivateLogsByTags(tags, page);
|
|
174
170
|
}
|
|
175
171
|
|
|
176
172
|
public getPublicLogsByTagsFromContract(
|
|
177
173
|
contractAddress: AztecAddress,
|
|
178
174
|
tags: Tag[],
|
|
179
175
|
page?: number,
|
|
180
|
-
upToBlockNumber?: BlockNumber,
|
|
181
176
|
): Promise<TxScopedL2Log[][]> {
|
|
182
|
-
return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page
|
|
177
|
+
return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page);
|
|
183
178
|
}
|
|
184
179
|
|
|
185
180
|
public getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { filterAsync } from '@aztec/foundation/collection';
|
|
2
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
5
|
import {
|
|
@@ -13,9 +14,11 @@ import {
|
|
|
13
14
|
import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
14
15
|
import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
15
16
|
import {
|
|
17
|
+
type ContractClassPublicWithCommitment,
|
|
16
18
|
type ExecutablePrivateFunctionWithMembershipProof,
|
|
17
19
|
type UtilityFunctionWithMembershipProof,
|
|
18
|
-
|
|
20
|
+
computeContractAddressFromInstance,
|
|
21
|
+
computeContractClassId,
|
|
19
22
|
isValidPrivateFunctionMembershipProof,
|
|
20
23
|
isValidUtilityFunctionMembershipProof,
|
|
21
24
|
} from '@aztec/stdlib/contract';
|
|
@@ -332,18 +335,37 @@ export class ArchiverDataStoreUpdater {
|
|
|
332
335
|
.filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
|
|
333
336
|
.map(log => ContractClassPublishedEvent.fromLog(log));
|
|
334
337
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
contractClasses.
|
|
338
|
-
|
|
339
|
-
// TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive
|
|
340
|
-
const commitments = await Promise.all(
|
|
341
|
-
contractClasses.map(c => computePublicBytecodeCommitment(c.packedBytecode)),
|
|
342
|
-
);
|
|
343
|
-
return await this.store.addContractClasses(contractClasses, commitments, blockNum);
|
|
344
|
-
} else if (operation == Operation.Delete) {
|
|
338
|
+
if (operation == Operation.Delete) {
|
|
339
|
+
const contractClasses = contractClassPublishedEvents.map(e => e.toContractClassPublic());
|
|
340
|
+
if (contractClasses.length > 0) {
|
|
341
|
+
contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
|
|
345
342
|
return await this.store.deleteContractClasses(contractClasses, blockNum);
|
|
346
343
|
}
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Compute bytecode commitments and validate class IDs in a single pass.
|
|
348
|
+
const contractClasses: ContractClassPublicWithCommitment[] = [];
|
|
349
|
+
for (const event of contractClassPublishedEvents) {
|
|
350
|
+
const contractClass = await event.toContractClassPublicWithBytecodeCommitment();
|
|
351
|
+
const computedClassId = await computeContractClassId({
|
|
352
|
+
artifactHash: contractClass.artifactHash,
|
|
353
|
+
privateFunctionsRoot: contractClass.privateFunctionsRoot,
|
|
354
|
+
publicBytecodeCommitment: contractClass.publicBytecodeCommitment,
|
|
355
|
+
});
|
|
356
|
+
if (!computedClassId.equals(contractClass.id)) {
|
|
357
|
+
this.log.warn(
|
|
358
|
+
`Skipping contract class with mismatched id at block ${blockNum}. Claimed ${contractClass.id}, computed ${computedClassId}`,
|
|
359
|
+
{ blockNum, contractClassId: event.contractClassId.toString() },
|
|
360
|
+
);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
contractClasses.push(contractClass);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (contractClasses.length > 0) {
|
|
367
|
+
contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
|
|
368
|
+
return await this.store.addContractClasses(contractClasses, blockNum);
|
|
347
369
|
}
|
|
348
370
|
return true;
|
|
349
371
|
}
|
|
@@ -356,10 +378,27 @@ export class ArchiverDataStoreUpdater {
|
|
|
356
378
|
blockNum: BlockNumber,
|
|
357
379
|
operation: Operation,
|
|
358
380
|
): Promise<boolean> {
|
|
359
|
-
const
|
|
381
|
+
const allInstances = allLogs
|
|
360
382
|
.filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
|
|
361
383
|
.map(log => ContractInstancePublishedEvent.fromLog(log))
|
|
362
384
|
.map(e => e.toContractInstance());
|
|
385
|
+
|
|
386
|
+
// Verify that each instance's address matches the one derived from its fields if we're adding
|
|
387
|
+
const contractInstances =
|
|
388
|
+
operation === Operation.Delete
|
|
389
|
+
? allInstances
|
|
390
|
+
: await filterAsync(allInstances, async instance => {
|
|
391
|
+
const computedAddress = await computeContractAddressFromInstance(instance);
|
|
392
|
+
if (!computedAddress.equals(instance.address)) {
|
|
393
|
+
this.log.warn(
|
|
394
|
+
`Found contract instance with mismatched address at block ${blockNum}. Claimed ${instance.address} but computed ${computedAddress}.`,
|
|
395
|
+
{ instanceAddress: instance.address.toString(), computedAddress: computedAddress.toString(), blockNum },
|
|
396
|
+
);
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
return true;
|
|
400
|
+
});
|
|
401
|
+
|
|
363
402
|
if (contractInstances.length > 0) {
|
|
364
403
|
contractInstances.forEach(c =>
|
|
365
404
|
this.log.verbose(`${Operation[operation]} contract instance at ${c.address.toString()}`),
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
3
2
|
import type { L2Block } from '@aztec/stdlib/block';
|
|
4
3
|
import type { CheckpointData } from '@aztec/stdlib/checkpoint';
|
|
5
|
-
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
6
|
-
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
7
4
|
import {
|
|
8
5
|
Attributes,
|
|
9
6
|
type Gauge,
|
|
@@ -41,8 +38,6 @@ export class ArchiverInstrumentation {
|
|
|
41
38
|
|
|
42
39
|
private blockProposalTxTargetCount: UpDownCounter;
|
|
43
40
|
|
|
44
|
-
private checkpointL1InclusionDelay: Histogram;
|
|
45
|
-
|
|
46
41
|
private log = createLogger('archiver:instrumentation');
|
|
47
42
|
|
|
48
43
|
private constructor(
|
|
@@ -90,8 +85,6 @@ export class ArchiverInstrumentation {
|
|
|
90
85
|
},
|
|
91
86
|
);
|
|
92
87
|
|
|
93
|
-
this.checkpointL1InclusionDelay = meter.createHistogram(Metrics.ARCHIVER_CHECKPOINT_L1_INCLUSION_DELAY);
|
|
94
|
-
|
|
95
88
|
this.dbMetrics = new LmdbMetrics(
|
|
96
89
|
meter,
|
|
97
90
|
{
|
|
@@ -168,17 +161,4 @@ export class ArchiverInstrumentation {
|
|
|
168
161
|
[Attributes.L1_BLOCK_PROPOSAL_USED_TRACE]: usedTrace,
|
|
169
162
|
});
|
|
170
163
|
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Records L1 inclusion timing for a checkpoint observed on L1 (seconds into the L2 slot).
|
|
174
|
-
*/
|
|
175
|
-
public processCheckpointL1Timing(data: {
|
|
176
|
-
slotNumber: SlotNumber;
|
|
177
|
-
l1Timestamp: bigint;
|
|
178
|
-
l1Constants: Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>;
|
|
179
|
-
}): void {
|
|
180
|
-
const slotStartTs = getTimestampForSlot(data.slotNumber, data.l1Constants);
|
|
181
|
-
const inclusionDelaySeconds = Number(data.l1Timestamp - slotStartTs);
|
|
182
|
-
this.checkpointL1InclusionDelay.record(inclusionDelaySeconds);
|
|
183
|
-
}
|
|
184
164
|
}
|
|
@@ -3,7 +3,6 @@ import { EpochCache } from '@aztec/epoch-cache';
|
|
|
3
3
|
import { InboxContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
4
4
|
import type { L1BlockId } from '@aztec/ethereum/l1-types';
|
|
5
5
|
import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
|
|
6
|
-
import { asyncPool } from '@aztec/foundation/async-pool';
|
|
7
6
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
8
7
|
import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
9
8
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
@@ -246,13 +245,22 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
246
245
|
const localFinalizedCheckpointNumber = await this.store.getFinalizedCheckpointNumber();
|
|
247
246
|
if (localFinalizedCheckpointNumber !== finalizedCheckpointNumber) {
|
|
248
247
|
await this.updater.setFinalizedCheckpointNumber(finalizedCheckpointNumber);
|
|
249
|
-
this.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
const finalizedL2BlockNumber = await this.store.getFinalizedL2BlockNumber();
|
|
249
|
+
this.log.info(
|
|
250
|
+
`Updated finalized chain to checkpoint ${finalizedCheckpointNumber} (L2 block ${finalizedL2BlockNumber})`,
|
|
251
|
+
{
|
|
252
|
+
finalizedCheckpointNumber,
|
|
253
|
+
previousFinalizedCheckpointNumber: localFinalizedCheckpointNumber,
|
|
254
|
+
finalizedL2BlockNumber,
|
|
255
|
+
finalizedL1BlockNumber,
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
} catch (err: any) {
|
|
260
|
+
// The rollup contract may not exist at the finalized L1 block right after deployment.
|
|
261
|
+
if (!err?.message?.includes('returned no data')) {
|
|
262
|
+
this.log.warn(`Failed to update finalized checkpoint: ${err}`);
|
|
253
263
|
}
|
|
254
|
-
} catch (err) {
|
|
255
|
-
this.log.warn(`Failed to update finalized checkpoint: ${err}`);
|
|
256
264
|
}
|
|
257
265
|
}
|
|
258
266
|
|
|
@@ -334,20 +342,17 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
334
342
|
|
|
335
343
|
const checkpointsToUnwind = localPendingCheckpointNumber - provenCheckpointNumber;
|
|
336
344
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
345
|
+
const checkpointPromises = Array.from({ length: checkpointsToUnwind })
|
|
346
|
+
.fill(0)
|
|
347
|
+
.map((_, i) => this.store.getCheckpointData(CheckpointNumber(i + pruneFrom)));
|
|
348
|
+
const checkpoints = await Promise.all(checkpointPromises);
|
|
349
|
+
|
|
350
|
+
const blockPromises = await Promise.all(
|
|
351
|
+
checkpoints
|
|
352
|
+
.filter(isDefined)
|
|
353
|
+
.map(cp => this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber))),
|
|
343
354
|
);
|
|
344
|
-
const newBlocks = (
|
|
345
|
-
await asyncPool(BATCH_SIZE, checkpoints, cp =>
|
|
346
|
-
this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber)),
|
|
347
|
-
)
|
|
348
|
-
)
|
|
349
|
-
.filter(isDefined)
|
|
350
|
-
.flat();
|
|
355
|
+
const newBlocks = blockPromises.filter(isDefined).flat();
|
|
351
356
|
|
|
352
357
|
// Emit an event for listening services to react to the chain prune
|
|
353
358
|
this.events.emit(L2BlockSourceEvents.L2PruneUnproven, {
|
|
@@ -395,7 +400,6 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
395
400
|
const localMessagesInserted = await this.store.getTotalL1ToL2MessageCount();
|
|
396
401
|
const localLastMessage = await this.store.getLastL1ToL2Message();
|
|
397
402
|
const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
|
|
398
|
-
await this.store.setInboxTreeInProgress(remoteMessagesState.treeInProgress);
|
|
399
403
|
|
|
400
404
|
this.log.trace(`Retrieved remote inbox state at L1 block ${currentL1BlockNumber}.`, {
|
|
401
405
|
localMessagesInserted,
|
|
@@ -830,14 +834,6 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
830
834
|
);
|
|
831
835
|
}
|
|
832
836
|
|
|
833
|
-
for (const published of validCheckpoints) {
|
|
834
|
-
this.instrumentation.processCheckpointL1Timing({
|
|
835
|
-
slotNumber: published.checkpoint.header.slotNumber,
|
|
836
|
-
l1Timestamp: published.l1.timestamp,
|
|
837
|
-
l1Constants: this.l1Constants,
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
|
|
841
837
|
try {
|
|
842
838
|
const updatedValidationResult =
|
|
843
839
|
rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
|
package/src/store/block_store.ts
CHANGED
|
@@ -227,21 +227,34 @@ export class BlockStore {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
return await this.db.transactionAsync(async () => {
|
|
230
|
-
// Check that the checkpoint immediately before the first block to be added is present in the store.
|
|
231
230
|
const firstCheckpointNumber = checkpoints[0].checkpoint.number;
|
|
232
231
|
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
233
232
|
|
|
234
|
-
|
|
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) {
|
|
235
247
|
throw new InitialCheckpointNumberNotSequentialError(firstCheckpointNumber, previousCheckpointNumber);
|
|
236
248
|
}
|
|
237
249
|
|
|
238
250
|
// Extract the previous checkpoint if there is one
|
|
251
|
+
const currentFirstCheckpointNumber = checkpoints[0].checkpoint.number;
|
|
239
252
|
let previousCheckpointData: CheckpointData | undefined = undefined;
|
|
240
|
-
if (
|
|
253
|
+
if (currentFirstCheckpointNumber - 1 !== INITIAL_CHECKPOINT_NUMBER - 1) {
|
|
241
254
|
// There should be a previous checkpoint
|
|
242
|
-
previousCheckpointData = await this.getCheckpointData(
|
|
255
|
+
previousCheckpointData = await this.getCheckpointData(CheckpointNumber(currentFirstCheckpointNumber - 1));
|
|
243
256
|
if (previousCheckpointData === undefined) {
|
|
244
|
-
throw new CheckpointNotFoundError(
|
|
257
|
+
throw new CheckpointNotFoundError(CheckpointNumber(currentFirstCheckpointNumber - 1));
|
|
245
258
|
}
|
|
246
259
|
}
|
|
247
260
|
|
|
@@ -331,6 +344,50 @@ export class BlockStore {
|
|
|
331
344
|
});
|
|
332
345
|
}
|
|
333
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
|
+
|
|
334
391
|
private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
|
|
335
392
|
const blockHash = await block.hash();
|
|
336
393
|
|
|
@@ -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
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import type { CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
17
17
|
import type {
|
|
18
18
|
ContractClassPublic,
|
|
19
|
+
ContractClassPublicWithCommitment,
|
|
19
20
|
ContractDataSource,
|
|
20
21
|
ContractInstanceUpdateWithAddress,
|
|
21
22
|
ContractInstanceWithAddress,
|
|
@@ -167,19 +168,14 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
167
168
|
|
|
168
169
|
/**
|
|
169
170
|
* Add new contract classes from an L2 block to the store's list.
|
|
170
|
-
* @param data - List of contract classes to be added.
|
|
171
|
-
* @param bytecodeCommitments - Bytecode commitments for the contract classes.
|
|
171
|
+
* @param data - List of contract classes (with bytecode commitments) to be added.
|
|
172
172
|
* @param blockNumber - Number of the L2 block the contracts were registered in.
|
|
173
173
|
* @returns True if the operation is successful.
|
|
174
174
|
*/
|
|
175
|
-
async addContractClasses(
|
|
176
|
-
data: ContractClassPublic[],
|
|
177
|
-
bytecodeCommitments: Fr[],
|
|
178
|
-
blockNumber: BlockNumber,
|
|
179
|
-
): Promise<boolean> {
|
|
175
|
+
async addContractClasses(data: ContractClassPublicWithCommitment[], blockNumber: BlockNumber): Promise<boolean> {
|
|
180
176
|
return (
|
|
181
177
|
await Promise.all(
|
|
182
|
-
data.map(
|
|
178
|
+
data.map(c => this.#contractClassStore.addContractClass(c, c.publicBytecodeCommitment, blockNumber)),
|
|
183
179
|
)
|
|
184
180
|
).every(Boolean);
|
|
185
181
|
}
|
|
@@ -474,11 +470,10 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
474
470
|
* array implies no logs match that tag.
|
|
475
471
|
* @param tags - The tags to search for.
|
|
476
472
|
* @param page - The page number (0-indexed) for pagination. Returns at most 10 logs per tag per page.
|
|
477
|
-
* @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
|
|
478
473
|
*/
|
|
479
|
-
getPrivateLogsByTags(tags: SiloedTag[], page?: number
|
|
474
|
+
getPrivateLogsByTags(tags: SiloedTag[], page?: number): Promise<TxScopedL2Log[][]> {
|
|
480
475
|
try {
|
|
481
|
-
return this.#logStore.getPrivateLogsByTags(tags, page
|
|
476
|
+
return this.#logStore.getPrivateLogsByTags(tags, page);
|
|
482
477
|
} catch (err) {
|
|
483
478
|
return Promise.reject(err);
|
|
484
479
|
}
|
|
@@ -490,16 +485,14 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
490
485
|
* @param contractAddress - The contract address to search logs for.
|
|
491
486
|
* @param tags - The tags to search for.
|
|
492
487
|
* @param page - The page number (0-indexed) for pagination. Returns at most 10 logs per tag per page.
|
|
493
|
-
* @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
|
|
494
488
|
*/
|
|
495
489
|
getPublicLogsByTagsFromContract(
|
|
496
490
|
contractAddress: AztecAddress,
|
|
497
491
|
tags: Tag[],
|
|
498
492
|
page?: number,
|
|
499
|
-
upToBlockNumber?: BlockNumber,
|
|
500
493
|
): Promise<TxScopedL2Log[][]> {
|
|
501
494
|
try {
|
|
502
|
-
return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags, page
|
|
495
|
+
return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags, page);
|
|
503
496
|
} catch (err) {
|
|
504
497
|
return Promise.reject(err);
|
|
505
498
|
}
|
|
@@ -606,11 +599,6 @@ export class KVArchiverDataStore implements ContractDataSource {
|
|
|
606
599
|
return this.#messageStore.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
|
|
607
600
|
}
|
|
608
601
|
|
|
609
|
-
/** Persists the inbox tree-in-progress checkpoint number from L1 state. */
|
|
610
|
-
public setInboxTreeInProgress(value: bigint): Promise<void> {
|
|
611
|
-
return this.#messageStore.setInboxTreeInProgress(value);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
602
|
/** Returns an async iterator to all L1 to L2 messages on the range. */
|
|
615
603
|
public iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {
|
|
616
604
|
return this.#messageStore.iterateL1ToL2Messages(range);
|
package/src/store/log_store.ts
CHANGED
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
} from '@aztec/stdlib/logs';
|
|
23
23
|
import { TxHash } from '@aztec/stdlib/tx';
|
|
24
24
|
|
|
25
|
-
import { OutOfOrderLogInsertionError } from '../errors.js';
|
|
26
25
|
import type { BlockStore } from './block_store.js';
|
|
27
26
|
|
|
28
27
|
/**
|
|
@@ -166,21 +165,10 @@ export class LogStore {
|
|
|
166
165
|
|
|
167
166
|
for (const taggedLogBuffer of currentPrivateTaggedLogs) {
|
|
168
167
|
if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const lastExisting = TxScopedL2Log.fromBuffer(taggedLogBuffer.logBuffers.at(-1)!);
|
|
174
|
-
const firstNew = TxScopedL2Log.fromBuffer(newLogs[0]);
|
|
175
|
-
if (lastExisting.blockNumber > firstNew.blockNumber) {
|
|
176
|
-
throw new OutOfOrderLogInsertionError(
|
|
177
|
-
'private',
|
|
178
|
-
taggedLogBuffer.tag,
|
|
179
|
-
lastExisting.blockNumber,
|
|
180
|
-
firstNew.blockNumber,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
privateTaggedLogs.set(taggedLogBuffer.tag, taggedLogBuffer.logBuffers.concat(newLogs));
|
|
168
|
+
privateTaggedLogs.set(
|
|
169
|
+
taggedLogBuffer.tag,
|
|
170
|
+
taggedLogBuffer.logBuffers!.concat(privateTaggedLogs.get(taggedLogBuffer.tag)!),
|
|
171
|
+
);
|
|
184
172
|
}
|
|
185
173
|
}
|
|
186
174
|
|
|
@@ -212,21 +200,10 @@ export class LogStore {
|
|
|
212
200
|
|
|
213
201
|
for (const taggedLogBuffer of currentPublicTaggedLogs) {
|
|
214
202
|
if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const lastExisting = TxScopedL2Log.fromBuffer(taggedLogBuffer.logBuffers.at(-1)!);
|
|
220
|
-
const firstNew = TxScopedL2Log.fromBuffer(newLogs[0]);
|
|
221
|
-
if (lastExisting.blockNumber > firstNew.blockNumber) {
|
|
222
|
-
throw new OutOfOrderLogInsertionError(
|
|
223
|
-
'public',
|
|
224
|
-
taggedLogBuffer.tag,
|
|
225
|
-
lastExisting.blockNumber,
|
|
226
|
-
firstNew.blockNumber,
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
publicTaggedLogs.set(taggedLogBuffer.tag, taggedLogBuffer.logBuffers.concat(newLogs));
|
|
203
|
+
publicTaggedLogs.set(
|
|
204
|
+
taggedLogBuffer.tag,
|
|
205
|
+
taggedLogBuffer.logBuffers!.concat(publicTaggedLogs.get(taggedLogBuffer.tag)!),
|
|
206
|
+
);
|
|
230
207
|
}
|
|
231
208
|
}
|
|
232
209
|
|
|
@@ -376,30 +353,17 @@ export class LogStore {
|
|
|
376
353
|
* array implies no logs match that tag.
|
|
377
354
|
* @param tags - The tags to search for.
|
|
378
355
|
* @param page - The page number (0-indexed) for pagination.
|
|
379
|
-
* @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
|
|
380
356
|
* @returns An array of log arrays, one per tag. Returns at most MAX_LOGS_PER_TAG logs per tag per page. If
|
|
381
357
|
* MAX_LOGS_PER_TAG logs are returned for a tag, the caller should fetch the next page to check for more logs.
|
|
382
358
|
*/
|
|
383
|
-
async getPrivateLogsByTags(
|
|
384
|
-
tags: SiloedTag[],
|
|
385
|
-
page: number = 0,
|
|
386
|
-
upToBlockNumber?: BlockNumber,
|
|
387
|
-
): Promise<TxScopedL2Log[][]> {
|
|
359
|
+
async getPrivateLogsByTags(tags: SiloedTag[], page: number = 0): Promise<TxScopedL2Log[][]> {
|
|
388
360
|
const logs = await Promise.all(tags.map(tag => this.#privateLogsByTag.getAsync(tag.toString())));
|
|
389
|
-
|
|
390
361
|
const start = page * MAX_LOGS_PER_TAG;
|
|
391
362
|
const end = start + MAX_LOGS_PER_TAG;
|
|
392
363
|
|
|
393
|
-
return logs.map(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const cutoff = deserialized.findIndex(log => log.blockNumber > upToBlockNumber);
|
|
397
|
-
if (cutoff !== -1) {
|
|
398
|
-
return deserialized.slice(0, cutoff);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return deserialized;
|
|
402
|
-
});
|
|
364
|
+
return logs.map(
|
|
365
|
+
logBuffers => logBuffers?.slice(start, end).map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? [],
|
|
366
|
+
);
|
|
403
367
|
}
|
|
404
368
|
|
|
405
369
|
/**
|
|
@@ -408,7 +372,6 @@ export class LogStore {
|
|
|
408
372
|
* @param contractAddress - The contract address to search logs for.
|
|
409
373
|
* @param tags - The tags to search for.
|
|
410
374
|
* @param page - The page number (0-indexed) for pagination.
|
|
411
|
-
* @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
|
|
412
375
|
* @returns An array of log arrays, one per tag. Returns at most MAX_LOGS_PER_TAG logs per tag per page. If
|
|
413
376
|
* MAX_LOGS_PER_TAG logs are returned for a tag, the caller should fetch the next page to check for more logs.
|
|
414
377
|
*/
|
|
@@ -416,7 +379,6 @@ export class LogStore {
|
|
|
416
379
|
contractAddress: AztecAddress,
|
|
417
380
|
tags: Tag[],
|
|
418
381
|
page: number = 0,
|
|
419
|
-
upToBlockNumber?: BlockNumber,
|
|
420
382
|
): Promise<TxScopedL2Log[][]> {
|
|
421
383
|
const logs = await Promise.all(
|
|
422
384
|
tags.map(tag => {
|
|
@@ -427,16 +389,9 @@ export class LogStore {
|
|
|
427
389
|
const start = page * MAX_LOGS_PER_TAG;
|
|
428
390
|
const end = start + MAX_LOGS_PER_TAG;
|
|
429
391
|
|
|
430
|
-
return logs.map(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const cutoff = deserialized.findIndex(log => log.blockNumber > upToBlockNumber);
|
|
434
|
-
if (cutoff !== -1) {
|
|
435
|
-
return deserialized.slice(0, cutoff);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
return deserialized;
|
|
439
|
-
});
|
|
392
|
+
return logs.map(
|
|
393
|
+
logBuffers => logBuffers?.slice(start, end).map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? [],
|
|
394
|
+
);
|
|
440
395
|
}
|
|
441
396
|
|
|
442
397
|
/**
|