Skip to content

Commit

Permalink
chore: prover-node e2e test
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed Jul 16, 2024
1 parent 8d52fcd commit 39e9955
Show file tree
Hide file tree
Showing 20 changed files with 273 additions and 59 deletions.
61 changes: 37 additions & 24 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export class Archiver implements ArchiveSource {
}

if (blockUntilSynced) {
this.log.info(`Performing initial chain sync...`);
this.log.info(`Performing initial chain sync`);
await this.sync(blockUntilSynced);
}

Expand All @@ -165,15 +165,13 @@ export class Archiver implements ArchiveSource {
*
* This code does not handle reorgs.
*/
const l1SynchPoint = await this.store.getSynchPoint();
const { blocksSynchedTo, messagesSynchedTo } = await this.store.getSynchPoint();
const currentL1BlockNumber = await this.publicClient.getBlockNumber();

if (
currentL1BlockNumber <= l1SynchPoint.blocksSynchedTo &&
currentL1BlockNumber <= l1SynchPoint.messagesSynchedTo
) {
if (currentL1BlockNumber <= blocksSynchedTo && currentL1BlockNumber <= messagesSynchedTo) {
// chain hasn't moved forward
// or it's been rolled back
this.log.debug(`Nothing to sync`, { currentL1BlockNumber, blocksSynchedTo, messagesSynchedTo });
return;
}

Expand Down Expand Up @@ -204,14 +202,14 @@ export class Archiver implements ArchiveSource {
this.publicClient,
this.inboxAddress,
blockUntilSynced,
l1SynchPoint.messagesSynchedTo + 1n,
messagesSynchedTo + 1n,
currentL1BlockNumber,
);

if (retrievedL1ToL2Messages.retrievedData.length !== 0) {
this.log.verbose(
`Retrieved ${retrievedL1ToL2Messages.retrievedData.length} new L1 -> L2 messages between L1 blocks ${
l1SynchPoint.messagesSynchedTo + 1n
messagesSynchedTo + 1n
} and ${currentL1BlockNumber}.`,
);
}
Expand All @@ -225,7 +223,7 @@ export class Archiver implements ArchiveSource {
this.publicClient,
this.availabilityOracleAddress,
blockUntilSynced,
l1SynchPoint.blocksSynchedTo + 1n,
blocksSynchedTo + 1n,
currentL1BlockNumber,
);

Expand All @@ -240,7 +238,7 @@ export class Archiver implements ArchiveSource {
this.publicClient,
this.rollupAddress,
blockUntilSynced,
l1SynchPoint.blocksSynchedTo + 1n,
blocksSynchedTo + 1n,
currentL1BlockNumber,
nextExpectedL2BlockNum,
);
Expand All @@ -259,15 +257,11 @@ export class Archiver implements ArchiveSource {
(blockMetadata, i) => new L2Block(blockMetadata[1], blockMetadata[0], blockBodiesFromStore[i]),
);

if (blocks.length === 0) {
return;
} else {
this.log.verbose(
`Retrieved ${blocks.length} new L2 blocks between L1 blocks ${
l1SynchPoint.blocksSynchedTo + 1n
} and ${currentL1BlockNumber}.`,
);
}
this.log.verbose(
`Retrieved ${blocks.length || 'no'} new L2 blocks between L1 blocks ${
blocksSynchedTo + 1n
} and ${currentL1BlockNumber}.`,
);

retrievedBlocks = {
lastProcessedL1BlockNumber: retrievedBlockMetadata.lastProcessedL1BlockNumber,
Expand Down Expand Up @@ -296,19 +290,25 @@ export class Archiver implements ArchiveSource {
}),
);

await this.store.addBlocks(retrievedBlocks);
this.instrumentation.processNewBlocks(retrievedBlocks.retrievedData);
if (retrievedBlocks.retrievedData.length > 0) {
await this.store.addBlocks(retrievedBlocks);
this.instrumentation.processNewBlocks(retrievedBlocks.retrievedData);
const lastL2BlockNumber = retrievedBlocks.retrievedData[retrievedBlocks.retrievedData.length - 1].number;
this.log.verbose(`Processed ${retrievedBlocks.retrievedData.length} new L2 blocks up to ${lastL2BlockNumber}`);
}

// Fetch the logs for proven blocks in the block range and update the last proven block number.
// Note it's ok to read repeated data here, since we're just using the largest number we see on the logs.
await this.updateLastProvenL2Block(l1SynchPoint.blocksSynchedTo, currentL1BlockNumber);
await this.updateLastProvenL2Block(blocksSynchedTo, currentL1BlockNumber);

(blockUntilSynced ? this.log.info : this.log.verbose)(`Synced to L1 block ${currentL1BlockNumber}`);
}

