diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cb4446fb73..3faaa5dd774 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,7 +106,7 @@ jobs: timeout-minutes: 40 uses: ./.github/ensure-tester-with-images with: - runner_type: ${{ matrix.test == 'client-prover-integration' && '32core-tester-x86' || '8core-tester-x86' }} + runner_type: ${{ contains(matrix.test, 'prover') && '64core-tester-x86' || '8core-tester-x86' }} builder_type: builder-x86 # these are copied to the tester and expected by the earthly command below # if they fail to copy, it will try to build them on the tester and fail @@ -135,7 +135,7 @@ jobs: uses: ./.github/ensure-tester-with-images timeout-minutes: 40 with: - runner_type: 16core-tester-x86 + runner_type: ${{ contains(matrix.test, 'prover') && '64core-tester-x86' || '16core-tester-x86' }} builder_type: builder-x86 # these are copied to the tester and expected by the earthly command below # if they fail to copy, it will try to build them on the tester and fail diff --git a/scripts/logs/download_base_benchmark_from_s3.sh b/scripts/logs/download_base_benchmark_from_s3.sh index dfe5d631ab4..6da6eb2a6bb 100755 --- a/scripts/logs/download_base_benchmark_from_s3.sh +++ b/scripts/logs/download_base_benchmark_from_s3.sh @@ -12,6 +12,8 @@ BASE_BENCHMARK_FILE_JSON="${BENCH_FOLDER}/base-benchmark.json" # If on a pull request, get the data from the most recent commit on master where it's available to generate a comment comparing them if [ -n "${PULL_REQUEST:-}" ]; then MASTER_COMMIT_HASH=$(curl -s "https://api.github.com/repos/AztecProtocol/aztec-packages/pulls/${PULL_REQUEST##*/}" | jq -r '.base.sha') + # master could have diverged since starting this job, refresh history + git fetch --depth 50 origin master MASTER_COMMIT_HASHES=($(git log $MASTER_COMMIT_HASH --format="%H" -n 50)) mkdir -p $BENCH_FOLDER diff --git a/yarn-project/bb-prover/src/bb/execute.ts b/yarn-project/bb-prover/src/bb/execute.ts index d899d2a13a8..60112d96851 100644 --- a/yarn-project/bb-prover/src/bb/execute.ts +++ b/yarn-project/bb-prover/src/bb/execute.ts @@ -32,6 +32,12 @@ export type BBFailure = { export type BBResult = BBSuccess | BBFailure; +type BBExecResult = { + status: BB_RESULT; + exitCode: number; + signal: string | undefined; +}; + /** * Invokes the Barretenberg binary with the provided command and args * @param pathToBB - The path to the BB binary @@ -47,26 +53,20 @@ export function executeBB( args: string[], logger: LogFn, resultParser = (code: number) => code === 0, -) { - return new Promise((resolve, reject) => { +): Promise { + return new Promise(resolve => { // spawn the bb process - const bb = proc.spawn(pathToBB, [command, ...args]); - bb.stdout.on('data', data => { - const message = data.toString('utf-8').replace(/\n$/, ''); - logger(message); + const bb = proc.spawn(pathToBB, [command, ...args], { + stdio: 'pipe', }); - bb.stderr.on('data', data => { - const message = data.toString('utf-8').replace(/\n$/, ''); - logger(message); - }); - bb.on('close', (code: number) => { - if (resultParser(code)) { - resolve(BB_RESULT.SUCCESS); + bb.on('close', (exitCode: number, signal?: string) => { + if (resultParser(exitCode)) { + resolve({ status: BB_RESULT.SUCCESS, exitCode, signal }); } else { - reject(); + resolve({ status: BB_RESULT.FAILURE, exitCode, signal }); } }); - }).catch(_ => BB_RESULT.FAILURE); + }).catch(_ => ({ status: BB_RESULT.FAILURE, exitCode: -1, signal: undefined })); } const bytecodeHashFilename = 'bytecode_hash'; @@ -154,14 +154,14 @@ export async function generateKeyForNoirCircuit( const timer = new Timer(); let result = await executeBB(pathToBB, `write_${key}`, args, log); // If we succeeded and the type of key if verification, have bb write the 'fields' version too - if (result == BB_RESULT.SUCCESS && key === 'vk') { + if (result.status == BB_RESULT.SUCCESS && key === 'vk') { const asFieldsArgs = ['-k', `${outputPath}/${VK_FILENAME}`, '-o', `${outputPath}/${VK_FIELDS_FILENAME}`, '-v']; result = await executeBB(pathToBB, `vk_as_fields`, asFieldsArgs, log); } const duration = timer.ms(); // Cleanup the bytecode file await fs.rm(bytecodePath, { force: true }); - if (result == BB_RESULT.SUCCESS) { + if (result.status == BB_RESULT.SUCCESS) { // Store the bytecode hash so we don't need to regenerate at a later time await fs.writeFile(bytecodeHashPath, bytecodeHash); return { @@ -173,7 +173,10 @@ export async function generateKeyForNoirCircuit( }; } // Not a great error message here but it is difficult to decipher what comes from bb - return { status: BB_RESULT.FAILURE, reason: `Failed to generate key` }; + return { + status: BB_RESULT.FAILURE, + reason: `Failed to generate key. Exit code: ${result.exitCode}. Signal ${result.signal}.`, + }; } catch (error) { return { status: BB_RESULT.FAILURE, reason: `${error}` }; } @@ -231,7 +234,7 @@ export async function generateProof( const duration = timer.ms(); // cleanup the bytecode await fs.rm(bytecodePath, { force: true }); - if (result == BB_RESULT.SUCCESS) { + if (result.status == BB_RESULT.SUCCESS) { return { status: BB_RESULT.SUCCESS, duration, @@ -241,7 +244,10 @@ export async function generateProof( }; } // Not a great error message here but it is difficult to decipher what comes from bb - return { status: BB_RESULT.FAILURE, reason: `Failed to generate proof` }; + return { + status: BB_RESULT.FAILURE, + reason: `Failed to generate proof. Exit code ${result.exitCode}. Signal ${result.signal}.`, + }; } catch (error) { return { status: BB_RESULT.FAILURE, reason: `${error}` }; } @@ -274,11 +280,14 @@ export async function verifyProof( const timer = new Timer(); const result = await executeBB(pathToBB, 'verify', args, log); const duration = timer.ms(); - if (result == BB_RESULT.SUCCESS) { + if (result.status == BB_RESULT.SUCCESS) { return { status: BB_RESULT.SUCCESS, duration }; } // Not a great error message here but it is difficult to decipher what comes from bb - return { status: BB_RESULT.FAILURE, reason: `Failed to verify proof` }; + return { + status: BB_RESULT.FAILURE, + reason: `Failed to verify proof. Exit code ${result.exitCode}. Signal ${result.signal}.`, + }; } catch (error) { return { status: BB_RESULT.FAILURE, reason: `${error}` }; } @@ -311,11 +320,14 @@ export async function writeVkAsFields( const timer = new Timer(); const result = await executeBB(pathToBB, 'vk_as_fields', args, log); const duration = timer.ms(); - if (result == BB_RESULT.SUCCESS) { + if (result.status == BB_RESULT.SUCCESS) { return { status: BB_RESULT.SUCCESS, duration, vkPath: verificationKeyPath }; } // Not a great error message here but it is difficult to decipher what comes from bb - return { status: BB_RESULT.FAILURE, reason: `Failed to create vk as fields` }; + return { + status: BB_RESULT.FAILURE, + reason: `Failed to create vk as fields. Exit code ${result.exitCode}. Signal ${result.signal}.`, + }; } catch (error) { return { status: BB_RESULT.FAILURE, reason: `${error}` }; } @@ -348,11 +360,14 @@ export async function writeProofAsFields( const timer = new Timer(); const result = await executeBB(pathToBB, 'proof_as_fields', args, log); const duration = timer.ms(); - if (result == BB_RESULT.SUCCESS) { + if (result.status == BB_RESULT.SUCCESS) { return { status: BB_RESULT.SUCCESS, duration, proofPath: proofPath }; } // Not a great error message here but it is difficult to decipher what comes from bb - return { status: BB_RESULT.FAILURE, reason: `Failed to create proof as fields` }; + return { + status: BB_RESULT.FAILURE, + reason: `Failed to create proof as fields. Exit code ${result.exitCode}. Signal ${result.signal}.`, + }; } catch (error) { return { status: BB_RESULT.FAILURE, reason: `${error}` }; } diff --git a/yarn-project/bb-prover/src/prover/bb_native_proof_creator.ts b/yarn-project/bb-prover/src/prover/bb_native_proof_creator.ts index cef282a7064..5e76f59f489 100644 --- a/yarn-project/bb-prover/src/prover/bb_native_proof_creator.ts +++ b/yarn-project/bb-prover/src/prover/bb_native_proof_creator.ts @@ -1,4 +1,5 @@ import { type AppCircuitProofOutput, type KernelProofOutput, type ProofCreator } from '@aztec/circuit-types'; +import { type CircuitProvingStats, type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; import { Fr, NESTED_RECURSIVE_PROOF_LENGTH, @@ -19,6 +20,7 @@ import { siloNoteHash } from '@aztec/circuits.js/hash'; import { randomBytes } from '@aztec/foundation/crypto'; import { createDebugLogger } from '@aztec/foundation/log'; import { type Tuple } from '@aztec/foundation/serialize'; +import { Timer } from '@aztec/foundation/timer'; import { ClientCircuitArtifacts, type ClientProtocolArtifact, @@ -50,6 +52,7 @@ import { generateProof, verifyProof, } from '../bb/execute.js'; +import { mapProtocolArtifactNameToCircuitName } from '../stats.js'; import { AGGREGATION_OBJECT_SIZE, CIRCUIT_PUBLIC_INPUTS_INDEX, @@ -58,28 +61,6 @@ import { type VerificationKeyData, } from './verification_key_data.js'; -type PrivateKernelProvingOps = { - convertOutputs: (outputs: WitnessMap) => PrivateKernelCircuitPublicInputs | PrivateKernelTailCircuitPublicInputs; -}; - -const PrivateKernelArtifactMapping: Record = { - PrivateKernelInitArtifact: { - convertOutputs: convertPrivateKernelInitOutputsFromWitnessMap, - }, - PrivateKernelInnerArtifact: { - convertOutputs: convertPrivateKernelInnerOutputsFromWitnessMap, - }, - PrivateKernelTailArtifact: { - convertOutputs: convertPrivateKernelTailOutputsFromWitnessMap, - }, - PrivateKernelResetArtifact: { - convertOutputs: convertPrivateKernelResetOutputsFromWitnessMap, - }, - PrivateKernelTailToPublicArtifact: { - convertOutputs: convertPrivateKernelTailForPublicOutputsFromWitnessMap, - }, -}; - /** * This proof creator implementation uses the native bb binary. * This is a temporary implementation until we make the WASM version work. @@ -109,45 +90,66 @@ export class BBNativeProofCreator implements ProofCreator { public async createProofInit( inputs: PrivateKernelInitCircuitPrivateInputs, ): Promise> { - const witnessMap = convertPrivateKernelInitInputsToWitnessMap(inputs); - return await this.createSafeProof(witnessMap, 'PrivateKernelInitArtifact'); + return await this.createSafeProof( + inputs, + 'PrivateKernelInitArtifact', + convertPrivateKernelInitInputsToWitnessMap, + convertPrivateKernelInitOutputsFromWitnessMap, + ); } public async createProofInner( inputs: PrivateKernelInnerCircuitPrivateInputs, ): Promise> { - const witnessMap = convertPrivateKernelInnerInputsToWitnessMap(inputs); - return await this.createSafeProof(witnessMap, 'PrivateKernelInnerArtifact'); + return await this.createSafeProof( + inputs, + 'PrivateKernelInnerArtifact', + convertPrivateKernelInnerInputsToWitnessMap, + convertPrivateKernelInnerOutputsFromWitnessMap, + ); } public async createProofReset( inputs: PrivateKernelResetCircuitPrivateInputs, ): Promise> { - const witnessMap = convertPrivateKernelResetInputsToWitnessMap(inputs); - return await this.createSafeProof(witnessMap, 'PrivateKernelResetArtifact'); + return await this.createSafeProof( + inputs, + 'PrivateKernelResetArtifact', + convertPrivateKernelResetInputsToWitnessMap, + convertPrivateKernelResetOutputsFromWitnessMap, + ); } public async createProofTail( inputs: PrivateKernelTailCircuitPrivateInputs, ): Promise> { if (!inputs.isForPublic()) { - const witnessMap = convertPrivateKernelTailInputsToWitnessMap(inputs); - return await this.createSafeProof(witnessMap, 'PrivateKernelTailArtifact'); + return await this.createSafeProof( + inputs, + 'PrivateKernelTailArtifact', + convertPrivateKernelTailInputsToWitnessMap, + convertPrivateKernelTailOutputsFromWitnessMap, + ); } - const witnessMap = convertPrivateKernelTailToPublicInputsToWitnessMap(inputs); - return await this.createSafeProof(witnessMap, 'PrivateKernelTailToPublicArtifact'); + return await this.createSafeProof( + inputs, + 'PrivateKernelTailToPublicArtifact', + convertPrivateKernelTailToPublicInputsToWitnessMap, + convertPrivateKernelTailForPublicOutputsFromWitnessMap, + ); } public async createAppCircuitProof( partialWitness: Map, bytecode: Buffer, + appCircuitName?: string, ): Promise { const directory = `${this.bbWorkingDirectory}/${randomBytes(8).toString('hex')}`; await fs.mkdir(directory, { recursive: true }); this.log.debug(`Created directory: ${directory}`); try { this.log.debug(`Proving app circuit`); - const proofOutput = await this.createProof(directory, partialWitness, bytecode, 'App'); + const proofOutput = await this.createProof(directory, partialWitness, bytecode, 'App', 0, 0, appCircuitName); if (proofOutput.proof.proof.length != RECURSIVE_PROOF_LENGTH) { throw new Error(`Incorrect proof length`); } @@ -276,48 +278,66 @@ export class BBNativeProofCreator implements ProofCreator { return await promise; } - private async createSafeProof( - inputs: WitnessMap, + private async createSafeProof Buffer }, O extends { toBuffer: () => Buffer }>( + inputs: I, circuitType: ClientProtocolArtifact, - ): Promise> { + convertInputs: (inputs: I) => WitnessMap, + convertOutputs: (outputs: WitnessMap) => O, + ): Promise> { const directory = `${this.bbWorkingDirectory}/${randomBytes(8).toString('hex')}`; await fs.mkdir(directory, { recursive: true }); this.log.debug(`Created directory: ${directory}`); try { - return await this.generateWitnessAndCreateProof(inputs, circuitType, directory); + return await this.generateWitnessAndCreateProof(inputs, circuitType, directory, convertInputs, convertOutputs); } finally { await fs.rm(directory, { recursive: true, force: true }); this.log.debug(`Deleted directory: ${directory}`); } } - private async generateWitnessAndCreateProof( - inputs: WitnessMap, + private async generateWitnessAndCreateProof< + I extends { toBuffer: () => Buffer }, + O extends { toBuffer: () => Buffer }, + >( + inputs: I, circuitType: ClientProtocolArtifact, directory: string, - ): Promise> { + convertInputs: (inputs: I) => WitnessMap, + convertOutputs: (outputs: WitnessMap) => O, + ): Promise> { this.log.debug(`Generating witness for ${circuitType}`); const compiledCircuit: NoirCompiledCircuit = ClientCircuitArtifacts[circuitType]; - const outputWitness = await this.simulator.simulateCircuit(inputs, compiledCircuit); - - this.log.debug(`Generated witness for ${circuitType}`); - - const publicInputs = PrivateKernelArtifactMapping[circuitType].convertOutputs(outputWitness) as T; + const witnessMap = convertInputs(inputs); + const timer = new Timer(); + const outputWitness = await this.simulator.simulateCircuit(witnessMap, compiledCircuit); + const output = convertOutputs(outputWitness); + + const inputSize = inputs.toBuffer().length; + const outputSize = output.toBuffer().length; + this.log.debug(`Generated witness for ${circuitType}`, { + eventName: 'circuit-witness-generation', + circuitName: mapProtocolArtifactNameToCircuitName(circuitType), + duration: timer.ms(), + inputSize, + outputSize, + } satisfies CircuitWitnessGenerationStats); const proofOutput = await this.createProof( directory, outputWitness, Buffer.from(compiledCircuit.bytecode, 'base64'), circuitType, + inputSize, + outputSize, ); if (proofOutput.proof.proof.length != NESTED_RECURSIVE_PROOF_LENGTH) { throw new Error(`Incorrect proof length`); } const nestedProof = proofOutput.proof as RecursiveProof; - const kernelOutput: KernelProofOutput = { - publicInputs, + const kernelOutput: KernelProofOutput = { + publicInputs: output, proof: nestedProof, verificationKey: proofOutput.verificationKey, }; @@ -329,6 +349,9 @@ export class BBNativeProofCreator implements ProofCreator { partialWitness: WitnessMap, bytecode: Buffer, circuitType: ClientProtocolArtifact | 'App', + inputSize: number, + outputSize: number, + appCircuitName?: string, ): Promise<{ proof: RecursiveProof | RecursiveProof; verificationKey: VerificationKeyAsFields; @@ -358,11 +381,36 @@ export class BBNativeProofCreator implements ProofCreator { if (circuitType === 'App') { const vkData = await this.convertVk(directory); const proof = await this.readProofAsFields(directory, circuitType, vkData); + + this.log.debug(`Generated proof`, { + eventName: 'circuit-proving', + circuitName: 'app-circuit', + duration: provingResult.duration, + inputSize, + outputSize, + proofSize: proof.binaryProof.buffer.length, + appCircuitName, + circuitSize: vkData.circuitSize, + numPublicInputs: vkData.numPublicInputs, + } as CircuitProvingStats); + return { proof, verificationKey: new VerificationKeyAsFields(vkData.keyAsFields, vkData.hash) }; } const vkData = await this.updateVerificationKeyAfterProof(directory, circuitType); const proof = await this.readProofAsFields(directory, circuitType, vkData); + + this.log.debug(`Generated proof`, { + circuitName: mapProtocolArtifactNameToCircuitName(circuitType), + duration: provingResult.duration, + eventName: 'circuit-proving', + inputSize, + outputSize, + proofSize: proof.binaryProof.buffer.length, + circuitSize: vkData.circuitSize, + numPublicInputs: vkData.numPublicInputs, + } as CircuitProvingStats); + return { proof, verificationKey: new VerificationKeyAsFields(vkData.keyAsFields, vkData.hash) }; } diff --git a/yarn-project/bb-prover/src/prover/bb_prover.ts b/yarn-project/bb-prover/src/prover/bb_prover.ts index bca2596b015..40c65c829d0 100644 --- a/yarn-project/bb-prover/src/prover/bb_prover.ts +++ b/yarn-project/bb-prover/src/prover/bb_prover.ts @@ -7,6 +7,7 @@ import { type ServerCircuitProver, makePublicInputsAndProof, } from '@aztec/circuit-types'; +import { type CircuitProvingStats, type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; import { type BaseOrMergeRollupPublicInputs, type BaseParityInputs, @@ -14,12 +15,11 @@ import { Fr, type KernelCircuitPublicInputs, type MergeRollupInputs, - type NESTED_RECURSIVE_PROOF_LENGTH, - type ParityPublicInputs, + NESTED_RECURSIVE_PROOF_LENGTH, type PreviousRollupData, Proof, type PublicKernelCircuitPublicInputs, - type RECURSIVE_PROOF_LENGTH, + RECURSIVE_PROOF_LENGTH, RecursiveProof, RollupTypes, RootParityInput, @@ -65,7 +65,7 @@ import { verifyProof, } from '../bb/execute.js'; import { PublicKernelArtifactMapping } from '../mappings/mappings.js'; -import { circuitTypeToCircuitName, emitCircuitProvingStats, emitCircuitWitnessGenerationStats } from '../stats.js'; +import { mapProtocolArtifactNameToCircuitName } from '../stats.js'; import { AGGREGATION_OBJECT_SIZE, CIRCUIT_PUBLIC_INPUTS_INDEX, @@ -114,11 +114,11 @@ export class BBNativeRollupProver implements ServerCircuitProver { * @returns The public inputs of the parity circuit. */ public async getBaseParityProof(inputs: BaseParityInputs): Promise> { - const witnessMap = convertBaseParityInputsToWitnessMap(inputs); - - const [circuitOutput, proof] = await this.createRecursiveProof( - witnessMap, + const [circuitOutput, proof] = await this.createRecursiveProof( + inputs, 'BaseParityArtifact', + RECURSIVE_PROOF_LENGTH, + convertBaseParityInputsToWitnessMap, convertBaseParityOutputsFromWitnessMap, ); @@ -137,12 +137,13 @@ export class BBNativeRollupProver implements ServerCircuitProver { public async getRootParityProof( inputs: RootParityInputs, ): Promise> { - const witnessMap = convertRootParityInputsToWitnessMap(inputs); - - const [circuitOutput, proof] = await this.createRecursiveProof< - typeof NESTED_RECURSIVE_PROOF_LENGTH, - ParityPublicInputs - >(witnessMap, 'RootParityArtifact', convertRootParityOutputsFromWitnessMap); + const [circuitOutput, proof] = await this.createRecursiveProof( + inputs, + 'RootParityArtifact', + NESTED_RECURSIVE_PROOF_LENGTH, + convertRootParityInputsToWitnessMap, + convertRootParityOutputsFromWitnessMap, + ); const verificationKey = await this.getVerificationKeyDataForCircuit('RootParityArtifact'); @@ -163,11 +164,13 @@ export class BBNativeRollupProver implements ServerCircuitProver { if (kernelOps === undefined) { throw new Error(`Unable to prove kernel type ${PublicKernelType[kernelRequest.type]}`); } - const witnessMap = kernelOps.convertInputs(kernelRequest.inputs); - - const [outputWitness, proof] = await this.createProof(witnessMap, kernelOps.artifact); + const [result, proof] = await this.createProof( + kernelRequest.inputs, + kernelOps.artifact, + kernelOps.convertInputs, + kernelOps.convertOutputs, + ); - const result = kernelOps.convertOutputs(outputWitness); return makePublicInputsAndProof(result, proof); } @@ -179,11 +182,13 @@ export class BBNativeRollupProver implements ServerCircuitProver { public async getPublicTailProof( kernelRequest: PublicKernelTailRequest, ): Promise> { - const witnessMap = convertPublicTailInputsToWitnessMap(kernelRequest.inputs); - - const [outputWitness, proof] = await this.createProof(witnessMap, 'PublicKernelTailArtifact'); + const [result, proof] = await this.createProof( + kernelRequest.inputs, + 'PublicKernelTailArtifact', + convertPublicTailInputsToWitnessMap, + convertPublicTailOutputFromWitnessMap, + ); - const result = convertPublicTailOutputFromWitnessMap(outputWitness); return makePublicInputsAndProof(result, proof); } @@ -195,11 +200,12 @@ export class BBNativeRollupProver implements ServerCircuitProver { public async getBaseRollupProof( input: BaseRollupInputs, ): Promise> { - const witnessMap = convertBaseRollupInputsToWitnessMap(input); - - const [outputWitness, proof] = await this.createProof(witnessMap, 'BaseRollupArtifact'); - - const result = convertBaseRollupOutputsFromWitnessMap(outputWitness); + const [result, proof] = await this.createProof( + input, + 'BaseRollupArtifact', + convertBaseRollupInputsToWitnessMap, + convertBaseRollupOutputsFromWitnessMap, + ); return makePublicInputsAndProof(result, proof); } @@ -214,11 +220,12 @@ export class BBNativeRollupProver implements ServerCircuitProver { // verify both inputs await Promise.all(input.previousRollupData.map(prev => this.verifyPreviousRollupProof(prev))); - const witnessMap = convertMergeRollupInputsToWitnessMap(input); - - const [outputWitness, proof] = await this.createProof(witnessMap, 'MergeRollupArtifact'); - - const result = convertMergeRollupOutputsFromWitnessMap(outputWitness); + const [result, proof] = await this.createProof( + input, + 'MergeRollupArtifact', + convertMergeRollupInputsToWitnessMap, + convertMergeRollupOutputsFromWitnessMap, + ); return makePublicInputsAndProof(result, proof); } @@ -232,18 +239,25 @@ export class BBNativeRollupProver implements ServerCircuitProver { // verify the inputs await Promise.all(input.previousRollupData.map(prev => this.verifyPreviousRollupProof(prev))); - const witnessMap = convertRootRollupInputsToWitnessMap(input); - - const [outputWitness, proof] = await this.createProof(witnessMap, 'RootRollupArtifact'); + const [result, proof] = await this.createProof( + input, + 'RootRollupArtifact', + convertRootRollupInputsToWitnessMap, + convertRootRollupOutputsFromWitnessMap, + ); await this.verifyProof('RootRollupArtifact', proof); - const result = convertRootRollupOutputsFromWitnessMap(outputWitness); return makePublicInputsAndProof(result, proof); } // TODO(@PhilWindle): Delete when no longer required - public async createProof(witnessMap: WitnessMap, circuitType: ServerProtocolArtifact): Promise<[WitnessMap, Proof]> { + public async createProof Buffer }, Output extends { toBuffer: () => Buffer }>( + input: Input, + circuitType: ServerProtocolArtifact, + convertInput: (input: Input) => WitnessMap, + convertOutput: (outputWitness: WitnessMap) => Output, + ): Promise<[Output, Proof]> { // Create random directory to be used for temp files const bbWorkingDirectory = `${this.config.bbWorkingDirectory}/${randomBytes(8).toString('hex')}`; await fs.mkdir(bbWorkingDirectory, { recursive: true }); @@ -265,15 +279,16 @@ export class BBNativeRollupProver implements ServerCircuitProver { logger.debug(`Generating witness data for ${circuitType}`); + const witnessMap = convertInput(input); const timer = new Timer(); const outputWitness = await simulator.simulateCircuit(witnessMap, artifact); - emitCircuitWitnessGenerationStats( - circuitTypeToCircuitName(circuitType), - timer.ms(), - witnessMap.size * Fr.SIZE_IN_BYTES, - outputWitness.size * Fr.SIZE_IN_BYTES, - logger, - ); + logger.debug(`Generated witness`, { + circuitName: mapProtocolArtifactNameToCircuitName(circuitType), + duration: timer.ms(), + inputSize: witnessMap.size * Fr.SIZE_IN_BYTES, + outputSize: outputWitness.size * Fr.SIZE_IN_BYTES, + eventName: 'circuit-witness-generation', + } satisfies CircuitWitnessGenerationStats); // Now prove the circuit from the generated witness logger.debug(`Proving ${circuitType}...`); @@ -293,38 +308,51 @@ export class BBNativeRollupProver implements ServerCircuitProver { } // Ensure our vk cache is up to date - await this.updateVerificationKeyAfterProof(provingResult.vkPath!, circuitType); + const vkData = await this.updateVerificationKeyAfterProof(provingResult.vkPath!, circuitType); // Read the proof and then cleanup up our temporary directory - const proof = await fs.readFile(`${provingResult.proofPath!}/${PROOF_FILENAME}`); - - // does not include reading the proof from disk above because duration comes from the bb wrapper - emitCircuitProvingStats( - circuitTypeToCircuitName(circuitType), - provingResult.duration, - witnessMap.size * Fr.SIZE_IN_BYTES, - outputWitness.size * Fr.SIZE_IN_BYTES, - proof.length, - logger, - ); + const rawProof = await fs.readFile(`${provingResult.proofPath!}/${PROOF_FILENAME}`); await fs.rm(bbWorkingDirectory, { recursive: true, force: true }); - logger.info(`Generated proof for ${circuitType} in ${provingResult.duration} ms, size: ${proof.length} fields`); + const output = convertOutput(outputWitness); + const proof = new Proof(rawProof); + logger.info( + `Generated proof for ${circuitType} in ${provingResult.duration} ms, size: ${proof.buffer.length} fields`, + { + circuitName: mapProtocolArtifactNameToCircuitName(circuitType), + // does not include reading the proof from disk + duration: provingResult.duration, + proofSize: proof.buffer.length, + eventName: 'circuit-proving', + inputSize: input.toBuffer().length, + outputSize: output.toBuffer().length, + circuitSize: vkData.circuitSize, + numPublicInputs: vkData.numPublicInputs, + } satisfies CircuitProvingStats, + ); - return [outputWitness, new Proof(proof)]; + return [output, proof]; } /** * Executes a circuit and returns it's outputs and corresponding proof with embedded aggregation object * @param witnessMap - The input witness * @param circuitType - The type of circuit to be executed + * @param proofLength - The length of the proof to be generated. This is a dummy parameter to aid in type checking + * @param convertInput - Function for mapping the input object to a witness map. * @param convertOutput - Function for parsing the output witness to it's corresponding object * @returns The circuits output object and it's proof */ - public async createRecursiveProof( - witnessMap: WitnessMap, + public async createRecursiveProof< + PROOF_LENGTH extends number, + CircuitInputType extends { toBuffer: () => Buffer }, + CircuitOutputType extends { toBuffer: () => Buffer }, + >( + input: CircuitInputType, circuitType: ServerProtocolArtifact, + proofLength: PROOF_LENGTH, + convertInput: (input: CircuitInputType) => WitnessMap, convertOutput: (outputWitness: WitnessMap) => CircuitOutputType, ): Promise<[CircuitOutputType, RecursiveProof]> { // Create random directory to be used for temp files @@ -350,17 +378,20 @@ export class BBNativeRollupProver implements ServerCircuitProver { logger.debug(`Generating witness data for ${circuitType}`); const timer = new Timer(); + const witnessMap = convertInput(input); const outputWitness = await simulator.simulateCircuit(witnessMap, artifact); - emitCircuitWitnessGenerationStats( - circuitTypeToCircuitName(circuitType), - timer.ms(), - witnessMap.size * Fr.SIZE_IN_BYTES, - outputWitness.size * Fr.SIZE_IN_BYTES, - logger, - ); + const output = convertOutput(outputWitness); - const outputType = convertOutput(outputWitness); + const inputSize = input.toBuffer().length; + const outputSize = output.toBuffer().length; + logger.debug(`Generated witness`, { + circuitName: mapProtocolArtifactNameToCircuitName(circuitType), + duration: timer.ms(), + inputSize, + outputSize, + eventName: 'circuit-witness-generation', + } satisfies CircuitWitnessGenerationStats); // Now prove the circuit from the generated witness logger.debug(`Proving ${circuitType}...`); @@ -380,25 +411,26 @@ export class BBNativeRollupProver implements ServerCircuitProver { } // Ensure our vk cache is up to date - await this.updateVerificationKeyAfterProof(provingResult.vkPath!, circuitType); + const vkData = await this.updateVerificationKeyAfterProof(provingResult.vkPath!, circuitType); // Read the proof and then cleanup up our temporary directory - const proof = await this.readProofAsFields(provingResult.proofPath!, circuitType); + const proof = await this.readProofAsFields(provingResult.proofPath!, circuitType, proofLength); logger.info( `Generated proof for ${circuitType} in ${provingResult.duration} ms, size: ${proof.proof.length} fields`, + { + circuitName: mapProtocolArtifactNameToCircuitName(circuitType), + circuitSize: vkData.circuitSize, + duration: provingResult.duration, + inputSize, + outputSize, + proofSize: proof.binaryProof.buffer.length, + eventName: 'circuit-proving', + numPublicInputs: vkData.numPublicInputs, + } satisfies CircuitProvingStats, ); - emitCircuitProvingStats( - circuitTypeToCircuitName(circuitType), - provingResult.duration, - witnessMap.size * Fr.SIZE_IN_BYTES, - outputWitness.size * Fr.SIZE_IN_BYTES, - proof.binaryProof.buffer.length, - logger, - ); - - return [outputType, proof]; + return [output, proof]; } finally { await fs.rm(bbWorkingDirectory, { recursive: true, force: true }); } @@ -515,13 +547,16 @@ export class BBNativeRollupProver implements ServerCircuitProver { * @param filePath - The directory containing the verification key data files * @param circuitType - The type of circuit to which the verification key corresponds */ - private async updateVerificationKeyAfterProof(filePath: string, circuitType: ServerProtocolArtifact) { + private async updateVerificationKeyAfterProof( + filePath: string, + circuitType: ServerProtocolArtifact, + ): Promise { let promise = this.verificationKeys.get(circuitType); if (!promise) { promise = this.convertVk(filePath); this.verificationKeys.set(circuitType, promise); } - await promise; + return promise; } /** @@ -533,6 +568,7 @@ export class BBNativeRollupProver implements ServerCircuitProver { private async readProofAsFields( filePath: string, circuitType: ServerProtocolArtifact, + proofLength: PROOF_LENGTH, ): Promise> { const [binaryProof, proofString] = await Promise.all([ fs.readFile(`${filePath}/${PROOF_FILENAME}`), @@ -552,6 +588,10 @@ export class BBNativeRollupProver implements ServerCircuitProver { `Circuit type: ${circuitType}, complete proof length: ${fields.length}, without public inputs: ${fieldsWithoutPublicInputs.length}, num public inputs: ${numPublicInputs}, circuit size: ${vkData.circuitSize}, is recursive: ${vkData.isRecursive}, raw length: ${binaryProof.length}`, ); const proof = new RecursiveProof(fieldsWithoutPublicInputs, new Proof(binaryProof)); + if (proof.proof.length !== proofLength) { + throw new Error("Proof length doesn't match expected length"); + } + return proof; } } diff --git a/yarn-project/bb-prover/src/stats.ts b/yarn-project/bb-prover/src/stats.ts index fbd68bde90b..9627d0db8f9 100644 --- a/yarn-project/bb-prover/src/stats.ts +++ b/yarn-project/bb-prover/src/stats.ts @@ -1,45 +1,6 @@ import { type PublicKernelRequest, PublicKernelType } from '@aztec/circuit-types'; -import type { CircuitName, CircuitProvingStats, CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; -import { type Logger } from '@aztec/foundation/log'; -import { type ServerProtocolArtifact } from '@aztec/noir-protocol-circuits-types'; - -export function emitCircuitWitnessGenerationStats( - circuitName: CircuitName, - duration: number, - inputSize: number, - outputSize: number, - logger: Logger, -) { - const stats: CircuitWitnessGenerationStats = { - eventName: 'circuit-witness-generation', - circuitName, - inputSize, - outputSize, - duration, - }; - - logger.debug('Circuit witness generation stats', stats); -} - -export function emitCircuitProvingStats( - circuitName: CircuitName, - duration: number, - inputSize: number, - outputSize: number, - proofSize: number, - logger: Logger, -) { - const stats: CircuitProvingStats = { - eventName: 'circuit-proving', - circuitName, - duration, - inputSize, - outputSize, - proofSize, - }; - - logger.debug('Circuit proving stats', stats); -} +import type { CircuitName } from '@aztec/circuit-types/stats'; +import { type ClientProtocolArtifact, type ServerProtocolArtifact } from '@aztec/noir-protocol-circuits-types'; export function mapPublicKernelToCircuitName(kernelType: PublicKernelRequest['type']): CircuitName { switch (kernelType) { @@ -56,8 +17,10 @@ export function mapPublicKernelToCircuitName(kernelType: PublicKernelRequest['ty } } -export function circuitTypeToCircuitName(circuitType: ServerProtocolArtifact): CircuitName { - switch (circuitType) { +export function mapProtocolArtifactNameToCircuitName( + artifact: ServerProtocolArtifact | ClientProtocolArtifact, +): CircuitName { + switch (artifact) { case 'BaseParityArtifact': return 'base-parity'; case 'RootParityArtifact': @@ -76,7 +39,17 @@ export function circuitTypeToCircuitName(circuitType: ServerProtocolArtifact): C return 'public-kernel-teardown'; case 'PublicKernelTailArtifact': return 'public-kernel-tail'; + case 'PrivateKernelInitArtifact': + return 'private-kernel-init'; + case 'PrivateKernelInnerArtifact': + return 'private-kernel-inner'; + case 'PrivateKernelTailArtifact': + return 'private-kernel-tail'; + case 'PrivateKernelTailToPublicArtifact': + return 'private-kernel-tail-to-public'; + case 'PrivateKernelResetArtifact': + return 'private-kernel-reset'; default: - throw new Error(`Unknown circuit type: ${circuitType}`); + throw new Error(`Unknown circuit type: ${artifact}`); } } diff --git a/yarn-project/circuit-types/src/interfaces/proof_creator.ts b/yarn-project/circuit-types/src/interfaces/proof_creator.ts index 9c66dcbdcb1..7a3ef4042dc 100644 --- a/yarn-project/circuit-types/src/interfaces/proof_creator.ts +++ b/yarn-project/circuit-types/src/interfaces/proof_creator.ts @@ -103,7 +103,12 @@ export interface ProofCreator { * * @param partialWitness - The witness produced via circuit simulation * @param bytecode - The circuit bytecode in gzipped bincode format + * @param appCircuitName - Optionally specify the name of the app circuit * @returns A Promise resolving to a Proof object */ - createAppCircuitProof(partialWitness: WitnessMap, bytecode: Buffer): Promise; + createAppCircuitProof( + partialWitness: WitnessMap, + bytecode: Buffer, + appCircuitName?: string, + ): Promise; } diff --git a/yarn-project/circuit-types/src/stats/metrics.ts b/yarn-project/circuit-types/src/stats/metrics.ts index 5b7217e42f5..41fbc543064 100644 --- a/yarn-project/circuit-types/src/stats/metrics.ts +++ b/yarn-project/circuit-types/src/stats/metrics.ts @@ -4,7 +4,8 @@ import { type StatsEventName } from './stats.js'; export type MetricGroupBy = | 'block-size' | 'chain-length' - | 'circuit-name' + | 'protocol-circuit-name' + | 'app-circuit-name' | 'classes-registered' | 'leaf-count' | 'data-writes' @@ -111,41 +112,101 @@ export const Metrics = [ events: ['note-processor-caught-up'], }, { - name: 'circuit_simulation_time_in_ms', - groupBy: 'circuit-name', + name: 'protocol_circuit_simulation_time_in_ms', + groupBy: 'protocol-circuit-name', description: 'Time to run a circuit simulation.', events: ['circuit-simulation'], }, { - name: 'circuit_witness_generation_time_in_ms', - groupBy: 'circuit-name', + name: 'protocol_circuit_witness_generation_time_in_ms', + groupBy: 'protocol-circuit-name', description: 'Time to generate the partial witness for a circuit', events: ['circuit-simulation'], }, { - name: 'circuit_proving_time_in_ms', - groupBy: 'circuit-name', + name: 'protocol_circuit_proving_time_in_ms', + groupBy: 'protocol-circuit-name', description: 'Time to prove circuit execution.', events: ['circuit-proving'], }, { - name: 'circuit_input_size_in_bytes', - groupBy: 'circuit-name', + name: 'protocol_circuit_input_size_in_bytes', + groupBy: 'protocol-circuit-name', description: 'Size of the inputs to a circuit simulation.', events: ['circuit-simulation'], }, { - name: 'circuit_output_size_in_bytes', - groupBy: 'circuit-name', + name: 'protocol_circuit_output_size_in_bytes', + groupBy: 'protocol-circuit-name', description: 'Size of the outputs (ie public inputs) from a circuit simulation.', events: ['circuit-simulation'], }, { - name: 'circuit_proof_size_in_bytes', - groupBy: 'circuit-name', + name: 'protocol_circuit_proof_size_in_bytes', + groupBy: 'protocol-circuit-name', description: 'Size of the proof produced by a circuit.', events: ['circuit-proving'], }, + { + name: 'protocol_circuit_num_public_inputs', + groupBy: 'protocol-circuit-name', + description: 'Number of public inputs.', + events: ['circuit-proving'], + }, + { + name: 'protocol_circuit_size_in_gates', + groupBy: 'protocol-circuit-name', + description: 'Size of the proof produced by a circuit.', + events: ['circuit-proving'], + }, + { + name: 'app_circuit_simulation_time_in_ms', + groupBy: 'app-circuit-name', + description: 'Time to run a circuit simulation.', + events: ['circuit-simulation'], + }, + { + name: 'app_circuit_input_size_in_bytes', + groupBy: 'app-circuit-name', + description: 'Size of the inputs to a circuit simulation.', + events: ['circuit-simulation'], + }, + { + name: 'app_circuit_output_size_in_bytes', + groupBy: 'app-circuit-name', + description: 'Size of the outputs (ie public inputs) from a circuit simulation.', + events: ['circuit-simulation'], + }, + { + name: 'app_circuit_proof_size_in_bytes', + groupBy: 'app-circuit-name', + description: 'Size of the proof produced by a circuit.', + events: ['circuit-proving'], + }, + { + name: 'app_circuit_witness_generation_time_in_ms', + groupBy: 'app-circuit-name', + description: 'Time to generate the partial witness for a circuit', + events: ['circuit-simulation'], + }, + { + name: 'app_circuit_proving_time_in_ms', + groupBy: 'app-circuit-name', + description: 'Duration of proving an app circuit.', + events: ['circuit-proving'], + }, + { + name: 'app_circuit_size_in_gates', + groupBy: 'app-circuit-name', + description: 'Size of an app circuit.', + events: ['circuit-proving'], + }, + { + name: 'app_circuit_num_public_inputs', + groupBy: 'app-circuit-name', + description: 'Number of public inputs.', + events: ['circuit-proving'], + }, { name: 'tx_size_in_bytes', groupBy: 'classes-registered', diff --git a/yarn-project/circuit-types/src/stats/stats.ts b/yarn-project/circuit-types/src/stats/stats.ts index 7e78a0cba7c..750735d8cef 100644 --- a/yarn-project/circuit-types/src/stats/stats.ts +++ b/yarn-project/circuit-types/src/stats/stats.ts @@ -52,12 +52,14 @@ export type CircuitName = | 'base-parity' | 'root-parity' | 'base-rollup' - | 'private-kernel-init' - | 'private-kernel-ordering' - | 'root-rollup' | 'merge-rollup' + | 'root-rollup' + | 'private-kernel-init' | 'private-kernel-inner' | 'private-kernel-reset' + | 'private-kernel-tail' + | 'private-kernel-tail-to-public' + | 'app-circuit' | 'public-kernel-setup' | 'public-kernel-app-logic' | 'public-kernel-teardown' @@ -69,6 +71,8 @@ export type CircuitSimulationStats = { eventName: 'circuit-simulation'; /** Name of the circuit. */ circuitName: CircuitName; + /** Optional. The function name that's being simulated */ + appCircuitName?: string; /** Duration in ms. */ duration: number; /** Size in bytes of circuit inputs. */ @@ -83,6 +87,8 @@ export type CircuitWitnessGenerationStats = { eventName: 'circuit-witness-generation'; /** Name of the circuit. */ circuitName: CircuitName; + /** Optional. The function name that's being proven */ + appCircuitName?: string; /** Duration in ms. */ duration: number; /** Size in bytes of circuit inputs. */ @@ -97,14 +103,20 @@ export type CircuitProvingStats = { eventName: 'circuit-proving'; /** Name of the circuit. */ circuitName: CircuitName; + /** Optional. The function name that was proven */ + appCircuitName?: string; /** Duration in ms. */ duration: number; + /** The size of the circuit (in gates) */ + circuitSize: number; /** Size in bytes of circuit inputs. */ inputSize: number; - /** Size in bytes of circuit outputs (aka public inputs). */ + /** Size in bytes of circuit output. */ outputSize: number; /** Size in bytes of the proof. */ proofSize: number; + /** The number of public inputs */ + numPublicInputs: number; }; /** Stats for an L2 block built by a sequencer. */ diff --git a/yarn-project/end-to-end/Earthfile b/yarn-project/end-to-end/Earthfile index db3bc073183..571ad75dc41 100644 --- a/yarn-project/end-to-end/Earthfile +++ b/yarn-project/end-to-end/Earthfile @@ -198,9 +198,9 @@ bench-tx-size: DO +E2E_COMPOSE_TEST --test=benchmarks/bench_tx_size_fees.test.ts --debug="aztec:benchmarks:*,aztec:sequencer,aztec:sequencer:*,aztec:world_state,aztec:merkle_trees" --enable_gas=1 --compose_file=./scripts/docker-compose-no-sandbox.yml DO ../../+UPLOAD_LOGS --PULL_REQUEST=$PULL_REQUEST --BRANCH=$BRANCH --COMMIT_HASH=$COMMIT_HASH -bench-proving: +bench-prover: ARG PULL_REQUEST ARG BRANCH ARG COMMIT_HASH - DO +E2E_COMPOSE_TEST --test=bench_proving --debug="aztec:benchmarks:*,aztec:prover*,aztec:bb*" --enable_gas=1 --compose_file=./scripts/docker-compose-no-sandbox.yml + DO +E2E_COMPOSE_TEST --test=bench_prover --debug="aztec:benchmarks:*,aztec:prover*,aztec:bb*,aztec:pxe*" --enable_gas=1 --compose_file=./scripts/docker-compose-no-sandbox.yml DO ../../+UPLOAD_LOGS --PULL_REQUEST=$PULL_REQUEST --BRANCH=$BRANCH --COMMIT_HASH=$COMMIT_HASH 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 new file mode 100644 index 00000000000..5a4ba2bcdf3 --- /dev/null +++ b/yarn-project/end-to-end/src/benchmarks/bench_prover.test.ts @@ -0,0 +1,246 @@ +import { getSchnorrAccount, getSchnorrWallet } from '@aztec/accounts/schnorr'; +import { type AztecNodeService } from '@aztec/aztec-node'; +import { EthAddress, PrivateFeePaymentMethod, PublicFeePaymentMethod, TxStatus } from '@aztec/aztec.js'; +import { type AccountWallet } from '@aztec/aztec.js/wallet'; +import { CompleteAddress, Fq, Fr, GasSettings } from '@aztec/circuits.js'; +import { FPCContract, GasTokenContract, TestContract, TokenContract } from '@aztec/noir-contracts.js'; +import { getCanonicalGasTokenAddress } 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'; + +import { getACVMConfig } from '../fixtures/get_acvm_config.js'; +import { getBBConfig } from '../fixtures/get_bb_config.js'; +import { type EndToEndContext, setup } from '../fixtures/utils.js'; + +jest.setTimeout(3_600_000); + +const txTimeoutSec = 3600; + +describe('benchmarks/proving', () => { + let ctx: EndToEndContext; + + let schnorrWalletSalt: Fr; + let schnorrWalletEncKey: Fr; + let schnorrWalletSigningKey: Fq; + let schnorrWalletAddress: CompleteAddress; + + let recipient: CompleteAddress; + + let initialGasContract: GasTokenContract; + let initialTestContract: TestContract; + let initialTokenContract: TokenContract; + let initialFpContract: FPCContract; + + let provingPxes: PXEService[]; + + let acvmCleanup: () => Promise; + let bbCleanup: () => Promise; + let proverPool: ProverPool; + + // setup the environment quickly using fake proofs + beforeAll(async () => { + ctx = await setup( + 1, + { + // do setup with fake proofs + realProofs: false, + proverAgents: 4, + proverAgentPollInterval: 10, + minTxsPerBlock: 1, + }, + {}, + true, // enable gas + ); + + schnorrWalletSalt = Fr.random(); + schnorrWalletEncKey = Fr.random(); + schnorrWalletSigningKey = Fq.random(); + const initialSchnorrWallet = await getSchnorrAccount( + ctx.pxe, + schnorrWalletEncKey, + schnorrWalletSigningKey, + schnorrWalletSalt, + ) + .deploy({ + skipClassRegistration: false, + skipPublicDeployment: false, + }) + .getWallet(); + schnorrWalletAddress = initialSchnorrWallet.getCompleteAddress(); + + initialTestContract = await TestContract.deploy(initialSchnorrWallet).send().deployed(); + initialTokenContract = await TokenContract.deploy( + initialSchnorrWallet, + initialSchnorrWallet.getAddress(), + 'test', + 't', + 18, + ) + .send() + .deployed(); + initialGasContract = await GasTokenContract.at( + getCanonicalGasTokenAddress(ctx.deployL1ContractsValues.l1ContractAddresses.gasPortalAddress), + initialSchnorrWallet, + ); + initialFpContract = await FPCContract.deploy( + initialSchnorrWallet, + initialTokenContract.address, + initialGasContract.address, + ) + .send() + .deployed(); + + await Promise.all([ + initialGasContract.methods.mint_public(initialFpContract.address, 1e12).send().wait(), + initialTokenContract.methods.mint_public(initialSchnorrWallet.getAddress(), 1e12).send().wait(), + initialTokenContract.methods.privately_mint_private_note(1e12).send().wait(), + ]); + + recipient = CompleteAddress.random(); + }); + + // remove the fake prover and setup the real one + beforeAll(async () => { + const [acvmConfig, bbConfig] = await Promise.all([getACVMConfig(ctx.logger), getBBConfig(ctx.logger)]); + if (!acvmConfig || !bbConfig) { + throw new Error('Missing ACVM or BB config'); + } + + 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, + // 4-tx blocks so that we have at least one merge level + minTxsPerBlock: 4, + }); + + 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 = []; + for (let i = 0; i < 4; i++) { + const pxe = await createPXEService( + ctx.aztecNode, + { + proverEnabled: true, + bbBinaryPath: bbConfig.bbBinaryPath, + bbWorkingDirectory: bbConfig.bbWorkingDirectory, + l2BlockPollingIntervalMS: 1000, + l2StartingBlock: 1, + }, + `proving-pxe-${i}`, + ); + + await getSchnorrAccount(pxe, schnorrWalletEncKey, schnorrWalletSigningKey, schnorrWalletSalt).register(); + await pxe.registerContract(initialTokenContract); + await pxe.registerContract(initialTestContract); + await pxe.registerContract(initialFpContract); + await pxe.registerContract(initialGasContract); + + await pxe.registerRecipient(recipient); + + provingPxes.push(pxe); + } + }); + + afterAll(async () => { + for (const pxe of provingPxes) { + await pxe.stop(); + } + await proverPool.stop(); + await ctx.teardown(); + await acvmCleanup(); + await bbCleanup(); + }); + + it('builds a full block', async () => { + ctx.logger.info('+----------------------+'); + ctx.logger.info('| |'); + ctx.logger.info('| STARTING BENCHMARK |'); + ctx.logger.info('| |'); + ctx.logger.info('+----------------------+'); + + const fnCalls = [ + (await getTestContractOnPXE(0)).methods.emit_nullifier(42), + (await getTestContractOnPXE(1)).methods.emit_unencrypted(43), + (await getTestContractOnPXE(2)).methods.create_l2_to_l1_message_public(45, 46, EthAddress.random()), + (await getTokenContract(3)).methods.transfer(schnorrWalletAddress.address, recipient.address, 1000, 0), + ]; + + const feeFnCall1 = { + gasSettings: GasSettings.default(), + paymentMethod: new PublicFeePaymentMethod( + initialTokenContract.address, + initialFpContract.address, + await getWalletOnPxe(1), + ), + }; + + const feeFnCall3 = { + gasSettings: GasSettings.default(), + paymentMethod: new PrivateFeePaymentMethod( + initialTokenContract.address, + initialFpContract.address, + await getWalletOnPxe(3), + ), + }; + + ctx.logger.info('Proving first two transactions'); + await Promise.all([ + fnCalls[0].prove(), + fnCalls[1].prove({ + fee: feeFnCall1, + }), + ]); + + ctx.logger.info('Proving the next transactions'); + await Promise.all([ + fnCalls[2].prove(), + fnCalls[3].prove({ + fee: feeFnCall3, + }), + ]); + + ctx.logger.info('Finished proving all transactions'); + + ctx.logger.info('Sending transactions'); + const txs = [ + fnCalls[0].send(), + fnCalls[1].send({ fee: feeFnCall1 }), + fnCalls[2].send(), + fnCalls[3].send({ fee: feeFnCall3 }), + ]; + + const receipts = await Promise.all(txs.map(tx => tx.wait({ timeout: txTimeoutSec }))); + expect(receipts.every(r => r.status === TxStatus.MINED)).toBe(true); + }); + + function getWalletOnPxe(idx: number): Promise { + return getSchnorrWallet(provingPxes[idx], schnorrWalletAddress.address, schnorrWalletSigningKey); + } + + async function getTestContractOnPXE(idx: number): Promise { + const wallet = await getWalletOnPxe(idx); + return TestContract.at(initialTestContract.address, wallet); + } + + async function getTokenContract(idx: number): Promise { + const wallet = await getWalletOnPxe(idx); + return TokenContract.at(initialTokenContract.address, wallet); + } +}); diff --git a/yarn-project/end-to-end/src/benchmarks/bench_proving.test.ts b/yarn-project/end-to-end/src/benchmarks/bench_proving.test.ts deleted file mode 100644 index b77ced7138c..00000000000 --- a/yarn-project/end-to-end/src/benchmarks/bench_proving.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { type AztecNodeService } from '@aztec/aztec-node'; -import { type AccountWallet, EthAddress, PublicFeePaymentMethod, TxStatus } from '@aztec/aztec.js'; -import { GasSettings } from '@aztec/circuits.js'; -import { FPCContract, GasTokenContract, TestContract, TokenContract } from '@aztec/noir-contracts.js'; -import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token'; -import { ProverPool } from '@aztec/prover-client/prover-pool'; - -import { jest } from '@jest/globals'; - -import { getACVMConfig } from '../fixtures/get_acvm_config.js'; -import { getBBConfig } from '../fixtures/get_bb_config.js'; -import { type EndToEndContext, publicDeployAccounts, setup } from '../fixtures/utils.js'; - -jest.setTimeout(900_000); - -const txTimeoutSec = 600; - -describe('benchmarks/proving', () => { - let ctx: EndToEndContext; - let wallet: AccountWallet; - let testContract: TestContract; - let tokenContract: TokenContract; - let fpContract: FPCContract; - let acvmCleanup: () => Promise; - let bbCleanup: () => Promise; - let proverPool: ProverPool; - - // setup the environment quickly using fake proofs - beforeAll(async () => { - ctx = await setup( - 1, - { - // do setup with fake proofs - realProofs: false, - proverAgents: 4, - proverAgentPollInterval: 10, - minTxsPerBlock: 1, - }, - {}, - true, // enable gas - ); - - wallet = ctx.wallet; - - await publicDeployAccounts(wallet, ctx.wallets); - - testContract = await TestContract.deploy(wallet).send().deployed(); - tokenContract = await TokenContract.deploy(wallet, wallet.getAddress(), 'test', 't', 18).send().deployed(); - const gas = await GasTokenContract.at( - getCanonicalGasTokenAddress(ctx.deployL1ContractsValues.l1ContractAddresses.gasPortalAddress), - wallet, - ); - fpContract = await FPCContract.deploy(wallet, tokenContract.address, gas.address).send().deployed(); - - await Promise.all([ - gas.methods.mint_public(fpContract.address, 1e12).send().wait(), - tokenContract.methods.mint_public(wallet.getAddress(), 1e12).send().wait(), - ]); - }); - - // remove the fake prover and setup the real one - beforeAll(async () => { - const [acvmConfig, bbConfig] = await Promise.all([getACVMConfig(ctx.logger), getBBConfig(ctx.logger)]); - if (!acvmConfig || !bbConfig) { - throw new Error('Missing ACVM or BB config'); - } - - acvmCleanup = acvmConfig.cleanup; - bbCleanup = bbConfig.cleanup; - - proverPool = ProverPool.nativePool( - { - ...acvmConfig, - ...bbConfig, - }, - 4, - 10, - ); - - ctx.logger.info('Stopping fake provers'); - await ctx.aztecNode.setConfig({ - // stop the fake provers - proverAgents: 0, - // 4-tx blocks so that we have at least one merge level - minTxsPerBlock: 4, - }); - - ctx.logger.info('Starting real provers'); - await proverPool.start((ctx.aztecNode as AztecNodeService).getProver().getProvingJobSource()); - }); - - afterAll(async () => { - await proverPool.stop(); - await ctx.teardown(); - await acvmCleanup(); - await bbCleanup(); - }); - - it('builds a full block', async () => { - const txs = [ - // fully private tx - testContract.methods.emit_nullifier(42).send(), - // tx with setup, app, teardown - testContract.methods.emit_unencrypted(43).send({ - fee: { - gasSettings: GasSettings.default(), - paymentMethod: new PublicFeePaymentMethod(tokenContract.address, fpContract.address, wallet), - }, - }), - // tx with messages - testContract.methods.create_l2_to_l1_message_public(45, 46, EthAddress.random()).send(), - // tx with private and public exec - testContract.methods.set_tx_max_block_number(100, true).send({ - fee: { - gasSettings: GasSettings.default(), - paymentMethod: new PublicFeePaymentMethod(tokenContract.address, fpContract.address, wallet), - }, - }), - ]; - - const receipts = await Promise.all(txs.map(tx => tx.wait({ timeout: txTimeoutSec }))); - expect(receipts.every(r => r.status === TxStatus.MINED)).toBe(true); - }, 1_200_000); -}); diff --git a/yarn-project/prover-client/src/stats.ts b/yarn-project/prover-client/src/stats.ts deleted file mode 100644 index fbd68bde90b..00000000000 --- a/yarn-project/prover-client/src/stats.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { type PublicKernelRequest, PublicKernelType } from '@aztec/circuit-types'; -import type { CircuitName, CircuitProvingStats, CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; -import { type Logger } from '@aztec/foundation/log'; -import { type ServerProtocolArtifact } from '@aztec/noir-protocol-circuits-types'; - -export function emitCircuitWitnessGenerationStats( - circuitName: CircuitName, - duration: number, - inputSize: number, - outputSize: number, - logger: Logger, -) { - const stats: CircuitWitnessGenerationStats = { - eventName: 'circuit-witness-generation', - circuitName, - inputSize, - outputSize, - duration, - }; - - logger.debug('Circuit witness generation stats', stats); -} - -export function emitCircuitProvingStats( - circuitName: CircuitName, - duration: number, - inputSize: number, - outputSize: number, - proofSize: number, - logger: Logger, -) { - const stats: CircuitProvingStats = { - eventName: 'circuit-proving', - circuitName, - duration, - inputSize, - outputSize, - proofSize, - }; - - logger.debug('Circuit proving stats', stats); -} - -export function mapPublicKernelToCircuitName(kernelType: PublicKernelRequest['type']): CircuitName { - switch (kernelType) { - 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: - throw new Error(`Unknown kernel type: ${kernelType}`); - } -} - -export function circuitTypeToCircuitName(circuitType: ServerProtocolArtifact): CircuitName { - switch (circuitType) { - case 'BaseParityArtifact': - return 'base-parity'; - case 'RootParityArtifact': - return 'root-parity'; - case 'BaseRollupArtifact': - return 'base-rollup'; - case 'MergeRollupArtifact': - return 'merge-rollup'; - case 'RootRollupArtifact': - return 'root-rollup'; - case 'PublicKernelSetupArtifact': - return 'public-kernel-setup'; - case 'PublicKernelAppLogicArtifact': - return 'public-kernel-app-logic'; - case 'PublicKernelTeardownArtifact': - return 'public-kernel-teardown'; - case 'PublicKernelTailArtifact': - return 'public-kernel-tail'; - default: - throw new Error(`Unknown circuit type: ${circuitType}`); - } -} diff --git a/yarn-project/pxe/src/kernel_oracle/index.ts b/yarn-project/pxe/src/kernel_oracle/index.ts index 54b848c5531..26728bd6be2 100644 --- a/yarn-project/pxe/src/kernel_oracle/index.ts +++ b/yarn-project/pxe/src/kernel_oracle/index.ts @@ -9,6 +9,7 @@ import { computeContractClassIdPreimage, computeSaltedInitializationHash, } from '@aztec/circuits.js'; +import { createDebugLogger } from '@aztec/foundation/log'; import { type Tuple } from '@aztec/foundation/serialize'; import { type ContractDataOracle } from '../contract_data_oracle/index.js'; @@ -20,7 +21,12 @@ import { type ProvingDataOracle } from './../kernel_prover/proving_data_oracle.j * A data oracle that provides information needed for simulating a transaction. */ export class KernelOracle implements ProvingDataOracle { - constructor(private contractDataOracle: ContractDataOracle, private keyStore: KeyStore, private node: AztecNode) {} + constructor( + private contractDataOracle: ContractDataOracle, + private keyStore: KeyStore, + private node: AztecNode, + private log = createDebugLogger('aztec:pxe:kernel_oracle'), + ) {} public async getContractAddressPreimage(address: AztecAddress) { const instance = await this.contractDataOracle.getContractInstance(address); @@ -64,4 +70,20 @@ export class KernelOracle implements ProvingDataOracle { public getMasterNullifierSecretKey(nullifierPublicKey: Point) { return this.keyStore.getMasterNullifierSecretKeyForPublicKey(nullifierPublicKey); } + + public async getFunctionName(contractAddress: AztecAddress, selector: FunctionSelector): Promise { + try { + const contractInstance = await this.contractDataOracle.getContractInstance(contractAddress); + + const [contractArtifact, functionArtifact] = await Promise.all([ + this.contractDataOracle.getContractArtifact(contractInstance.contractClassId), + this.contractDataOracle.getFunctionArtifact(contractAddress, selector), + ]); + + return `${contractArtifact.name}:${functionArtifact.name}`; + } catch (e) { + this.log.error(`Failed to get function name for contract ${contractAddress} and selector ${selector}: ${e}`); + return 'Unknown'; + } + } } diff --git a/yarn-project/pxe/src/kernel_prover/kernel_prover.ts b/yarn-project/pxe/src/kernel_prover/kernel_prover.ts index 6c9f033c274..b9c74e48f36 100644 --- a/yarn-project/pxe/src/kernel_prover/kernel_prover.ts +++ b/yarn-project/pxe/src/kernel_prover/kernel_prover.ts @@ -84,9 +84,15 @@ export class KernelProver { ? CallRequest.empty() : currentExecution.publicTeardownFunctionCall.toCallRequest(); + const functionName = await this.oracle.getFunctionName( + currentExecution.callStackItem.contractAddress, + currentExecution.callStackItem.functionData.selector, + ); + const proofOutput = await this.proofCreator.createAppCircuitProof( currentExecution.partialWitness, currentExecution.acir, + functionName, ); const privateCallData = await this.createPrivateCallData( diff --git a/yarn-project/pxe/src/kernel_prover/proving_data_oracle.ts b/yarn-project/pxe/src/kernel_prover/proving_data_oracle.ts index 04af3cad3ed..f1dc0b39dda 100644 --- a/yarn-project/pxe/src/kernel_prover/proving_data_oracle.ts +++ b/yarn-project/pxe/src/kernel_prover/proving_data_oracle.ts @@ -76,4 +76,6 @@ export interface ProvingDataOracle { * @returns the master nullifier secret key. */ getMasterNullifierSecretKey(nullifierPublicKey: Point): Promise; + + getFunctionName(contractAddress: AztecAddress, selector: FunctionSelector): Promise; } diff --git a/yarn-project/pxe/src/kernel_prover/test/test_circuit_prover.ts b/yarn-project/pxe/src/kernel_prover/test/test_circuit_prover.ts index ce5ce55a1cf..ff919257d4b 100644 --- a/yarn-project/pxe/src/kernel_prover/test/test_circuit_prover.ts +++ b/yarn-project/pxe/src/kernel_prover/test/test_circuit_prover.ts @@ -89,7 +89,7 @@ export class TestProofCreator implements ProofCreator { ); this.log.debug(`Simulated private kernel ordering`, { eventName: 'circuit-simulation', - circuitName: 'private-kernel-ordering', + circuitName: 'private-kernel-tail', duration, inputSize: privateInputs.toBuffer().length, outputSize: result.toBuffer().length, diff --git a/yarn-project/pxe/src/pxe_service/create_pxe_service.ts b/yarn-project/pxe/src/pxe_service/create_pxe_service.ts index 47e0bf633d1..2a33dd3dd66 100644 --- a/yarn-project/pxe/src/pxe_service/create_pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/create_pxe_service.ts @@ -1,6 +1,7 @@ import { BBNativeProofCreator } from '@aztec/bb-prover'; import { type AztecNode, type ProofCreator } from '@aztec/circuit-types'; import { randomBytes } from '@aztec/foundation/crypto'; +import { createDebugLogger } from '@aztec/foundation/log'; import { TestKeyStore } from '@aztec/key-store'; import { AztecLmdbStore } from '@aztec/kv-store/lmdb'; import { initStoreForRollup } from '@aztec/kv-store/utils'; @@ -54,7 +55,11 @@ export async function createPXEService( } prover = !config.proverEnabled ? new TestProofCreator() - : new BBNativeProofCreator(config.bbBinaryPath!, config.bbWorkingDirectory!); + : new BBNativeProofCreator( + config.bbBinaryPath!, + config.bbWorkingDirectory!, + createDebugLogger('aztec:pxe:bb-native-prover' + (logSuffix ? `:${logSuffix}` : '')), + ); } const server = new PXEService(keyStore, aztecNode, db, prover, config, logSuffix); diff --git a/yarn-project/scripts/src/benchmarks/aggregate.ts b/yarn-project/scripts/src/benchmarks/aggregate.ts index f0bf75811f9..4d2a35f559f 100644 --- a/yarn-project/scripts/src/benchmarks/aggregate.ts +++ b/yarn-project/scripts/src/benchmarks/aggregate.ts @@ -97,13 +97,20 @@ function processRollupBlockSynced(entry: L2BlockHandledStats, results: Benchmark * Buckets are circuit names */ function processCircuitSimulation(entry: CircuitSimulationStats, results: BenchmarkCollectedResults) { - const bucket = entry.circuitName; - if (!bucket) { - return; + if (entry.circuitName === 'app-circuit') { + const bucket = entry.appCircuitName; + if (!bucket) { + return; + } + append(results, 'app_circuit_simulation_time_in_ms', bucket, entry.duration); + append(results, 'app_circuit_input_size_in_bytes', bucket, entry.inputSize); + append(results, 'app_circuit_output_size_in_bytes', bucket, entry.outputSize); + } else { + const bucket = entry.circuitName; + append(results, 'protocol_circuit_simulation_time_in_ms', bucket, entry.duration); + append(results, 'protocol_circuit_input_size_in_bytes', bucket, entry.inputSize); + append(results, 'protocol_circuit_output_size_in_bytes', bucket, entry.outputSize); } - append(results, 'circuit_simulation_time_in_ms', bucket, entry.duration); - append(results, 'circuit_input_size_in_bytes', bucket, entry.inputSize); - append(results, 'circuit_output_size_in_bytes', bucket, entry.outputSize); } /** @@ -111,12 +118,22 @@ function processCircuitSimulation(entry: CircuitSimulationStats, results: Benchm * Buckets are circuit names */ function processCircuitProving(entry: CircuitProvingStats, results: BenchmarkCollectedResults) { - const bucket = entry.circuitName; - if (!bucket) { - return; + if (entry.circuitName === 'app-circuit') { + const bucket = entry.appCircuitName; + if (!bucket) { + return; + } + append(results, 'app_circuit_proving_time_in_ms', bucket, entry.duration); + append(results, 'app_circuit_proof_size_in_bytes', bucket, entry.proofSize); + append(results, 'app_circuit_size_in_gates', bucket, entry.circuitSize); + append(results, 'app_circuit_num_public_inputs', bucket, entry.numPublicInputs); + } else { + const bucket = entry.circuitName; + append(results, 'protocol_circuit_proving_time_in_ms', bucket, entry.duration); + append(results, 'protocol_circuit_proof_size_in_bytes', bucket, entry.proofSize); + append(results, 'protocol_circuit_size_in_gates', bucket, entry.circuitSize); + append(results, 'protocol_circuit_num_public_inputs', bucket, entry.numPublicInputs); } - append(results, 'circuit_proving_time_in_ms', bucket, entry.duration); - append(results, 'circuit_proof_size_in_bytes', bucket, entry.proofSize); } /** @@ -124,11 +141,16 @@ function processCircuitProving(entry: CircuitProvingStats, results: BenchmarkCol * Buckets are circuit names */ function processCircuitWitnessGeneration(entry: CircuitWitnessGenerationStats, results: BenchmarkCollectedResults) { - const bucket = entry.circuitName; - if (!bucket) { - return; + if (entry.circuitName === 'app-circuit') { + const bucket = entry.appCircuitName; + if (!bucket) { + return; + } + append(results, 'app_circuit_witness_generation_time_in_ms', bucket, entry.duration); + } else { + const bucket = entry.circuitName; + append(results, 'protocol_circuit_witness_generation_time_in_ms', bucket, entry.duration); } - append(results, 'circuit_witness_generation_time_in_ms', bucket, entry.duration); } /** * Processes an entry with event name 'note-processor-caught-up' and updates results diff --git a/yarn-project/scripts/src/benchmarks/markdown.ts b/yarn-project/scripts/src/benchmarks/markdown.ts index 772238679a9..5730067f7c5 100644 --- a/yarn-project/scripts/src/benchmarks/markdown.ts +++ b/yarn-project/scripts/src/benchmarks/markdown.ts @@ -185,7 +185,8 @@ export function getMarkdown(prNumber: number) { const metricsByBlockSize = Metrics.filter(m => m.groupBy === 'block-size').map(m => m.name); const metricsByChainLength = Metrics.filter(m => m.groupBy === 'chain-length').map(m => m.name); - const metricsByCircuitName = Metrics.filter(m => m.groupBy === 'circuit-name').map(m => m.name); + const kernelCircuitMetrics = Metrics.filter(m => m.groupBy === 'protocol-circuit-name').map(m => m.name); + const appCircuitMetrics = Metrics.filter(m => m.groupBy === 'app-circuit-name').map(m => m.name); const metricsByClassesRegistered = Metrics.filter(m => m.groupBy === 'classes-registered').map(m => m.name); const metricsByFeePaymentMethod = Metrics.filter(m => m.groupBy === 'fee-payment-method').map(m => m.name); const metricsByLeafCount = Metrics.filter(m => m.groupBy === 'leaf-count').map(m => m.name); @@ -229,8 +230,11 @@ ${getTableContent(pick(benchmark, metricsByChainLength), baseBenchmark, 'blocks' ### Circuits stats -Stats on running time and I/O sizes collected for every circuit run across all benchmarks. -${getTableContent(transpose(pick(benchmark, metricsByCircuitName)), transpose(baseBenchmark), '', 'Circuit')} +Stats on running time and I/O sizes collected for every kernel circuit run across all benchmarks. +${getTableContent(transpose(pick(benchmark, kernelCircuitMetrics)), transpose(baseBenchmark), '', 'Circuit')} + +Stats on running time collected for app circuits +${getTableContent(transpose(pick(benchmark, appCircuitMetrics)), transpose(baseBenchmark), '', 'Function')} ### Tree insertion stats