@aztec/prover-client 0.0.1-commit.b9865e97 → 0.0.1-commit.be03c316

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dest/light/lightweight_checkpoint_builder.d.ts +1 -1
  2. package/dest/light/lightweight_checkpoint_builder.d.ts.map +1 -1
  3. package/dest/light/lightweight_checkpoint_builder.js +3 -1
  4. package/dest/mocks/test_context.d.ts +9 -11
  5. package/dest/mocks/test_context.d.ts.map +1 -1
  6. package/dest/mocks/test_context.js +13 -22
  7. package/dest/orchestrator/block-building-helpers.d.ts +1 -1
  8. package/dest/orchestrator/checkpoint-proving-state.d.ts +13 -36
  9. package/dest/orchestrator/checkpoint-proving-state.d.ts.map +1 -1
  10. package/dest/orchestrator/checkpoint-proving-state.js +18 -125
  11. package/dest/orchestrator/checkpoint-sub-tree-orchestrator.d.ts +116 -62
  12. package/dest/orchestrator/checkpoint-sub-tree-orchestrator.d.ts.map +1 -1
  13. package/dest/orchestrator/checkpoint-sub-tree-orchestrator.js +1005 -90
  14. package/dest/orchestrator/chonk-cache.d.ts +39 -0
  15. package/dest/orchestrator/chonk-cache.d.ts.map +1 -0
  16. package/dest/orchestrator/chonk-cache.js +80 -0
  17. package/dest/orchestrator/index.d.ts +2 -3
  18. package/dest/orchestrator/index.d.ts.map +1 -1
  19. package/dest/orchestrator/index.js +1 -2
  20. package/dest/orchestrator/proving-scheduler.d.ts +26 -17
  21. package/dest/orchestrator/proving-scheduler.d.ts.map +1 -1
  22. package/dest/orchestrator/proving-scheduler.js +28 -25
  23. package/dest/orchestrator/top-tree-orchestrator.d.ts +11 -6
  24. package/dest/orchestrator/top-tree-orchestrator.d.ts.map +1 -1
  25. package/dest/orchestrator/top-tree-orchestrator.js +71 -21
  26. package/dest/orchestrator/top-tree-proving-state.d.ts +1 -1
  27. package/dest/orchestrator/top-tree-proving-state.d.ts.map +1 -1
  28. package/dest/orchestrator/tx-proving-state.d.ts +3 -3
  29. package/dest/orchestrator/tx-proving-state.d.ts.map +1 -1
  30. package/dest/prover-client/prover-client.d.ts +17 -19
  31. package/dest/prover-client/prover-client.d.ts.map +1 -1
  32. package/dest/prover-client/prover-client.js +24 -25
  33. package/dest/proving_broker/broker_prover_facade.d.ts +3 -3
  34. package/dest/proving_broker/broker_prover_facade.d.ts.map +1 -1
  35. package/dest/test/epoch_settlement.d.ts +36 -0
  36. package/dest/test/epoch_settlement.d.ts.map +1 -0
  37. package/dest/test/epoch_settlement.js +52 -0
  38. package/dest/test/index.d.ts +2 -0
  39. package/dest/test/index.d.ts.map +1 -0
  40. package/dest/test/index.js +1 -0
  41. package/dest/test/mock_prover.d.ts +1 -1
  42. package/dest/test/mock_prover.js +2 -2
  43. package/package.json +17 -16
  44. package/src/light/lightweight_checkpoint_builder.ts +2 -0
  45. package/src/mocks/test_context.ts +14 -34
  46. package/src/orchestrator/checkpoint-proving-state.ts +14 -168
  47. package/src/orchestrator/checkpoint-sub-tree-orchestrator.ts +902 -129
  48. package/src/orchestrator/chonk-cache.ts +99 -0
  49. package/src/orchestrator/index.ts +1 -2
  50. package/src/orchestrator/proving-scheduler.ts +29 -25
  51. package/src/orchestrator/top-tree-orchestrator.ts +114 -44
  52. package/src/orchestrator/top-tree-proving-state.ts +0 -1
  53. package/src/orchestrator/tx-proving-state.ts +3 -3
  54. package/src/prover-client/prover-client.ts +35 -49
  55. package/src/proving_broker/broker_prover_facade.ts +2 -2
  56. package/src/test/epoch_settlement.ts +82 -0
  57. package/src/test/index.ts +1 -0
  58. package/src/test/mock_prover.ts +2 -2
  59. package/dest/orchestrator/epoch-proving-context.d.ts +0 -51
  60. package/dest/orchestrator/epoch-proving-context.d.ts.map +0 -1
  61. package/dest/orchestrator/epoch-proving-context.js +0 -81
  62. package/dest/orchestrator/epoch-proving-state.d.ts +0 -75
  63. package/dest/orchestrator/epoch-proving-state.d.ts.map +0 -1
  64. package/dest/orchestrator/epoch-proving-state.js +0 -269
  65. package/dest/orchestrator/orchestrator.d.ts +0 -118
  66. package/dest/orchestrator/orchestrator.d.ts.map +0 -1
  67. package/dest/orchestrator/orchestrator.js +0 -1113
  68. package/dest/orchestrator/top-tree-proving-scheduler.d.ts +0 -62
  69. package/dest/orchestrator/top-tree-proving-scheduler.d.ts.map +0 -1
  70. package/dest/orchestrator/top-tree-proving-scheduler.js +0 -73
  71. package/dest/prover-client/server-epoch-prover.d.ts +0 -32
  72. package/dest/prover-client/server-epoch-prover.d.ts.map +0 -1
  73. package/dest/prover-client/server-epoch-prover.js +0 -40
  74. package/src/orchestrator/epoch-proving-context.ts +0 -101
  75. package/src/orchestrator/epoch-proving-state.ts +0 -380
  76. package/src/orchestrator/orchestrator.ts +0 -1111
  77. package/src/orchestrator/top-tree-proving-scheduler.ts +0 -154
  78. package/src/prover-client/server-epoch-prover.ts +0 -69
@@ -0,0 +1,99 @@
1
+ import type { NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH } from '@aztec/constants';
2
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
3
+ import type { L2Block } from '@aztec/stdlib/block';
4
+ import type { PublicInputsAndRecursiveProof } from '@aztec/stdlib/interfaces/server';
5
+ import type { PublicChonkVerifierPublicInputs } from '@aztec/stdlib/rollup';
6
+
7
+ /** Result of a chonk-verifier proof, cached by tx hash. */
8
+ export type ChonkVerifierProofResult = PublicInputsAndRecursiveProof<
9
+ PublicChonkVerifierPublicInputs,
10
+ typeof NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH
11
+ >;
12
+
13
+ /**
14
+ * Shared cache of `getPublicChonkVerifierProof` results, keyed by tx hash.
15
+ *
16
+ * Owned by the prover-node and shared across every epoch / session: a tx remined into
17
+ * a different block (e.g. after a brief L1 reorg) reuses the already-computed chonk
18
+ * proof rather than redoing it. Entries are released via `releaseForBlocks` once the
19
+ * containing block is no longer interesting to the caller (e.g. its epoch's proof
20
+ * submission window has expired).
21
+ */
22
+ export class ChonkCache {
23
+ private readonly cache = new Map<string, Promise<ChonkVerifierProofResult>>();
24
+ private readonly pending = new Map<string, AbortController>();
25
+ private readonly log: Logger;
26
+ private stopped = false;
27
+
28
+ constructor(bindings?: LoggerBindings) {
29
+ this.log = createLogger('prover-client:chonk-cache', bindings);
30
+ }
31
+
32
+ /** Returns the cached promise for `txHash`, or `undefined` if none is registered. */
33
+ public get(txHash: string): Promise<ChonkVerifierProofResult> | undefined {
34
+ return this.cache.get(txHash);
35
+ }
36
+
37
+ /**
38
+ * Atomic get-or-compute: returns the cached promise for `txHash`, or runs `factory`
39
+ * (passing an AbortSignal the cache controls) and caches its result. Concurrent
40
+ * callers for the same `txHash` share the same promise.
41
+ *
42
+ * Rejected promises are evicted so a future caller can retry. The factory's
43
+ * AbortSignal fires when the cache is stopped.
44
+ */
45
+ public getOrCompute(
46
+ txHash: string,
47
+ factory: (signal: AbortSignal) => Promise<ChonkVerifierProofResult>,
48
+ ): Promise<ChonkVerifierProofResult> {
49
+ if (this.stopped) {
50
+ return Promise.reject(new Error('ChonkCache is stopped'));
51
+ }
52
+ const existing = this.cache.get(txHash);
53
+ if (existing) {
54
+ return existing;
55
+ }
56
+ const controller = new AbortController();
57
+ this.pending.set(txHash, controller);
58
+ this.log.debug(`Enqueueing chonk-verifier circuit`, { txHash });
59
+ const promise = factory(controller.signal).finally(() => this.pending.delete(txHash));
60
+ // Silently observe the rejection branch and evict so a retry is possible.
61
+ promise.catch(err => {
62
+ this.cache.delete(txHash);
63
+ this.log.debug(`Chonk-verifier proof failed; evicted from cache`, { txHash, error: `${err}` });
64
+ });
65
+ this.cache.set(txHash, promise);
66
+ return promise;
67
+ }
68
+
69
+ /** Drops cache entries for every tx in the supplied blocks. */
70
+ public releaseForBlocks(blocks: L2Block[]): void {
71
+ let released = 0;
72
+ for (const block of blocks) {
73
+ for (const txEffect of block.body.txEffects) {
74
+ if (this.cache.delete(txEffect.txHash.toString())) {
75
+ released++;
76
+ }
77
+ }
78
+ }
79
+ if (released > 0) {
80
+ this.log.debug(`Released ${released} chonk-verifier cache entries`, {
81
+ blockCount: blocks.length,
82
+ releasedCount: released,
83
+ });
84
+ }
85
+ }
86
+
87
+ /** Aborts every in-flight chonk-verifier job and clears the cache. */
88
+ public stop(): void {
89
+ if (this.stopped) {
90
+ return;
91
+ }
92
+ this.stopped = true;
93
+ for (const controller of this.pending.values()) {
94
+ controller.abort();
95
+ }
96
+ this.pending.clear();
97
+ this.cache.clear();
98
+ }
99
+ }
@@ -1,6 +1,5 @@
1
- export { ProvingOrchestrator } from './orchestrator.js';
2
1
  export { CheckpointSubTreeOrchestrator, type SubTreeResult } from './checkpoint-sub-tree-orchestrator.js';
3
- export { EpochProvingContext, type ChonkVerifierProofResult } from './epoch-proving-context.js';
2
+ export { ChonkCache, type ChonkVerifierProofResult } from './chonk-cache.js';
4
3
  export {
5
4
  TopTreeOrchestrator,
6
5
  TopTreeCancelledError,
@@ -4,9 +4,9 @@ import { SerialQueue } from '@aztec/foundation/queue';
4
4
  import { sleep } from '@aztec/foundation/sleep';
5
5
 
6
6
  /**
7
- * Minimal surface a deferred-proving state must expose. Both `EpochProvingState` /
8
- * `CheckpointProvingState` / `BlockProvingState` (used by `ProvingOrchestrator`) and
9
- * `TopTreeProvingState` (used by `TopTreeOrchestrator`) satisfy it.
7
+ * Minimal surface a deferred-proving state must expose. Both `CheckpointProvingState` /
8
+ * `BlockProvingState` (used by `CheckpointSubTreeOrchestrator`) and `TopTreeProvingState`
9
+ * (used by `TopTreeOrchestrator`) satisfy it.
10
10
  */
11
11
  export interface ProvingStateLike {
12
12
  /** Returns false once the state has been cancelled or otherwise invalidated. */
@@ -19,7 +19,16 @@ export interface ProvingStateLike {
19
19
  * Common scheduling infrastructure shared by every orchestrator that drives broker
20
20
  * proving jobs:
21
21
  *
22
- * - One `SerialQueue` (`deferredJobQueue`) acting as the enqueue-throttle.
22
+ * - A shared `SerialQueue` (`deferredJobQueue`) that serialises the *act of handing a
23
+ * job to the broker*, one initiation per event-loop tick. Each queue task kicks off
24
+ * `safeJob` (which submits to the broker) without awaiting it, then yields with
25
+ * `sleep(0)`; the next task therefore runs on the following macrotask. This does NOT
26
+ * cap how many broker jobs are concurrently in flight — the broker's own queue absorbs
27
+ * that. What it bounds is the burst rate: a sub-tree that synchronously discovers
28
+ * thousands of ready jobs can't flood the broker (and monopolise the event loop) in a
29
+ * single tick. The queue is owned by the `ProverClient` and shared across every
30
+ * orchestrator (every sub-tree and top-tree across every concurrent epoch session), so
31
+ * this pacing is applied once globally rather than once-per-orchestrator.
23
32
  * - A list of `AbortController`s (`pendingProvingJobs`) so a `cancel()` can abort
24
33
  * in-flight broker jobs when needed.
25
34
  * - A `deferredProving<T>(state, request, callback, isCancelled?)` method that wraps
@@ -28,23 +37,20 @@ export interface ProvingStateLike {
28
37
  *
29
38
  * Subclasses own their own concrete proving state and define `cancelInternal()` for
30
39
  * the rest of the cleanup work (closing world-state forks, marking sub-trees
31
- * cancelled, etc.). `stop()` lives on the base class and follows the standard pattern
32
- * of grabbing the old queue, calling `cancelInternal()` (which recreates the queue),
33
- * and awaiting the old queue's drain.
40
+ * cancelled, etc.). Because the queue is shared, neither `cancel()` nor `stop()` touch
41
+ * it they only abort this orchestrator's in-flight broker jobs. Queued-but-unrun jobs
42
+ * for a cancelled orchestrator no-op via the guards in `deferredProving`.
34
43
  */
35
44
  export abstract class ProvingScheduler {
36
45
  protected pendingProvingJobs: AbortController[] = [];
37
46
  protected logger: Logger;
38
- private deferredJobQueue: SerialQueue;
39
47
 
40
48
  constructor(
41
- private readonly enqueueConcurrency: number,
49
+ private readonly deferredJobQueue: SerialQueue,
42
50
  loggerName = 'prover-client:proving-scheduler',
43
51
  bindings?: LoggerBindings,
44
52
  ) {
45
53
  this.logger = createLogger(loggerName, bindings);
46
- this.deferredJobQueue = new SerialQueue();
47
- this.deferredJobQueue.start(this.enqueueConcurrency);
48
54
  }
49
55
 
50
56
  /** Number of broker jobs currently in flight. */
@@ -53,16 +59,14 @@ export abstract class ProvingScheduler {
53
59
  }
54
60
 
55
61
  /**
56
- * Drains the deferred-job queue, recreates it (so the subclass can be reused), and
57
- * optionally aborts every in-flight broker job. Aborting is the right choice on
62
+ * Optionally aborts every in-flight broker job. Aborting is the right choice on
58
63
  * reorg-driven cancel (where the in-flight inputs are no longer valid) and the
59
64
  * wrong choice on shutdown (where leaving jobs in the broker queue lets a restart
60
- * pick them up).
65
+ * pick them up). The shared queue is not touched — queued-but-unrun jobs belonging
66
+ * to this orchestrator no-op once their controller is aborted (or once their state
67
+ * is marked invalid by the subclass).
61
68
  */
62
69
  protected resetSchedulerState(abortJobs: boolean): void {
63
- void this.deferredJobQueue.cancel();
64
- this.deferredJobQueue = new SerialQueue();
65
- this.deferredJobQueue.start(this.enqueueConcurrency);
66
70
  if (abortJobs) {
67
71
  for (const controller of this.pendingProvingJobs) {
68
72
  controller.abort();
@@ -78,14 +82,12 @@ export abstract class ProvingScheduler {
78
82
  protected abstract cancelInternal(): void;
79
83
 
80
84
  /**
81
- * Standard stop: grab the old queue, cancel (which recreates the queue), then
82
- * await the old queue's drain so any final job tear-down has unwound before we
83
- * return.
85
+ * Standard stop: cancel this orchestrator's work. The shared queue is owned by the
86
+ * `ProverClient` and outlives every orchestrator, so it is not drained here.
84
87
  */
85
- public async stop(): Promise<void> {
86
- const oldQueue = this.deferredJobQueue;
88
+ public stop(): Promise<void> {
87
89
  this.cancelInternal();
88
- await oldQueue.cancel();
90
+ return Promise.resolve();
89
91
  }
90
92
 
91
93
  /**
@@ -147,9 +149,11 @@ export abstract class ProvingScheduler {
147
149
  };
148
150
 
149
151
  void this.deferredJobQueue.put(async () => {
152
+ // Kick off the broker submission without awaiting it — awaiting here would serialise all
153
+ // proving (one job at a time) and kill parallelism. The `sleep(0)` yields the event loop
154
+ // so the next queued job initiates on the following macrotask, pacing bursts rather than
155
+ // bounding in-flight broker concurrency (the broker's own queue handles that).
150
156
  void safeJob();
151
- // Yield to the macrotask queue so Node has a chance to interleave other work
152
- // between enqueues.
153
157
  await sleep(0);
154
158
  });
155
159
  }
@@ -13,8 +13,9 @@ import { BLS12Point } from '@aztec/foundation/curves/bls12';
13
13
  import { Fr } from '@aztec/foundation/curves/bn254';
14
14
  import type { LoggerBindings } from '@aztec/foundation/log';
15
15
  import { promiseWithResolvers } from '@aztec/foundation/promise';
16
+ import type { SerialQueue } from '@aztec/foundation/queue';
16
17
  import type { Tuple } from '@aztec/foundation/serialize';
17
- import { MerkleTreeCalculator, shaMerkleHash } from '@aztec/foundation/trees';
18
+ import { MerkleTreeCalculator, type TreeNodeLocation, shaMerkleHash } from '@aztec/foundation/trees';
18
19
  import type { EthAddress } from '@aztec/stdlib/block';
19
20
  import type { PublicInputsAndRecursiveProof, ServerCircuitProver } from '@aztec/stdlib/interfaces/server';
20
21
  import { computeCheckpointOutHash } from '@aztec/stdlib/messaging';
@@ -31,7 +32,7 @@ import type { BlockHeader } from '@aztec/stdlib/tx';
31
32
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
32
33
 
33
34
  import { buildBlobHints, toProofData } from './block-building-helpers.js';
34
- import { TopTreeProvingScheduler } from './top-tree-proving-scheduler.js';
35
+ import { ProvingScheduler } from './proving-scheduler.js';
35
36
  import { TopTreeProvingState } from './top-tree-proving-state.js';
36
37
 
37
38
  /** Per-checkpoint data fed into the top tree. */
@@ -86,18 +87,18 @@ type OutHashHint = {
86
87
  * and each checkpoint's root rollup fires the moment its sub-tree's `blockProofs`
87
88
  * promise resolves. Later checkpoints can still be block-level proving in parallel.
88
89
  */
89
- export class TopTreeOrchestrator extends TopTreeProvingScheduler {
90
+ export class TopTreeOrchestrator extends ProvingScheduler {
90
91
  private state: TopTreeProvingState | undefined;
91
92
  private cancelled = false;
92
93
 
93
94
  constructor(
94
- prover: ServerCircuitProver,
95
+ protected readonly prover: ServerCircuitProver,
95
96
  private readonly proverId: EthAddress,
96
- enqueueConcurrency: number,
97
+ deferredJobQueue: SerialQueue,
97
98
  _telemetryClient: TelemetryClient = getTelemetryClient(),
98
99
  bindings?: LoggerBindings,
99
100
  ) {
100
- super(prover, enqueueConcurrency, 'prover-client:top-tree-orchestrator', bindings);
101
+ super(deferredJobQueue, 'prover-client:top-tree-orchestrator', bindings);
101
102
  }
102
103
 
103
104
  public getProverId(): EthAddress {
@@ -133,10 +134,9 @@ export class TopTreeOrchestrator extends TopTreeProvingScheduler {
133
134
  }
134
135
 
135
136
  const { promise: completionPromise, resolve, reject } = promiseWithResolvers<void>();
136
- // The completion promise is awaited inside the try/catch below. Attach a no-op catch
137
- // here as well so any spurious unhandled-rejection detection during cancellation
138
- // (where reject() can fire synchronously before the await microtask installs a handler)
139
- // is silenced.
137
+ // The completion promise is awaited below. Attach a no-op catch here as well so any
138
+ // spurious unhandled-rejection detection during cancellation (where reject() can fire
139
+ // synchronously before the await microtask installs a handler) is silenced.
140
140
  completionPromise.catch(() => {});
141
141
  const startBlobAccumulator = BatchedBlobAccumulator.newWithChallenges(finalBlobBatchingChallenges);
142
142
 
@@ -189,16 +189,13 @@ export class TopTreeOrchestrator extends TopTreeProvingScheduler {
189
189
  );
190
190
  }
191
191
 
192
- try {
193
- await completionPromise;
194
- await this.state.finalizeBatchedBlob();
195
- return this.state.getEpochProofResult();
196
- } catch (err: any) {
197
- if (this.cancelled) {
198
- throw new TopTreeCancelledError();
199
- }
200
- throw err;
201
- }
192
+ // The error type is stamped atomically at rejection time by the rejectionCallback above
193
+ // (TopTreeCancelledError iff cancel() drove the rejection). Re-deriving it here from the
194
+ // live `cancelled` flag would mask a genuine failure that lost a race with a late cancel
195
+ // (A-1035), so let the original error propagate untouched.
196
+ await completionPromise;
197
+ await this.state.finalizeBatchedBlob();
198
+ return this.state.getEpochProofResult();
202
199
  }
203
200
 
204
201
  /**
@@ -220,10 +217,6 @@ export class TopTreeOrchestrator extends TopTreeProvingScheduler {
220
217
 
221
218
  // --- internal: per-checkpoint enqueue path ---
222
219
 
223
- protected override onRootRollupComplete(state: TopTreeProvingState) {
224
- state.resolve();
225
- }
226
-
227
220
  private enqueueCheckpointRoot(
228
221
  state: TopTreeProvingState,
229
222
  checkpointIndex: number,
@@ -235,26 +228,36 @@ export class TopTreeOrchestrator extends TopTreeProvingScheduler {
235
228
  outHashHint: OutHashHint,
236
229
  startBlobAccumulator: BatchedBlobAccumulator,
237
230
  ) {
238
- void this.buildCheckpointRootInputs(blockProofs, cd, outHashHint, startBlobAccumulator).then(inputs => {
239
- this.deferredProving(
240
- state,
241
- signal => {
242
- if (inputs instanceof CheckpointRootSingleBlockRollupPrivateInputs) {
243
- return this.prover.getCheckpointRootSingleBlockRollupProof(inputs, signal, state.epochNumber);
244
- }
245
- return this.prover.getCheckpointRootRollupProof(inputs, signal, state.epochNumber);
246
- },
247
- result => {
248
- this.logger.debug(`Completed checkpoint root proof for checkpoint ${checkpointIndex}`);
249
- const leafLocation = state.setCheckpointRootRollupProof(checkpointIndex, result);
250
- if (state.totalNumCheckpoints === 1) {
251
- this.enqueueEpochPadding(state);
252
- } else {
253
- this.checkAndEnqueueNextCheckpointMergeRollup(state, leafLocation);
254
- }
255
- },
256
- );
257
- });
231
+ void this.buildCheckpointRootInputs(blockProofs, cd, outHashHint, startBlobAccumulator).then(
232
+ inputs => {
233
+ this.deferredProving(
234
+ state,
235
+ signal => {
236
+ if (inputs instanceof CheckpointRootSingleBlockRollupPrivateInputs) {
237
+ return this.prover.getCheckpointRootSingleBlockRollupProof(inputs, signal, state.epochNumber);
238
+ }
239
+ return this.prover.getCheckpointRootRollupProof(inputs, signal, state.epochNumber);
240
+ },
241
+ result => {
242
+ this.logger.debug(`Completed checkpoint root proof for checkpoint ${checkpointIndex}`);
243
+ const leafLocation = state.setCheckpointRootRollupProof(checkpointIndex, result);
244
+ if (state.totalNumCheckpoints === 1) {
245
+ this.enqueueEpochPadding(state);
246
+ } else {
247
+ this.checkAndEnqueueNextCheckpointMergeRollup(state, leafLocation);
248
+ }
249
+ },
250
+ );
251
+ },
252
+ // Without this, an input-building failure rejects a discarded promise, state.reject() is
253
+ // never called, and prove() hangs forever on its completion promise (A-1036).
254
+ err => {
255
+ if (this.cancelled) {
256
+ return;
257
+ }
258
+ state.reject(`Building checkpoint root inputs for checkpoint ${checkpointIndex} failed: ${err}`);
259
+ },
260
+ );
258
261
  }
259
262
 
260
263
  private async buildCheckpointRootInputs(
@@ -286,6 +289,73 @@ export class TopTreeOrchestrator extends TopTreeProvingScheduler {
286
289
  : new CheckpointRootRollupPrivateInputs([proofDatas[0], proofDatas[1]], hints);
287
290
  }
288
291
 
292
+ // --- internal: top-tree proof orchestration (formerly TopTreeProvingScheduler) ---
293
+
294
+ private enqueueCheckpointMergeRollup(state: TopTreeProvingState, location: TreeNodeLocation) {
295
+ if (!state.verifyState() || !state.tryStartProvingCheckpointMerge(location)) {
296
+ return;
297
+ }
298
+ const inputs = state.getCheckpointMergeRollupInputs(location);
299
+ this.deferredProving(
300
+ state,
301
+ signal => this.prover.getCheckpointMergeRollupProof(inputs, signal, state.epochNumber),
302
+ result => {
303
+ state.setCheckpointMergeRollupProof(location, result);
304
+ this.checkAndEnqueueNextCheckpointMergeRollup(state, location);
305
+ },
306
+ );
307
+ }
308
+
309
+ private enqueueEpochPadding(state: TopTreeProvingState) {
310
+ if (!state.verifyState() || !state.tryStartProvingPaddingCheckpoint()) {
311
+ return;
312
+ }
313
+ const inputs = state.getPaddingCheckpointInputs();
314
+ this.deferredProving(
315
+ state,
316
+ signal => this.prover.getCheckpointPaddingRollupProof(inputs, signal, state.epochNumber),
317
+ result => {
318
+ state.setCheckpointPaddingProof(result);
319
+ this.checkAndEnqueueRootRollup(state);
320
+ },
321
+ );
322
+ }
323
+
324
+ private enqueueRootRollup(state: TopTreeProvingState) {
325
+ if (!state.verifyState() || !state.tryStartProvingRootRollup()) {
326
+ return;
327
+ }
328
+ const inputs = state.getRootRollupInputs();
329
+ this.deferredProving(
330
+ state,
331
+ signal => this.prover.getRootRollupProof(inputs, signal, state.epochNumber),
332
+ result => {
333
+ this.logger.verbose(`Completed root rollup for epoch ${state.epochNumber}`);
334
+ state.setRootRollupProof(result);
335
+ state.resolve();
336
+ },
337
+ );
338
+ }
339
+
340
+ private checkAndEnqueueNextCheckpointMergeRollup(state: TopTreeProvingState, currentLocation: TreeNodeLocation) {
341
+ if (!state.isReadyForCheckpointMerge(currentLocation)) {
342
+ return;
343
+ }
344
+ const parentLocation = state.getParentLocation(currentLocation);
345
+ if (parentLocation.level === 0) {
346
+ this.checkAndEnqueueRootRollup(state);
347
+ } else {
348
+ this.enqueueCheckpointMergeRollup(state, parentLocation);
349
+ }
350
+ }
351
+
352
+ private checkAndEnqueueRootRollup(state: TopTreeProvingState) {
353
+ if (!state.isReadyForRootRollup()) {
354
+ return;
355
+ }
356
+ this.enqueueRootRollup(state);
357
+ }
358
+
289
359
  private async computeOutHashHints(checkpointData: CheckpointTopTreeData[]): Promise<OutHashHint[]> {
290
360
  const treeCalculator = await MerkleTreeCalculator.create(OUT_HASH_TREE_HEIGHT, undefined, (left, right) =>
291
361
  Promise.resolve(shaMerkleHash(left, right)),
@@ -41,7 +41,6 @@ export class TopTreeProvingState {
41
41
  private endBlobAccumulator: BatchedBlobAccumulator | undefined;
42
42
  private finalBatchedBlob: BatchedBlob | undefined;
43
43
  private lifecycle = TOP_TREE_LIFECYCLE.CREATED;
44
-
45
44
  constructor(
46
45
  public readonly epochNumber: EpochNumber,
47
46
  public readonly totalNumCheckpoints: number,
@@ -1,4 +1,4 @@
1
- import { AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED, NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH } from '@aztec/constants';
1
+ import { AVM_V2_PROOF_LENGTH_IN_FIELDS, NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH } from '@aztec/constants';
2
2
  import type { Fr } from '@aztec/foundation/curves/bn254';
3
3
  import { getVkData } from '@aztec/noir-protocol-circuits-types/server/vks';
4
4
  import type { AvmCircuitInputs } from '@aztec/stdlib/avm';
@@ -32,7 +32,7 @@ export class TxProvingState {
32
32
  PublicChonkVerifierPublicInputs,
33
33
  typeof NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH
34
34
  >;
35
- private avmProof?: RecursiveProof<typeof AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED>;
35
+ private avmProof?: RecursiveProof<typeof AVM_V2_PROOF_LENGTH_IN_FIELDS>;
36
36
 
37
37
  constructor(
38
38
  public readonly processedTx: ProcessedTx,
@@ -80,7 +80,7 @@ export class TxProvingState {
80
80
  this.publicChonkVerifier = publicChonkVerifierProofAndVk;
81
81
  }
82
82
 
83
- public setAvmProof(avmProof: RecursiveProof<typeof AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED>) {
83
+ public setAvmProof(avmProof: RecursiveProof<typeof AVM_V2_PROOF_LENGTH_IN_FIELDS>) {
84
84
  this.avmProof = avmProof;
85
85
  }
86
86
 
@@ -4,10 +4,10 @@ import { times } from '@aztec/foundation/collection';
4
4
  import type { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import type { EthAddress } from '@aztec/foundation/eth-address';
6
6
  import { type Logger, createLogger } from '@aztec/foundation/log';
7
+ import { SerialQueue } from '@aztec/foundation/queue';
7
8
  import { NativeACVMSimulator } from '@aztec/simulator/server';
8
9
  import {
9
10
  type ActualProverConfig,
10
- type EpochProver,
11
11
  type EpochProverManager,
12
12
  type ForkMerkleTreeOperations,
13
13
  type ProvingJobBroker,
@@ -23,13 +23,11 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien
23
23
 
24
24
  import type { ProverClientConfig } from '../config.js';
25
25
  import { CheckpointSubTreeOrchestrator } from '../orchestrator/checkpoint-sub-tree-orchestrator.js';
26
- import { EpochProvingContext } from '../orchestrator/epoch-proving-context.js';
27
- import { ProvingOrchestrator } from '../orchestrator/orchestrator.js';
26
+ import type { ChonkCache } from '../orchestrator/chonk-cache.js';
28
27
  import { TopTreeOrchestrator } from '../orchestrator/top-tree-orchestrator.js';
29
28
  import { BrokerCircuitProverFacade } from '../proving_broker/broker_prover_facade.js';
30
29
  import { InlineProofStore, type ProofStore, createProofStore } from '../proving_broker/proof_store/index.js';
31
30
  import { ProvingAgent } from '../proving_broker/proving_agent.js';
32
- import { ServerEpochProver } from './server-epoch-prover.js';
33
31
 
34
32
  /**
35
33
  * The factory surface that `EpochProvingJob` (in `prover-node`) depends on. Implemented
@@ -49,14 +47,13 @@ import { ServerEpochProver } from './server-epoch-prover.js';
49
47
  export interface EpochProverFactory {
50
48
  getProverId(): EthAddress;
51
49
  /**
52
- * Constructs a per-epoch shared context for the caching of e.g. chonk verifier results
53
- */
54
- createEpochProvingContext(epochNumber: EpochNumber): EpochProvingContext;
55
- /**
56
- * Constructs and starts a `CheckpointSubTreeOrchestrator` for a single checkpoint.
50
+ * Constructs and starts a `CheckpointSubTreeOrchestrator` for a single checkpoint
51
+ * against the supplied shared `chonkCache`. The cache is owned by the prover-node
52
+ * and survives across epochs / sessions.
57
53
  */
58
54
  createCheckpointSubTreeOrchestrator(
59
- epochContext: EpochProvingContext,
55
+ chonkCache: ChonkCache,
56
+ epochNumber: EpochNumber,
60
57
  checkpointConstants: CheckpointConstantData,
61
58
  l1ToL2Messages: Fr[],
62
59
  totalNumBlocks: number,
@@ -75,6 +72,13 @@ export class ProverClient implements EpochProverManager, EpochProverFactory {
75
72
  * `EpochProverFactory` for why a single shared facade is required.
76
73
  */
77
74
  private facade: BrokerCircuitProverFacade | undefined;
75
+ /**
76
+ * Single deferred-proving-job queue shared across every orchestrator (sub-trees and
77
+ * top-trees, across every concurrent epoch session). Throttles the total rate of job
78
+ * submission to the broker once, rather than once per orchestrator. Started lazily
79
+ * alongside the facade and cancelled on `stop()`.
80
+ */
81
+ private deferredJobQueue: SerialQueue | undefined;
78
82
 
79
83
  private constructor(
80
84
  private config: ProverClientConfig,
@@ -112,40 +116,21 @@ export class ProverClient implements EpochProverManager, EpochProverFactory {
112
116
  return this.facade;
113
117
  }
114
118
 
115
- /**
116
- * Legacy single-class epoch prover. Each call constructs its own
117
- * `BrokerCircuitProverFacade`; the new factory methods (`createCheckpointSubTreeOrchestrator`,
118
- * `createTopTreeOrchestrator`, `createEpochProvingContext`) share a single facade
119
- * owned by `ProverClient`. Both APIs coexist while the prover-node migrates onto
120
- * the new pair.
121
- */
122
- public createEpochProver(): EpochProver {
123
- const bindings = this.log.getBindings();
124
- const facade = new BrokerCircuitProverFacade(
125
- this.orchestratorClient,
126
- this.proofStore,
127
- this.failedProofStore,
128
- undefined,
129
- bindings,
130
- );
131
- const orchestrator = new ProvingOrchestrator(
132
- this.worldState,
133
- facade,
134
- this.config.proverId,
135
- this.config.cancelJobsOnStop,
136
- this.config.enqueueConcurrency,
137
- this.telemetry,
138
- bindings,
139
- );
140
- return new ServerEpochProver(facade, orchestrator);
141
- }
142
-
143
- public createEpochProvingContext(epochNumber: EpochNumber): EpochProvingContext {
144
- return new EpochProvingContext(this.getFacade(), epochNumber, this.log.getBindings());
119
+ /** Lazy-init the shared deferred-job queue, started with the configured enqueue concurrency. */
120
+ private getDeferredJobQueue(): SerialQueue {
121
+ if (!this.running) {
122
+ throw new Error('ProverClient is not running; call start() before constructing orchestrators.');
123
+ }
124
+ if (!this.deferredJobQueue) {
125
+ this.deferredJobQueue = new SerialQueue();
126
+ this.deferredJobQueue.start(this.config.enqueueConcurrency);
127
+ }
128
+ return this.deferredJobQueue;
145
129
  }
146
130
 
147
131
  public createCheckpointSubTreeOrchestrator(
148
- epochContext: EpochProvingContext,
132
+ chonkCache: ChonkCache,
133
+ epochNumber: EpochNumber,
149
134
  checkpointConstants: CheckpointConstantData,
150
135
  l1ToL2Messages: Fr[],
151
136
  totalNumBlocks: number,
@@ -155,9 +140,10 @@ export class ProverClient implements EpochProverManager, EpochProverFactory {
155
140
  this.worldState,
156
141
  this.getFacade(),
157
142
  this.config.proverId,
158
- epochContext,
143
+ chonkCache,
144
+ epochNumber,
159
145
  this.config.cancelJobsOnStop,
160
- this.config.enqueueConcurrency,
146
+ this.getDeferredJobQueue(),
161
147
  checkpointConstants,
162
148
  l1ToL2Messages,
163
149
  totalNumBlocks,
@@ -171,7 +157,7 @@ export class ProverClient implements EpochProverManager, EpochProverFactory {
171
157
  return new TopTreeOrchestrator(
172
158
  this.getFacade(),
173
159
  this.config.proverId,
174
- this.config.enqueueConcurrency,
160
+ this.getDeferredJobQueue(),
175
161
  this.telemetry,
176
162
  this.log.getBindings(),
177
163
  );
@@ -216,12 +202,12 @@ export class ProverClient implements EpochProverManager, EpochProverFactory {
216
202
  }
217
203
  this.running = false;
218
204
  await this.stopAgents();
205
+ if (this.deferredJobQueue) {
206
+ await this.deferredJobQueue.cancel();
207
+ this.deferredJobQueue = undefined;
208
+ }
219
209
  if (this.facade) {
220
- try {
221
- await this.facade.stop();
222
- } catch (err) {
223
- this.log.error('Error stopping shared broker facade', err);
224
- }
210
+ await tryStop(this.facade, this.log);
225
211
  this.facade = undefined;
226
212
  }
227
213
  await tryStop(this.orchestratorClient);
@@ -1,5 +1,5 @@
1
1
  import type {
2
- AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED,
2
+ AVM_V2_PROOF_LENGTH_IN_FIELDS,
3
3
  NESTED_RECURSIVE_PROOF_LENGTH,
4
4
  NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH,
5
5
  RECURSIVE_PROOF_LENGTH,
@@ -394,7 +394,7 @@ export class BrokerCircuitProverFacade implements ServerCircuitProver {
394
394
  inputs: AvmCircuitInputs,
395
395
  signal?: AbortSignal,
396
396
  epochNumber?: EpochNumber,
397
- ): Promise<RecursiveProof<typeof AVM_V2_PROOF_LENGTH_IN_FIELDS_PADDED>> {
397
+ ): Promise<RecursiveProof<typeof AVM_V2_PROOF_LENGTH_IN_FIELDS>> {
398
398
  return this.enqueueJob(
399
399
  this.generateId(ProvingRequestType.PUBLIC_VM, inputs, epochNumber),
400
400
  ProvingRequestType.PUBLIC_VM,