private async updateLastProvenL2Block(fromBlock: bigint, toBlock: bigint) {
const logs = await this.publicClient.getLogs({
address: this.rollupAddress.toString(),
fromBlock,
toBlock,
toBlock: toBlock + 1n, // toBlock is exclusive
strict: true,
event: getAbiItem({ abi: RollupAbi, name: 'L2ProofVerified' }),
});
Expand All @@ -319,7 +319,15 @@ export class Archiver implements ArchiveSource {
}

const provenBlockNumber = lastLog.args.blockNumber;
await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
if (!provenBlockNumber) {
throw new Error(`Missing argument blockNumber from L2ProofVerified event`);
}

const currentProvenBlockNumber = await this.store.getProvenL2BlockNumber();
if (provenBlockNumber > currentProvenBlockNumber) {
this.log.verbose(`Updated last proven block number from ${currentProvenBlockNumber} to ${provenBlockNumber}`);
await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
}
}

/**
Expand Down Expand Up @@ -502,6 +510,11 @@ export class Archiver implements ArchiveSource {
return this.store.getProvenL2BlockNumber();
}

/** Forcefully updates the last proven block number. Use for testing. */
public setProvenBlockNumber(block: number): Promise<void> {
return this.store.setProvenL2BlockNumber(block);
}

public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
return this.store.getContractClass(id);
}
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ export class AztecNodeService implements AztecNode {
return this.prover;
}

public getBlockSource(): L2BlockSource {
return this.blockSource;
}

/**
* Method to return the currently deployed L1 contract addresses.
* @returns - The currently deployed L1 contract addresses.
Expand Down Expand Up @@ -254,6 +258,10 @@ export class AztecNodeService implements AztecNode {
return await this.blockSource.getBlockNumber();
}

public async getProvenBlockNumber(): Promise<number> {
return await this.blockSource.getProvenBlockNumber();
}

/**
* Method to fetch the version of the package.
* @returns The node package version
Expand Down
7 changes: 7 additions & 0 deletions yarn-project/circuit-types/src/interfaces/aztec-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ export interface AztecNode {
* @returns The block number.
*/
getBlockNumber(): Promise<number>;

/**
* Fetches the latest proven block number.
* @returns The block number.
*/
getProvenBlockNumber(): Promise<number>;

/**
* Method to determine if the node is ready to accept transactions.
* @returns - Flag indicating the readiness for tx submission.
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/circuit-types/src/interfaces/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ export interface SequencerConfig {
maxBlockSizeInBytes?: number;
/** Whether to require every tx to have a fee payer */
enforceFees?: boolean;
/** Temporary flag to skip submitting proofs, so a prover-node takes care of it. */
skipSubmitProofs?: boolean;
}
1 change: 1 addition & 0 deletions yarn-project/end-to-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@aztec/p2p": "workspace:^",
"@aztec/protocol-contracts": "workspace:^",
"@aztec/prover-client": "workspace:^",
"@aztec/prover-node": "workspace:^",
"@aztec/pxe": "workspace:^",
"@aztec/sequencer-client": "workspace:^",
"@aztec/simulator": "workspace:^",
Expand Down
111 changes: 111 additions & 0 deletions yarn-project/end-to-end/src/e2e_prover_node.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { getSchnorrAccount } from '@aztec/accounts/schnorr';
import { type Archiver } from '@aztec/archiver';
import {
type AccountWalletWithSecretKey,
type AztecAddress,
type DebugLogger,
type FieldsOf,
type TxReceipt,
createDebugLogger,
retryUntil,
sleep,
} from '@aztec/aztec.js';
import { StatefulTestContract } from '@aztec/noir-contracts.js';
import { createProverNode } from '@aztec/prover-node';

