Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Do not start block root rollup proof before block is built #8952

Merged
merged 1 commit into from
Oct 2, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class BlockProvingState {
private rootParityInputs: Array<RootParityInput<typeof RECURSIVE_PROOF_LENGTH> | undefined> = [];
private finalRootParityInputs: RootParityInput<typeof NESTED_RECURSIVE_PROOF_LENGTH> | undefined;
public blockRootRollupPublicInputs: BlockRootOrBlockMergePublicInputs | undefined;
public blockRootRollupStarted: boolean = false;
public finalProof: Proof | undefined;
public block: L2Block | undefined;
private txs: TxProvingState[] = [];
Expand Down Expand Up @@ -180,6 +181,7 @@ export class BlockProvingState {
// Returns true if we have sufficient inputs to execute the block root rollup
public isReadyForBlockRootRollup() {
return !(
this.block === undefined ||
this.mergeRollupInputs[0] === undefined ||
this.finalRootParityInput === undefined ||
this.mergeRollupInputs[0].inputs.findIndex(p => !p) !== -1
Expand Down
18 changes: 15 additions & 3 deletions yarn-project/prover-client/src/orchestrator/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ export class ProvingOrchestrator implements EpochProver {
logger.verbose(`Block ${provingState.globalVariables.blockNumber} completed. Assembling header.`);
await this.buildBlock(provingState);

// If the proofs were faster than the block building, then we need to try the block root rollup again here
this.checkAndEnqueueBlockRootRollup(provingState);
return provingState.block!;
}

Expand Down Expand Up @@ -871,11 +873,17 @@ export class ProvingOrchestrator implements EpochProver {
}

// Executes the block root rollup circuit
private enqueueBlockRootRollup(provingState: BlockProvingState | undefined) {
if (!provingState?.verifyState()) {
private enqueueBlockRootRollup(provingState: BlockProvingState) {
if (!provingState.block) {
throw new Error(`Invalid proving state for block root rollup, block not available`);
}

if (!provingState.verifyState()) {
logger.debug('Not running block root rollup, state no longer valid');
return;
}

provingState.blockRootRollupStarted = true;
const mergeInputData = provingState.getMergeInputs(0);
const rootParityInput = provingState.finalRootParityInput!;

Expand Down Expand Up @@ -1063,11 +1071,15 @@ export class ProvingOrchestrator implements EpochProver {
);
}

private checkAndEnqueueBlockRootRollup(provingState: BlockProvingState | undefined) {
private checkAndEnqueueBlockRootRollup(provingState: BlockProvingState) {
if (!provingState?.isReadyForBlockRootRollup()) {
logger.debug('Not ready for root rollup');
return;
}
if (provingState.blockRootRollupStarted) {
logger.debug('Block root rollup already started');
return;
}
this.enqueueBlockRootRollup(provingState);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,71 +1,103 @@
import { type ServerCircuitProver } from '@aztec/circuit-types';
import {
Fr,
type GlobalVariables,
NESTED_RECURSIVE_PROOF_LENGTH,
NUM_BASE_PARITY_PER_ROOT_PARITY,
RECURSIVE_PROOF_LENGTH,
type RootParityInput,
} from '@aztec/circuits.js';
import { makeGlobalVariables, makeRootParityInput } from '@aztec/circuits.js/testing';
import { makeRootParityInput } from '@aztec/circuits.js/testing';
import { createDebugLogger } from '@aztec/foundation/log';
import { promiseWithResolvers } from '@aztec/foundation/promise';
import { sleep } from '@aztec/foundation/sleep';
import { openTmpStore } from '@aztec/kv-store/utils';
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
import { type MerkleTreeOperations, MerkleTrees } from '@aztec/world-state';
import { type MerkleTreeOperations } from '@aztec/world-state';

import { type MockProxy, mock } from 'jest-mock-extended';

import { ProvingOrchestrator } from './orchestrator.js';
import { makeBloatedProcessedTx } from '../mocks/fixtures.js';
import { TestContext } from '../mocks/test_context.js';
import { type ProvingOrchestrator } from './orchestrator.js';

const logger = createDebugLogger('aztec:orchestrator-workflow');

describe('prover/orchestrator', () => {
describe('workflow', () => {
let orchestrator: ProvingOrchestrator;
let mockProver: MockProxy<ServerCircuitProver>;
let actualDb: MerkleTreeOperations;
beforeEach(async () => {
const telemetryClient = new NoopTelemetryClient();
actualDb = await MerkleTrees.new(openTmpStore(), telemetryClient).then(t => t.asLatest());
mockProver = mock<ServerCircuitProver>();
orchestrator = new ProvingOrchestrator(actualDb, mockProver, telemetryClient);
});
let globalVariables: GlobalVariables;
let context: TestContext;

it('calls root parity circuit only when ready', async () => {
// create a custom L2 to L1 message
const message = Fr.random();
describe('with mock prover', () => {
let mockProver: MockProxy<ServerCircuitProver>;

// and delay its proof
const pendingBaseParityResult = promiseWithResolvers<RootParityInput<typeof RECURSIVE_PROOF_LENGTH>>();
const expectedBaseParityResult = makeRootParityInput(RECURSIVE_PROOF_LENGTH, 0xff);
beforeEach(async () => {
mockProver = mock<ServerCircuitProver>();
context = await TestContext.new(logger, 'legacy', 4, () => Promise.resolve(mockProver));
({ actualDb, orchestrator, globalVariables } = context);
});

mockProver.getRootParityProof.mockResolvedValue(makeRootParityInput(NESTED_RECURSIVE_PROOF_LENGTH));
it('calls root parity circuit only when ready', async () => {
// create a custom L2 to L1 message
const message = Fr.random();

mockProver.getBaseParityProof.mockImplementation(inputs => {
if (inputs.msgs[0].equals(message)) {
return pendingBaseParityResult.promise;
} else {
return Promise.resolve(makeRootParityInput(RECURSIVE_PROOF_LENGTH));
}
});
// and delay its proof
const pendingBaseParityResult = promiseWithResolvers<RootParityInput<typeof RECURSIVE_PROOF_LENGTH>>();
const expectedBaseParityResult = makeRootParityInput(RECURSIVE_PROOF_LENGTH, 0xff);

orchestrator.startNewEpoch(1, 1);
await orchestrator.startNewBlock(2, makeGlobalVariables(1), [message]);
mockProver.getRootParityProof.mockResolvedValue(makeRootParityInput(NESTED_RECURSIVE_PROOF_LENGTH));

await sleep(10);
expect(mockProver.getBaseParityProof).toHaveBeenCalledTimes(NUM_BASE_PARITY_PER_ROOT_PARITY);
expect(mockProver.getRootParityProof).not.toHaveBeenCalled();
mockProver.getBaseParityProof.mockImplementation(inputs => {
if (inputs.msgs[0].equals(message)) {
return pendingBaseParityResult.promise;
} else {
return Promise.resolve(makeRootParityInput(RECURSIVE_PROOF_LENGTH));
}
});

await sleep(10);
// even now the root parity should not have been called
expect(mockProver.getRootParityProof).not.toHaveBeenCalled();
orchestrator.startNewEpoch(1, 1);
await orchestrator.startNewBlock(2, globalVariables, [message]);

// only after the base parity proof is resolved, the root parity should be called
pendingBaseParityResult.resolve(expectedBaseParityResult);
await sleep(10);
expect(mockProver.getBaseParityProof).toHaveBeenCalledTimes(NUM_BASE_PARITY_PER_ROOT_PARITY);
expect(mockProver.getRootParityProof).not.toHaveBeenCalled();

// give the orchestrator a chance to calls its callbacks
await sleep(10);
expect(mockProver.getRootParityProof).toHaveBeenCalledTimes(1);
await sleep(10);
// even now the root parity should not have been called
expect(mockProver.getRootParityProof).not.toHaveBeenCalled();

orchestrator.cancel();
// only after the base parity proof is resolved, the root parity should be called
pendingBaseParityResult.resolve(expectedBaseParityResult);

// give the orchestrator a chance to calls its callbacks
await sleep(10);
expect(mockProver.getRootParityProof).toHaveBeenCalledTimes(1);

orchestrator.cancel();
});
});

describe('with simulated prover', () => {
beforeEach(async () => {
context = await TestContext.new(logger);
({ actualDb, orchestrator, globalVariables } = context);
});

it('waits for block to be completed before enqueueing block root proof', async () => {
orchestrator.startNewEpoch(1, 1);
await orchestrator.startNewBlock(2, globalVariables, []);
await orchestrator.addNewTx(makeBloatedProcessedTx(actualDb, 1));
await orchestrator.addNewTx(makeBloatedProcessedTx(actualDb, 2));

// wait for the block root proof to try to be enqueued
await sleep(1000);

// now finish the block
await orchestrator.setBlockCompleted();

const result = await orchestrator.finaliseEpoch();
expect(result.proof).toBeDefined();
});
});
});
});
Loading