diff --git a/yarn-project/Earthfile b/yarn-project/Earthfile index 164e92af29e..14b724a28b5 100644 --- a/yarn-project/Earthfile +++ b/yarn-project/Earthfile @@ -184,6 +184,7 @@ end-to-end: ENV BB_BINARY_PATH=/usr/src/barretenberg/cpp/build/bin/bb ENV ACVM_WORKING_DIRECTORY=/usr/src/acvm ENV ACVM_BINARY_PATH=/usr/src/noir/noir-repo/target/release/acvm + ENV PROVER_AGENT_CONCURRENCY=8 RUN mkdir -p $BB_WORKING_DIRECTORY $ACVM_WORKING_DIRECTORY COPY +anvil/anvil /opt/foundry/bin/anvil COPY +end-to-end-prod/usr/src /usr/src diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 6c817777b8c..92e1ce63174 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -147,9 +147,7 @@ export class AztecNodeService implements AztecNode { // start the prover if we have been told to const simulationProvider = await getSimulationProvider(config, log); - const prover = config.disableProver - ? await DummyProver.new() - : await TxProver.new(config, simulationProvider, worldStateSynchronizer); + const prover = config.disableProver ? await DummyProver.new() : await TxProver.new(config, worldStateSynchronizer); // now create the sequencer const sequencer = config.disableSequencer diff --git a/yarn-project/aztec/package.json b/yarn-project/aztec/package.json index 6b61ea53b10..aa5007751e9 100644 --- a/yarn-project/aztec/package.json +++ b/yarn-project/aztec/package.json @@ -31,6 +31,7 @@ "@aztec/archiver": "workspace:^", "@aztec/aztec-node": "workspace:^", "@aztec/aztec.js": "workspace:^", + "@aztec/bb-prover": "workspace:^", "@aztec/builder": "workspace:^", "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", diff --git a/yarn-project/aztec/src/cli/cmds/start_node.ts b/yarn-project/aztec/src/cli/cmds/start_node.ts index 152f02433a1..925871cda17 100644 --- a/yarn-project/aztec/src/cli/cmds/start_node.ts +++ b/yarn-project/aztec/src/cli/cmds/start_node.ts @@ -6,7 +6,7 @@ import { import { NULL_KEY } from '@aztec/ethereum'; import { type ServerList } from '@aztec/foundation/json-rpc/server'; import { type LogFn } from '@aztec/foundation/log'; -import { createProvingJobSourceServer } from '@aztec/prover-client/prover-pool'; +import { createProvingJobSourceServer } from '@aztec/prover-client/prover-agent'; import { type PXEServiceConfig, createPXERpcServer, getPXEServiceConfig } from '@aztec/pxe'; import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; diff --git a/yarn-project/aztec/src/cli/cmds/start_prover.ts b/yarn-project/aztec/src/cli/cmds/start_prover.ts index d5bd721961a..4b299ab5661 100644 --- a/yarn-project/aztec/src/cli/cmds/start_prover.ts +++ b/yarn-project/aztec/src/cli/cmds/start_prover.ts @@ -1,60 +1,56 @@ -import { type ProvingJobSource } from '@aztec/circuit-types'; -import { type ProverClientConfig, getProverEnvVars } from '@aztec/prover-client'; -import { ProverPool, createProvingJobSourceClient } from '@aztec/prover-client/prover-pool'; - -import { tmpdir } from 'node:os'; +import { BBNativeRollupProver, TestCircuitProver } from '@aztec/bb-prover'; +import { type ServerCircuitProver } from '@aztec/circuit-types'; +import { getProverEnvVars } from '@aztec/prover-client'; +import { ProverAgent, createProvingJobSourceClient } from '@aztec/prover-client/prover-agent'; import { type ServiceStarter, parseModuleOptions } from '../util.js'; -type ProverOptions = ProverClientConfig & - Partial<{ - proverUrl: string; - agents: string; - acvmBinaryPath?: string; - bbBinaryPath?: string; - simulate?: string; - }>; - export const startProver: ServiceStarter = async (options, signalHandlers, logger) => { - const proverOptions: ProverOptions = { - proverUrl: process.env.PROVER_URL, + const proverOptions = { ...getProverEnvVars(), ...parseModuleOptions(options.prover), }; - let source: ProvingJobSource; - if (typeof proverOptions.proverUrl === 'string') { - logger(`Connecting to prover at ${proverOptions.proverUrl}`); - source = createProvingJobSourceClient(proverOptions.proverUrl, 'provingJobSource'); - } else { + if (!proverOptions.nodeUrl) { throw new Error('Starting prover without an orchestrator is not supported'); } - const agentCount = proverOptions.agents ? parseInt(proverOptions.agents, 10) : proverOptions.proverAgents; - if (agentCount === 0 || !Number.isSafeInteger(agentCount)) { - throw new Error('Cannot start prover without agents'); - } - - let pool: ProverPool; - if (proverOptions.simulate) { - pool = ProverPool.testPool(undefined, agentCount); - } else if (proverOptions.acvmBinaryPath && proverOptions.bbBinaryPath) { - pool = ProverPool.nativePool( - { - acvmBinaryPath: proverOptions.acvmBinaryPath, - bbBinaryPath: proverOptions.bbBinaryPath, - acvmWorkingDirectory: tmpdir(), - bbWorkingDirectory: tmpdir(), - }, - agentCount, - ); + logger(`Connecting to prover at ${proverOptions.nodeUrl}`); + const source = createProvingJobSourceClient(proverOptions.nodeUrl, 'provingJobSource'); + + const agentConcurrency = + // string if it was set as a CLI option, ie start --prover proverAgentConcurrency=10 + typeof proverOptions.proverAgentConcurrency === 'string' + ? parseInt(proverOptions.proverAgentConcurrency, 10) + : proverOptions.proverAgentConcurrency; + + const pollInterval = + // string if it was set as a CLI option, ie start --prover proverAgentPollInterval=10 + typeof proverOptions.proverAgentPollInterval === 'string' + ? parseInt(proverOptions.proverAgentPollInterval, 10) + : proverOptions.proverAgentPollInterval; + + let circuitProver: ServerCircuitProver; + if (proverOptions.realProofs) { + if (!proverOptions.acvmBinaryPath || !proverOptions.bbBinaryPath) { + throw new Error('Cannot start prover without simulation or native prover options'); + } + + circuitProver = await BBNativeRollupProver.new({ + acvmBinaryPath: proverOptions.acvmBinaryPath, + bbBinaryPath: proverOptions.bbBinaryPath, + acvmWorkingDirectory: proverOptions.acvmWorkingDirectory, + bbWorkingDirectory: proverOptions.bbWorkingDirectory, + }); } else { - throw new Error('Cannot start prover without simulation or native prover options'); + circuitProver = new TestCircuitProver(); } - logger(`Starting ${agentCount} prover agents`); - await pool.start(source); - signalHandlers.push(() => pool.stop()); + const agent = new ProverAgent(circuitProver, agentConcurrency, pollInterval); + agent.start(source); + logger(`Started prover agent with concurrency limit of ${agentConcurrency}`); + + signalHandlers.push(() => agent.stop()); return Promise.resolve([]); }; diff --git a/yarn-project/aztec/tsconfig.json b/yarn-project/aztec/tsconfig.json index f6615a77d26..ef88fd56147 100644 --- a/yarn-project/aztec/tsconfig.json +++ b/yarn-project/aztec/tsconfig.json @@ -18,6 +18,9 @@ { "path": "../aztec.js" }, + { + "path": "../bb-prover" + }, { "path": "../builder" }, diff --git a/yarn-project/bb-prover/src/config.ts b/yarn-project/bb-prover/src/config.ts new file mode 100644 index 00000000000..c097693e544 --- /dev/null +++ b/yarn-project/bb-prover/src/config.ts @@ -0,0 +1,9 @@ +export interface BBConfig { + bbBinaryPath: string; + bbWorkingDirectory: string; +} + +export interface ACVMConfig { + acvmBinaryPath: string; + acvmWorkingDirectory: string; +} diff --git a/yarn-project/bb-prover/src/index.ts b/yarn-project/bb-prover/src/index.ts index 6d611b60b68..feba65f4496 100644 --- a/yarn-project/bb-prover/src/index.ts +++ b/yarn-project/bb-prover/src/index.ts @@ -1,3 +1,4 @@ export * from './prover/index.js'; export * from './test/index.js'; export * from './verifier/index.js'; +export * from './config.js'; diff --git a/yarn-project/bb-prover/src/prover/bb_prover.ts b/yarn-project/bb-prover/src/prover/bb_prover.ts index 91d968c464e..55cc887ed0c 100644 --- a/yarn-project/bb-prover/src/prover/bb_prover.ts +++ b/yarn-project/bb-prover/src/prover/bb_prover.ts @@ -63,6 +63,7 @@ import { verifyProof, writeProofAsFields, } from '../bb/execute.js'; +import type { ACVMConfig, BBConfig } from '../config.js'; import { PublicKernelArtifactMapping } from '../mappings/mappings.js'; import { mapProtocolArtifactNameToCircuitName } from '../stats.js'; import { extractVkData } from '../verification_key/verification_key_data.js'; @@ -71,14 +72,10 @@ const logger = createDebugLogger('aztec:bb-prover'); const CIRCUITS_WITHOUT_AGGREGATION: Set = new Set(['BaseParityArtifact']); -export type BBProverConfig = { - bbBinaryPath: string; - bbWorkingDirectory: string; - acvmBinaryPath: string; - acvmWorkingDirectory: string; +export interface BBProverConfig extends BBConfig, ACVMConfig { // list of circuits supported by this prover. defaults to all circuits if empty circuitFilter?: ServerProtocolArtifact[]; -}; +} /** * Prover implementation that uses barretenberg native proving diff --git a/yarn-project/bb-prover/src/verifier/bb_verifier.ts b/yarn-project/bb-prover/src/verifier/bb_verifier.ts index 3b24313247c..a0b5d05d8dd 100644 --- a/yarn-project/bb-prover/src/verifier/bb_verifier.ts +++ b/yarn-project/bb-prover/src/verifier/bb_verifier.ts @@ -6,13 +6,9 @@ import { type ProtocolArtifact, ProtocolCircuitArtifacts } from '@aztec/noir-pro import * as fs from 'fs/promises'; import { BB_RESULT, generateContractForCircuit, generateKeyForNoirCircuit, verifyProof } from '../bb/execute.js'; +import { type BBConfig } from '../config.js'; import { extractVkData } from '../verification_key/verification_key_data.js'; -export type BBConfig = { - bbBinaryPath: string; - bbWorkingDirectory: string; -}; - export class BBCircuitVerifier { private constructor( private config: BBConfig, diff --git a/yarn-project/circuit-types/src/interfaces/prover-client.ts b/yarn-project/circuit-types/src/interfaces/prover-client.ts index 323bbf22688..cf5ef775d2e 100644 --- a/yarn-project/circuit-types/src/interfaces/prover-client.ts +++ b/yarn-project/circuit-types/src/interfaces/prover-client.ts @@ -5,10 +5,16 @@ import { type ProvingJobSource } from './proving-job.js'; * The prover configuration. */ export type ProverConfig = { - /** How many agents to run */ - proverAgents: number; + /** The URL to the Aztec node to take proving jobs from */ + nodeUrl?: string; /** Whether to construct real proofs */ realProofs: boolean; + /** Whether this prover has a local prover agent */ + proverAgentEnabled: boolean; + /** The interval agents poll for jobs at */ + proverAgentPollInterval: number; + /** The maximum number of proving jobs to be run in parallel */ + proverAgentConcurrency: number; }; /** diff --git a/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts b/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts index 76d5a4a2002..c0fe808e84d 100644 --- a/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts +++ b/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts @@ -1,11 +1,9 @@ import { getSchnorrAccount, getSchnorrWallet } from '@aztec/accounts/schnorr'; -import { type AztecNodeService } from '@aztec/aztec-node'; import { TxStatus } from '@aztec/aztec.js'; import { type AccountWallet } from '@aztec/aztec.js/wallet'; import { CompleteAddress, Fq, Fr } from '@aztec/circuits.js'; import { FPCContract, GasTokenContract, TestContract, TokenContract } from '@aztec/noir-contracts.js'; import { GasTokenAddress } from '@aztec/protocol-contracts/gas-token'; -import { ProverPool } from '@aztec/prover-client/prover-pool'; import { type PXEService, createPXEService } from '@aztec/pxe'; import { jest } from '@jest/globals'; @@ -39,7 +37,6 @@ describe('benchmarks/proving', () => { let acvmCleanup: () => Promise; let bbCleanup: () => Promise; - let proverPool: ProverPool; // setup the environment quickly using fake proofs beforeAll(async () => { @@ -48,7 +45,7 @@ describe('benchmarks/proving', () => { { // do setup with fake proofs realProofs: false, - proverAgents: 4, + proverAgentConcurrency: 4, proverAgentPollInterval: 10, minTxsPerBlock: 1, }, @@ -110,25 +107,14 @@ describe('benchmarks/proving', () => { acvmCleanup = acvmConfig.cleanup; bbCleanup = bbConfig.cleanup; - proverPool = ProverPool.nativePool( - { - ...acvmConfig, - ...bbConfig, - }, - 2, - 10, - ); - ctx.logger.info('Stopping fake provers'); await ctx.aztecNode.setConfig({ - // stop the fake provers - proverAgents: 0, + proverAgentConcurrency: 1, realProofs: true, minTxsPerBlock: 2, }); ctx.logger.info('Starting real provers'); - await proverPool.start((ctx.aztecNode as AztecNodeService).getProver().getProvingJobSource()); ctx.logger.info('Starting PXEs configured with real proofs'); provingPxes = []; @@ -161,7 +147,6 @@ describe('benchmarks/proving', () => { for (const pxe of provingPxes) { await pxe.stop(); } - await proverPool.stop(); await ctx.teardown(); await acvmCleanup(); await bbCleanup(); diff --git a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts index 9b4cd85a9a7..cf89ebec9da 100644 --- a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts @@ -39,7 +39,6 @@ import { AvailabilityOracleAbi, InboxAbi, OutboxAbi, RollupAbi } from '@aztec/l1 import { SHA256Trunc, StandardTree } from '@aztec/merkle-tree'; import { TxProver } from '@aztec/prover-client'; import { type L1Publisher, getL1Publisher } from '@aztec/sequencer-client'; -import { WASMSimulator } from '@aztec/simulator'; import { MerkleTrees, ServerWorldStateSynchronizer, type WorldStateConfig } from '@aztec/world-state'; import { beforeEach, describe, expect, it } from '@jest/globals'; @@ -145,7 +144,7 @@ describe('L1Publisher integration', () => { }; const worldStateSynchronizer = new ServerWorldStateSynchronizer(tmpStore, builderDb, blockSource, worldStateConfig); await worldStateSynchronizer.start(); - builder = await TxProver.new(config, new WASMSimulator(), worldStateSynchronizer); + builder = await TxProver.new(config, worldStateSynchronizer); l2Proof = makeEmptyProof(); publisher = getL1Publisher({ diff --git a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts index 27b90f29185..238c9a12fcc 100644 --- a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts @@ -1,5 +1,4 @@ import { SchnorrAccountContractArtifact, getSchnorrAccount } from '@aztec/accounts/schnorr'; -import { type AztecNodeService } from '@aztec/aztec-node'; import { type AccountWalletWithSecretKey, type AztecNode, @@ -18,7 +17,6 @@ import { import { BBCircuitVerifier, BBNativeProofCreator } from '@aztec/bb-prover'; import { RollupAbi } from '@aztec/l1-artifacts'; import { TokenContract } from '@aztec/noir-contracts.js'; -import { ProverPool } from '@aztec/prover-client/prover-pool'; import { type PXEService } from '@aztec/pxe'; // @ts-expect-error solc-js doesn't publish its types https://github.com/ethereum/solc-js/issues/689 @@ -67,7 +65,6 @@ export class FullProverTest { tokenSim!: TokenSimulator; aztecNode!: AztecNode; pxe!: PXEService; - private proverPool!: ProverPool; private provenComponents: ProvenSetup[] = []; private bbConfigCleanup?: () => Promise; private acvmConfigCleanup?: () => Promise; @@ -153,25 +150,13 @@ export class FullProverTest { this.circuitProofVerifier = await BBCircuitVerifier.new(bbConfig); - this.proverPool = ProverPool.nativePool( - { - ...acvmConfig, - ...bbConfig, - }, - 4, - 10, - ); - this.logger.debug(`Configuring the node for real proofs...`); await this.aztecNode.setConfig({ - // stop the fake provers - proverAgents: 0, + proverAgentConcurrency: 1, realProofs: true, minTxsPerBlock: 2, // min 2 txs per block }); - await this.proverPool.start((this.aztecNode as AztecNodeService).getProver().getProvingJobSource()); - this.proofCreator = new BBNativeProofCreator(bbConfig.bbBinaryPath, bbConfig.bbWorkingDirectory); this.logger.debug(`Main setup completed, initializing full prover PXE and Node...`); diff --git a/yarn-project/prover-client/package.json b/yarn-project/prover-client/package.json index 9c5545541d9..df4f3a5adc6 100644 --- a/yarn-project/prover-client/package.json +++ b/yarn-project/prover-client/package.json @@ -4,7 +4,7 @@ "type": "module", "exports": { ".": "./dest/index.js", - "./prover-pool": "./dest/prover-pool/index.js" + "./prover-agent": "./dest/prover-agent/index.js" }, "typedocOptions": { "entryPoints": [ diff --git a/yarn-project/prover-client/src/config.ts b/yarn-project/prover-client/src/config.ts index 5cc1bc47577..65763196446 100644 --- a/yarn-project/prover-client/src/config.ts +++ b/yarn-project/prover-client/src/config.ts @@ -14,8 +14,6 @@ export type ProverClientConfig = ProverConfig & { bbWorkingDirectory: string; /** The path to the bb binary */ bbBinaryPath: string; - /** The interval agents poll for jobs at */ - proverAgentPollInterval: number; }; /** @@ -29,25 +27,31 @@ export function getProverEnvVars(): ProverClientConfig { ACVM_BINARY_PATH = '', BB_WORKING_DIRECTORY = tmpdir(), BB_BINARY_PATH = '', + /** @deprecated */ PROVER_AGENTS = '1', - PROVER_AGENT_POLL_INTERVAL_MS = '50', + PROVER_AGENT_ENABLED = '1', + PROVER_AGENT_CONCURRENCY = PROVER_AGENTS, + PROVER_AGENT_POLL_INTERVAL_MS = '100', PROVER_REAL_PROOFS = '', } = process.env; - const parsedProverAgents = parseInt(PROVER_AGENTS, 10); - const proverAgents = Number.isSafeInteger(parsedProverAgents) ? parsedProverAgents : 0; + const realProofs = ['1', 'true'].includes(PROVER_REAL_PROOFS); + const proverAgentEnabled = ['1', 'true'].includes(PROVER_AGENT_ENABLED); + const parsedProverConcurrency = parseInt(PROVER_AGENT_CONCURRENCY, 10); + const proverAgentConcurrency = Number.isSafeInteger(parsedProverConcurrency) ? parsedProverConcurrency : 1; const parsedProverAgentPollInterval = parseInt(PROVER_AGENT_POLL_INTERVAL_MS, 10); const proverAgentPollInterval = Number.isSafeInteger(parsedProverAgentPollInterval) ? parsedProverAgentPollInterval - : 50; + : 100; return { acvmWorkingDirectory: ACVM_WORKING_DIRECTORY, acvmBinaryPath: ACVM_BINARY_PATH, bbBinaryPath: BB_BINARY_PATH, bbWorkingDirectory: BB_WORKING_DIRECTORY, - proverAgents, - realProofs: ['1', 'true'].includes(PROVER_REAL_PROOFS), + realProofs, + proverAgentEnabled, proverAgentPollInterval, + proverAgentConcurrency, }; } diff --git a/yarn-project/prover-client/src/mocks/test_context.ts b/yarn-project/prover-client/src/mocks/test_context.ts index efcce0f96ba..2b344c955b4 100644 --- a/yarn-project/prover-client/src/mocks/test_context.ts +++ b/yarn-project/prover-client/src/mocks/test_context.ts @@ -38,9 +38,8 @@ import { type MockProxy, mock } from 'jest-mock-extended'; import { TestCircuitProver } from '../../../bb-prover/src/test/test_circuit_prover.js'; import { ProvingOrchestrator } from '../orchestrator/orchestrator.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 { MemoryProvingQueue } from '../prover-agent/memory-proving-queue.js'; +import { ProverAgent } from '../prover-agent/prover-agent.js'; import { getEnvironmentConfig, getSimulationProvider, makeGlobals } from './fixtures.js'; class DummyProverClient implements BlockProver { @@ -78,7 +77,7 @@ export class TestContext { public globalVariables: GlobalVariables, public actualDb: MerkleTreeOperations, public prover: ServerCircuitProver, - public proverPool: ProverPool, + public proverAgent: ProverAgent, public orchestrator: ProvingOrchestrator, public blockNumber: number, public directoriesToCleanup: string[], @@ -130,10 +129,10 @@ export class TestContext { } const queue = new MemoryProvingQueue(); - const proverPool = new ProverPool(proverCount, i => new ProverAgent(localProver, 10, `${i}`)); const orchestrator = new ProvingOrchestrator(actualDb, queue); + const agent = new ProverAgent(localProver, proverCount); - await proverPool.start(queue); + agent.start(queue); return new this( publicExecutor, @@ -144,7 +143,7 @@ export class TestContext { globalVariables, actualDb, localProver, - proverPool, + agent, orchestrator, blockNumber, [config?.directoryToCleanup ?? ''], @@ -153,7 +152,7 @@ export class TestContext { } async cleanup() { - await this.proverPool.stop(); + await this.proverAgent.stop(); for (const dir of this.directoriesToCleanup.filter(x => x !== '')) { await fs.rm(dir, { recursive: true, force: true }); } 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 f65475aadf7..f67408e7c5b 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator_failures.test.ts @@ -8,9 +8,6 @@ import { jest } from '@jest/globals'; import { TestCircuitProver } from '../../../bb-prover/src/test/test_circuit_prover.js'; import { makeEmptyProcessedTestTx } from '../mocks/fixtures.js'; import { TestContext } from '../mocks/test_context.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 { ProvingOrchestrator } from './orchestrator.js'; const logger = createDebugLogger('aztec:orchestrator-failures'); @@ -18,7 +15,6 @@ const logger = createDebugLogger('aztec:orchestrator-failures'); describe('prover/orchestrator/failures', () => { let context: TestContext; let orchestrator: ProvingOrchestrator; - let proverPool: ProverPool; beforeEach(async () => { context = await TestContext.new(logger); @@ -30,18 +26,10 @@ describe('prover/orchestrator/failures', () => { describe('error handling', () => { let mockProver: ServerCircuitProver; - let queue: MemoryProvingQueue; - beforeEach(async () => { + beforeEach(() => { mockProver = new TestCircuitProver(new WASMSimulator()); - 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 () => { - await proverPool.stop(); + orchestrator = new ProvingOrchestrator(context.actualDb, mockProver); }); it.each([ diff --git a/yarn-project/prover-client/src/prover-pool/index.ts b/yarn-project/prover-client/src/prover-agent/index.ts similarity index 75% rename from yarn-project/prover-client/src/prover-pool/index.ts rename to yarn-project/prover-client/src/prover-agent/index.ts index 353448bcbce..f9aafea995b 100644 --- a/yarn-project/prover-client/src/prover-pool/index.ts +++ b/yarn-project/prover-client/src/prover-agent/index.ts @@ -1,4 +1,3 @@ export * from './prover-agent.js'; export * from './memory-proving-queue.js'; -export * from './prover-pool.js'; export * from './rpc.js'; diff --git a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.test.ts b/yarn-project/prover-client/src/prover-agent/memory-proving-queue.test.ts similarity index 100% rename from yarn-project/prover-client/src/prover-pool/memory-proving-queue.test.ts rename to yarn-project/prover-client/src/prover-agent/memory-proving-queue.test.ts diff --git a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts b/yarn-project/prover-client/src/prover-agent/memory-proving-queue.ts similarity index 97% rename from yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts rename to yarn-project/prover-client/src/prover-agent/memory-proving-queue.ts index 9bc850f311c..50465b86c19 100644 --- a/yarn-project/prover-client/src/prover-pool/memory-proving-queue.ts +++ b/yarn-project/prover-client/src/prover-agent/memory-proving-queue.ts @@ -40,6 +40,10 @@ const MAX_RETRIES = 3; const defaultIdGenerator = () => randomBytes(4).toString('hex'); +/** + * A helper class that sits in between services that need proofs created and agents that can create them. + * The queue accumulates jobs and provides them to agents in FIFO order. + */ export class MemoryProvingQueue implements ServerCircuitProver, ProvingJobSource { private log = createDebugLogger('aztec:prover-client:prover-pool:queue'); private queue = new MemoryFifo(); diff --git a/yarn-project/prover-client/src/prover-pool/prover-agent.test.ts b/yarn-project/prover-client/src/prover-agent/prover-agent.test.ts similarity index 100% rename from yarn-project/prover-client/src/prover-pool/prover-agent.test.ts rename to yarn-project/prover-client/src/prover-agent/prover-agent.test.ts diff --git a/yarn-project/prover-client/src/prover-agent/prover-agent.ts b/yarn-project/prover-client/src/prover-agent/prover-agent.ts new file mode 100644 index 00000000000..0842794f93f --- /dev/null +++ b/yarn-project/prover-client/src/prover-agent/prover-agent.ts @@ -0,0 +1,147 @@ +import { + type ProvingJob, + type ProvingJobSource, + type ProvingRequest, + type ProvingRequestResult, + ProvingRequestType, + type ServerCircuitProver, + makePublicInputsAndProof, +} from '@aztec/circuit-types'; +import { NESTED_RECURSIVE_PROOF_LENGTH, VerificationKeyData, makeEmptyRecursiveProof } from '@aztec/circuits.js'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { RunningPromise } from '@aztec/foundation/running-promise'; +import { elapsed } from '@aztec/foundation/timer'; + +import { ProvingError } from './proving-error.js'; + +/** + * A helper class that encapsulates a circuit prover and connects it to a job source. + */ +export class ProverAgent { + private inFlightPromises = new Set>(); + private runningPromise?: RunningPromise; + + constructor( + /** The prover implementation to defer jobs to */ + private circuitProver: ServerCircuitProver, + /** How many proving jobs this agent can handle in parallel */ + private maxConcurrency = 1, + /** How long to wait between jobs */ + private pollIntervalMs = 100, + private log = createDebugLogger('aztec:prover-client:prover-agent'), + ) {} + + setMaxConcurrency(maxConcurrency: number): void { + if (maxConcurrency < 1) { + throw new Error('Concurrency must be at least 1'); + } + this.maxConcurrency = maxConcurrency; + } + + setCircuitProver(circuitProver: ServerCircuitProver): void { + this.circuitProver = circuitProver; + } + + isRunning() { + return this.runningPromise?.isRunning() ?? false; + } + + start(jobSource: ProvingJobSource): void { + if (this.runningPromise) { + throw new Error('Agent is already running'); + } + + this.runningPromise = new RunningPromise(async () => { + while (this.inFlightPromises.size < this.maxConcurrency) { + const job = await jobSource.getProvingJob(); + if (!job) { + // job source is fully drained, sleep for a bit and try again + return; + } + + const promise = this.work(jobSource, job).finally(() => this.inFlightPromises.delete(promise)); + this.inFlightPromises.add(promise); + } + }, this.pollIntervalMs); + + this.runningPromise.start(); + this.log.info('Agent started'); + } + + async stop(): Promise { + if (!this.runningPromise?.isRunning()) { + return; + } + + await this.runningPromise.stop(); + this.runningPromise = undefined; + + this.log.info('Agent stopped'); + } + + private async work(jobSource: ProvingJobSource, job: ProvingJob): Promise { + try { + const [time, result] = await elapsed(this.getProof(job.request)); + await jobSource.resolveProvingJob(job.id, result); + this.log.debug( + `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 jobSource.rejectProvingJob(job.id, new ProvingError((err as any)?.message ?? String(err))); + } + } + + private getProof(request: ProvingRequest): Promise> { + const { type, inputs } = request; + switch (type) { + case ProvingRequestType.PUBLIC_VM: { + return Promise.resolve( + makePublicInputsAndProof( + {}, + makeEmptyRecursiveProof(NESTED_RECURSIVE_PROOF_LENGTH), + VerificationKeyData.makeFake(), + ), + ); + } + + case ProvingRequestType.PUBLIC_KERNEL_NON_TAIL: { + return this.circuitProver.getPublicKernelProof({ + type: request.kernelType, + inputs, + }); + } + + case ProvingRequestType.PUBLIC_KERNEL_TAIL: { + return this.circuitProver.getPublicTailProof({ + type: request.kernelType, + inputs, + }); + } + + case ProvingRequestType.BASE_ROLLUP: { + return this.circuitProver.getBaseRollupProof(inputs); + } + + case ProvingRequestType.MERGE_ROLLUP: { + return this.circuitProver.getMergeRollupProof(inputs); + } + + case ProvingRequestType.ROOT_ROLLUP: { + return this.circuitProver.getRootRollupProof(inputs); + } + + case ProvingRequestType.BASE_PARITY: { + return this.circuitProver.getBaseParityProof(inputs); + } + + case ProvingRequestType.ROOT_PARITY: { + return this.circuitProver.getRootParityProof(inputs); + } + + default: { + return Promise.reject(new Error(`Invalid proof request type: ${type}`)); + } + } + } +} diff --git a/yarn-project/prover-client/src/prover-pool/proving-error.ts b/yarn-project/prover-client/src/prover-agent/proving-error.ts similarity index 100% rename from yarn-project/prover-client/src/prover-pool/proving-error.ts rename to yarn-project/prover-client/src/prover-agent/proving-error.ts diff --git a/yarn-project/prover-client/src/prover-pool/rpc.ts b/yarn-project/prover-client/src/prover-agent/rpc.ts similarity index 100% rename from yarn-project/prover-client/src/prover-pool/rpc.ts rename to yarn-project/prover-client/src/prover-agent/rpc.ts diff --git a/yarn-project/prover-client/src/prover-pool/prover-agent.ts b/yarn-project/prover-client/src/prover-pool/prover-agent.ts deleted file mode 100644 index 6c88d759d87..00000000000 --- a/yarn-project/prover-client/src/prover-pool/prover-agent.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { - type ProvingJobSource, - type ProvingRequest, - type ProvingRequestResult, - ProvingRequestType, - type ServerCircuitProver, - makePublicInputsAndProof, -} from '@aztec/circuit-types'; -import { NESTED_RECURSIVE_PROOF_LENGTH, VerificationKeyData, makeEmptyRecursiveProof } from '@aztec/circuits.js'; -import { createDebugLogger } from '@aztec/foundation/log'; -import { RunningPromise } from '@aztec/foundation/running-promise'; -import { elapsed } from '@aztec/foundation/timer'; - -import { ProvingError } from './proving-error.js'; - -export class ProverAgent { - private runningPromise?: RunningPromise; - - constructor( - /** The prover implementation to defer jobs to */ - private prover: ServerCircuitProver, - /** 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.debug( - `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, new ProvingError((err as any)?.message ?? String(err))); - } - }, this.intervalMs); - - this.runningPromise.start(); - this.log.info('Agent started'); - } - - async stop(): Promise { - if (!this.runningPromise?.isRunning()) { - return; - } - - await this.runningPromise.stop(); - this.runningPromise = undefined; - - this.log.info('Agent stopped'); - } - - private work(request: ProvingRequest): Promise> { - const { type, inputs } = request; - switch (type) { - case ProvingRequestType.PUBLIC_VM: { - return Promise.resolve( - makePublicInputsAndProof( - {}, - makeEmptyRecursiveProof(NESTED_RECURSIVE_PROOF_LENGTH), - VerificationKeyData.makeFake(), - ), - ); - } - - 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 deleted file mode 100644 index 0a46a4281ad..00000000000 --- a/yarn-project/prover-client/src/prover-pool/prover-pool.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { BBNativeRollupProver, type BBProverConfig, TestCircuitProver } from '@aztec/bb-prover'; -import { type ProvingJobSource } from '@aztec/circuit-types'; -import { sleep } from '@aztec/foundation/sleep'; -import { type SimulationProvider } from '@aztec/simulator'; - -import { mkdtemp } from 'fs/promises'; -import { join } from 'path'; - -import { ProverAgent } from './prover-agent.js'; - -/** - * Utility class that spawns N prover agents all connected to the same queue - */ -export class ProverPool { - private agents: ProverAgent[] = []; - private running = false; - - constructor(private size: number, private agentFactory: (i: number) => ProverAgent | Promise) {} - - async start(source: ProvingJobSource): Promise { - if (this.running) { - throw new Error('Prover pool is already running'); - } - - // lock the pool state here since creating agents is async - this.running = true; - - // handle start, stop, start cycles by only creating as many agents as were requested - for (let i = this.agents.length; i < this.size; i++) { - this.agents.push(await this.agentFactory(i)); - } - - for (const agent of this.agents) { - agent.start(source); - // stagger that start of each agent to avoid contention - await sleep(10); - } - } - - async stop(): Promise { - if (!this.running) { - return; - } - - for (const agent of this.agents) { - await agent.stop(); - } - - this.running = false; - } - - async rescale(newSize: number): Promise { - if (newSize > this.size) { - this.size = newSize; - for (let i = this.agents.length; i < newSize; i++) { - this.agents.push(await this.agentFactory(i)); - } - } else if (newSize < this.size) { - this.size = newSize; - while (this.agents.length > newSize) { - await this.agents.pop()?.stop(); - } - } - } - - static testPool(simulationProvider?: SimulationProvider, size = 1, agentPollIntervalMS = 10): ProverPool { - return new ProverPool( - size, - i => new ProverAgent(new TestCircuitProver(simulationProvider), agentPollIntervalMS, `test-prover-${i}`), - ); - } - - static nativePool(config: Omit, size: number, agentPollIntervalMS = 10): ProverPool { - // TODO generate keys ahead of time so that each agent doesn't have to do it - return new ProverPool(size, async i => { - const [acvmWorkingDirectory, bbWorkingDirectory] = await Promise.all([ - mkdtemp(join(config.acvmWorkingDirectory, 'agent-')), - mkdtemp(join(config.bbWorkingDirectory, 'agent-')), - ]); - return new ProverAgent( - await BBNativeRollupProver.new({ - acvmBinaryPath: config.acvmBinaryPath, - acvmWorkingDirectory, - bbBinaryPath: config.bbBinaryPath, - bbWorkingDirectory, - }), - agentPollIntervalMS, - `bb-prover-${i}`, - ); - }); - } -} 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 127679ccabf..cdbd246960f 100644 --- a/yarn-project/prover-client/src/tx-prover/tx-prover.ts +++ b/yarn-project/prover-client/src/tx-prover/tx-prover.ts @@ -1,27 +1,28 @@ -import { BBCircuitVerifier, type BBProverConfig } from '@aztec/bb-prover'; +import { BBCircuitVerifier, type BBConfig, BBNativeRollupProver, TestCircuitProver } from '@aztec/bb-prover'; import { type ProcessedTx } from '@aztec/circuit-types'; import { type BlockResult, type ProverClient, type ProvingJobSource, type ProvingTicket, + type ServerCircuitProver, } from '@aztec/circuit-types/interfaces'; import { type Fr, type GlobalVariables, type VerificationKeys, getMockVerificationKeys } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; -import { type SimulationProvider } from '@aztec/simulator'; +import { NativeACVMSimulator } from '@aztec/simulator'; import { type WorldStateSynchronizer } from '@aztec/world-state'; import { type ProverClientConfig } from '../config.js'; import { ProvingOrchestrator } from '../orchestrator/orchestrator.js'; -import { MemoryProvingQueue } from '../prover-pool/memory-proving-queue.js'; -import { ProverPool } from '../prover-pool/prover-pool.js'; +import { MemoryProvingQueue } from '../prover-agent/memory-proving-queue.js'; +import { ProverAgent } from '../prover-agent/prover-agent.js'; const logger = createDebugLogger('aztec:tx-prover'); const PRIVATE_KERNEL = 'PrivateKernelTailArtifact'; const PRIVATE_KERNEL_TO_PUBLIC = 'PrivateKernelTailToPublicArtifact'; -async function retrieveRealPrivateKernelVerificationKeys(config: BBProverConfig) { +async function retrieveRealPrivateKernelVerificationKeys(config: BBConfig) { logger.info(`Retrieving private kernel verification keys`); const bbVerifier = await BBCircuitVerifier.new(config, [PRIVATE_KERNEL, PRIVATE_KERNEL_TO_PUBLIC]); const vks: VerificationKeys = { @@ -37,38 +38,58 @@ async function retrieveRealPrivateKernelVerificationKeys(config: BBProverConfig) export class TxProver implements ProverClient { private orchestrator: ProvingOrchestrator; private queue = new MemoryProvingQueue(); + private running = false; constructor( private config: ProverClientConfig, private worldStateSynchronizer: WorldStateSynchronizer, - protected vks: VerificationKeys, - private proverPool?: ProverPool, + private vks: VerificationKeys, + private agent?: ProverAgent, ) { - logger.info(`BB ${config.bbBinaryPath}, directory: ${config.bbWorkingDirectory}`); this.orchestrator = new ProvingOrchestrator(worldStateSynchronizer.getLatest(), this.queue); } async updateProverConfig(config: Partial): Promise { - if (typeof config.proverAgents === 'number') { - await this.proverPool?.rescale(config.proverAgents); + const newConfig = { ...this.config, ...config }; + + if (newConfig.realProofs !== this.config.realProofs) { + this.vks = await (newConfig.realProofs + ? retrieveRealPrivateKernelVerificationKeys(newConfig) + : getMockVerificationKeys()); + + const circuitProver = await TxProver.buildCircuitProver(newConfig); + this.agent?.setCircuitProver(circuitProver); } - if (typeof config.realProofs === 'boolean' && config.realProofs) { - this.vks = await retrieveRealPrivateKernelVerificationKeys(this.config); + + if (this.config.proverAgentConcurrency !== newConfig.proverAgentConcurrency) { + this.agent?.setMaxConcurrency(newConfig.proverAgentConcurrency); } + + this.config = newConfig; } /** * Starts the prover instance */ - public async start() { - await this.proverPool?.start(this.queue); + public start() { + if (this.running) { + return Promise.resolve(); + } + + this.running = true; + this.agent?.start(this.queue); + return Promise.resolve(); } /** * Stops the prover instance */ public async stop() { - await this.proverPool?.stop(); + if (!this.running) { + return; + } + this.running = false; + await this.agent?.stop(); } /** @@ -77,34 +98,34 @@ export class TxProver implements ProverClient { * @param worldStateSynchronizer - An instance of the world state * @returns An instance of the prover, constructed and started. */ - public static async new( - config: ProverClientConfig, - simulationProvider: SimulationProvider, - worldStateSynchronizer: WorldStateSynchronizer, - ) { - let pool: ProverPool | undefined; - if (config.proverAgents === 0) { - pool = undefined; - } else if (config.realProofs) { - if ( - !config.acvmBinaryPath || - !config.acvmWorkingDirectory || - !config.bbBinaryPath || - !config.bbWorkingDirectory - ) { - throw new Error(); - } - - pool = ProverPool.nativePool(config, config.proverAgents, config.proverAgentPollInterval); - } else { - pool = ProverPool.testPool(simulationProvider, config.proverAgents, config.proverAgentPollInterval); + public static async new(config: ProverClientConfig, worldStateSynchronizer: WorldStateSynchronizer) { + const agent = config.proverAgentEnabled + ? new ProverAgent( + await TxProver.buildCircuitProver(config), + config.proverAgentConcurrency, + config.proverAgentPollInterval, + ) + : undefined; + + const vks = await (config.realProofs + ? retrieveRealPrivateKernelVerificationKeys(config) + : getMockVerificationKeys()); + + const prover = new TxProver(config, worldStateSynchronizer, vks, agent); + await prover.start(); + return prover; + } + + private static async buildCircuitProver(config: ProverClientConfig): Promise { + if (config.realProofs) { + return await BBNativeRollupProver.new(config); } - const vks = config.realProofs ? await retrieveRealPrivateKernelVerificationKeys(config) : getMockVerificationKeys(); + const simulationProvider = config.acvmBinaryPath + ? new NativeACVMSimulator(config.acvmWorkingDirectory, config.acvmBinaryPath) + : undefined; - const prover = new TxProver(config, worldStateSynchronizer, vks, pool); - await prover.start(); - return prover; + return new TestCircuitProver(simulationProvider); } /** @@ -155,7 +176,7 @@ export class TxProver implements ProverClient { return this.orchestrator.setBlockCompleted(); } - getProvingJobSource(): ProvingJobSource { + public getProvingJobSource(): ProvingJobSource { return this.queue; } } diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 1f675e04c7c..1fd146bb63e 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -189,6 +189,7 @@ __metadata: "@aztec/archiver": "workspace:^" "@aztec/aztec-node": "workspace:^" "@aztec/aztec.js": "workspace:^" + "@aztec/bb-prover": "workspace:^" "@aztec/builder": "workspace:^" "@aztec/circuit-types": "workspace:^" "@aztec/circuits.js": "workspace:^"