import {
type ISnapshotManager,
type SubsystemsContext,
addAccounts,
createSnapshotManager,
} from './fixtures/snapshot_manager.js';

// Tests simple block building with a sequencer that does not upload proofs to L1,
// and then follows with a prover node run (with real proofs disabled, but
// still simulating all circuits via a prover-client), in order to test
// the coordination between the sequencer and the prover node.
describe('e2e_prover_node', () => {
let ctx: SubsystemsContext;
let wallet: AccountWalletWithSecretKey;
let recipient: AztecAddress;
let contract: StatefulTestContract;
let txReceipts: FieldsOf<TxReceipt>[];

let logger: DebugLogger;

let snapshotManager: ISnapshotManager;

beforeAll(async () => {
logger = createDebugLogger('aztec:e2e_prover_node');
const config = { skipSubmitProofs: true };
snapshotManager = createSnapshotManager(`e2e_prover_node`, process.env.E2E_DATA_PATH, config);

await snapshotManager.snapshot('setup', addAccounts(2, logger), async ({ accountKeys }, ctx) => {
const accountManagers = accountKeys.map(ak => getSchnorrAccount(ctx.pxe, ak[0], ak[1], 1));
await Promise.all(accountManagers.map(a => a.register()));
const wallets = await Promise.all(accountManagers.map(a => a.getWallet()));
wallets.forEach((w, i) => logger.verbose(`Wallet ${i} address: ${w.getAddress()}`));
wallet = wallets[0];
recipient = wallets[1].getAddress();
});

await snapshotManager.snapshot(
'deploy-test-contract',
async () => {
const owner = wallet.getAddress();
const contract = await StatefulTestContract.deploy(wallet, owner, owner, 42).send().deployed();
return { contractAddress: contract.address };
},
async ({ contractAddress }) => {
contract = await StatefulTestContract.at(contractAddress, wallet);
},
);

await snapshotManager.snapshot(
'create-blocks',
async () => {
const txReceipt1 = await contract.methods.create_note(recipient, recipient, 10).send().wait();
const txReceipt2 = await contract.methods.increment_public_value(recipient, 20).send().wait();
return { txReceipt1, txReceipt2 };
},
({ txReceipt1, txReceipt2 }) => {
txReceipts = [txReceipt1, txReceipt2];
return Promise.resolve();
},
);

ctx = await snapshotManager.setup();
});

it('submits two blocks, then prover proves the first one', async () => {
// Check everything went well during setup and txs were mined in two different blocks
const [txReceipt1, txReceipt2] = txReceipts;
const firstBlock = txReceipt1.blockNumber!;
expect(txReceipt2.blockNumber).toEqual(firstBlock + 1);
expect(await contract.methods.get_public_value(recipient).simulate()).toEqual(20n);
expect(await contract.methods.summed_values(recipient).simulate()).toEqual(10n);
expect(await ctx.aztecNode.getProvenBlockNumber()).toEqual(0);

// Trick archiver into thinking everything has been proven up to this point.
// TODO: Add cheat code to flag current block as proven on L1, which will be needed when we assert on L1 that proofs do not have any gaps.
await (ctx.aztecNode.getBlockSource() as Archiver).setProvenBlockNumber(firstBlock - 1);
expect(await ctx.aztecNode.getProvenBlockNumber()).toEqual(firstBlock - 1);

// Kick off a prover node
await sleep(1000);
logger.info('Creating prover node');
// HACK: We have to use the existing archiver to fetch L2 data, since anvil's chain dump/load used by the
// snapshot manager does not include events nor txs, so a new archiver would not "see" old blocks.
const proverConfig = { ...ctx.aztecNodeConfig, txProviderNodeUrl: undefined, dataDirectory: undefined };
const archiver = ctx.aztecNode.getBlockSource() as Archiver;
const proverNode = await createProverNode(proverConfig, { aztecNodeTxProvider: ctx.aztecNode, archiver });

// Prove block from first tx and block until it is proven
logger.info(`Proving block ${firstBlock}`);
await proverNode.prove(firstBlock, firstBlock);

logger.info(`Proof submitted. Awaiting aztec node to sync...`);
await retryUntil(async () => (await ctx.aztecNode.getProvenBlockNumber()) === firstBlock, 'proven-block', 10, 1);
expect(await ctx.aztecNode.getProvenBlockNumber()).toEqual(firstBlock);
});
});
20 changes: 12 additions & 8 deletions yarn-project/end-to-end/src/fixtures/snapshot_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ type SnapshotEntry = {
snapshotPath: string;
};

