Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions yarn-project/bb-prover/src/prover/server/bb_prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { mapProtocolArtifactNameToCircuitName } from '@aztec/noir-protocol-circu
import type { WitnessMap } from '@aztec/noir-types';
import { NativeACVMSimulator } from '@aztec/simulator/server';
import type { AvmCircuitInputs, AvmCircuitPublicInputs } from '@aztec/stdlib/avm';
import { type AvmProvingInputs, AvmProvingResult } from '@aztec/stdlib/block_execution';
import { ProvingError } from '@aztec/stdlib/errors';
import {
type PublicInputsAndRecursiveProof,
Expand Down Expand Up @@ -180,18 +181,23 @@ export class BBNativeRollupProver implements ServerCircuitProver {

/**
* Creates an AVM proof and verifies it.
* @param inputs - The inputs to the AVM circuit.
* @returns The proof.
* @param inputs - Wrapped AVM proving inputs (AVM circuit inputs + optional execution-agent passenger data).
* @returns The AVM proof, with the passenger data passed through unchanged.
*/
@trackSpan('BBNativeRollupProver.getAvmProof', inputs => ({
[Attributes.APP_CIRCUIT_NAME]: inputs.hints.tx.hash,
[Attributes.APP_CIRCUIT_NAME]: inputs.avmCircuitInputs.hints.tx.hash,
}))
public async getAvmProof(
inputs: AvmCircuitInputs,
): Promise<RecursiveProof<typeof AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED>> {
const proof = await this.createAvmProof(inputs);
await this.verifyAvmProof(proof.binaryProof, inputs.publicInputs);
return proof;
public async getAvmProof(inputs: AvmProvingInputs): Promise<AvmProvingResult> {
const { avmCircuitInputs, executionTxData } = inputs;
const proof = await this.createAvmProof(avmCircuitInputs);
await this.verifyAvmProof(proof.binaryProof, avmCircuitInputs.publicInputs);
return new AvmProvingResult(proof, executionTxData);
}

public executeBlock(): Promise<never> {
return Promise.reject(
new Error('BBNativeRollupProver does not handle BLOCK_EXECUTION jobs; use a dedicated execution agent'),
);
}

public async getPublicChonkVerifierProof(
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/bb-prover/src/test/delay_values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const WITGEN_DELAY_MS: Record<ProvingRequestType, number> = {
[ProvingRequestType.CHECKPOINT_ROOT_ROLLUP]: 40_000,
[ProvingRequestType.CHECKPOINT_PADDING_ROLLUP]: 0,
[ProvingRequestType.PRIVATE_TX_BASE_ROLLUP]: 2_500, // Guess based on public
[ProvingRequestType.BLOCK_EXECUTION]: 0,
};

export const PROOF_DELAY_MS: Record<ProvingRequestType, number> = {
Expand All @@ -44,4 +45,5 @@ export const PROOF_DELAY_MS: Record<ProvingRequestType, number> = {
[ProvingRequestType.CHECKPOINT_ROOT_ROLLUP]: 35_000,
[ProvingRequestType.CHECKPOINT_PADDING_ROLLUP]: 0,
[ProvingRequestType.PRIVATE_TX_BASE_ROLLUP]: 22_000,
[ProvingRequestType.BLOCK_EXECUTION]: 0,
};
23 changes: 12 additions & 11 deletions yarn-project/bb-prover/src/test/test_circuit_prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,14 @@ import { ProtocolCircuitVks } from '@aztec/noir-protocol-circuits-types/server/v
import { mapProtocolArtifactNameToCircuitName } from '@aztec/noir-protocol-circuits-types/types';
import type { WitnessMap } from '@aztec/noir-types';
import { type CircuitSimulator, WASMSimulatorWithBlobs, emitCircuitSimulationStats } from '@aztec/simulator/server';
import type { AvmCircuitInputs } from '@aztec/stdlib/avm';
import { type AvmProvingInputs, AvmProvingResult } from '@aztec/stdlib/block_execution';
import {
type PublicInputsAndRecursiveProof,
type ServerCircuitProver,
makePublicInputsAndRecursiveProof,
} from '@aztec/stdlib/interfaces/server';
import type { ParityBasePrivateInputs, ParityPublicInputs, ParityRootPrivateInputs } from '@aztec/stdlib/parity';
import {
type Proof,
ProvingRequestType,
RecursiveProof,
makeEmptyRecursiveProof,
makeRecursiveProof,
} from '@aztec/stdlib/proofs';
import { type Proof, ProvingRequestType, makeEmptyRecursiveProof, makeRecursiveProof } from '@aztec/stdlib/proofs';
import {
type BlockMergeRollupPrivateInputs,
type BlockRollupPublicInputs,
Expand Down Expand Up @@ -402,13 +396,20 @@ export class TestCircuitProver implements ServerCircuitProver {
);
}

public getAvmProof(_inputs: AvmCircuitInputs): Promise<RecursiveProof<typeof AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED>> {
public async getAvmProof(inputs: AvmProvingInputs): Promise<AvmProvingResult> {
// We can't simulate the AVM because we don't have enough context to do so (e.g., DBs).
// We just return an empty proof.
// We just return an empty proof, with the passenger data passed through.
this.logger.debug('Skipping AVM simulation in TestCircuitProver.');
return this.applyDelay(ProvingRequestType.PUBLIC_VM, () =>
const proof = await this.applyDelay(ProvingRequestType.PUBLIC_VM, () =>
makeEmptyRecursiveProof(AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED),
);
return new AvmProvingResult(proof, inputs.executionTxData);
}

public executeBlock(): Promise<never> {
return Promise.reject(
new Error('TestCircuitProver does not handle BLOCK_EXECUTION jobs; use a dedicated execution agent'),
);
}

private async applyDelay<F extends () => any>(type: ProvingRequestType, fn: F): Promise<Awaited<ReturnType<F>>> {
Expand Down
1 change: 1 addition & 0 deletions yarn-project/prover-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"type": "module",
"exports": {
".": "./dest/index.js",
"./block_execution": "./dest/block_execution/index.js",
"./broker": "./dest/proving_broker/index.js",
"./broker/config": "./dest/proving_broker/config.js",
"./orchestrator": "./dest/orchestrator/index.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { SpongeBlob } from '@aztec/blob-lib/types';
import { BlockNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
import { Fr } from '@aztec/foundation/curves/bn254';
import type { PublicProcessor, PublicProcessorFactory } from '@aztec/simulator/server';
import { BlockExecutionInputs } from '@aztec/stdlib/block_execution';
import type {
ForkMerkleTreeOperations,
MerkleTreeWriteOperations,
ProofUri,
ProvingJobId,
ProvingJobProducer,
} from '@aztec/stdlib/interfaces/server';
import { BlockHeader, StateReference, type Tx, TxHash } from '@aztec/stdlib/tx';

import { jest } from '@jest/globals';

import type { ProofStore } from '../proving_broker/proof_store/index.js';
import { BlockExecutionHandler, type TxFetcher } from './block_execution_handler.js';

// Lightweight tests covering input validation and lifecycle. End-to-end coverage of
// hint computation, per-tx enqueue, and PRIVATE_TX_BASE_ROLLUP / PUBLIC_VM ID
// generation lives in the orchestrator-side TestContext flow (Phase 4 onwards),
// where the real `PublicProcessor` produces the `ProcessedTx` shape that
// `insertSideEffectsAndBuildBaseRollupHints` reads from.
describe('BlockExecutionHandler', () => {
const epochNumber = EpochNumber(7);
const blockNumber = BlockNumber(42);
const slotNumber = SlotNumber(123);
const checkpointIndex = 2;
const proverId = new Fr(0xabc);

let dbProvider: jest.Mocked<Pick<ForkMerkleTreeOperations, 'fork'>>;
let publicProcessorFactory: jest.Mocked<Pick<PublicProcessorFactory, 'create'>>;
let publicProcessor: jest.Mocked<Pick<PublicProcessor, 'process'>>;
let txFetcher: jest.MockedFunction<TxFetcher>;
let proofStore: jest.Mocked<ProofStore>;
let broker: jest.Mocked<Pick<ProvingJobProducer, 'enqueueProvingJob'>>;
let fork: jest.Mocked<
Pick<MerkleTreeWriteOperations, 'close' | 'appendLeaves' | 'getTreeInfo' | 'getStateReference'>
>;

let handler: BlockExecutionHandler;
let header: BlockHeader;

function makeTxStub(): Tx {
const hash = TxHash.random();
return {
getTxHash: () => hash,
} as unknown as Tx;
}

beforeEach(() => {
fork = {
close: jest.fn(() => Promise.resolve()),
appendLeaves: jest.fn(() => Promise.resolve()),
getTreeInfo: jest.fn(() => Promise.resolve({ root: Buffer.alloc(32), size: 0n, treeId: 0, depth: 0 })),
getStateReference: jest.fn(() => Promise.resolve(StateReference.empty())),
};
dbProvider = { fork: jest.fn(() => Promise.resolve(fork as unknown as MerkleTreeWriteOperations)) };

publicProcessor = { process: jest.fn() };
publicProcessorFactory = {
create: jest.fn(() => publicProcessor as unknown as PublicProcessor),
};

txFetcher = jest.fn() as jest.MockedFunction<TxFetcher>;

proofStore = {
saveProofInput: jest.fn((id: ProvingJobId) => Promise.resolve(`uri:${id}` as ProofUri)),
saveProofOutput: jest.fn(() => Promise.resolve('' as ProofUri)),
getProofInput: jest.fn(),
getProofOutput: jest.fn(),
};

broker = { enqueueProvingJob: jest.fn(() => Promise.resolve({ status: 'in-queue' as const })) };

handler = new BlockExecutionHandler(
dbProvider,
publicProcessorFactory as unknown as PublicProcessorFactory,
txFetcher,
proofStore,
broker,
proverId,
);

header = BlockHeader.random({ blockNumber, slotNumber });
});

function buildInputs(numTxs: number, opts: { isFirstBlockInCheckpoint?: boolean } = {}) {
const txs = Array.from({ length: numTxs }, () => makeTxStub());
const inputs = new BlockExecutionInputs(
epochNumber,
checkpointIndex,
header,
txs.map(t => t.getTxHash()),
opts.isFirstBlockInCheckpoint ?? false,
[],
SpongeBlob.empty(),
);
return { inputs, txs };
}

it('forks at the parent block and closes the fork on success', async () => {
const { inputs } = buildInputs(0);
txFetcher.mockResolvedValueOnce([]);

const result = await handler.executeBlock(inputs);

expect(dbProvider.fork).toHaveBeenCalledWith(BlockNumber(Number(blockNumber) - 1));
expect(fork.close).toHaveBeenCalled();
expect(result.blockNumber).toEqual(blockNumber);
});

it('rejects when the tx fetcher returns a tx with a mismatched hash', async () => {
const { inputs } = buildInputs(1);
txFetcher.mockResolvedValueOnce([makeTxStub()]); // different hash

await expect(handler.executeBlock(inputs)).rejects.toThrow(/mismatched tx/);
// fork is created lazily after fetching txs, so it should not be opened
expect(dbProvider.fork).not.toHaveBeenCalled();
});

it('inserts L1-to-L2 messages only when the block is the first in its checkpoint', async () => {
const { inputs } = buildInputs(0, { isFirstBlockInCheckpoint: true });
txFetcher.mockResolvedValueOnce([]);

await handler.executeBlock(inputs);

expect(fork.appendLeaves).toHaveBeenCalled();
});
});
Loading
Loading