@aztec/prover-node 0.0.1-commit.a072138 → 0.0.1-commit.a5db02d

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 (75) hide show
  1. package/README.md +511 -0
  2. package/dest/actions/download-epoch-proving-job.js +1 -1
  3. package/dest/actions/rerun-epoch-proving-job.d.ts +5 -4
  4. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
  5. package/dest/actions/rerun-epoch-proving-job.js +103 -23
  6. package/dest/actions/upload-epoch-proof-failure.d.ts +2 -2
  7. package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -1
  8. package/dest/bin/run-failed-epoch.js +6 -5
  9. package/dest/checkpoint-store.d.ts +88 -0
  10. package/dest/checkpoint-store.d.ts.map +1 -0
  11. package/dest/checkpoint-store.js +169 -0
  12. package/dest/config.d.ts +5 -8
  13. package/dest/config.d.ts.map +1 -1
  14. package/dest/config.js +17 -20
  15. package/dest/factory.d.ts +19 -13
  16. package/dest/factory.d.ts.map +1 -1
  17. package/dest/factory.js +40 -65
  18. package/dest/index.d.ts +2 -1
  19. package/dest/index.d.ts.map +1 -1
  20. package/dest/index.js +1 -0
  21. package/dest/job/checkpoint-prover.d.ts +124 -0
  22. package/dest/job/checkpoint-prover.d.ts.map +1 -0
  23. package/dest/job/checkpoint-prover.js +330 -0
  24. package/dest/job/epoch-session.d.ts +148 -0
  25. package/dest/job/epoch-session.d.ts.map +1 -0
  26. package/dest/job/{epoch-proving-job.js → epoch-session.js} +278 -297
  27. package/dest/job/top-tree-job.d.ts +82 -0
  28. package/dest/job/top-tree-job.d.ts.map +1 -0
  29. package/dest/job/top-tree-job.js +152 -0
  30. package/dest/metrics.d.ts +40 -3
  31. package/dest/metrics.d.ts.map +1 -1
  32. package/dest/metrics.js +101 -4
  33. package/dest/monitors/epoch-monitor.d.ts +1 -1
  34. package/dest/monitors/epoch-monitor.d.ts.map +1 -1
  35. package/dest/monitors/epoch-monitor.js +11 -9
  36. package/dest/proof-publishing-service.d.ts +161 -0
  37. package/dest/proof-publishing-service.d.ts.map +1 -0
  38. package/dest/proof-publishing-service.js +335 -0
  39. package/dest/prover-node-publisher.d.ts +22 -15
  40. package/dest/prover-node-publisher.d.ts.map +1 -1
  41. package/dest/prover-node-publisher.js +198 -61
  42. package/dest/prover-node.d.ts +118 -70
  43. package/dest/prover-node.d.ts.map +1 -1
  44. package/dest/prover-node.js +487 -226
  45. package/dest/prover-publisher-factory.d.ts +4 -4
  46. package/dest/prover-publisher-factory.d.ts.map +1 -1
  47. package/dest/prover-publisher-factory.js +3 -3
  48. package/dest/session-manager.d.ts +158 -0
  49. package/dest/session-manager.d.ts.map +1 -0
  50. package/dest/session-manager.js +486 -0
  51. package/dest/test/index.d.ts +7 -6
  52. package/dest/test/index.d.ts.map +1 -1
  53. package/package.json +23 -22
  54. package/src/actions/download-epoch-proving-job.ts +1 -1
  55. package/src/actions/rerun-epoch-proving-job.ts +115 -29
  56. package/src/actions/upload-epoch-proof-failure.ts +1 -1
  57. package/src/bin/run-failed-epoch.ts +5 -3
  58. package/src/checkpoint-store.ts +194 -0
  59. package/src/config.ts +25 -32
  60. package/src/factory.ts +68 -111
  61. package/src/index.ts +1 -0
  62. package/src/job/checkpoint-prover.ts +442 -0
  63. package/src/job/epoch-session.ts +438 -0
  64. package/src/job/top-tree-job.ts +227 -0
  65. package/src/metrics.ts +123 -10
  66. package/src/monitors/epoch-monitor.ts +5 -6
  67. package/src/proof-publishing-service.ts +427 -0
  68. package/src/prover-node-publisher.ts +232 -78
  69. package/src/prover-node.ts +562 -256
  70. package/src/prover-publisher-factory.ts +5 -5
  71. package/src/session-manager.ts +585 -0
  72. package/src/test/index.ts +6 -6
  73. package/dest/job/epoch-proving-job.d.ts +0 -63
  74. package/dest/job/epoch-proving-job.d.ts.map +0 -1
  75. package/src/job/epoch-proving-job.ts +0 -435
package/README.md CHANGED
@@ -1 +1,512 @@
1
1
  # Prover Node
2
+
3
+ The prover node turns sequenced checkpoints into epoch proofs that get submitted to the L1
4
+ rollup contract. It runs alongside an Aztec validator/full-node and consumes the
5
+ canonical chain view those nodes emit, proving epochs **optimistically** — sub-tree
6
+ work begins the moment a checkpoint lands on L1, not when the epoch closes.
7
+
8
+ This document describes the internal architecture: the state held by the prover-node,
9
+ the events that drive it, and the data flow from a fresh `chain-checkpointed` event
10
+ through to a `submitEpochRootProof` on L1.
11
+
12
+ ## Contents
13
+
14
+ 1. [Architecture](#architecture)
15
+ 2. [CheckpointProver lifecycle](#checkpointprover-lifecycle)
16
+ 3. [EpochSession lifecycle](#epochsession-lifecycle)
17
+ 4. [Event flow](#event-flow)
18
+ 5. [Walkthroughs](#walkthroughs)
19
+ 6. [Design rationale](#design-rationale)
20
+ 7. [Configuration](#configuration)
21
+ 8. [Failure handling and observability](#failure-handling-and-observability)
22
+
23
+ ## Architecture
24
+
25
+ ```mermaid
26
+ flowchart TB
27
+ L2BlockStream -->|chain-checkpointed| ProverNode
28
+ L2BlockStream -->|chain-pruned| ProverNode
29
+ L2BlockStream -->|chain-proven| ProverNode
30
+ L2BlockStream -->|any event| ProverNode
31
+ ProverNode --> CheckpointStore
32
+ ProverNode --> ChonkCache
33
+ ProverNode --> SessionManager
34
+ ProverNode --> ProofPublishingService
35
+ SessionManager --> EpochTicker[(periodic tick)]
36
+ SessionManager --> FullSessions[(fullSessions)]
37
+ SessionManager --> PartialSessions[(partialSessions)]
38
+ FullSessions -.referenced checkpoints.-> CheckpointStore
39
+ PartialSessions -.referenced checkpoints.-> CheckpointStore
40
+ FullSessions --> TopTreeJob
41
+ PartialSessions --> TopTreeJob
42
+ TopTreeJob -->|PublishCandidate| ProofPublishingService
43
+ ProofPublishingService -->|fresh per publish| ProverNodePublisher
44
+ ProverNodePublisher --> L1[L1 Rollup]
45
+ ```
46
+
47
+ The prover-node splits responsibility between four classes:
48
+
49
+ - **`ProverNode`** — owns the long-lived collections, wires the L2BlockStream, and
50
+ translates each chain event into a single method call on the `SessionManager` or
51
+ `ProofPublishingService`. It also performs the per-event side effects that don't
52
+ belong on an `EpochSession` (registering new checkpoints with the store, sweeping
53
+ expired epochs out of the cache and the store, etc.) and runs the failure-upload
54
+ action when an `EpochSession` exits with `failed`.
55
+ - **`CheckpointStore`** — a registry of `CheckpointProver` instances keyed by
56
+ `(checkpointNumber, slot, archiveRoot)`. Each `CheckpointProver` runs its own sub-tree pipeline
57
+ (tx gather → block processing → block-rollup proofs), starting eagerly the moment a
58
+ checkpoint is registered. The store holds the canonical checkpoint content that
59
+ `EpochSession`s query when assembling their subsets; a prune cancels and removes the affected
60
+ provers, so every prover in the store is canonical.
61
+ - **`SessionManager`** — owns every live `EpochSession`, the serial reconcile queue,
62
+ the periodic tick, and all `EpochSession` lifecycle decisions. `ProverNode` calls into it
63
+ via `onCheckpointAdded`, `onPrune`, and `startProof`. Every trigger it receives is
64
+ translated into a `reconcile(trigger)` call, a single idempotent function that walks
65
+ all `EpochSession`s, cancels any whose canonical content has shifted, re-creates them with
66
+ the new content, and opens fresh full `EpochSession`s for any epoch that has become provable.
67
+ Reconcile runs on a `SerialQueue` (from `@aztec/foundation/queue`), so two concurrent
68
+ triggers can never interleave on an `await` and race on the `EpochSession` maps.
69
+ - **`ProofPublishingService`** — central owner of L1 proof submission. `EpochSession`s hand
70
+ their top-tree proofs to the service as `PublishCandidate`s; the service serialises
71
+ one publish at a time against a freshly-created `ProverNodePublisher`, gates eligibility
72
+ on the proven block tip, picks the longest candidate per epoch as the winner
73
+ (others resolve `'superseded'`), and enforces a per-candidate `deadline`. It runs its
74
+ own `drain()` on a separate `SerialQueue`: submits, withdrawals, chain-proven advances,
75
+ and per-candidate deadline expiries all enqueue a drain pass, so the eligibility
76
+ re-check and the L1 publish never interleave with each other.
77
+
78
+ ## CheckpointProver lifecycle
79
+
80
+ A `CheckpointProver` is content-addressed by `(checkpoint.number, slot, archiveRoot)`,
81
+ where `archiveRoot` is the checkpoint's own archive root (its post-state). Keying on the
82
+ post-state makes the identity precise: two checkpoints are "the same" iff they produce
83
+ the same archive — so a reorg branch, or a replacement built on the same predecessor but
84
+ with different content, yields a different archive root and a distinct `CheckpointProver`. A prune
85
+ cancels and removes a prover — its sub-tree work forks world-state per block and cannot survive the
86
+ prune — so a re-add, even of identical content, constructs a fresh `CheckpointProver` (block-rollup
87
+ jobs already completed in the proving broker are still reused, as they are content-addressed).
88
+
89
+ ```mermaid
90
+ stateDiagram-v2
91
+ [*] --> Created
92
+ Created --> Proving: gather + execute
93
+ Proving --> Proven: sub-tree resolves blockProofs
94
+ Proving --> Cancelled: cancel() (prune / shutdown / epoch-session error)
95
+ Proven --> Cancelled: cancel() (prune / reap)
96
+ Proven --> Reaped: reapExpired(epoch)
97
+ Cancelled --> [*]
98
+ ```
99
+
100
+ A prune **cancels and removes** the affected `CheckpointProver`s from the store: a prover's
101
+ sub-tree work forks world-state per block, and an L1 prune of a base block faults those reads,
102
+ so there is nothing to preserve across a reorg. A re-add — even of identical content — therefore
103
+ constructs a fresh `CheckpointProver`. `EpochSession`s ask the store for the checkpoints in a slot
104
+ range; every prover in the store is canonical.
105
+
106
+ ### Removal rules
107
+
108
+ - **Pruned**: `CheckpointStore.cancelAndRemoveAboveBlock(target)` cancels and removes every
109
+ `CheckpointProver` whose last block is above the prune target, the moment a `chain-pruned`
110
+ event arrives.
111
+ - **Expired**: `CheckpointStore.reapExpired(expiredEpoch)` drops any `CheckpointProver` whose
112
+ epoch is at or below the supplied expired epoch. Once an epoch's proof-submission window has
113
+ closed, its proof can no longer be accepted on L1, so the `CheckpointProver` is no longer needed.
114
+ - **Shutdown**: `CheckpointStore.stop()` cancels every remaining prover and awaits its teardown
115
+ (including teardowns still in flight for provers already removed by a prune or reap).
116
+
117
+ ### Eager tx gathering
118
+
119
+ A `CheckpointProver` starts its tx gather + sub-tree pipeline **in its constructor**.
120
+ The tx provider is injected as a dependency,
121
+ and the `CheckpointProver` pulls its own txs via `txProvider.getTxsForBlock(block)` for each
122
+ block in its checkpoint.
123
+
124
+ This means the moment a checkpoint lands on L1, sub-tree proving is already in flight.
125
+ By the time the epoch closes (and the `EpochSession` is constructed), most or all of the
126
+ block-rollup proofs are already done — the `EpochSession` only has to drive the top tree.
127
+
128
+ ## EpochSession lifecycle
129
+
130
+ An `EpochSession` is identified by a slot-based **spec**:
131
+
132
+ ```ts
133
+ interface SessionSpec {
134
+ kind: 'full' | 'partial';
135
+ epochNumber: EpochNumber;
136
+ fromSlot: SlotNumber;
137
+ toSlot: SlotNumber;
138
+ }
139
+ ```
140
+
141
+ The spec declares *what to prove* (a slot range). The concrete checkpoint set the
142
+ `EpochSession` holds is the *implementation* of the spec — frozen at construction time,
143
+ derived from the canonical content for that slot range.
144
+
145
+ ```mermaid
146
+ stateDiagram-v2
147
+ [*] --> initialized
148
+ initialized --> awaiting_checkpoints: start()
149
+ awaiting_checkpoints --> awaiting_root: sub-tree proofs ready, top-tree prove begins
150
+ awaiting_root --> publishing_proof: epoch proof ready, submit to L1
151
+ publishing_proof --> completed: publish succeeds
152
+ publishing_proof --> superseded: longer same-epoch candidate wins
153
+ publishing_proof --> failed: L1 submission errored
154
+ awaiting_checkpoints --> failed: top-tree prove errored
155
+ awaiting_root --> failed: top-tree prove errored
156
+ initialized --> timed_out: deadline
157
+ awaiting_checkpoints --> timed_out: deadline (EpochSession or candidate)
158
+ awaiting_root --> timed_out: deadline (EpochSession or candidate)
159
+ awaiting_checkpoints --> cancelled: cancel()
160
+ awaiting_root --> cancelled: cancel()
161
+ completed --> [*]
162
+ superseded --> [*]
163
+ cancelled --> [*]
164
+ timed_out --> [*]
165
+ failed --> [*]
166
+ ```
167
+
168
+ The non-terminal states track the window between `start()` and the L1 submission:
169
+
170
+ - `awaiting-checkpoints` — a `TopTreeJob` is awaiting each checkpoint's sub-tree result
171
+ (`CheckpointProver.whenBlockProofsReady`) before the top-tree prove can begin.
172
+ - `awaiting-root` — the sub-tree proofs are ready and the top-tree (root) prove is running,
173
+ assembling the epoch proof (set via the `TopTreeJob` `beforeProve` hook).
174
+ - `publishing-proof` — the epoch proof is being submitted to L1 via `ProofPublishingService`.
175
+
176
+ `cancel()` and the deadline can fire during any of these pre-submit phases; a terminal state
177
+ set that way wins over the phase transitions (which are guarded by `isTerminal()`).
178
+
179
+ The `EpochSession` does three sequential things: (1) run a `TopTreeJob` over the frozen
180
+ checkpoint subset, (2) hand the resulting proof to `ProofPublishingService` as a
181
+ `PublishCandidate`, (3) translate the service's outcome into a terminal state.
182
+ Predecessor gating, same-epoch dedup, deadline enforcement, and the L1 tx are all
183
+ the `ProofPublishingService`'s concern; the `EpochSession` is just the producer of one
184
+ candidate and the observer of its outcome.
185
+
186
+ Outcome → state mapping:
187
+
188
+ | `PublishOutcome` | `EpochSession` state |
189
+ |---|---|
190
+ | `published` | `completed` |
191
+ | `superseded` | `superseded` |
192
+ | `failed` | `failed` |
193
+ | `expired` | `timed-out` |
194
+ | `withdrawn` | `cancelled` |
195
+
196
+ There is a single deadline — the proof submission window — that applies across both
197
+ proving and publishing. Before submission, the `EpochSession` arms its own timer against
198
+ it: if proving doesn't finish in time, the `EpochSession` enters `timed-out` via
199
+ `cancel('deadline')`. After submission, the publishing service enforces the same deadline
200
+ on the candidate. It's the same instant throughout; only which component enforces it
201
+ changes once the candidate has been handed off.
202
+
203
+ ### Full vs partial
204
+
205
+ Every `EpochSession` — full or partial — has `fromSlot = firstSlotOfEpoch(N)`. The L1 rollup
206
+ contract requires every proof to extend from the previous epoch's proven tip, so
207
+ there's no value in starting later than the epoch boundary. The two kinds differ
208
+ only in `toSlot` and in how the publishing service treats their candidate:
209
+
210
+ - **Full** `EpochSession`s are opened by reconcile when the epoch is complete on L1 *and*
211
+ every archiver-reported checkpoint is present in the store. Their `toSlot` is
212
+ the epoch's last slot. The publishing service never auto-supersedes a `full`
213
+ candidate on proven-tip subsumption — the L1 contract records a `(epoch, prover-id)`
214
+ submission for every full-epoch proof, so even after another prover-node has
215
+ landed first, this prover's submission is still worthwhile.
216
+ - **Partial** `EpochSession`s are constructed by an explicit `startProof(epochNumber)` API
217
+ call. Their `toSlot` is the last canonical slot present at request time, which may
218
+ be earlier than the epoch's last slot. Partial candidates are an early-finish
219
+ optimisation: if the proven chain has caught up to or past `endBlock` by the time
220
+ the publishing service picks the winner, the partial resolves `'superseded'`
221
+ without spending L1 gas. Dedup: if the partial's spec collapses to the full's spec
222
+ (canonical content already covers the whole epoch), `startProof` awaits the
223
+ existing full `EpochSession` instead of opening a duplicate.
224
+
225
+ ## ProofPublishingService
226
+
227
+ The service is a single per-prover-node owner of L1 submission. `EpochSession`s call
228
+ `submit(candidate)` and await one of five outcomes:
229
+
230
+ | Outcome | Meaning |
231
+ |---|---|
232
+ | `published` | L1 accepted the proof. |
233
+ | `superseded` | A longer same-epoch candidate won, or (for `partial` candidates) the proven tip has caught up to `endBlock`. |
234
+ | `failed` | L1 submission errored. |
235
+ | `expired` | The candidate's `deadline` elapsed before publishing started. |
236
+ | `withdrawn` | An `EpochSession` called `withdraw(uuid)` on a still-queued candidate. |
237
+
238
+ Key invariants:
239
+
240
+ - **One publish at a time** via a `SerialQueue` drain.
241
+ - **Fresh publisher per publish.** Each drain call constructs a new `ProverNodePublisher`
242
+ via the factory. There is no shared in-memory state across publishes.
243
+ - **Once an L1 publish starts, it runs to completion.** `withdraw` is a queue-only
244
+ operation: it removes a candidate that hasn't started publishing. An in-flight
245
+ candidate is left alone and its outcome reports whatever L1 returned. The
246
+ originating `EpochSession` has already moved to a terminal state via `cancel()` and
247
+ ignores the late outcome.
248
+ - **Drain reads the proven block number afresh** from `l2BlockSource` inside the
249
+ serial queue, so the eligibility
250
+ check is consistent with the publish that follows it on the same drain pass.
251
+ - **Per-candidate `deadline`** arms a `setTimeout` (against the injected `DateProvider`).
252
+ When it fires, a still-queued candidate resolves `'expired'`. An in-flight publish
253
+ is left alone (its outcome reports the natural L1 result).
254
+ - **Transient `publisherFactory.create()` failures are retried.** Instead of resolving
255
+ the candidate as `'failed'`, the service schedules another drain after a 1s backoff
256
+ and leaves the candidate in the queue. The candidate's `deadline` caps the total
257
+ retry window — persistent acquire failure resolves as `'expired'`.
258
+
259
+ ### Eligibility
260
+
261
+ A candidate is eligible to publish when its **predecessor block is proven**
262
+ (`startBlock - 1 <= proven`). Among eligible candidates for the same epoch, the
263
+ one with the **highest `endBlock`** wins; the others resolve `'superseded'`.
264
+ Partial candidates whose `endBlock <= proven` are dropped before this check
265
+ (early-finish optimisation no longer helps); full candidates are never
266
+ auto-superseded on the proven tip.
267
+
268
+ ## Event flow
269
+
270
+ ### chain-checkpointed
271
+
272
+ ```mermaid
273
+ sequenceDiagram
274
+ participant L2 as L2BlockStream
275
+ participant PN as ProverNode
276
+ participant CS as CheckpointStore
277
+ participant CP as CheckpointProver
278
+ participant SM as SessionManager
279
+
280
+ L2->>PN: chain-checkpointed{checkpoint}
281
+ PN->>PN: collectRegisterData (prev-header, l1ToL2 messages, sibling path)
282
+ PN->>CS: addOrUpdate(checkpoint, data)
283
+ alt content key new
284
+ CS->>CP: new CheckpointProver(args)
285
+ CP->>CP: eager gather + sub-tree start
286
+ else content key already live
287
+ CS->>CP: reuse existing prover
288
+ end
289
+ PN->>SM: onCheckpointAdded(epoch)
290
+ SM->>SM: queue reconcile({kind:'checkpoint', epoch})
291
+ SM->>SM: walk EpochSessions, recreate invalid
292
+ SM->>SM: open full EpochSession if epoch ready
293
+ ```
294
+
295
+ ### chain-pruned
296
+
297
+ ```mermaid
298
+ sequenceDiagram
299
+ participant L2 as L2BlockStream
300
+ participant PN as ProverNode
301
+ participant CS as CheckpointStore
302
+ participant SM as SessionManager
303
+
304
+ L2->>PN: chain-pruned{block}
305
+ PN->>CS: cancelAndRemoveAboveBlock(prunedToBlock)
306
+ CS->>CS: cancel + remove every CheckpointProver whose last block is above the target
307
+ PN->>SM: onPrune(affectedEpochs)
308
+ SM->>SM: queue reconcile({kind:'prune', affectedEpochs})
309
+ SM->>SM: walk EpochSessions, cancel-and-recreate those with shifted content
310
+ ```
311
+
312
+ ### chain-proven
313
+
314
+ ```mermaid
315
+ sequenceDiagram
316
+ participant L2 as L2BlockStream
317
+ participant PN as ProverNode
318
+ participant PS as ProofPublishingService
319
+
320
+ L2->>PN: chain-proven{block}
321
+ PN->>PS: onChainProven(blockNumber)
322
+ PS->>PS: scheduleDrain (wake-up only, no state cached)
323
+ PS->>PS: drain reads proven afresh, re-checks eligibility
324
+ ```
325
+
326
+ ### Per-event expiry sweep
327
+
328
+ ```mermaid
329
+ sequenceDiagram
330
+ participant L2 as L2BlockStream
331
+ participant PN as ProverNode
332
+ participant CC as ChonkCache
333
+ participant CS as CheckpointStore
334
+
335
+ L2->>PN: any event
336
+ PN->>L2: getSyncedL2SlotNumber()
337
+ PN->>PN: latestEpoch = getEpochAtSlot(latestSlot)
338
+ PN->>PN: newlyExpiredUpTo = latestEpoch - (proofSubmissionEpochs + 1)
339
+ loop for each newly-expired epoch
340
+ PN->>L2: getCheckpointsData({epoch}) + getBlocks(...)
341
+ PN->>CC: releaseForBlocks(blocks)
342
+ PN->>CS: reapExpired(epoch)
343
+ end
344
+ ```
345
+
346
+ Expiry runs at the end of every `handleBlockStreamEvent` call (not on any specific
347
+ event type). An epoch `E` is expired once the chain reaches the start of epoch
348
+ `E + proofSubmissionEpochs + 1` — the deadline beyond which an L1 submission for
349
+ `E` would be rejected. A monotonic high-water mark (`lastExpiredEpoch`) makes the
350
+ sweep cheap: it advances per event and never revisits an epoch. It is seeded at
351
+ `start()` from the last fully-proven epoch (computed in `computeStartupState`),
352
+ so on a restart we never re-sweep epochs that already reached L1.
353
+
354
+ ### Periodic tick
355
+
356
+ `SessionManager.start()` arms a `RunningPromise` that fires
357
+ `reconcile({ kind: 'tick' })` every `tickIntervalMs`. The tick picks up epochs that
358
+ became complete by time alone (no fresh checkpoint event) and advances to the
359
+ next unproven epoch once the previous one lands on L1. A monotonic high-water
360
+ mark (`lastTickEpoch`) prevents the tick from re-opening an epoch whose `EpochSession`
361
+ already terminated; the mark advances only after an `EpochSession` actually exists for
362
+ the epoch, so transient blockers (max-pending-jobs reached, archiver still
363
+ indexing) leave the mark in place and the next tick retries.
364
+
365
+ ## Walkthroughs
366
+
367
+ ### checkpoint-added → prune → checkpoint-added (reorg resilience)
368
+
369
+ State: epoch N has checkpoints c1..c4 (slots s1..s4). `fullSessions[N]` holds `EpochSession`
370
+ **A** with spec `{kind:'full', N, fromSlot:s1, toSlot:s4}`, referencing `[c1, c2, c3, c4]`.
371
+
372
+ 1. **chain-pruned arrives, target c3.** The store cancels and removes c4 (its last block is
373
+ above the prune target). Reconcile fires: `EpochSession` A's frozen set `[c1, c2, c3, c4]`
374
+ includes the now-cancelled c4, so it no longer matches the store's `[c1, c2, c3]` →
375
+ `A.cancel('canonical content changed')`. Epoch N still complete on L1 → reconcile constructs
376
+ `EpochSession` **B** with the same spec `{full, N, s1, s4}` but checkpoints `[c1, c2, c3]`.
377
+
378
+ 2. **`EpochSession` B starts top-tree proving over [c1, c2, c3].**
379
+
380
+ 3. **chain-checkpointed arrives, target c4_re (same content key as old c4).** The old c4
381
+ prover was removed on the prune, so the store constructs a **fresh** `CheckpointProver` for
382
+ c4_re and starts its sub-tree work from scratch. (Its block-rollup jobs are content-addressed
383
+ in the proving broker, so any the old c4 already completed are reused rather than re-proved.)
384
+
385
+ 4. **Reconcile fires.** `EpochSession` B's content for `(s1, s4)` is now `[c1, c2, c3, c4_re]`,
386
+ doesn't match its frozen `[c1, c2, c3]` → `B.cancel(...)`. Construct `EpochSession` **C** with
387
+ the same spec but checkpoints `[c1, c2, c3, c4_re]`.
388
+
389
+ 5. **`EpochSession` C reuses the long-lived c1..c3 `CheckpointProver` instances and the fresh
390
+ c4_re.** Only c4_re's witnesses are regenerated; the top-tree is recomputed. The chonk cache
391
+ survived the reorg because no epoch in this range has expired yet.
392
+
393
+
394
+ ### Partial request dedups against a running full `EpochSession`
395
+
396
+ The operator calls `startProof(N)` while the full `EpochSession` for epoch N is running with
397
+ c1..c4. Current canonical slot range is `(s1, s4)`, so the partial's computed spec is
398
+ `{partial, N, s1, s4}` — its `fromSlot`/`toSlot` exactly match the running full `EpochSession`'s. `startProof`
399
+ detects this and awaits the existing full instead of opening a duplicate: no partial
400
+ `EpochSession` is created and no second `TopTreeJob` is built. The caller simply blocks on the
401
+ full session's result and the epoch is proven once.
402
+
403
+ ### True partial proof
404
+
405
+ The operator calls `startProof(N)` when only c1, c2 are canonical (epoch incomplete).
406
+ `fromSlot` is the epoch's first slot; `toSlot` is `s2` (the last canonical slot).
407
+ Partial `EpochSession` created with spec `{partial, N, firstSlotOfEpoch(N), s2}` and
408
+ checkpoints `[c1, c2]`.
409
+
410
+ When c3 later arrives in slot s3, the partial is **not** invalidated — c3's slot is
411
+ outside its range. If c2 is then pruned, the partial **is** invalidated (canonical
412
+ content for the same slot range is now just `[c1]`) and recreated with the same
413
+ spec but checkpoints `[c1]`. If c2 re-adds, the partial is invalidated again and
414
+ recreated with `[c1, c2]`.
415
+
416
+ ## Design rationale
417
+
418
+ ### Why slot-based specs (not checkpoint-based)?
419
+
420
+ A spec like "prove checkpoints 7..10" is invalidated by any reorg that renumbers
421
+ those checkpoints. A spec like "prove slots 350..399" survives renumbering — the
422
+ slot range is determined by epoch math and L1 constants, not by which checkpoints
423
+ happen to be canonical at the moment. Reconciliation preserves the slot range
424
+ across cancel-and-recreate cycles.
425
+
426
+ ### Why does every `EpochSession` start at the epoch's first slot?
427
+
428
+ The L1 rollup contract validates that every submitted proof extends from the previous
429
+ proven tip — the `fromCheckpoint` of any submission must be the checkpoint immediately
430
+ after the current L1 proven head. Starting a partial `EpochSession` at a later slot would
431
+ mean the partial's `fromCheckpoint` lies past the proven tip, which the contract
432
+ rejects. Fixing `fromSlot` to `firstSlotOfEpoch(N)` for both kinds means partials and
433
+ fulls always share the same starting point; they differ only in `toSlot` and in the
434
+ submission decision.
435
+
436
+ ### Why does a publishing service own L1 submission instead of the `EpochSession`?
437
+
438
+ Concentrating L1 submission gives us three properties for free that were awkward
439
+ or impossible when each `EpochSession` called the publisher directly:
440
+
441
+ 1. **Atomic same-epoch dedup.** Multiple candidates for the same epoch (full +
442
+ partial, or partial-then-full as canonical content extends) can be in flight
443
+ at once; the service picks the winner under the serial drain so only one L1
444
+ tx is ever sent for the longer candidate.
445
+ 2. **One source of truth for the proven tip.** Reading the proven block number
446
+ inside the drain means the eligibility check and the publish that follows are
447
+ guaranteed to use the same value. `EpochSession`s can't race each other on stale
448
+ reads.
449
+ 3. **Per-candidate deadline and retry.** The service owns expiry timers and the
450
+ `publisherFactory.create()` retry loop. `EpochSession`s don't need to know about
451
+ either — they just await the outcome.
452
+
453
+ ### Why is the chonk cache keyed by tx hash and released on finality?
454
+
455
+ Chonk-verifier proofs are tx-scoped: they prove a transaction's chonk circuit is
456
+ valid, independently of which block or epoch the tx lands in. A tx that gets
457
+ reorged out of one block and re-mined into another should not need to be re-proved.
458
+ Keying by tx hash makes the cache survive any reorg up to finality; releasing on
459
+ finality means we don't grow the cache indefinitely while still keeping every
460
+ reorg-relevant proof.
461
+
462
+ ### Why is a pruned `CheckpointProver` removed rather than kept for a possible re-add?
463
+
464
+ A prover's sub-tree work forks world-state per block, and an L1 prune of a base block faults
465
+ those in-flight reads — so the work cannot survive the prune, and there is nothing to preserve
466
+ by keeping the prover around. Removing it on prune and rebuilding on re-add is therefore both
467
+ correct and simpler; the expensive block-rollup proofs are still reused when content re-appears,
468
+ because the proving broker is content-addressed independently of the prover's lifetime.
469
+
470
+ ## Configuration
471
+
472
+ | Env var | Description |
473
+ |---|---|
474
+ | `PROVER_NODE_POLLING_INTERVAL_MS` | Polling interval for the L2BlockStream and the SessionManager periodic tick. Default 1000 ms. |
475
+ | `PROVER_NODE_MAX_PENDING_JOBS` | Cap on the number of non-terminal `EpochSession`s (full + partial). When at limit, reconcile defers opening new full `EpochSession`s; explicit `startProof` calls throw. |
476
+ | `PROVER_NODE_EPOCH_PROVING_DELAY_MS` | Optional sleep at the start of each `EpochSession`, before the TopTreeJob is constructed. Used in tests to give late events time to land. |
477
+ | `TX_GATHERING_TIMEOUT_MS` | Per-block tx gather deadline used by each `CheckpointProver`. |
478
+ | `PROVER_NODE_FAILED_EPOCH_STORE` | If set, failed `EpochSession`s upload their proving data (every `CheckpointProver`'s txs + register-time data, regardless of sub-tree completion) to this file store. |
479
+ | `PROVER_NODE_DISABLE_PROOF_PUBLISH` | If true, the publishing service runs `analyzeEpochProofSubmission` (estimates L1 fees) instead of actually submitting. |
480
+
481
+ ## Failure handling and observability
482
+
483
+ Loggers:
484
+
485
+ - `prover-node` — `ProverNode` itself (event dispatch, lifecycle).
486
+ - `prover-node:session-manager` — reconcile decisions, `EpochSession` opens / drops, tick.
487
+ - `prover-node:epoch-session` — per-`EpochSession` lifecycle (`Created EpochSession`,
488
+ `Top-tree proof ready`, `Submitted proof for epoch N`, etc.).
489
+ - `prover-node:proof-publishing-service` — candidate submit / withdraw / expire,
490
+ drain, publish attempts, transient acquire retries.
491
+ - `prover-node:l1-tx-publisher` — the per-publish `ProverNodePublisher`'s L1 work.
492
+ - `prover-node:checkpoint-store` — content-key collisions, reap decisions.
493
+ - `prover-node:checkpoint-prover` — sub-tree pipeline (gather, block processing).
494
+ - `prover-client:chonk-cache` — chonk-verifier cache enqueue / release events.
495
+
496
+ On `failed` exit, `SessionManager.runSession` invokes the `onSessionFailed` callback
497
+ the manager was constructed with. `ProverNode` wires this to `tryUploadSessionFailure`,
498
+ which calls `SessionManager.buildSessionProvingData(session)` to walk every `CheckpointProver`
499
+ referenced by the `EpochSession` and assemble an `EpochProvingJobData` snapshot — including
500
+ every `CheckpointProver`'s txs and register-time data even if its sub-tree never reached
501
+ `isCompleted()`. This snapshot is what `uploadEpochProofFailure` ships to the
502
+ configured file store along with a world-state + archiver backup, so the failure
503
+ can be reproduced offline via `rerunEpochProvingJob`.
504
+
505
+ Metrics emitted by `EpochSession`s:
506
+
507
+ - `aztec.prover_node.execution_duration` — wall-clock time from `EpochSession` start to terminal.
508
+ - `aztec.prover_node.job_duration` — same, in seconds.
509
+ - `aztec.prover_node.job_checkpoints` / `_blocks` / `_transactions` — sizes of the
510
+ proven range.
511
+ - `aztec.prover_node.block_processing_duration` /
512
+ `aztec.prover_node.checkpoint_processing_duration` — sub-tree breakdown.
@@ -25,7 +25,7 @@ import { deserializeEpochProvingJobData } from '../job/epoch-proving-job-data.js
25
25
  }, log, {
26
26
  ...config,
27
27
  ...metadata,
28
- snapshotsUrl: location
28
+ fileStore
29
29
  });
30
30
  const dataPath = urlJoin(location, 'data.bin');
31
31
  const localPath = config.jobDataDownloadPath;
@@ -1,12 +1,13 @@
1
1
  import type { L1ContractsConfig } from '@aztec/ethereum/config';
2
2
  import type { Logger } from '@aztec/foundation/log';
3
- import type { DataStoreConfig } from '@aztec/kv-store/config';
4
3
  import { type ProverClientConfig } from '@aztec/prover-client';
5
4
  import { ProverBrokerConfig } from '@aztec/prover-client/broker';
5
+ import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
6
+ import type { GenesisData } from '@aztec/stdlib/world-state';
6
7
  /**
7
8
  * Given a local folder where `downloadEpochProvingJob` was called, creates a new archiver and world state
8
- * using the state snapshots, and creates a new epoch proving job to prove the downloaded proving job.
9
+ * using the state snapshots, and creates a new epoch proving session to prove the downloaded proving job.
9
10
  * Proving is done with a local proving broker and agents as specified by the config.
10
11
  */
11
- export declare function rerunEpochProvingJob(localPath: string, log: Logger, config: DataStoreConfig & ProverBrokerConfig & ProverClientConfig & Pick<L1ContractsConfig, 'aztecEpochDuration'>): Promise<"awaiting-prover" | "completed" | "failed" | "initialized" | "processing" | "publishing-proof" | "reorg" | "stopped" | "timed-out">;
12
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVydW4tZXBvY2gtcHJvdmluZy1qb2IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hY3Rpb25zL3JlcnVuLWVwb2NoLXByb3Zpbmctam9iLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDaEUsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDcEQsT0FBTyxLQUFLLEVBQUUsZUFBZSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDOUQsT0FBTyxFQUFFLEtBQUssa0JBQWtCLEVBQXNCLE1BQU0sc0JBQXNCLENBQUM7QUFDbkYsT0FBTyxFQUFFLGtCQUFrQixFQUErQixNQUFNLDZCQUE2QixDQUFDO0FBVzlGOzs7O0dBSUc7QUFDSCx3QkFBc0Isb0JBQW9CLENBQ3hDLFNBQVMsRUFBRSxNQUFNLEVBQ2pCLEdBQUcsRUFBRSxNQUFNLEVBQ1gsTUFBTSxFQUFFLGVBQWUsR0FBRyxrQkFBa0IsR0FBRyxrQkFBa0IsR0FBRyxJQUFJLENBQUMsaUJBQWlCLEVBQUUsb0JBQW9CLENBQUMsK0lBc0NsSCJ9
12
+ export declare function rerunEpochProvingJob(localPath: string, log: Logger, config: DataStoreConfig & ProverBrokerConfig & ProverClientConfig & Pick<L1ContractsConfig, 'aztecEpochDuration'>, genesis?: GenesisData): Promise<"awaiting-checkpoints" | "awaiting-predecessor" | "awaiting-root" | "cancelled" | "completed" | "failed" | "initialized" | "publishing-proof" | "stopped" | "superseded" | "timed-out">;
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVydW4tZXBvY2gtcHJvdmluZy1qb2IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hY3Rpb25zL3JlcnVuLWVwb2NoLXByb3Zpbmctam9iLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFaEUsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFcEQsT0FBTyxFQUFFLEtBQUssa0JBQWtCLEVBQXNCLE1BQU0sc0JBQXNCLENBQUM7QUFDbkYsT0FBTyxFQUFFLGtCQUFrQixFQUErQixNQUFNLDZCQUE2QixDQUFDO0FBTzlGLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRzlELE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBVzdEOzs7O0dBSUc7QUFDSCx3QkFBc0Isb0JBQW9CLENBQ3hDLFNBQVMsRUFBRSxNQUFNLEVBQ2pCLEdBQUcsRUFBRSxNQUFNLEVBQ1gsTUFBTSxFQUFFLGVBQWUsR0FBRyxrQkFBa0IsR0FBRyxrQkFBa0IsR0FBRyxJQUFJLENBQUMsaUJBQWlCLEVBQUUsb0JBQW9CLENBQUMsRUFDakgsT0FBTyxDQUFDLEVBQUUsV0FBVyxtTUF5RnRCIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"rerun-epoch-proving-job.d.ts","sourceRoot":"","sources":["../../src/actions/rerun-epoch-proving-job.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAA+B,MAAM,6BAA6B,CAAC;AAW9F;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,eAAe,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,IAAI,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,+IAsClH"}
1
+ {"version":3,"file":"rerun-epoch-proving-job.d.ts","sourceRoot":"","sources":["../../src/actions/rerun-epoch-proving-job.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAA+B,MAAM,6BAA6B,CAAC;AAO9F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAW7D;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,eAAe,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,IAAI,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,EACjH,OAAO,CAAC,EAAE,WAAW,mMAyFtB"}