export function createSnapshotManager(testName: string, dataPath?: string) {
return dataPath ? new SnapshotManager(testName, dataPath) : new MockSnapshotManager(testName);
export function createSnapshotManager(testName: string, dataPath?: string, config: Partial<AztecNodeConfig> = {}) {
return dataPath ? new SnapshotManager(testName, dataPath, config) : new MockSnapshotManager(testName, config);
}

export interface ISnapshotManager {
Expand All @@ -73,7 +73,7 @@ class MockSnapshotManager implements ISnapshotManager {
private context?: SubsystemsContext;
private logger: DebugLogger;

constructor(testName: string) {
constructor(testName: string, private config: Partial<AztecNodeConfig> = {}) {
this.logger = createDebugLogger(`aztec:snapshot_manager:${testName}`);
this.logger.warn(`No data path given, will not persist any snapshots.`);
}
Expand All @@ -95,7 +95,7 @@ class MockSnapshotManager implements ISnapshotManager {

public async setup() {
if (!this.context) {
this.context = await setupFromFresh(undefined, this.logger);
this.context = await setupFromFresh(undefined, this.logger, this.config);
}
return this.context;
}
Expand All @@ -116,7 +116,7 @@ class SnapshotManager implements ISnapshotManager {
private livePath: string;
private logger: DebugLogger;

constructor(testName: string, private dataPath: string) {
constructor(testName: string, private dataPath: string, private config: Partial<AztecNodeConfig> = {}) {
this.livePath = join(this.dataPath, 'live', testName);
this.logger = createDebugLogger(`aztec:snapshot_manager:${testName}`);
}
Expand Down Expand Up @@ -193,7 +193,7 @@ class SnapshotManager implements ISnapshotManager {
this.logger.verbose(`Restoration of ${e.name} complete.`);
});
} else {
this.context = await setupFromFresh(this.livePath, this.logger);
this.context = await setupFromFresh(this.livePath, this.logger, this.config);
}
}
return this.context;
Expand Down Expand Up @@ -227,12 +227,16 @@ async function teardown(context: SubsystemsContext | undefined) {
* If given a statePath, the state will be written to the path.
* If there is no statePath, in-memory and temporary state locations will be used.
*/
async function setupFromFresh(statePath: string | undefined, logger: Logger): Promise<SubsystemsContext> {
async function setupFromFresh(
statePath: string | undefined,
logger: Logger,
config: Partial<AztecNodeConfig> = {},
): Promise<SubsystemsContext> {
logger.verbose(`Initializing state...`);

// Fetch the AztecNode config.
// TODO: For some reason this is currently the union of a bunch of subsystems. That needs fixing.
const aztecNodeConfig: AztecNodeConfig = getConfigEnvVars();
const aztecNodeConfig: AztecNodeConfig = { ...getConfigEnvVars(), ...config };
aztecNodeConfig.dataDirectory = statePath;

// Start anvil. We go via a wrapper script to ensure if the parent dies, anvil dies.
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/end-to-end/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
{
"path": "../prover-client"
},
{
"path": "../prover-node"
},
{
"path": "../pxe"
},
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/foundation/src/log/log_fn.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** Structured log data to include with the message. */
export type LogData = Record<string, string | number | bigint | boolean | { toString(): string }>;
export type LogData = Record<string, string | number | bigint | boolean | { toString(): string } | undefined>;

/** A callable logger instance. */
export type LogFn = (msg: string, data?: LogData) => void;
Loading

0 comments on commit 39e9955

Please sign in to comment.