Skip to content

Commit

Permalink
feat: Handle reorgs in world state synchronizer
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed Oct 10, 2024
1 parent f4674d2 commit 21413ae
Show file tree
Hide file tree
Showing 41 changed files with 1,251 additions and 575 deletions.
28 changes: 28 additions & 0 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
type InboxLeaf,
type L1ToL2MessageSource,
type L2Block,
type L2BlockId,
type L2BlockL2Logs,
type L2BlockSource,
type L2LogsSource,
type L2Tips,
type LogFilter,
type LogType,
type TxEffect,
Expand Down Expand Up @@ -654,6 +656,32 @@ export class Archiver implements ArchiveSource {
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
return this.store.getContractArtifact(address);
}

async getL2Tips(): Promise<L2Tips> {
const [latestBlockNumber, provenBlockNumber] = await Promise.all([
this.getBlockNumber(),
this.getProvenBlockNumber(),
] as const);

const [latestBlockHeader, provenBlockHeader] = await Promise.all([
latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
provenBlockNumber > 0 ? this.getBlockHeader(provenBlockNumber) : undefined,
] as const);

if (latestBlockNumber > 0 && !latestBlockHeader) {
throw new Error('Failed to retrieve latest block header');
}

if (provenBlockNumber > 0 && !provenBlockHeader) {
throw new Error('Failed to retrieve proven block header');
}

return {
latest: { number: latestBlockNumber, hash: latestBlockHeader?.hash().toString() } as L2BlockId,
proven: { number: provenBlockNumber, hash: provenBlockHeader?.hash().toString() } as L2BlockId,
finalized: { number: provenBlockNumber, hash: provenBlockHeader?.hash().toString() } as L2BlockId,
};
}
}

enum Operation {
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/archiver/src/test/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './mock_l2_block_source.js';
export * from './mock_l1_to_l2_message_source.js';
export * from './mock_archiver.js';
55 changes: 55 additions & 0 deletions yarn-project/archiver/src/test/mock_archiver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { type L1ToL2MessageSource, type L2Block, type L2BlockSource } from '@aztec/circuit-types';
import { type Fr } from '@aztec/circuits.js';

import { MockL1ToL2MessageSource } from './mock_l1_to_l2_message_source.js';
import { MockL2BlockSource } from './mock_l2_block_source.js';

/**
* A mocked implementation of the archiver that implements L2BlockSource and L1ToL2MessageSource.
*/
export class MockArchiver extends MockL2BlockSource implements L2BlockSource, L1ToL2MessageSource {
private messageSource = new MockL1ToL2MessageSource(0);

public setL1ToL2Messages(blockNumber: number, msgs: Fr[]) {
this.messageSource.setL1ToL2Messages(blockNumber, msgs);
}

getL1ToL2Messages(blockNumber: bigint): Promise<Fr[]> {
return this.messageSource.getL1ToL2Messages(blockNumber);
}

getL1ToL2MessageIndex(_l1ToL2Message: Fr, _startIndex: bigint): Promise<bigint | undefined> {
return this.messageSource.getL1ToL2MessageIndex(_l1ToL2Message, _startIndex);
}
}

/**
* A mocked implementation of the archiver with a set of precomputed blocks and messages.
*/
export class MockPrefilledArchiver extends MockArchiver {
private precomputed: L2Block[];

constructor(precomputed: L2Block[], messages: Fr[][]) {
super();
this.precomputed = precomputed.slice();
messages.forEach((msgs, i) => this.setL1ToL2Messages(i + 1, msgs));
}

public setPrefilledBlocks(blocks: L2Block[], messages: Fr[][]) {
for (const block of blocks) {
this.precomputed[block.number - 1] = block;
}
messages.forEach((msgs, i) => this.setL1ToL2Messages(blocks[i].number, msgs));
}

public override createBlocks(numBlocks: number) {
if (this.l2Blocks.length + numBlocks > this.precomputed.length) {
throw new Error(
`Not enough precomputed blocks to create ${numBlocks} more blocks (already at ${this.l2Blocks.length})`,
);
}

const fromBlock = this.l2Blocks.length;
this.addBlocks(this.precomputed.slice(fromBlock, fromBlock + numBlocks));
}
}
31 changes: 31 additions & 0 deletions yarn-project/archiver/src/test/mock_l1_to_l2_message_source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { type L1ToL2MessageSource } from '@aztec/circuit-types';
import { type Fr } from '@aztec/circuits.js';

/**
* A mocked implementation of L1ToL2MessageSource to be used in tests.
*/
export class MockL1ToL2MessageSource implements L1ToL2MessageSource {
private messagesPerBlock = new Map<number, Fr[]>();

constructor(private blockNumber: number) {}

public setL1ToL2Messages(blockNumber: number, msgs: Fr[]) {
this.messagesPerBlock.set(blockNumber, msgs);
}

public setBlockNumber(blockNumber: number) {
this.blockNumber = blockNumber;
}

getL1ToL2Messages(blockNumber: bigint): Promise<Fr[]> {
return Promise.resolve(this.messagesPerBlock.get(Number(blockNumber)) ?? []);
}

getL1ToL2MessageIndex(_l1ToL2Message: Fr, _startIndex: bigint): Promise<bigint | undefined> {
throw new Error('Method not implemented.');
}

getBlockNumber(): Promise<number> {
return Promise.resolve(this.blockNumber);
}
}
59 changes: 42 additions & 17 deletions yarn-project/archiver/src/test/mock_l2_block_source.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import { L2Block, type L2BlockSource, type TxEffect, type TxHash, TxReceipt, TxStatus } from '@aztec/circuit-types';
import { L2Block, type L2BlockSource, type L2Tips, type TxHash, TxReceipt, TxStatus } from '@aztec/circuit-types';
import { EthAddress, type Header } from '@aztec/circuits.js';
import { createDebugLogger } from '@aztec/foundation/log';

import { getSlotRangeForEpoch } from '../archiver/epoch_helpers.js';

/**
* A mocked implementation of L2BlockSource to be used in p2p tests.
* A mocked implementation of L2BlockSource to be used in tests.
*/
export class MockBlockSource implements L2BlockSource {
private l2Blocks: L2Block[] = [];
private txEffects: TxEffect[] = [];
export class MockL2BlockSource implements L2BlockSource {
protected l2Blocks: L2Block[] = [];

private provenEpochNumber: number = 0;
private provenBlockNumber: number = 0;

constructor(numBlocks = 100, private provenBlockNumber?: number) {
this.addBlocks(numBlocks);
}
private log = createDebugLogger('aztec:archiver:mock_l2_block_source');

public addBlocks(numBlocks: number) {
public createBlocks(numBlocks: number) {
for (let i = 0; i < numBlocks; i++) {
const blockNum = this.l2Blocks.length;
const block = L2Block.random(blockNum, blockNum);
const blockNum = this.l2Blocks.length + 1;
const block = L2Block.random(blockNum);
this.l2Blocks.push(block);
this.txEffects.push(...block.body.txEffects);
}

this.log.verbose(`Created ${numBlocks} blocks in the mock L2 block source`);
}

public addBlocks(blocks: L2Block[]) {
this.l2Blocks.push(...blocks);
this.log.verbose(`Added ${blocks.length} blocks to the mock L2 block source`);
}

public removeBlocks(numBlocks: number) {
this.l2Blocks = this.l2Blocks.slice(0, -numBlocks);
this.log.verbose(`Removed ${numBlocks} blocks from the mock L2 block source`);
}

public setProvenBlockNumber(provenBlockNumber: number) {
Expand Down Expand Up @@ -53,7 +64,7 @@ export class MockBlockSource implements L2BlockSource {
* @returns In this mock instance, returns the number of L2 blocks that we've mocked.
*/
public getBlockNumber() {
return Promise.resolve(this.l2Blocks.length - 1);
return Promise.resolve(this.l2Blocks.length);
}

public async getProvenBlockNumber(): Promise<number> {
Expand All @@ -70,7 +81,7 @@ export class MockBlockSource implements L2BlockSource {
* @returns The requested L2 block.
*/
public getBlock(number: number) {
return Promise.resolve(this.l2Blocks[number]);
return Promise.resolve(this.l2Blocks[number - 1]);
}

/**
Expand All @@ -82,13 +93,13 @@ export class MockBlockSource implements L2BlockSource {
public getBlocks(from: number, limit: number, proven?: boolean) {
return Promise.resolve(
this.l2Blocks
.slice(from, from + limit)
.slice(from - 1, from - 1 + limit)
.filter(b => !proven || this.provenBlockNumber === undefined || b.number <= this.provenBlockNumber),
);
}

getBlockHeader(number: number | 'latest'): Promise<Header | undefined> {
return Promise.resolve(this.l2Blocks.at(typeof number === 'number' ? number : -1)?.header);
return Promise.resolve(this.l2Blocks.at(typeof number === 'number' ? number - 1 : -1)?.header);
}

getBlocksForEpoch(epochNumber: bigint): Promise<L2Block[]> {
Expand All @@ -106,7 +117,7 @@ export class MockBlockSource implements L2BlockSource {
* @returns The requested tx effect.
*/
public getTxEffect(txHash: TxHash) {
const txEffect = this.txEffects.find(tx => tx.txHash.equals(txHash));
const txEffect = this.l2Blocks.flatMap(b => b.body.txEffects).find(tx => tx.txHash.equals(txHash));
return Promise.resolve(txEffect);
}

Expand Down Expand Up @@ -135,6 +146,20 @@ export class MockBlockSource implements L2BlockSource {
return Promise.resolve(undefined);
}

async getL2Tips(): Promise<L2Tips> {
const [latest, proven, finalized] = [
await this.getBlockNumber(),
await this.getProvenBlockNumber(),
await this.getProvenBlockNumber(),
] as const;

return {
latest: { number: latest, hash: this.l2Blocks[latest - 1]?.hash().toString() },
proven: { number: proven, hash: this.l2Blocks[proven - 1]?.hash().toString() },
finalized: { number: finalized, hash: this.l2Blocks[finalized - 1]?.hash().toString() },
};
}

getL2EpochNumber(): Promise<bigint> {
throw new Error('Method not implemented.');
}
Expand Down
7 changes: 0 additions & 7 deletions yarn-project/aztec/src/cli/aztec_start_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,6 @@ export const aztecStartOptions: { [key: string]: AztecStartOption[] } = {
defaultValue: undefined,
envVar: 'L1_PRIVATE_KEY',
},
{
flag: '--node.l2QueueSize <value>',
description: 'Size of queue of L2 blocks to store in world state',
defaultValue: 1000,
envVar: 'L2_QUEUE_SIZE',
parseVal: val => parseInt(val, 10),
},
{
flag: '--node.worldStateBlockCheckIntervalMS <value>',
description: 'Frequency in which to check for blocks in ms',
Expand Down
8 changes: 0 additions & 8 deletions yarn-project/circuit-types/src/interfaces/world_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,6 @@ export interface WorldStateSynchronizer {
*/
syncImmediate(minBlockNumber?: number): Promise<number>;

/**
* Pauses the synchronizer, syncs to the target block number, forks world state, and resumes.
* @param targetBlockNumber - The block number to sync to.
* @param forkIncludeUncommitted - Whether to include uncommitted data in the fork.
* @returns The db forked at the requested target block number.
*/
syncImmediateAndFork(targetBlockNumber: number): Promise<MerkleTreeWriteOperations>;

/**
* Forks the current in-memory state based off the current committed state, and returns an instance that cannot modify the underlying data store.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './l2_block_downloader.js';
export * from './l2_block_stream.js';
Loading

0 comments on commit 21413ae

Please sign in to comment.