Skip to content

Commit

Permalink
feat: restore latest block number (#2474)
Browse files Browse the repository at this point in the history
This PR updates the `WorldStateSynchroniser` to save and restore the
last L2 block it has seen.

Fix #2357 

# Checklist:
Remove the checklist to signal you've completed it. Enable auto-merge if
the PR is ready to merge.
- [ ] If the pull request requires a cryptography review (e.g.
cryptographic algorithm implementations) I have added the 'crypto' tag.
- [ ] I have reviewed my diff in github, line by line and removed
unexpected formatting changes, testing logs, or commented-out code.
- [ ] Every change is related to the PR description.
- [ ] I have
[linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
this pull request to relevant issues (if any exist).

Co-authored-by: PhilWindle <[email protected]>
  • Loading branch information
alexghr and PhilWindle authored Sep 28, 2023
1 parent 47d0d37 commit 6dc2da7
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 26 deletions.
7 changes: 7 additions & 0 deletions yarn-project/archiver/src/archiver/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export interface ArchiverConfig {
* The deployed L1 contract addresses
*/
l1Contracts: L1ContractAddresses;

/**
* Optional dir to store data. If omitted will store in memory.
*/
dataDirectory?: string;
}

/**
Expand All @@ -59,6 +64,7 @@ export function getConfigEnvVars(): ArchiverConfig {
API_KEY,
INBOX_CONTRACT_ADDRESS,
REGISTRY_CONTRACT_ADDRESS,
DATA_DIRECTORY,
} = process.env;
// Populate the relevant addresses for use by the archiver.
const addresses: L1ContractAddresses = {
Expand All @@ -78,5 +84,6 @@ export function getConfigEnvVars(): ArchiverConfig {
searchStartBlock: SEARCH_START_BLOCK ? +SEARCH_START_BLOCK : 0,
apiKey: API_KEY,
l1Contracts: addresses,
dataDirectory: DATA_DIRECTORY,
};
}
1 change: 1 addition & 0 deletions yarn-project/aztec-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@jest/globals": "^29.5.0",
"@rushstack/eslint-patch": "^1.1.4",
"@types/jest": "^29.5.0",
"@types/leveldown": "^4.0.4",
"@types/levelup": "^5.1.2",
"@types/memdown": "^3.0.0",
"@types/node": "^18.7.23",
Expand Down
60 changes: 60 additions & 0 deletions yarn-project/aztec-node/src/aztec-node/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { LevelDown, default as leveldown } from 'leveldown';
import { LevelUp, default as levelup } from 'levelup';
import { MemDown, default as memdown } from 'memdown';
import { join } from 'node:path';

import { AztecNodeConfig } from './config.js';

export const createMemDown = () => (memdown as any)() as MemDown<any, any>;
export const createLevelDown = (path: string) => (leveldown as any)(path) as LevelDown;

const DB_SUBDIR = 'aztec-node';
const NODE_METADATA_KEY = '@@aztec_node_metadata';

/**
* The metadata for an aztec node.
*/
type NodeMetadata = {
/**
* The address of the rollup contract on L1
*/
rollupContractAddress: string;
};

/**
* Opens the database for the aztec node.
* @param config - The configuration to be used by the aztec node.
* @returns The database for the aztec node.
*/
export async function openDb(config: AztecNodeConfig): Promise<LevelUp> {
const nodeMetadata: NodeMetadata = {
rollupContractAddress: config.l1Contracts.rollupAddress.toString(),
};

const db = levelup(config.dataDirectory ? createLevelDown(join(config.dataDirectory, DB_SUBDIR)) : createMemDown());
const prevNodeMetadata = await getNodeMetadata(db);

// if the rollup addresses are different, wipe the local database and start over
if (nodeMetadata.rollupContractAddress !== prevNodeMetadata.rollupContractAddress) {
await db.clear();
}

await db.put(NODE_METADATA_KEY, JSON.stringify(nodeMetadata));
return db;
}

/**
* Gets the metadata for the aztec node.
* @param db - The database for the aztec node.
* @returns Node metadata.
*/
async function getNodeMetadata(db: LevelUp): Promise<NodeMetadata> {
try {
const value: Buffer = await db.get(NODE_METADATA_KEY);
return JSON.parse(value.toString('utf-8'));
} catch {
return {
rollupContractAddress: '',
};
}
}
14 changes: 6 additions & 8 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@ import {
getConfigEnvVars as getWorldStateConfig,
} from '@aztec/world-state';

import { default as levelup } from 'levelup';
import { MemDown, default as memdown } from 'memdown';
import levelup from 'levelup';

import { AztecNodeConfig } from './config.js';

export const createMemDown = () => (memdown as any)() as MemDown<any, any>;
import { openDb } from './db.js';

/**
* The aztec node.
Expand Down Expand Up @@ -90,10 +88,10 @@ export class AztecNodeService implements AztecNode {
const p2pClient = await createP2PClient(config, new InMemoryTxPool(), archiver);

// now create the merkle trees and the world state syncher
const merkleTreesDb = levelup(createMemDown());
const merkleTrees = await MerkleTrees.new(merkleTreesDb, await CircuitsWasm.get());
const db = await openDb(config);
const merkleTrees = await MerkleTrees.new(db, await CircuitsWasm.get());
const worldStateConfig: WorldStateConfig = getWorldStateConfig();
const worldStateSynchronizer = new ServerWorldStateSynchronizer(merkleTrees, archiver, worldStateConfig);
const worldStateSynchronizer = await ServerWorldStateSynchronizer.new(db, merkleTrees, archiver, worldStateConfig);

// start both and wait for them to sync from the block source
await Promise.all([p2pClient.start(), worldStateSynchronizer.start()]);
Expand All @@ -120,7 +118,7 @@ export class AztecNodeService implements AztecNode {
config.chainId,
config.version,
getGlobalVariableBuilder(config),
merkleTreesDb,
db,
);
}

Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@types/lodash.times": "^4.3.7",
"@types/lodash.zip": "^4.2.7",
"@types/lodash.zipwith": "^4.2.7",
"@types/memdown": "^3.0.3",
"@types/node": "^18.7.23",
"jest": "^29.5.0",
"koa": "^2.14.2",
Expand All @@ -57,6 +58,7 @@
"lodash.times": "^4.3.2",
"lodash.zip": "^4.2.0",
"lodash.zipwith": "^4.2.0",
"memdown": "^6.1.1",
"puppeteer": "^21.3.4",
"string-argv": "^0.3.2",
"ts-jest": "^29.1.0",
Expand Down
5 changes: 3 additions & 2 deletions yarn-project/end-to-end/src/integration_l1_publisher.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMemDown, getConfigEnvVars } from '@aztec/aztec-node';
import { getConfigEnvVars } from '@aztec/aztec-node';
import {
AztecAddress,
GlobalVariables,
Expand Down Expand Up @@ -33,6 +33,7 @@ import { MerkleTreeOperations, MerkleTrees } from '@aztec/world-state';

import { beforeEach, describe, expect, it } from '@jest/globals';
import { default as levelup } from 'levelup';
import memdown from 'memdown';
import {
Address,
Chain,
Expand Down Expand Up @@ -123,7 +124,7 @@ describe('L1Publisher integration', () => {
publicClient,
});

builderDb = await MerkleTrees.new(levelup(createMemDown())).then(t => t.asLatest());
builderDb = await MerkleTrees.new(levelup((memdown as any)())).then(t => t.asLatest());
const vks = getVerificationKeys();
const simulator = await WasmRollupCircuitSimulator.new();
const prover = new EmptyRollupProver();
Expand Down
1 change: 1 addition & 0 deletions yarn-project/world-state/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@types/memdown": "^3.0.0",
"@types/node": "^18.7.23",
"jest": "^29.5.0",
"memdown": "^6.1.1",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import {
} from '@aztec/types';

import { jest } from '@jest/globals';
import levelup from 'levelup';
import times from 'lodash.times';
import { default as memdown } from 'memdown';

import { MerkleTreeDb, MerkleTrees, WorldStateConfig } from '../index.js';
import { ServerWorldStateSynchronizer } from './server_world_state_synchronizer.js';
Expand Down Expand Up @@ -96,12 +98,25 @@ const getMockBlock = (blockNumber: number, newContractsCommitments?: Buffer[]) =
return block;
};

const createSynchronizer = (merkleTreeDb: any, rollupSource: any, blockCheckInterval = 100) => {
const createMockDb = () => levelup((memdown as any)());

const createSynchronizer = async (
db: levelup.LevelUp,
merkleTreeDb: any,
rollupSource: any,
blockCheckInterval = 100,
) => {
const worldStateConfig: WorldStateConfig = {
worldStateBlockCheckIntervalMS: blockCheckInterval,
l2QueueSize: 1000,
};
return new ServerWorldStateSynchronizer(merkleTreeDb as MerkleTrees, rollupSource as L2BlockSource, worldStateConfig);

return await ServerWorldStateSynchronizer.new(
db,
merkleTreeDb as MerkleTrees,
rollupSource as L2BlockSource,
worldStateConfig,
);
};

const log = createDebugLogger('aztec:server_world_state_synchronizer_test');
Expand Down Expand Up @@ -152,12 +167,32 @@ describe('server_world_state_synchronizer', () => {
expect(status.syncedToL2Block).toBe(LATEST_BLOCK_NUMBER);
};

it('can be constructed', () => {
expect(() => createSynchronizer(merkleTreeDb, rollupSource)).not.toThrow();
const performSubsequentSync = async (server: ServerWorldStateSynchronizer, count: number) => {
// test initial state
let status = await server.status();
expect(status.syncedToL2Block).toEqual(LATEST_BLOCK_NUMBER);
expect(status.state).toEqual(WorldStateRunningState.IDLE);

// create the initial blocks
nextBlocks = Array(count)
.fill(0)
.map((_, index: number) => getMockBlock(LATEST_BLOCK_NUMBER + index + 1));

rollupSource.getBlockNumber.mockReturnValueOnce(LATEST_BLOCK_NUMBER + count);

// start the sync process and await it
await server.start().catch(err => log.error('Sync not completed: ', err));

status = await server.status();
expect(status.syncedToL2Block).toBe(LATEST_BLOCK_NUMBER + count);
};

it('can be constructed', async () => {
await expect(createSynchronizer(createMockDb(), merkleTreeDb, rollupSource)).resolves.toBeTruthy();
});

it('updates sync progress', async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource);

// test initial state
let status = await server.status();
Expand Down Expand Up @@ -206,7 +241,7 @@ describe('server_world_state_synchronizer', () => {
});

it('enables blocking until synced', async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource);
let currentBlockNumber = 0;

const newBlocks = async () => {
Expand Down Expand Up @@ -237,7 +272,7 @@ describe('server_world_state_synchronizer', () => {
});

it('handles multiple calls to start', async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource);
let currentBlockNumber = 0;

const newBlocks = async () => {
Expand All @@ -264,7 +299,7 @@ describe('server_world_state_synchronizer', () => {
});

it('immediately syncs if no new blocks', async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource);
rollupSource.getBlockNumber.mockImplementationOnce(() => {
return Promise.resolve(0);
});
Expand All @@ -282,7 +317,7 @@ describe('server_world_state_synchronizer', () => {
});

it("can't be started if already stopped", async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource);
rollupSource.getBlockNumber.mockImplementationOnce(() => {
return Promise.resolve(0);
});
Expand All @@ -297,7 +332,7 @@ describe('server_world_state_synchronizer', () => {

it('adds the received L2 blocks', async () => {
merkleTreeDb.handleL2Block.mockReset();
const server = createSynchronizer(merkleTreeDb, rollupSource);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource);
const totalBlocks = LATEST_BLOCK_NUMBER + 1;
nextBlocks = Array(totalBlocks)
.fill(0)
Expand All @@ -310,7 +345,7 @@ describe('server_world_state_synchronizer', () => {
});

it('can immediately sync to latest', async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource, 10000);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource, 10000);

await performInitialSync(server);

Expand Down Expand Up @@ -338,7 +373,7 @@ describe('server_world_state_synchronizer', () => {
});

it('can immediately sync to a minimum block number', async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource, 10000);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource, 10000);

await performInitialSync(server);

Expand All @@ -363,7 +398,7 @@ describe('server_world_state_synchronizer', () => {
});

it('can immediately sync to a minimum block in the past', async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource, 10000);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource, 10000);

await performInitialSync(server);
// syncing to a block in the past should succeed
Expand All @@ -385,7 +420,7 @@ describe('server_world_state_synchronizer', () => {
});

it('throws if you try to sync to an unavailable block', async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource, 10000);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource, 10000);

await performInitialSync(server);

Expand All @@ -411,7 +446,7 @@ describe('server_world_state_synchronizer', () => {
});

it('throws if you try to immediate sync when not running', async () => {
const server = createSynchronizer(merkleTreeDb, rollupSource, 10000);
const server = await createSynchronizer(createMockDb(), merkleTreeDb, rollupSource, 10000);

// test initial state
const status = await server.status();
Expand All @@ -425,4 +460,31 @@ describe('server_world_state_synchronizer', () => {

await expect(server.syncImmediate()).rejects.toThrow(`World State is not running, unable to perform sync`);
});

it('restores the last synced block', async () => {
const db = createMockDb();
const initialServer = await createSynchronizer(db, merkleTreeDb, rollupSource, 10000);

await performInitialSync(initialServer);
await initialServer.stop();

const server = await createSynchronizer(db, merkleTreeDb, rollupSource, 10000);
const status = await server.status();
expect(status).toEqual({
state: WorldStateRunningState.IDLE,
syncedToL2Block: LATEST_BLOCK_NUMBER,
});
});

it('starts syncing from the last block', async () => {
const db = createMockDb();
const initialServer = await createSynchronizer(db, merkleTreeDb, rollupSource, 10000);

await performInitialSync(initialServer);
await initialServer.stop();

const server = await createSynchronizer(db, merkleTreeDb, rollupSource, 10000);
await performSubsequentSync(server, 2);
await server.stop();
});
});
Loading

0 comments on commit 6dc2da7

Please sign in to comment.