Skip to content

feat(prover): offload block execution to stateful prover-agent (stack)#23125

Draft
PhilWindle wants to merge 11 commits intopw/optimistic-finalfrom
phil/execution-offload-v2
Draft

feat(prover): offload block execution to stateful prover-agent (stack)#23125
PhilWindle wants to merge 11 commits intopw/optimistic-finalfrom
phil/execution-offload-v2

Conversation

@PhilWindle
Copy link
Copy Markdown
Collaborator

Summary

Phase 2 of the prover-stack redesign: BLOCK_EXECUTION as a first-class proving-job type, wired through the broker and per-tx job IDs so a stateful prover-agent can run public processing + base-rollup hint generation alongside private base-rollup proving and AVM proving.

This stack is the rebase of the original phil/execution-offload (PR #22916) onto pw/optimistic-final, dropping the dependency on the now-defunct phil/proving-orchestrator-split PR.

Stack contents (10 commits, oldest first)

  1. feat(stdlib): add deterministic execution-result job IDs and BLOCK_EXECUTION job typemakeExecutionResultJobId, new BLOCK_EXECUTION ProvingRequestType, BlockExecutionInputs / BlockExecutionResult, broker queue + priority slot, exhaustive-switch placeholder.
  2. feat(prover-client): add BlockExecutionHandler for BLOCK_EXECUTION jobs — agent-side handler skeleton.
  3. feat(prover-client): orchestrator addBlockForExecution path with watched AVM proofs — orchestrator entrypoint that enqueues per-tx watchers.
  4. feat(prover-node): run BLOCK_EXECUTION agents inside the prover nodeInternalExecutionAgents pool (later replaced).
  5. feat(stdlib): wrap AVM proving job inputs/result for execution-agent passenger dataBlockExecutionTxData passenger on AVM jobs.
  6. feat(prover-client): per-tx BLOCK_EXECUTION agent that bundles base-rollup hints — agent walks the block, collects hints per tx.
  7. feat(prover-client): orchestrator addBlockForExecution takes raw Tx[] and watches per-tx jobs — orchestrator wiring against the agent's per-tx outputs.
  8. feat(prover-client): existing prover agents handle BLOCK_EXECUTION via a composite prover — drops the separate InternalExecutionAgents pool; one composite ServerCircuitProver per existing agent dispatches executeBlock to a BlockExecutionHandler.
  9. feat(prover-node): block-summary plumbing and dispatchOffloadedBlock helperBlockExecutionResult carries the block summary; orchestrator exposes applyBlockExecutionResult / getBlockStartSpongeBlob; EpochProvingJob.dispatchOffloadedBlock wires per-tx watchers + dispatch (scaffolding, not yet on the production call-site).
  10. chore: remove unused jest import

Notes

  • The legacy publicProcessor.process + addTxs flow is still the production call site. Flipping the switch to dispatchOffloadedBlock is a follow-up alongside test updates.
  • All 4 cherry-picks that conflicted on the rebase were import-order / signature-evolution conflicts — no behavioural changes.

Test plan

  • yarn lint for prover-client, prover-node, stdlib, bb-prover
  • TS compile for those packages
  • Existing prover-client / prover-node unit tests
  • e2e epoch-proving tests

PhilWindle added 10 commits May 9, 2026 18:50
…ECUTION job type

Foundational pieces for the execution offload:

- `makeExecutionResultJobId(epoch, blockNumber, slotNumber, txIndex, type)` — produces
  IDs that the orchestrator and an execution agent can compute independently from the
  same coordinates without exchanging data. Format keeps `getEpochFromProvingJobId`
  working.
- New `BLOCK_EXECUTION` `ProvingRequestType` with `BlockExecutionInputs` (epoch,
  checkpoint index, block header, tx hashes) and a `BlockExecutionResult` marker. Wired
  through `ProvingJobInputs` / `ProvingJobResult` (and their maps), the broker's
  per-type queues, and the priority order — placed at the top, since execution gates
  the rest of the proving DAG.
- `ProvingJobController` exhaustive switch gets a placeholder case; the agent-side
  handler lands in a follow-up.
- Adds `executeBlock` to `ServerCircuitProver`. `BBNativeRollupProver`,
  `TestCircuitProver`, `MockProver`, and `BrokerCircuitProverFacade` get
  implementations: the first three reject (general-purpose proving agents
  never receive BLOCK_EXECUTION); the facade enqueues the job to the broker
  for the orchestrator side.
- New `BlockExecutionHandler` (also `ServerCircuitProver`-shaped, with all
  non-execution methods rejecting). It fetches the txs, forks world state at
  the parent block, runs `PublicProcessor.process`, persists each public tx's
  AVM circuit inputs to the proof store, and enqueues the per-tx AVM jobs
  under deterministic IDs computed from `(epoch, blockNumber, slotNumber,
  txIndex)`. The fork is closed in a `finally` on both success and error.
- New `BrokerCircuitProverFacade.expectJob(id, type, signal)` reserves a
  Promise for a job ID that the facade did not enqueue itself. Used in the
  next commit by the orchestrator to await the agent-enqueued AVM proofs.
- `ProvingJobController` BLOCK_EXECUTION case now dispatches to
  `circuitProver.executeBlock` instead of throwing.
- Adds `@aztec/stdlib/block_execution` subpath export.
…hed AVM proofs

Adds `ProvingOrchestrator.addBlockForExecution(txs, expectAvmProofForTx)` as a
parallel to `addTxs`. The new path runs the same per-tx setup (validate,
prepareBaseRollupInputs, chonk verifier kickoff, base rollup) but obtains the
AVM proof from a caller-supplied callback instead of enqueueing a fresh AVM
proving job locally. Callers wire the callback to the broker facade's
`expectJob` (added in the previous commit) against deterministic IDs the
execution agent uses when enqueueing per-tx AVM jobs.

The shared per-tx loop is factored into a private `addProcessedTxsToBlock`
helper that takes a callback for "how to obtain the AVM proof". `addTxs`
keeps its current behaviour (enqueue) and the new method passes a "watch
deterministic-ID job" callback.

Integration test (`orchestrator_block_execution.test.ts`) drives a four-tx
block alternating private/public through the new path and asserts the
callback fires exactly once per public tx index, the block header matches,
and proving completes end-to-end via the same TestContext fixtures used by
the existing addTxs tests.
Adds an opt-in set of in-process execution agents controlled by two new env
vars:

- `PROVER_NODE_EXECUTION_AGENT_COUNT` (default `0` — feature off)
- `PROVER_NODE_EXECUTION_AGENT_POLL_INTERVAL_MS` (default `100` — tighter
  than regular proving agents because execution gates the rest of the DAG)

`InternalExecutionAgents` (in `prover-client/src/block_execution/`) wires N
`ProvingAgent`s with allowList `[BLOCK_EXECUTION]`, each backed by a
`BlockExecutionHandler` constructed against the prover node's broker, world
state, public processor factory and tx provider. The class exposes
start/stop and is owned by the `ProverNode` so its lifecycle matches the
rest of the node.

The factory wires the tx provider through to the handler via a small
adapter that calls `getAvailableTxs` and validates ordering, and constructs
its own `PublicProcessorFactory` against the existing archiver — no RPC
archiver yet, that comes in Phase 3.

Tests cover the happy path (agents spin up, ask the broker only for
BLOCK_EXECUTION jobs, stop cleanly) and the disabled-by-default case.
…passenger data

Introduces three new types in `@aztec/stdlib/block_execution`:

- `BlockExecutionTxData` — per-tx execution data the agent will compute and the
  orchestrator will use to enqueue the public base rollup without touching its
  own world-state fork: `{ baseRollupHints, avmCircuitPublicInputs }`.
- `AvmProvingInputs` — wraps `AvmCircuitInputs` with optional `executionTxData`.
  Becomes `ProvingJobInputsMap[PUBLIC_VM]`.
- `AvmProvingResult` — wraps the AVM `RecursiveProof` with the same passenger
  field, passed through unchanged by the proving agent. Becomes
  `ProvingJobResultsMap[PUBLIC_VM]`.

The proving agent (BBNative/Test/Mock) reads `inputs.avmCircuitInputs`,
generates the proof from those inputs only, and packages the result with
`inputs.executionTxData` passed through. The legacy `addTxs` path constructs
inputs via `AvmProvingInputs.fromAvmCircuitInputs(...)` (passenger undefined)
and unwraps the result with `.proof`, so existing call sites continue to work.

`ServerCircuitProver.getAvmProof` signature updates accordingly. All four
existing implementations (`BBNativeRollupProver`, `TestCircuitProver`,
`MockProver`, `BrokerCircuitProverFacade`) and the orchestrator's `enqueueVM`
are updated. Issue 5's `BlockExecutionHandler` keeps compiling by wrapping
`AvmCircuitInputs` with empty passenger data — Phase 3 rewrites it to
populate the passenger.
…ollup hints

Rewrites `BlockExecutionHandler` to do all the per-tx work the orchestrator
used to do for the public-tx path, so the orchestrator's base rollup is
input-independent of any local fork.

Per tx, in tx-order against the agent's fork:

- `publicProcessor.process([tx])` (single-tx call, accumulates fork state).
- `insertSideEffectsAndBuildBaseRollupHints` to compute the per-tx hints.
- For private-only txs: build `PrivateTxBaseRollupPrivateInputs` from the Tx
  + the hints, save inputs to the proof store, and enqueue
  `PRIVATE_TX_BASE_ROLLUP` directly with a deterministic ID. The orchestrator
  watches by ID and never has to construct or enqueue the job itself.
- For public txs: bundle `BlockExecutionTxData = { baseRollupHints,
  avmCircuitPublicInputs }` into `AvmProvingInputs`, enqueue `PUBLIC_VM` with
  a deterministic ID. The proving agent passes the passenger data through to
  `AvmProvingResult` so the orchestrator gets it alongside the AVM proof.

Block-level state shipped with the job (via `BlockExecutionInputs`):
`isFirstBlockInCheckpoint` + `l1ToL2Messages` (so the agent inserts them on
its fork only for the first block of the checkpoint) and `startSpongeBlob`
(carried through across blocks in the checkpoint). The agent returns the
`endSpongeBlob` in `BlockExecutionResult` so the orchestrator can carry it
forward.

The handler reports `BLOCK_EXECUTION` complete only after the per-tx jobs
are enqueued. Orchestrator-side per-tx pipelining is preserved: each tx's
proving job becomes visible to the broker as soon as the agent finishes
that tx's execution.
… and watches per-tx jobs

Reshapes `ProvingOrchestrator.addBlockForExecution` to drive the offloaded-
execution proving DAG without holding a per-block fork or constructing
ProcessedTxs:

- Signature is now `(blockNumber, txs: Tx[], watchers)`. The orchestrator
  classifies each tx by `tx.data.forPublic` and sets up a watcher.
- Private-only tx: the agent has already enqueued `PRIVATE_TX_BASE_ROLLUP`
  with a deterministic ID (relaxes the plan's "agent never enqueues base
  rollup" rule, only for the private case where the agent has every input).
  `watchers.expectPrivateBaseRollupProofForTx` resolves with the proof; the
  orchestrator pipes it into `setBaseRollupProof` and on into the merge
  tree.
- Public tx: orchestrator enqueues the chonk verifier itself from the raw
  `Tx`. `watchers.expectAvmProofForTx` resolves with the AVM proof + the
  `BlockExecutionTxData` passenger. Once both are ready, the orchestrator
  builds `PublicTxBaseRollupPrivateInputs` from the passenger hints + the
  two proofs and enqueues `PUBLIC_TX_BASE_ROLLUP` itself.

Block-level summary state (end sponge, end state, total fees, total mana
used) is intentionally not handled here — Phase 6 will wire that up
alongside the EpochProvingJob cutover, since `setBlockCompleted` and
buildBlockHeader still want per-tx data the orchestrator no longer carries.

The Issue 6 integration test (`orchestrator_block_execution.test.ts`) exercised
the old `ProcessedTx[]` signature end-to-end; it's removed here and Phase 6
will add coverage that runs through the new shape with a real
`BlockExecutionDispatcher`.

The legacy `addTxs` / `addProcessedTxsToBlock` path is untouched.
…a a composite prover

Drops the separate `InternalExecutionAgents` agent pool added in Issue 7 and
replaces it with a single composite `ServerCircuitProver` shared by the
existing prover-client agents. One agent count, one polling loop, one allowlist
— the broker hands jobs to whichever agent is free and the composite dispatches
internally:

- Proving methods (`getAvmProof`, `getPrivateTxBaseRollupProof`, parity, merge,
  block-root, etc.) delegate to the regular prover (`BBNativeRollupProver` or
  `TestCircuitProver`).
- `executeBlock` delegates to a `BlockExecutionHandler` instance constructed
  against the supplied world state, public processor factory and tx fetcher.

Wiring:

- `ProverClient.new(...)` and `createProverClient(...)` take an optional
  `ProverClientBlockExecutionDeps` (`publicProcessorFactory + txFetcher`).
  When supplied, every agent gets the composite. When absent, agents stay
  proving-only and `executeBlock` rejects.
- The prover node factory now always supplies these deps (it has every
  ingredient already — archiver, world state synchronizer, p2p client). No
  new config switch — execution capability is automatic when the prover
  node has the prerequisites.
- `proverAgentCount` continues to control the agent count. There is no
  second pool.

Removes:
- `InternalExecutionAgents` class + its test.
- `proverNodeExecutionAgentCount` / `proverNodeExecutionAgentPollIntervalMs`
  config and the matching env vars.
- The optional `internalExecutionAgents` constructor arg on `ProverNode` and
  the corresponding start/stop calls.

Adds a small unit test exercising the composite's dispatch behaviour.
…helper

Wires the orchestrator-side and prover-node-side scaffolding for the
EpochProvingJob cutover (without flipping the production call site yet —
the legacy `publicProcessor.process` + `addTxs` path still runs by default
because the existing job tests assert against it; flipping the switch is a
follow-up that goes hand-in-hand with rewriting those tests).

- `BlockExecutionResult` now carries everything the orchestrator needs to
  finish a block without touching `ProcessedTx`: `endSpongeBlob`,
  `endState`, `totalFees`, `totalManaUsed`, and per-tx `txEffects`. The
  agent populates these during execution and the prover node hands them to
  the orchestrator.
- `BlockExecutionHandler` collects the per-tx data and the aggregates as it
  walks the block, then returns the full summary in
  `BlockExecutionResult`.
- `BlockProvingState` gains a `setBlockSummary` setter and overrides
  `getTxEffects`, `getTotalFees`, `getTotalManaUsed`, and `isAcceptingTxs`
  to read from the summary when supplied. The legacy per-tx
  `TxProvingState` path is otherwise unchanged.
- `ProvingOrchestrator` gains `applyBlockExecutionResult` (set summary +
  end state + end sponge with block-end blob fields absorbed) and
  `getBlockStartSpongeBlob` (so the caller can build
  `BlockExecutionInputs` for the next block).
- `ProverClient`/`EpochProverFactory` exposes
  `getBrokerCircuitProverFacade()` so EpochProvingJob can dispatch
  `BLOCK_EXECUTION` and watch deterministic-ID per-tx jobs through the
  same facade the orchestrators use.
- `EpochProvingJob.dispatchOffloadedBlock(...)` is the new code path:
  registers per-tx watchers (private base rollup + AVM with passenger),
  builds `BlockExecutionInputs`, awaits the agent, and applies the
  summary. Not yet invoked from the per-block loop — that flip will follow
  alongside test updates.

Job test gains a stub for `getBrokerCircuitProverFacade()` so the new
helper is callable from tests when needed.
@PhilWindle PhilWindle added ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure labels May 9, 2026
@AztecBot
Copy link
Copy Markdown
Collaborator

AztecBot commented May 9, 2026

Flakey Tests

🤖 says: This CI run detected 1 tests that failed, but were tolerated due to a .test_patterns.yml entry.

\033FLAKED\033 (8;;http://ci.aztec-labs.com/93d838654b262804�93d838654b2628048;;�):  yarn-project/end-to-end/scripts/run_test.sh simple src/e2e_epochs/epochs_optimistic_proving.parallel.test.ts "replaces a reorged checkpoint and proves the epoch" (270s) (code: 0) group:e2e-p2p-epoch-flakes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-draft Run CI on draft PRs. ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants