diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index b35cd83dcdf1..4d47ca9c6582 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -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); } @@ -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; } @@ -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}.`, ); } @@ -225,7 +223,7 @@ export class Archiver implements ArchiveSource { this.publicClient, this.availabilityOracleAddress, blockUntilSynced, - l1SynchPoint.blocksSynchedTo + 1n, + blocksSynchedTo + 1n, currentL1BlockNumber, ); @@ -240,7 +238,7 @@ export class Archiver implements ArchiveSource { this.publicClient, this.rollupAddress, blockUntilSynced, - l1SynchPoint.blocksSynchedTo + 1n, + blocksSynchedTo + 1n, currentL1BlockNumber, nextExpectedL2BlockNum, ); @@ -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, @@ -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' }), }); @@ -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)); + } } /** @@ -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 { + return this.store.setProvenL2BlockNumber(block); + } + public getContractClass(id: Fr): Promise { return this.store.getContractClass(id); } diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 3accbba3f754..dc50d2b0d13b 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -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. @@ -254,6 +258,10 @@ export class AztecNodeService implements AztecNode { return await this.blockSource.getBlockNumber(); } + public async getProvenBlockNumber(): Promise { + return await this.blockSource.getProvenBlockNumber(); + } + /** * Method to fetch the version of the package. * @returns The node package version diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index c8e2b331b6db..4973715724f7 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -176,6 +176,13 @@ export interface AztecNode { * @returns The block number. */ getBlockNumber(): Promise; + + /** + * Fetches the latest proven block number. + * @returns The block number. + */ + getProvenBlockNumber(): Promise; + /** * Method to determine if the node is ready to accept transactions. * @returns - Flag indicating the readiness for tx submission. diff --git a/yarn-project/circuit-types/src/interfaces/configs.ts b/yarn-project/circuit-types/src/interfaces/configs.ts index 389c76f15f56..ddfb98276cc9 100644 --- a/yarn-project/circuit-types/src/interfaces/configs.ts +++ b/yarn-project/circuit-types/src/interfaces/configs.ts @@ -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; } diff --git a/yarn-project/end-to-end/package.json b/yarn-project/end-to-end/package.json index c60db38d6f99..d942bdecdc73 100644 --- a/yarn-project/end-to-end/package.json +++ b/yarn-project/end-to-end/package.json @@ -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:^", diff --git a/yarn-project/end-to-end/src/e2e_prover_node.test.ts b/yarn-project/end-to-end/src/e2e_prover_node.test.ts new file mode 100644 index 000000000000..12ae440db480 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_prover_node.test.ts @@ -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[]; + + 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); + }); +}); diff --git a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts index 4b1473323f67..d9e69a1808a0 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -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 = {}) { + return dataPath ? new SnapshotManager(testName, dataPath, config) : new MockSnapshotManager(testName, config); } export interface ISnapshotManager { @@ -73,7 +73,7 @@ class MockSnapshotManager implements ISnapshotManager { private context?: SubsystemsContext; private logger: DebugLogger; - constructor(testName: string) { + constructor(testName: string, private config: Partial = {}) { this.logger = createDebugLogger(`aztec:snapshot_manager:${testName}`); this.logger.warn(`No data path given, will not persist any snapshots.`); } @@ -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; } @@ -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 = {}) { this.livePath = join(this.dataPath, 'live', testName); this.logger = createDebugLogger(`aztec:snapshot_manager:${testName}`); } @@ -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; @@ -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 { +async function setupFromFresh( + statePath: string | undefined, + logger: Logger, + config: Partial = {}, +): Promise { 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. diff --git a/yarn-project/end-to-end/tsconfig.json b/yarn-project/end-to-end/tsconfig.json index caad50a63042..a5d5341ad0bb 100644 --- a/yarn-project/end-to-end/tsconfig.json +++ b/yarn-project/end-to-end/tsconfig.json @@ -60,6 +60,9 @@ { "path": "../prover-client" }, + { + "path": "../prover-node" + }, { "path": "../pxe" }, diff --git a/yarn-project/foundation/src/log/log_fn.ts b/yarn-project/foundation/src/log/log_fn.ts index 5dd11204e28d..f01419098867 100644 --- a/yarn-project/foundation/src/log/log_fn.ts +++ b/yarn-project/foundation/src/log/log_fn.ts @@ -1,5 +1,5 @@ /** Structured log data to include with the message. */ -export type LogData = Record; +export type LogData = Record; /** A callable logger instance. */ export type LogFn = (msg: string, data?: LogData) => void; diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index a473f7c0fe39..b1c308244570 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -179,13 +179,18 @@ export class P2PClient implements P2P { this.syncPromise = this.syncPromise.then(() => this.publishStoredTxs()); // start looking for further blocks - const blockProcess = async () => { + const processLatest = async () => { while (!this.stopping) { await this.latestBlockDownloader.getBlocks().then(this.handleLatestL2Blocks.bind(this)); + } + }; + const processProven = async () => { + while (!this.stopping) { await this.provenBlockDownloader.getBlocks().then(this.handleProvenL2Blocks.bind(this)); } }; - this.runningPromise = blockProcess(); + + this.runningPromise = Promise.all([processLatest(), processProven()]).then(() => {}); this.latestBlockDownloader.start(syncedLatestBlock); this.provenBlockDownloader.start(syncedLatestBlock); this.log.verbose(`Started block downloader from block ${syncedLatestBlock}`); @@ -193,10 +198,6 @@ export class P2PClient implements P2P { return this.syncPromise; } - private isSyncedToStartPoint(latestBlock: number, provenBlock: number) { - return latestBlock >= this.latestBlockNumberAtStart && provenBlock >= this.provenBlockNumberAtStart; - } - /** * Allows consumers to stop the instance of the P2P client. * 'ready' will now return 'false' and the running promise that keeps the client synced is interrupted. @@ -209,6 +210,7 @@ export class P2PClient implements P2P { await this.latestBlockDownloader.stop(); await this.provenBlockDownloader.stop(); this.log.debug('Stopped block downloader'); + // TODO: This running promise will never resolve if there are no new blocks to sync. await this.runningPromise; this.setCurrentState(P2PClientState.STOPPED); this.log.info('P2P client stopped.'); diff --git a/yarn-project/p2p/src/tx_pool/aztec_kv_tx_pool.ts b/yarn-project/p2p/src/tx_pool/aztec_kv_tx_pool.ts index a8b126450bbb..16fa4be870f8 100644 --- a/yarn-project/p2p/src/tx_pool/aztec_kv_tx_pool.ts +++ b/yarn-project/p2p/src/tx_pool/aztec_kv_tx_pool.ts @@ -95,7 +95,7 @@ export class AztecKVTxPool implements TxPool { } satisfies TxAddedToPoolStats); const key = txHash.toString(); - void this.#txs.set(txHash.toString(), tx.toBuffer()); + void this.#txs.set(key, tx.toBuffer()); if (!this.#minedTxs.has(key)) { // REFACTOR: Use an lmdb conditional write to avoid race conditions with this write tx void this.#pendingTxs.add(key); diff --git a/yarn-project/p2p/src/tx_pool/tx_pool_test_suite.ts b/yarn-project/p2p/src/tx_pool/tx_pool_test_suite.ts index 7031752d29a2..2c72c1afa807 100644 --- a/yarn-project/p2p/src/tx_pool/tx_pool_test_suite.ts +++ b/yarn-project/p2p/src/tx_pool/tx_pool_test_suite.ts @@ -20,6 +20,7 @@ export function describeTxPool(getTxPool: () => TxPool) { const poolTx = pool.getTxByHash(tx1.getTxHash()); expect(poolTx!.getTxHash()).toEqual(tx1.getTxHash()); expect(pool.getTxStatus(tx1.getTxHash())).toEqual('pending'); + expect(pool.getPendingTxHashes()).toEqual([tx1.getTxHash()]); }); it('Removes txs from the pool', async () => { diff --git a/yarn-project/prover-client/src/tx-prover/tx-prover.ts b/yarn-project/prover-client/src/tx-prover/tx-prover.ts index fe66a536f5e0..4e43d37bfb08 100644 --- a/yarn-project/prover-client/src/tx-prover/tx-prover.ts +++ b/yarn-project/prover-client/src/tx-prover/tx-prover.ts @@ -107,7 +107,9 @@ export class TxProver implements ProverClient { ) : undefined; - const lastBlock = await blockSource.getBlock(-1); + // Use the first block to get the initial header. Note we cannot use the latest, since it may not be proven, + // and it would have an unknown archive tree root for prover clients (who follow the proven chain). + const lastBlock = await blockSource.getBlock(0); const initialHeader = lastBlock?.header ?? (await worldStateSynchronizer.getCommitted().buildInitialHeader()); const prover = new TxProver(config, worldStateSynchronizer, telemetry, agent, initialHeader); diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index 51195cad31ef..6b5c65d20f2a 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -1,5 +1,6 @@ -import { createArchiver } from '@aztec/archiver'; -import { createDebugLogger } from '@aztec/foundation/log'; +import { type Archiver, createArchiver } from '@aztec/archiver'; +import { type AztecNode } from '@aztec/circuit-types'; +import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { createStore } from '@aztec/kv-store/utils'; import { createProverClient } from '@aztec/prover-client'; import { getL1Publisher } from '@aztec/sequencer-client'; @@ -10,18 +11,28 @@ import { createWorldStateSynchronizer } from '@aztec/world-state'; import { type ProverNodeConfig } from './config.js'; import { ProverNode } from './prover-node.js'; +import { AztecNodeTxProvider } from './tx-provider/aztec-node-tx-provider.js'; import { createTxProvider } from './tx-provider/factory.js'; /** Creates a new prover node given a config. */ export async function createProverNode( config: ProverNodeConfig, - telemetry: TelemetryClient = new NoopTelemetryClient(), - log = createDebugLogger('aztec:prover'), - storeLog = createDebugLogger('aztec:prover:lmdb'), + deps: { + telemetry?: TelemetryClient; + log?: DebugLogger; + storeLog?: DebugLogger; + aztecNodeTxProvider?: AztecNode; + archiver?: Archiver; + } = {}, ) { + const telemetry = deps.telemetry ?? new NoopTelemetryClient(); + const log = deps.log ?? createDebugLogger('aztec:prover'); + const storeLog = deps.storeLog ?? createDebugLogger('aztec:prover:lmdb'); + const store = await createStore(config, config.l1Contracts.rollupAddress, storeLog); - const archiver = await createArchiver(config, store, telemetry, { blockUntilSync: true }); + const archiver = deps.archiver ?? (await createArchiver(config, store, telemetry, { blockUntilSync: true })); + log.verbose(`Created archiver and synced to block ${await archiver.getBlockNumber()}`); const worldStateConfig = { ...config, worldStateProvenBlocksOnly: true }; const worldStateSynchronizer = await createWorldStateSynchronizer(worldStateConfig, store, archiver); @@ -37,7 +48,9 @@ export async function createProverNode( const latestWorldState = worldStateSynchronizer.getLatest(); const publicProcessorFactory = new PublicProcessorFactory(latestWorldState, archiver, simulationProvider, telemetry); - const txProvider = createTxProvider(config); + const txProvider = deps.aztecNodeTxProvider + ? new AztecNodeTxProvider(deps.aztecNodeTxProvider) + : createTxProvider(config); return new ProverNode(prover!, publicProcessorFactory, publisher, archiver, txProvider); } diff --git a/yarn-project/prover-node/src/index.ts b/yarn-project/prover-node/src/index.ts index e69de29bb2d1..04c0eb2f1f75 100644 --- a/yarn-project/prover-node/src/index.ts +++ b/yarn-project/prover-node/src/index.ts @@ -0,0 +1,3 @@ +export * from './factory.js'; +export * from './config.js'; +export * from './prover-node.js'; diff --git a/yarn-project/prover-node/src/job/block-proving-job.ts b/yarn-project/prover-node/src/job/block-proving-job.ts index 64e6d7af0ebb..91b9a0397c0d 100644 --- a/yarn-project/prover-node/src/job/block-proving-job.ts +++ b/yarn-project/prover-node/src/job/block-proving-job.ts @@ -3,6 +3,7 @@ import { EmptyTxValidator, type L2Block, type L2BlockSource, + PROVING_STATUS, type ProcessedTx, type Tx, type TxHash, @@ -46,19 +47,37 @@ export class BlockProvingJob { const block = await this.getBlock(blockNumber); const globalVariables = block.header.globalVariables; const txHashes = block.body.txEffects.map(tx => tx.txHash); + const txCount = block.body.numberOfTxsIncludingPadded; const l1ToL2Messages: Fr[] = []; // TODO: grab L1 to L2 messages for this block - this.log.debug(`Starting block processing`, { blockNumber: block.number, blockHash: block.hash().toString() }); - await this.prover.startNewBlock(txHashes.length, globalVariables, l1ToL2Messages); + this.log.verbose(`Starting block processing`, { + number: block.number, + blockHash: block.hash().toString(), + lastArchive: block.header.lastArchive.root, + noteHashTreeRoot: block.header.state.partial.noteHashTree.root, + nullifierTreeRoot: block.header.state.partial.nullifierTree.root, + publicDataTreeRoot: block.header.state.partial.publicDataTree.root, + historicalHeader: historicalHeader?.hash(), + ...globalVariables, + }); + const provingTicket = await this.prover.startNewBlock(txCount, globalVariables, l1ToL2Messages); const publicProcessor = await this.publicProcessorFactory.create(historicalHeader, globalVariables); const txs = await this.getTxs(txHashes); - const txCount = block.body.numberOfTxsIncludingPadded; await this.processTxs(publicProcessor, txs, txCount); - this.log.debug(`Processed all txs for block`, { blockNumber: block.number, blockHash: block.hash().toString() }); + this.log.verbose(`Processed all txs for block`, { + blockNumber: block.number, + blockHash: block.hash().toString(), + }); + await this.prover.setBlockCompleted(); + const result = await provingTicket.provingPromise; + if (result.status === PROVING_STATUS.FAILURE) { + throw new Error(`Block proving failed: ${result.reason}`); + } + historicalHeader = block.header; } diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 6e10ac7501f9..d41310a3923e 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -50,6 +50,7 @@ export class Sequencer { private allowedInSetup: AllowedElement[] = []; private allowedInTeardown: AllowedElement[] = []; private maxBlockSizeInBytes: number = 1024 * 1024; + private skipSubmitProofs: boolean = false; public readonly tracer: Tracer; @@ -102,6 +103,10 @@ export class Sequencer { if (config.allowedInTeardown) { this.allowedInTeardown = config.allowedInTeardown; } + // TODO(palla/prover) This flag should not be needed: the sequencer should be initialized with a blockprover + // that does not return proofs at all (just simulates circuits), and use that to determine whether to submit + // proofs or not. + this.skipSubmitProofs = !!config.skipSubmitProofs; } /** @@ -175,7 +180,7 @@ export class Sequencer { // Do not go forward with new block if not my turn if (!(await this.publisher.isItMyTurnToSubmit(newBlockNumber))) { - this.log.verbose('Not my turn to submit block'); + this.log.debug('Not my turn to submit block'); return; } @@ -184,6 +189,9 @@ export class Sequencer { // Get txs to build the new block const pendingTxs = this.p2pClient.getTxs('pending'); if (pendingTxs.length < this.minTxsPerBLock) { + this.log.debug( + `Not creating block because there are not enough txs in the pool (got ${pendingTxs.length} min ${this.minTxsPerBLock})`, + ); return; } this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`); @@ -309,10 +317,10 @@ export class Sequencer { await this.publishL2Block(block); this.log.info(`Submitted rollup block ${block.number} with ${processedTxs.length} transactions`); - // Submit the proof if we have configured this sequencer to run with a prover. + // Submit the proof if we have configured this sequencer to run with an actual prover. // This is temporary while we submit one proof per block, but will have to change once we // move onto proving batches of multiple blocks at a time. - if (aggregationObject && proof) { + if (aggregationObject && proof && !this.skipSubmitProofs) { await this.publisher.submitProof(block.header, block.archive.root, aggregationObject, proof); this.log.info(`Submitted proof for block ${block.number}`); } @@ -378,7 +386,15 @@ export class Sequencer { this.l1ToL2MessageSource.getBlockNumber(), ]); const min = Math.min(...syncedBlocks); - return min >= this.lastPublishedBlock; + const [worldState, p2p, l2BlockSource, l1ToL2MessageSource] = syncedBlocks; + const result = min >= this.lastPublishedBlock; + this.log.debug(`Sync check to last published block ${this.lastPublishedBlock} ${result ? 'succeeded' : 'failed'}`, { + worldState, + p2p, + l2BlockSource, + l1ToL2MessageSource, + }); + return result; } get coinbase(): EthAddress { diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index 2277545a7595..f8281f9ae6d2 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -128,6 +128,12 @@ export class PublicProcessor { const [processedTx, returnValues] = !tx.hasPublicCalls() ? [makeProcessedTx(tx, tx.data.toKernelCircuitPublicInputs(), [])] : await this.processTxWithPublicCalls(tx); + this.log.debug(`Processed tx`, { + txHash: processedTx.hash, + historicalHeaderHash: processedTx.data.constants.historicalHeader.hash(), + blockNumber: processedTx.data.constants.globalVariables.blockNumber, + lastArchiveRoot: processedTx.data.constants.historicalHeader.lastArchive.root, + }); // Set fee payment update request into the processed tx processedTx.finalPublicDataUpdateRequests = await this.createFinalDataUpdateRequests(processedTx); diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts index 579a0cbf6ba3..d7ee9504f71d 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts @@ -44,7 +44,7 @@ export class ServerWorldStateSynchronizer implements WorldStateSynchronizer { store: AztecKVStore, private merkleTreeDb: MerkleTrees, private l2BlockSource: L2BlockSource & L1ToL2MessageSource, - config: WorldStateConfig, + private config: WorldStateConfig, private log = createDebugLogger('aztec:world_state'), ) { this.blockNumber = store.openSingleton('world_state_synch_last_block_number'); @@ -76,7 +76,9 @@ export class ServerWorldStateSynchronizer implements WorldStateSynchronizer { } // get the current latest block number - this.latestBlockNumberAtStart = await this.l2BlockSource.getBlockNumber(); + this.latestBlockNumberAtStart = await (this.config.worldStateProvenBlocksOnly + ? this.l2BlockSource.getProvenBlockNumber() + : this.l2BlockSource.getBlockNumber()); const blockToDownloadFrom = this.currentL2BlockNum + 1; diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 46a36131973b..26bf99596bff 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -427,6 +427,7 @@ __metadata: "@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:^" @@ -802,7 +803,7 @@ __metadata: languageName: unknown linkType: soft -"@aztec/prover-node@workspace:prover-node": +"@aztec/prover-node@workspace:^, @aztec/prover-node@workspace:prover-node": version: 0.0.0-use.local resolution: "@aztec/prover-node@workspace:prover-node" dependencies: