diff --git a/yarn-project/aztec/tsconfig.json b/yarn-project/aztec/tsconfig.json index ff9caf3d51b6..457e0776787e 100644 --- a/yarn-project/aztec/tsconfig.json +++ b/yarn-project/aztec/tsconfig.json @@ -18,6 +18,9 @@ { "path": "../aztec.js" }, + { + "path": "../builder" + }, { "path": "../circuit-types" }, @@ -39,9 +42,6 @@ { "path": "../l1-artifacts" }, - { - "path": "../builder" - }, { "path": "../noir-contracts.js" }, diff --git a/yarn-project/circuit-types/src/interfaces/index.ts b/yarn-project/circuit-types/src/interfaces/index.ts index 25d6f63bd82f..71bfeeda4a2e 100644 --- a/yarn-project/circuit-types/src/interfaces/index.ts +++ b/yarn-project/circuit-types/src/interfaces/index.ts @@ -6,4 +6,5 @@ export * from './configs.js'; export * from './nullifier_tree.js'; export * from './public_data_tree.js'; export * from './prover-client.js'; +export * from './proving-job.js'; export * from './block-prover.js'; diff --git a/yarn-project/prover-client/src/prover-pool/proving-request.ts b/yarn-project/circuit-types/src/interfaces/proving-job.ts similarity index 82% rename from yarn-project/prover-client/src/prover-pool/proving-request.ts rename to yarn-project/circuit-types/src/interfaces/proving-job.ts index ef98e7e18cd2..503de6bd820a 100644 --- a/yarn-project/prover-client/src/prover-pool/proving-request.ts +++ b/yarn-project/circuit-types/src/interfaces/proving-job.ts @@ -1,4 +1,3 @@ -import { type PublicKernelNonTailRequest, type PublicKernelTailRequest } from '@aztec/circuit-types'; import { type BaseOrMergeRollupPublicInputs, type BaseParityInputs, @@ -13,6 +12,13 @@ import { type RootRollupPublicInputs, } from '@aztec/circuits.js'; +import type { PublicKernelNonTailRequest, PublicKernelTailRequest } from '../tx/processed_tx.js'; + +export type ProvingJob = { + id: string; + request: T; +}; + export enum ProvingRequestType { PUBLIC_VM, @@ -79,3 +85,11 @@ export type ProvingRequestPublicInputs = { }; export type ProvingRequestResult = [ProvingRequestPublicInputs[T], Proof]; + +export interface ProvingJobSource { + getProvingJob(): Promise | null>; + + resolveProvingJob(jobId: string, result: ProvingRequestResult): Promise; + + rejectProvingJob(jobId: string, reason: Error): Promise; +} diff --git a/yarn-project/end-to-end/package.json b/yarn-project/end-to-end/package.json index 524ecfedde82..e5e5c0ba6bab 100644 --- a/yarn-project/end-to-end/package.json +++ b/yarn-project/end-to-end/package.json @@ -110,7 +110,14 @@ "@swc/jest" ] }, - "reporters": [["default", {"summaryThreshold": 9999}]], + "reporters": [ + [ + "default", + { + "summaryThreshold": 9999 + } + ] + ], "moduleNameMapper": { "^(\\.{1,2}/.*)\\.[cm]?js$": "$1" }, diff --git a/yarn-project/noir-protocol-circuits-types/tsconfig.json b/yarn-project/noir-protocol-circuits-types/tsconfig.json index b932dd11a804..632b9eed7788 100644 --- a/yarn-project/noir-protocol-circuits-types/tsconfig.json +++ b/yarn-project/noir-protocol-circuits-types/tsconfig.json @@ -7,13 +7,13 @@ }, "references": [ { - "path": "../circuits.js" + "path": "../builder" }, { - "path": "../foundation" + "path": "../circuits.js" }, { - "path": "../builder" + "path": "../foundation" }, { "path": "../types" diff --git a/yarn-project/prover-client/src/mocks/test_context.ts b/yarn-project/prover-client/src/mocks/test_context.ts index 0fe75860cc0b..75abb24ada64 100644 --- a/yarn-project/prover-client/src/mocks/test_context.ts +++ b/yarn-project/prover-client/src/mocks/test_context.ts @@ -21,7 +21,8 @@ import * as fs from 'fs/promises'; import { type MockProxy, mock } from 'jest-mock-extended'; import { ProvingOrchestrator } from '../orchestrator/orchestrator.js'; -import { CircuitProverAgent } from '../prover-pool/circuit-prover-agent.js'; +import { MemoryProvingQueue } from '../prover-pool/memory-proving-queue.js'; +import { ProverAgent } from '../prover-pool/prover-agent.js'; import { ProverPool } from '../prover-pool/prover-pool.js'; import { type BBProverConfig } from '../prover/bb_prover.js'; import { type CircuitProver } from '../prover/interface.js'; @@ -87,10 +88,11 @@ export class TestContext { localProver = await createProver(bbConfig); } - const proverPool = new ProverPool(proverCount, i => new CircuitProverAgent(localProver, 10, `${i}`)); - const orchestrator = new ProvingOrchestrator(actualDb, proverPool.queue); + const queue = new MemoryProvingQueue(); + const proverPool = new ProverPool(proverCount, i => new ProverAgent(localProver, 10, `${i}`)); + const orchestrator = new ProvingOrchestrator(actualDb, queue); - await proverPool.start(); + await proverPool.start(queue); return new this( publicExecutor, diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator.ts b/yarn-project/prover-client/src/orchestrator/orchestrator.ts index 743d1319d32b..84e5554361da 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator.ts @@ -3,6 +3,7 @@ import { L2Block, MerkleTreeId, type ProcessedTx, + type PublicKernelRequest, PublicKernelType, type TxEffect, toTxEffect, @@ -20,11 +21,13 @@ import { type BaseRollupInputs, Fr, type GlobalVariables, + type KernelCircuitPublicInputs, L1_TO_L2_MSG_SUBTREE_HEIGHT, L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NUM_BASE_PARITY_PER_ROOT_PARITY, type Proof, + type PublicKernelCircuitPublicInputs, RootParityInput, RootParityInputs, makeEmptyProof, @@ -34,17 +37,13 @@ import { padArrayEnd } from '@aztec/foundation/collection'; import { createDebugLogger } from '@aztec/foundation/log'; import { promiseWithResolvers } from '@aztec/foundation/promise'; import { type Tuple } from '@aztec/foundation/serialize'; +import { sleep } from '@aztec/foundation/sleep'; import { Timer } from '@aztec/foundation/timer'; import { type MerkleTreeOperations } from '@aztec/world-state'; import { inspect } from 'util'; -import { type ProvingQueue } from '../prover-pool/proving-queue.js'; -import { - type ProvingRequest, - type ProvingRequestPublicInputs, - ProvingRequestType, -} from '../prover-pool/proving-request.js'; +import { type CircuitProver } from '../prover/interface.js'; import { buildBaseRollupInput, createMergeRollupInputs, @@ -81,7 +80,7 @@ const KernelTypesWithoutFunctions: Set = new Set( + private deferredProving( provingState: ProvingState | undefined, - request: T, - callback: (output: ProvingRequestPublicInputs[T['type']], proof: Proof) => void | Promise, + request: () => Promise, + callback: (result: T, durationMs: number) => void | Promise, ) { if (!provingState?.verifyState()) { - logger.debug(`Not enqueuing job type ${ProvingRequestType[request.type]}, state no longer valid`); + logger.debug(`Not enqueuing job, state no longer valid`); return; } // We use a 'safeJob'. We don't want promise rejections in the proving pool, we want to capture the error here @@ -315,32 +313,17 @@ export class ProvingOrchestrator { const safeJob = async () => { try { const timer = new Timer(); - const [publicInputs, proof] = await this.queue.prove(request); + const result = await request(); const duration = timer.ms(); - const inputSize = 'toBuffer' in request.inputs ? request.inputs.toBuffer().length : 0; - const outputSize = 'toBuffer' in publicInputs ? publicInputs.toBuffer().length : 0; - const circuitName = this.getCircuitNameFromRequest(request); - const stats: CircuitSimulationStats | undefined = circuitName - ? { - eventName: 'circuit-simulation', - circuitName, - duration, - inputSize, - outputSize, - } - : undefined; - - logger.debug(`Simulated ${ProvingRequestType[request.type]} circuit duration=${duration}ms`, stats); - if (!provingState?.verifyState()) { - logger.debug(`State no longer valid, discarding result of job type ${ProvingRequestType[request.type]}`); + logger.debug(`State no longer valid, discarding result`); return; } - await callback(publicInputs, proof); + await callback(result, duration); } catch (err) { - logger.error(`Error thrown when proving job type ${ProvingRequestType[request.type]}: ${err}`); + logger.error(`Error thrown when proving job`); provingState!.reject(`${err}`); } }; @@ -349,38 +332,34 @@ export class ProvingOrchestrator { setImmediate(safeJob); } - private getCircuitNameFromRequest(request: ProvingRequest): CircuitSimulationStats['circuitName'] | null { - switch (request.type) { - case ProvingRequestType.PUBLIC_VM: - return null; - case ProvingRequestType.PUBLIC_KERNEL_NON_TAIL: - switch (request.kernelType) { - case PublicKernelType.SETUP: - return 'public-kernel-setup'; - case PublicKernelType.APP_LOGIC: - return 'public-kernel-app-logic'; - case PublicKernelType.TEARDOWN: - return 'public-kernel-teardown'; - default: - return null; - } - case ProvingRequestType.PUBLIC_KERNEL_TAIL: - switch (request.kernelType) { - case PublicKernelType.TAIL: - return 'public-kernel-tail'; - default: - return null; + private emitCircuitSimulationStats( + circuitName: CircuitSimulationStats['circuitName'] | null, + inputSize: number, + outputSize: number, + duration: number, + ) { + const stats: CircuitSimulationStats | undefined = circuitName + ? { + eventName: 'circuit-simulation', + circuitName, + duration, + inputSize, + outputSize, } - case ProvingRequestType.BASE_ROLLUP: - return 'base-rollup'; - case ProvingRequestType.MERGE_ROLLUP: - return 'merge-rollup'; - case ProvingRequestType.ROOT_ROLLUP: - return 'root-rollup'; - case ProvingRequestType.BASE_PARITY: - return 'base-parity'; - case ProvingRequestType.ROOT_PARITY: - return 'root-parity'; + : undefined; + logger.debug(`Simulated ${circuitName} circuit duration=${duration}ms`, stats); + } + + private getPublicKernelCircuitName(request: PublicKernelRequest) { + switch (request.type) { + case PublicKernelType.SETUP: + return 'public-kernel-setup'; + case PublicKernelType.APP_LOGIC: + return 'public-kernel-app-logic'; + case PublicKernelType.TEARDOWN: + return 'public-kernel-teardown'; + case PublicKernelType.TAIL: + return 'public-kernel-tail'; default: return null; } @@ -458,13 +437,17 @@ export class ProvingOrchestrator { return; } - this.enqueueJob( + this.deferredProving( provingState, - { - inputs: tx.baseRollupInputs, - type: ProvingRequestType.BASE_ROLLUP, - }, - (publicInputs, proof) => { + () => this.prover.getBaseRollupProof(tx.baseRollupInputs), + ([publicInputs, proof], duration) => { + this.emitCircuitSimulationStats( + 'base-rollup', + tx.baseRollupInputs.toBuffer().length, + publicInputs.toBuffer().length, + duration, + ); + validatePartialState(publicInputs.end, tx.treeSnapshots); const currentLevel = provingState.numMergeLevels + 1n; this.storeAndExecuteNextMergeLevel(provingState, currentLevel, index, [publicInputs, proof]); @@ -485,13 +468,16 @@ export class ProvingOrchestrator { [mergeInputData.inputs[1]!, mergeInputData.proofs[1]!], ); - this.enqueueJob( + this.deferredProving( provingState, - { - type: ProvingRequestType.MERGE_ROLLUP, - inputs, - }, - (publicInputs, proof) => { + () => this.prover.getMergeRollupProof(inputs), + ([publicInputs, proof], duration) => { + this.emitCircuitSimulationStats( + 'merge-rollup', + inputs.toBuffer().length, + publicInputs.toBuffer().length, + duration, + ); this.storeAndExecuteNextMergeLevel(provingState, level, index, [publicInputs, proof]); }, ); @@ -518,13 +504,17 @@ export class ProvingOrchestrator { this.db, ); - this.enqueueJob( + this.deferredProving( provingState, - { - type: ProvingRequestType.ROOT_ROLLUP, - inputs, - }, - (publicInputs, proof) => { + () => this.prover.getRootRollupProof(inputs), + ([publicInputs, proof], duration) => { + this.emitCircuitSimulationStats( + 'root-rollup', + inputs.toBuffer().length, + publicInputs.toBuffer().length, + duration, + ); + provingState.rootRollupPublicInputs = publicInputs; provingState.finalProof = proof; @@ -539,13 +529,16 @@ export class ProvingOrchestrator { // Executes the base parity circuit and stores the intermediate state for the root parity circuit // Enqueues the root parity circuit if all inputs are available private enqueueBaseParityCircuit(provingState: ProvingState, inputs: BaseParityInputs, index: number) { - this.enqueueJob( + this.deferredProving( provingState, - { - inputs, - type: ProvingRequestType.BASE_PARITY, - }, - (publicInputs, proof) => { + () => this.prover.getBaseParityProof(inputs), + ([publicInputs, proof], duration) => { + this.emitCircuitSimulationStats( + 'base-parity', + inputs.toBuffer().length, + publicInputs.toBuffer().length, + duration, + ); const rootInput = new RootParityInput(proof, publicInputs); provingState.setRootParityInputs(rootInput, index); const rootParityInputs = new RootParityInputs( @@ -559,13 +552,16 @@ export class ProvingOrchestrator { // Runs the root parity circuit ans stored the outputs // Enqueues the root rollup proof if all inputs are available private enqueueRootParityCircuit(provingState: ProvingState | undefined, inputs: RootParityInputs) { - this.enqueueJob( + this.deferredProving( provingState, - { - type: ProvingRequestType.ROOT_PARITY, - inputs, - }, - async (publicInputs, proof) => { + () => this.prover.getRootParityProof(inputs), + async ([publicInputs, proof], duration) => { + this.emitCircuitSimulationStats( + 'root-parity', + inputs.toBuffer().length, + publicInputs.toBuffer().length, + duration, + ); const rootInput = new RootParityInput(proof, publicInputs); provingState!.finalRootParityInput = rootInput; await this.checkAndEnqueueRootRollup(provingState); @@ -629,13 +625,10 @@ export class ProvingOrchestrator { // Prove the VM if this is a kernel that requires one if (!KernelTypesWithoutFunctions.has(publicFunction.publicKernelRequest.type)) { // Just sleep for a small amount of time - this.enqueueJob( + this.deferredProving( provingState, - { - type: ProvingRequestType.PUBLIC_VM, - inputs: {}, - }, - (_1, _2) => { + () => sleep(100), + () => { logger.debug(`Proven VM for function index ${functionIndex} of tx index ${txIndex}`); this.checkAndEnqueuePublicKernel(provingState, txIndex, functionIndex); }, @@ -672,30 +665,46 @@ export class ProvingOrchestrator { } const txProvingState = provingState.getTxProvingState(txIndex); - const provingRequest = txProvingState.getPublicFunctionState(functionIndex).provingRequest; - - this.enqueueJob(provingState, provingRequest, (_, proof) => { - logger.debug(`Proven ${PublicKernelType[provingRequest.type]} at index ${functionIndex} for tx index ${txIndex}`); - const nextKernelRequest = txProvingState.getNextPublicKernelFromKernelProof(functionIndex, proof); - // What's the status of the next kernel? - if (nextKernelRequest.code === TX_PROVING_CODE.NOT_READY) { - // Must be waiting on a VM proof - return; - } + const request = txProvingState.getPublicFunctionState(functionIndex).publicKernelRequest; - if (nextKernelRequest.code === TX_PROVING_CODE.COMPLETED) { - // We must have completed all public function proving, we now move to the base rollup - logger.debug(`Public functions completed for tx ${txIndex} enqueueing base rollup`); - this.enqueueBaseRollup(provingState, BigInt(txIndex), txProvingState); - return; - } - // There must be another kernel ready to be proven - if (nextKernelRequest.function === undefined) { - // Should not be possible - throw new Error(`Error occurred, public function request undefined after kernel proof completed`); - } + this.deferredProving( + provingState, + (): Promise<[KernelCircuitPublicInputs | PublicKernelCircuitPublicInputs, Proof]> => { + if (request.type === PublicKernelType.TAIL) { + return this.prover.getPublicTailProof(request); + } else { + return this.prover.getPublicKernelProof(request); + } + }, + ([_, proof], duration) => { + this.emitCircuitSimulationStats( + this.getPublicKernelCircuitName(request), + request.inputs.toBuffer().length, + 0, + duration, + ); - this.enqueuePublicKernel(provingState, txIndex, functionIndex + 1); - }); + const nextKernelRequest = txProvingState.getNextPublicKernelFromKernelProof(functionIndex, proof); + // What's the status of the next kernel? + if (nextKernelRequest.code === TX_PROVING_CODE.NOT_READY) { + // Must be waiting on a VM proof + return; + } + + if (nextKernelRequest.code === TX_PROVING_CODE.COMPLETED) { + // We must have completed all public function proving, we now move to the base rollup + logger.debug(`Public functions completed for tx ${txIndex} enqueueing base rollup`); + this.enqueueBaseRollup(provingState, BigInt(txIndex), txProvingState); + return; + } + // There must be another kernel ready to be proven + if (nextKernelRequest.function === undefined) { + // Should not be possible + throw new Error(`Error occurred, public function request undefined after kernel proof completed`); + } + + this.enqueuePublicKernel(provingState, txIndex, functionIndex + 1); + }, + ); } } diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts b/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts index d8b7f1e6c690..5b06d8418fe9 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts @@ -6,7 +6,8 @@ import { jest } from '@jest/globals'; import { makeEmptyProcessedTestTx } from '../mocks/fixtures.js'; import { TestContext } from '../mocks/test_context.js'; -import { CircuitProverAgent } from '../prover-pool/circuit-prover-agent.js'; +import { MemoryProvingQueue } from '../prover-pool/memory-proving-queue.js'; +import { ProverAgent } from '../prover-pool/prover-agent.js'; import { ProverPool } from '../prover-pool/prover-pool.js'; import { type CircuitProver } from '../prover/index.js'; import { TestCircuitProver } from '../prover/test_circuit_prover.js'; @@ -29,12 +30,14 @@ describe('prover/orchestrator/failures', () => { describe('error handling', () => { let mockProver: CircuitProver; + let queue: MemoryProvingQueue; beforeEach(async () => { mockProver = new TestCircuitProver(new WASMSimulator()); - proverPool = new ProverPool(1, i => new CircuitProverAgent(mockProver, 10, `${i}`)); - orchestrator = new ProvingOrchestrator(context.actualDb, proverPool.queue); - await proverPool.start(); + proverPool = new ProverPool(1, i => new ProverAgent(mockProver, 10, `${i}`)); + queue = new MemoryProvingQueue(); + orchestrator = new ProvingOrchestrator(context.actualDb, queue); + await proverPool.start(queue); }); afterEach(async () => { diff --git a/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts b/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts index 73339d00d6d5..ab8802d2fccc 100644 --- a/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts +++ b/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts @@ -1,8 +1,6 @@ import { type MerkleTreeId, type ProcessedTx, type PublicKernelRequest, PublicKernelType } from '@aztec/circuit-types'; import { type AppendOnlyTreeSnapshot, type BaseRollupInputs, type Proof } from '@aztec/circuits.js'; -import { type ProvingRequest, ProvingRequestType } from '../prover-pool/proving-request.js'; - export enum TX_PROVING_CODE { NOT_READY, READY, @@ -14,7 +12,6 @@ export type PublicFunction = { previousProofType: PublicKernelType; previousKernelProof: Proof | undefined; publicKernelRequest: PublicKernelRequest; - provingRequest: ProvingRequest; }; // Type encapsulating the instruction to the orchestrator as to what @@ -40,24 +37,11 @@ export class TxProvingState { let previousKernelProof: Proof | undefined = processedTx.proof; let previousProofType = PublicKernelType.NON_PUBLIC; for (const kernelRequest of processedTx.publicKernelRequests) { - const provingRequest: ProvingRequest = - kernelRequest.type === PublicKernelType.TAIL - ? { - type: ProvingRequestType.PUBLIC_KERNEL_TAIL, - kernelType: kernelRequest.type, - inputs: kernelRequest.inputs, - } - : { - type: ProvingRequestType.PUBLIC_KERNEL_NON_TAIL, - kernelType: kernelRequest.type, - inputs: kernelRequest.inputs, - }; const publicFunction: PublicFunction = { vmProof: undefined, previousProofType, previousKernelProof, publicKernelRequest: kernelRequest, - provingRequest, }; this.publicFunctions.push(publicFunction); previousKernelProof = undefined; diff --git a/yarn-project/prover-client/src/prover-pool/circuit-prover-agent.ts b/yarn-project/prover-client/src/prover-pool/circuit-prover-agent.ts deleted file mode 100644 index 76553512eb24..000000000000 --- a/yarn-project/prover-client/src/prover-pool/circuit-prover-agent.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { makeEmptyProof } from '@aztec/circuits.js'; -import { createDebugLogger } from '@aztec/foundation/log'; -import { RunningPromise } from '@aztec/foundation/running-promise'; -import { elapsed } from '@aztec/foundation/timer'; - -import { type CircuitProver } from '../prover/interface.js'; -import { type ProvingAgent } from './prover-agent.js'; -import { type ProvingQueueConsumer } from './proving-queue.js'; -import { type ProvingRequest, type ProvingRequestResult, ProvingRequestType } from './proving-request.js'; - -export class CircuitProverAgent implements ProvingAgent { - private runningPromise?: RunningPromise; - - constructor( - /** The prover implementation to defer jobs to */ - private prover: CircuitProver, - /** How long to wait between jobs */ - private intervalMs = 10, - /** A name for this agent (if there are multiple agents running) */ - name = '', - private log = createDebugLogger('aztec:prover-client:prover-pool:agent' + (name ? `:${name}` : '')), - ) {} - - start(queue: ProvingQueueConsumer): void { - if (this.runningPromise) { - throw new Error('Agent is already running'); - } - - this.runningPromise = new RunningPromise(async () => { - const job = await queue.getProvingJob(); - if (!job) { - return; - } - - try { - const [time, result] = await elapsed(() => this.work(job.request)); - await queue.resolveProvingJob(job.id, result); - this.log.info( - `Processed proving job id=${job.id} type=${ProvingRequestType[job.request.type]} duration=${time}ms`, - ); - } catch (err) { - this.log.error( - `Error processing proving job id=${job.id} type=${ProvingRequestType[job.request.type]}: ${err}`, - ); - await queue.rejectProvingJob(job.id, err as Error); - } - }, this.intervalMs); - - this.runningPromise.start(); - } - - async stop(): Promise { - if (!this.runningPromise) { - throw new Error('Agent is not running'); - } - - await this.runningPromise.stop(); - this.runningPromise = undefined; - } - - private work(request: ProvingRequest): Promise> { - const { type, inputs } = request; - switch (type) { - case ProvingRequestType.PUBLIC_VM: { - return Promise.resolve([{}, makeEmptyProof()] as const); - } - - case ProvingRequestType.PUBLIC_KERNEL_NON_TAIL: { - return this.prover.getPublicKernelProof({ - type: request.kernelType, - inputs, - }); - } - - case ProvingRequestType.PUBLIC_KERNEL_TAIL: { - return this.prover.getPublicTailProof({ - type: request.kernelType, - inputs, - }); - } - - case ProvingRequestType.BASE_ROLLUP: { - return this.prover.getBaseRollupProof(inputs); - } - - case ProvingRequestType.MERGE_ROLLUP: { - return this.prover.getMergeRollupProof(inputs); - } - - case ProvingRequestType.ROOT_ROLLUP: { - return this.prover.getRootRollupProof(inputs); - } - - case ProvingRequestType.BASE_PARITY: { - return this.prover.getBaseParityProof(inputs); - } - - case ProvingRequestType.ROOT_PARITY: { - return this.prover.getRootParityProof(inputs); - } - - default: { - return Promise.reject(new Error(`Invalid proof request type: ${type}`)); - } - } - } -} diff --git a/yarn-project/prover-client/src/prover-pool/index.ts b/yarn-project/prover-client/src/prover-pool/index.ts new file mode 100644 index 000000000000..eaae01068bcf --- /dev/null +++ b/yarn-project/prover-client/src/prover-pool/index.ts @@ -0,0 +1,3 @@ +export * from './prover-agent.js'; +export * from './memory-proving-queue.js'; +export * from './prover-pool.js'; diff --git a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.test.ts b/yarn-project/prover-client/src/prover-pool/memory-proving-queue.test.ts deleted file mode 100644 index cea156acfc65..000000000000 --- a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - makeBaseParityInputs, - makeBaseRollupInputs, - makeParityPublicInputs, - makeProof, -} from '@aztec/circuits.js/testing'; - -import { MemoryProvingQueue } from './memory-proving-queue.js'; -import { type ProvingQueue } from './proving-queue.js'; -import { ProvingRequestType } from './proving-request.js'; - -describe('MemoryProvingQueue', () => { - let queue: ProvingQueue; - - beforeEach(() => { - queue = new MemoryProvingQueue(); - }); - - it('returns jobs in order', async () => { - void queue.prove({ - type: ProvingRequestType.BASE_PARITY, - inputs: makeBaseParityInputs(), - }); - - void queue.prove({ - type: ProvingRequestType.BASE_ROLLUP, - inputs: makeBaseRollupInputs(), - }); - - const job1 = await queue.getProvingJob(); - expect(job1?.request.type).toEqual(ProvingRequestType.BASE_PARITY); - - const job2 = await queue.getProvingJob(); - expect(job2?.request.type).toEqual(ProvingRequestType.BASE_ROLLUP); - }); - - it('returns null when no jobs are available', async () => { - await expect(queue.getProvingJob({ timeoutSec: 0 })).resolves.toBeNull(); - }); - - it('notifies of completion', async () => { - const inputs = makeBaseParityInputs(); - const promise = queue.prove({ - inputs, - type: ProvingRequestType.BASE_PARITY, - }); - - const job = await queue.getProvingJob(); - expect(job?.request.inputs).toEqual(inputs); - - const publicInputs = makeParityPublicInputs(); - const proof = makeProof(); - await queue.resolveProvingJob(job!.id, [publicInputs, proof]); - await expect(promise).resolves.toEqual([publicInputs, proof]); - }); - - it('notifies of errors', async () => { - const inputs = makeBaseParityInputs(); - const promise = queue.prove({ - inputs, - type: ProvingRequestType.BASE_PARITY, - }); - const job = await queue.getProvingJob(); - expect(job?.request.inputs).toEqual(inputs); - - const error = new Error('test error'); - await queue.rejectProvingJob(job!.id, error); - await expect(promise).rejects.toEqual(error); - }); -}); diff --git a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts b/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts index 155b548ff2e1..ce8fd7f20576 100644 --- a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts +++ b/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts @@ -1,17 +1,38 @@ +import { + type ProvingJob, + type ProvingJobSource, + type ProvingRequest, + type ProvingRequestResult, + ProvingRequestType, + type PublicKernelNonTailRequest, + type PublicKernelTailRequest, +} from '@aztec/circuit-types'; +import type { + BaseOrMergeRollupPublicInputs, + BaseParityInputs, + BaseRollupInputs, + KernelCircuitPublicInputs, + MergeRollupInputs, + ParityPublicInputs, + Proof, + PublicKernelCircuitPublicInputs, + RootParityInputs, + RootRollupInputs, + RootRollupPublicInputs, +} from '@aztec/circuits.js'; import { TimeoutError } from '@aztec/foundation/error'; import { MemoryFifo } from '@aztec/foundation/fifo'; import { createDebugLogger } from '@aztec/foundation/log'; import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise'; -import { type ProvingJob, type ProvingQueue } from './proving-queue.js'; -import { type ProvingRequest, type ProvingRequestResult, ProvingRequestType } from './proving-request.js'; +import { type CircuitProver } from '../prover/interface.js'; type ProvingJobWithResolvers = { id: string; request: T; } & PromiseWithResolvers>; -export class MemoryProvingQueue implements ProvingQueue { +export class MemoryProvingQueue implements CircuitProver, ProvingJobSource { private jobId = 0; private log = createDebugLogger('aztec:prover-client:prover-pool:queue'); private queue = new MemoryFifo(); @@ -60,7 +81,7 @@ export class MemoryProvingQueue implements ProvingQueue { return Promise.resolve(); } - prove(request: T): Promise> { + private enqueue(request: T): Promise> { const { promise, resolve, reject } = promiseWithResolvers>(); const item: ProvingJobWithResolvers = { id: String(this.jobId++), @@ -79,8 +100,90 @@ export class MemoryProvingQueue implements ProvingQueue { return promise; } - cancelAll(): void { - this.queue.cancel(); - this.queue = new MemoryFifo(); + /** + * Creates a proof for the given input. + * @param input - Input to the circuit. + */ + getBaseParityProof(inputs: BaseParityInputs): Promise<[ParityPublicInputs, Proof]> { + return this.enqueue({ + type: ProvingRequestType.BASE_PARITY, + inputs, + }); + } + + /** + * Creates a proof for the given input. + * @param input - Input to the circuit. + */ + getRootParityProof(inputs: RootParityInputs): Promise<[ParityPublicInputs, Proof]> { + return this.enqueue({ + type: ProvingRequestType.ROOT_PARITY, + inputs, + }); + } + + /** + * Creates a proof for the given input. + * @param input - Input to the circuit. + */ + getBaseRollupProof(input: BaseRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { + return this.enqueue({ + type: ProvingRequestType.BASE_ROLLUP, + inputs: input, + }); + } + + /** + * Creates a proof for the given input. + * @param input - Input to the circuit. + */ + getMergeRollupProof(input: MergeRollupInputs): Promise<[BaseOrMergeRollupPublicInputs, Proof]> { + return this.enqueue({ + type: ProvingRequestType.MERGE_ROLLUP, + inputs: input, + }); + } + + /** + * Creates a proof for the given input. + * @param input - Input to the circuit. + */ + getRootRollupProof(input: RootRollupInputs): Promise<[RootRollupPublicInputs, Proof]> { + return this.enqueue({ + type: ProvingRequestType.ROOT_ROLLUP, + inputs: input, + }); + } + + /** + * Create a public kernel proof. + * @param kernelRequest - Object containing the details of the proof required + */ + getPublicKernelProof(kernelRequest: PublicKernelNonTailRequest): Promise<[PublicKernelCircuitPublicInputs, Proof]> { + return this.enqueue({ + type: ProvingRequestType.PUBLIC_KERNEL_NON_TAIL, + kernelType: kernelRequest.type, + inputs: kernelRequest.inputs, + }); + } + + /** + * Create a public kernel tail proof. + * @param kernelRequest - Object containing the details of the proof required + */ + getPublicTailProof(kernelRequest: PublicKernelTailRequest): Promise<[KernelCircuitPublicInputs, Proof]> { + return this.enqueue({ + type: ProvingRequestType.PUBLIC_KERNEL_TAIL, + kernelType: kernelRequest.type, + inputs: kernelRequest.inputs, + }); + } + + /** + * Verifies a circuit proof + */ + verifyProof(): Promise { + // no-op + return Promise.resolve(); } } diff --git a/yarn-project/prover-client/src/prover-pool/circuit-prover-agent.test.ts b/yarn-project/prover-client/src/prover-pool/prover-agent.test.ts similarity index 70% rename from yarn-project/prover-client/src/prover-pool/circuit-prover-agent.test.ts rename to yarn-project/prover-client/src/prover-pool/prover-agent.test.ts index a5e8b224cc9d..7c9bab82d9c1 100644 --- a/yarn-project/prover-client/src/prover-pool/circuit-prover-agent.test.ts +++ b/yarn-project/prover-client/src/prover-pool/prover-agent.test.ts @@ -3,21 +3,18 @@ import { makeBaseParityInputs, makeParityPublicInputs, makeProof } from '@aztec/ import { type MockProxy, mock } from 'jest-mock-extended'; import { type CircuitProver } from '../prover/interface.js'; -import { CircuitProverAgent } from './circuit-prover-agent.js'; import { MemoryProvingQueue } from './memory-proving-queue.js'; -import { type ProvingAgent } from './prover-agent.js'; -import { type ProvingQueue } from './proving-queue.js'; -import { ProvingRequestType } from './proving-request.js'; +import { ProverAgent } from './prover-agent.js'; -describe('LocalProvingAgent', () => { - let queue: ProvingQueue; - let agent: ProvingAgent; +describe('ProverAgent', () => { + let queue: MemoryProvingQueue; + let agent: ProverAgent; let prover: MockProxy; beforeEach(() => { prover = mock(); queue = new MemoryProvingQueue(); - agent = new CircuitProverAgent(prover); + agent = new ProverAgent(prover); }); beforeEach(() => { @@ -34,11 +31,7 @@ describe('LocalProvingAgent', () => { prover.getBaseParityProof.mockResolvedValue([publicInputs, proof]); const inputs = makeBaseParityInputs(); - const promise = queue.prove({ - type: ProvingRequestType.BASE_PARITY, - inputs, - }); - + const promise = queue.getBaseParityProof(inputs); await expect(promise).resolves.toEqual([publicInputs, proof]); expect(prover.getBaseParityProof).toHaveBeenCalledWith(inputs); }); @@ -48,10 +41,7 @@ describe('LocalProvingAgent', () => { prover.getBaseParityProof.mockRejectedValue(error); const inputs = makeBaseParityInputs(); - const promise = queue.prove({ - type: ProvingRequestType.BASE_PARITY, - inputs, - }); + const promise = queue.getBaseParityProof(inputs); await expect(promise).rejects.toEqual(error); expect(prover.getBaseParityProof).toHaveBeenCalledWith(inputs); @@ -63,18 +53,12 @@ describe('LocalProvingAgent', () => { prover.getBaseParityProof.mockResolvedValue([publicInputs, proof]); const inputs = makeBaseParityInputs(); - const promise1 = queue.prove({ - type: ProvingRequestType.BASE_PARITY, - inputs, - }); + const promise1 = queue.getBaseParityProof(inputs); await expect(promise1).resolves.toEqual([publicInputs, proof]); const inputs2 = makeBaseParityInputs(); - const promise2 = queue.prove({ - type: ProvingRequestType.BASE_PARITY, - inputs: inputs2, - }); + const promise2 = queue.getBaseParityProof(inputs2); await expect(promise2).resolves.toEqual([publicInputs, proof]); diff --git a/yarn-project/prover-client/src/prover-pool/prover-agent.ts b/yarn-project/prover-client/src/prover-pool/prover-agent.ts index 6d408e3a0743..030bcc35f908 100644 --- a/yarn-project/prover-client/src/prover-pool/prover-agent.ts +++ b/yarn-project/prover-client/src/prover-pool/prover-agent.ts @@ -1,15 +1,110 @@ -import { type ProvingQueueConsumer } from './proving-queue.js'; - -/** An agent that reads proving jobs from the queue, creates the proof and submits back the result */ -export interface ProvingAgent { - /** - * Starts the agent to read proving jobs from the queue. - * @param queue - The queue to read proving jobs from. - */ - start(queue: ProvingQueueConsumer): void; - - /** - * Stops the agent. Does nothing if the agent is not running. - */ - stop(): Promise; +import { + type ProvingJobSource, + type ProvingRequest, + type ProvingRequestResult, + ProvingRequestType, +} from '@aztec/circuit-types'; +import { makeEmptyProof } from '@aztec/circuits.js'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { RunningPromise } from '@aztec/foundation/running-promise'; +import { elapsed } from '@aztec/foundation/timer'; + +import { type CircuitProver } from '../prover/interface.js'; + +export class ProverAgent { + private runningPromise?: RunningPromise; + + constructor( + /** The prover implementation to defer jobs to */ + private prover: CircuitProver, + /** How long to wait between jobs */ + private intervalMs = 10, + /** A name for this agent (if there are multiple agents running) */ + name = '', + private log = createDebugLogger('aztec:prover-client:prover-pool:agent' + (name ? `:${name}` : '')), + ) {} + + start(queue: ProvingJobSource): void { + if (this.runningPromise) { + throw new Error('Agent is already running'); + } + + this.runningPromise = new RunningPromise(async () => { + const job = await queue.getProvingJob(); + if (!job) { + return; + } + + try { + const [time, result] = await elapsed(() => this.work(job.request)); + await queue.resolveProvingJob(job.id, result); + this.log.info( + `Processed proving job id=${job.id} type=${ProvingRequestType[job.request.type]} duration=${time}ms`, + ); + } catch (err) { + this.log.error( + `Error processing proving job id=${job.id} type=${ProvingRequestType[job.request.type]}: ${err}`, + ); + await queue.rejectProvingJob(job.id, err as Error); + } + }, this.intervalMs); + + this.runningPromise.start(); + } + + async stop(): Promise { + if (!this.runningPromise) { + throw new Error('Agent is not running'); + } + + await this.runningPromise.stop(); + this.runningPromise = undefined; + } + + private work(request: ProvingRequest): Promise> { + const { type, inputs } = request; + switch (type) { + case ProvingRequestType.PUBLIC_VM: { + return Promise.resolve([{}, makeEmptyProof()] as const); + } + + case ProvingRequestType.PUBLIC_KERNEL_NON_TAIL: { + return this.prover.getPublicKernelProof({ + type: request.kernelType, + inputs, + }); + } + + case ProvingRequestType.PUBLIC_KERNEL_TAIL: { + return this.prover.getPublicTailProof({ + type: request.kernelType, + inputs, + }); + } + + case ProvingRequestType.BASE_ROLLUP: { + return this.prover.getBaseRollupProof(inputs); + } + + case ProvingRequestType.MERGE_ROLLUP: { + return this.prover.getMergeRollupProof(inputs); + } + + case ProvingRequestType.ROOT_ROLLUP: { + return this.prover.getRootRollupProof(inputs); + } + + case ProvingRequestType.BASE_PARITY: { + return this.prover.getBaseParityProof(inputs); + } + + case ProvingRequestType.ROOT_PARITY: { + return this.prover.getRootParityProof(inputs); + } + + default: { + return Promise.reject(new Error(`Invalid proof request type: ${type}`)); + } + } + } } diff --git a/yarn-project/prover-client/src/prover-pool/prover-pool.ts b/yarn-project/prover-client/src/prover-pool/prover-pool.ts index defeed61ce7e..407b0a19fe65 100644 --- a/yarn-project/prover-client/src/prover-pool/prover-pool.ts +++ b/yarn-project/prover-client/src/prover-pool/prover-pool.ts @@ -1,21 +1,19 @@ -import { MemoryProvingQueue } from './memory-proving-queue.js'; -import { type ProvingAgent } from './prover-agent.js'; -import { type ProvingQueue } from './proving-queue.js'; +import { type ProvingJobSource } from '@aztec/circuit-types'; +import { type SimulationProvider } from '@aztec/simulator'; + +import { TestCircuitProver } from '../prover/test_circuit_prover.js'; +import { ProverAgent } from './prover-agent.js'; /** * Utility class that spawns N prover agents all connected to the same queue */ export class ProverPool { - private agents: ProvingAgent[] = []; + private agents: ProverAgent[] = []; private running = false; - constructor( - private size: number, - private agentFactory: (i: number) => ProvingAgent | Promise, - public readonly queue: ProvingQueue = new MemoryProvingQueue(), - ) {} + constructor(private size: number, private agentFactory: (i: number) => ProverAgent | Promise) {} - async start(): Promise { + async start(source: ProvingJobSource): Promise { if (this.running) { throw new Error('Prover pool is already running'); } @@ -29,7 +27,7 @@ export class ProverPool { } for (const agent of this.agents) { - agent.start(this.queue); + agent.start(source); } } @@ -44,4 +42,11 @@ export class ProverPool { this.running = false; } + + static testPool(simulationProvider: SimulationProvider, size = 1, agentPollIntervalMS = 10): ProverPool { + return new ProverPool( + size, + i => new ProverAgent(new TestCircuitProver(simulationProvider), agentPollIntervalMS, `${i}`), + ); + } } diff --git a/yarn-project/prover-client/src/prover-pool/proving-queue.ts b/yarn-project/prover-client/src/prover-pool/proving-queue.ts deleted file mode 100644 index 3ab8b0153453..000000000000 --- a/yarn-project/prover-client/src/prover-pool/proving-queue.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ProvingRequest, ProvingRequestResult, ProvingRequestType } from './proving-request.js'; - -export type GetJobOptions = { - timeoutSec?: number; -}; - -export type ProvingJob = { - id: string; - request: T; -}; - -export interface ProvingRequestProducer { - prove(request: T): Promise>; - cancelAll(): void; -} - -export interface ProvingQueueConsumer { - getProvingJob(options?: GetJobOptions): Promise | null>; - resolveProvingJob(jobId: string, result: ProvingRequestResult): Promise; - rejectProvingJob(jobId: string, reason: Error): Promise; -} - -export interface ProvingQueue extends ProvingQueueConsumer, ProvingRequestProducer {} 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 14f36024a39c..90daea5a7fa4 100644 --- a/yarn-project/prover-client/src/tx-prover/tx-prover.ts +++ b/yarn-project/prover-client/src/tx-prover/tx-prover.ts @@ -7,7 +7,8 @@ import { type WorldStateSynchronizer } from '@aztec/world-state'; import { type ProverConfig } from '../config.js'; import { type VerificationKeys, getVerificationKeys } from '../mocks/verification_keys.js'; import { ProvingOrchestrator } from '../orchestrator/orchestrator.js'; -import { CircuitProverAgent } from '../prover-pool/circuit-prover-agent.js'; +import { MemoryProvingQueue } from '../prover-pool/memory-proving-queue.js'; +import { ProverAgent } from '../prover-pool/prover-agent.js'; import { ProverPool } from '../prover-pool/prover-pool.js'; import { TestCircuitProver } from '../prover/test_circuit_prover.js'; @@ -17,6 +18,7 @@ import { TestCircuitProver } from '../prover/test_circuit_prover.js'; export class TxProver implements ProverClient { private orchestrator: ProvingOrchestrator; private proverPool: ProverPool; + private queue = new MemoryProvingQueue(); constructor( private worldStateSynchronizer: WorldStateSynchronizer, @@ -27,17 +29,17 @@ export class TxProver implements ProverClient { ) { this.proverPool = new ProverPool( agentCount, - i => new CircuitProverAgent(new TestCircuitProver(simulationProvider), agentPollIntervalMS, `${i}`), + i => new ProverAgent(new TestCircuitProver(simulationProvider), agentPollIntervalMS, `${i}`), ); - this.orchestrator = new ProvingOrchestrator(worldStateSynchronizer.getLatest(), this.proverPool.queue); + this.orchestrator = new ProvingOrchestrator(worldStateSynchronizer.getLatest(), this.queue); } /** * Starts the prover instance */ public async start() { - await this.proverPool.start(); + await this.proverPool.start(this.queue); } /** diff --git a/yarn-project/pxe/tsconfig.json b/yarn-project/pxe/tsconfig.json index e32a4d6aa27b..a9b056c037db 100644 --- a/yarn-project/pxe/tsconfig.json +++ b/yarn-project/pxe/tsconfig.json @@ -6,6 +6,9 @@ "tsBuildInfoFile": ".tsbuildinfo" }, "references": [ + { + "path": "../builder" + }, { "path": "../circuit-types" }, @@ -24,9 +27,6 @@ { "path": "../kv-store" }, - { - "path": "../builder" - }, { "path": "../noir-protocol-circuits-types" },