@aztec/prover-node 6.0.0-nightly.20260724 → 6.0.0-nightly.20260726
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.
- package/README.md +148 -94
- package/dest/actions/rerun-epoch-proving-job.d.ts +11 -2
- package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
- package/dest/actions/rerun-epoch-proving-job.js +103 -44
- package/dest/checkpoint-store.d.ts +47 -39
- package/dest/checkpoint-store.d.ts.map +1 -1
- package/dest/checkpoint-store.js +71 -79
- package/dest/job/checkpoint-prover.d.ts +37 -17
- package/dest/job/checkpoint-prover.d.ts.map +1 -1
- package/dest/job/checkpoint-prover.js +51 -41
- package/dest/job/epoch-session.d.ts +17 -3
- package/dest/job/epoch-session.d.ts.map +1 -1
- package/dest/job/epoch-session.js +28 -4
- package/dest/job/top-tree-job.d.ts +2 -2
- package/dest/job/top-tree-job.d.ts.map +1 -1
- package/dest/job/top-tree-job.js +4 -4
- package/dest/metrics.js +1 -1
- package/dest/prover-node.d.ts +34 -7
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +81 -22
- package/dest/session-manager.d.ts +17 -17
- package/dest/session-manager.d.ts.map +1 -1
- package/dest/session-manager.js +77 -43
- package/package.json +24 -24
- package/src/actions/rerun-epoch-proving-job.ts +138 -67
- package/src/checkpoint-store.ts +82 -88
- package/src/job/checkpoint-prover.ts +72 -44
- package/src/job/epoch-session.ts +30 -4
- package/src/job/top-tree-job.ts +4 -4
- package/src/metrics.ts +1 -1
- package/src/prover-node.ts +97 -23
- package/src/session-manager.ts +83 -46
package/README.md
CHANGED
|
@@ -35,7 +35,6 @@ flowchart TB
|
|
|
35
35
|
SessionManager --> EpochTicker[(periodic tick)]
|
|
36
36
|
SessionManager --> FullSessions[(fullSessions)]
|
|
37
37
|
SessionManager --> PartialSessions[(partialSessions)]
|
|
38
|
-
CheckpointStore --> SlotWatcher
|
|
39
38
|
FullSessions -.referenced checkpoints.-> CheckpointStore
|
|
40
39
|
PartialSessions -.referenced checkpoints.-> CheckpointStore
|
|
41
40
|
FullSessions --> TopTreeJob
|
|
@@ -49,23 +48,30 @@ The prover-node splits responsibility between four classes:
|
|
|
49
48
|
|
|
50
49
|
- **`ProverNode`** — owns the long-lived collections, wires the L2BlockStream, and
|
|
51
50
|
translates each chain event into a single method call on the `SessionManager` or
|
|
52
|
-
`ProofPublishingService`. It also performs
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
`ProofPublishingService`. It also performs side effects that don't belong on an
|
|
52
|
+
`EpochSession`: registering new checkpoints with the store per event, and sweeping expired
|
|
53
|
+
epochs out of the cache and the store on a periodic ticker (see below). It uploads post-mortem
|
|
54
|
+
snapshots on failure at two levels: per checkpoint when a `CheckpointProver` fails (its `onFailed`
|
|
55
|
+
callback), and per session when a full session fails on its own account (`onSessionFailed` /
|
|
56
|
+
`EpochSession.hasFailed()`).
|
|
56
57
|
- **`CheckpointStore`** — a registry of `CheckpointProver` instances keyed by
|
|
57
58
|
`(checkpointNumber, slot, archiveRoot)`. Each `CheckpointProver` runs its own sub-tree pipeline
|
|
58
59
|
(tx gather → block processing → block-rollup proofs), starting eagerly the moment a
|
|
59
|
-
checkpoint is registered. The store
|
|
60
|
-
|
|
60
|
+
checkpoint is registered. The store holds the canonical checkpoint content that
|
|
61
|
+
`EpochSession`s query when assembling their subsets; a prune cancels and removes the affected
|
|
62
|
+
provers, so every prover in the store is canonical.
|
|
61
63
|
- **`SessionManager`** — owns every live `EpochSession`, the serial reconcile queue,
|
|
62
64
|
the periodic tick, and all `EpochSession` lifecycle decisions. `ProverNode` calls into it
|
|
63
65
|
via `onCheckpointAdded`, `onPrune`, and `startProof`. Every trigger it receives is
|
|
64
66
|
translated into a `reconcile(trigger)` call, a single idempotent function that walks
|
|
65
67
|
all `EpochSession`s, cancels any whose canonical content has shifted, re-creates them with
|
|
66
68
|
the new content, and opens fresh full `EpochSession`s for any epoch that has become provable.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
It never builds a session over a **failed** `CheckpointProver` (one whose block proofs rejected —
|
|
70
|
+
a sub-tree fault or a prune-induced fork fault): a session over it could only fail, so the epoch is
|
|
71
|
+
cheaply skipped until a prune/re-add installs a fresh prover in its place, at which point a rebuilt
|
|
72
|
+
session reuses every already-completed sub-proof from the content-addressed broker. Reconcile runs on
|
|
73
|
+
a `SerialQueue` (from `@aztec/foundation/queue`), so two concurrent triggers can never interleave on an
|
|
74
|
+
`await` and race on the `EpochSession` maps.
|
|
69
75
|
- **`ProofPublishingService`** — central owner of L1 proof submission. `EpochSession`s hand
|
|
70
76
|
their top-tree proofs to the service as `PublishCandidate`s; the service serialises
|
|
71
77
|
one publish at a time against a freshly-created `ProverNodePublisher`, gates eligibility
|
|
@@ -81,43 +87,38 @@ A `CheckpointProver` is content-addressed by `(checkpoint.number, slot, archiveR
|
|
|
81
87
|
where `archiveRoot` is the checkpoint's own archive root (its post-state). Keying on the
|
|
82
88
|
post-state makes the identity precise: two checkpoints are "the same" iff they produce
|
|
83
89
|
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
|
|
85
|
-
|
|
90
|
+
with different content, yields a different archive root and a distinct `CheckpointProver`. A prune
|
|
91
|
+
cancels and removes a prover — its sub-tree work forks world-state per block and cannot survive the
|
|
92
|
+
prune — so a re-add, even of identical content, constructs a fresh `CheckpointProver` (block-rollup
|
|
93
|
+
jobs already completed in the proving broker are still reused, as they are content-addressed).
|
|
86
94
|
|
|
87
95
|
```mermaid
|
|
88
96
|
stateDiagram-v2
|
|
89
97
|
[*] --> Created
|
|
90
98
|
Created --> Proving: gather + execute
|
|
91
99
|
Proving --> Proven: sub-tree resolves blockProofs
|
|
92
|
-
Proving --> Cancelled: cancel()
|
|
100
|
+
Proving --> Cancelled: cancel() (prune / shutdown / epoch-session error)
|
|
101
|
+
Proven --> Cancelled: cancel() (prune / reap)
|
|
93
102
|
Proven --> Reaped: reapExpired(epoch)
|
|
94
103
|
Cancelled --> [*]
|
|
95
|
-
|
|
96
|
-
state "Pruned (side)" as Pruned
|
|
97
|
-
Proving --> Pruned: markPruned()
|
|
98
|
-
Pruned --> Proving: markCanonical()
|
|
99
|
-
Proven --> Pruned: markPruned()
|
|
100
|
-
Pruned --> Reaped: SlotWatcher (slot < syncedSlot)
|
|
101
104
|
```
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
###
|
|
110
|
-
|
|
111
|
-
- **Pruned**:
|
|
112
|
-
`
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
- **Cancelled**: removed immediately by whichever path called `cancel()` (store
|
|
120
|
-
shutdown, prune past-slot, `EpochSession` error).
|
|
106
|
+
A prune **cancels and removes** the affected `CheckpointProver`s from the store: a prover's
|
|
107
|
+
sub-tree work forks world-state per block, and an L1 prune of a base block faults those reads,
|
|
108
|
+
so there is nothing to preserve across a reorg. A re-add — even of identical content — therefore
|
|
109
|
+
constructs a fresh `CheckpointProver`. `EpochSession`s ask the store for the checkpoints in a slot
|
|
110
|
+
range; every prover in the store is canonical.
|
|
111
|
+
|
|
112
|
+
### Removal rules
|
|
113
|
+
|
|
114
|
+
- **Pruned**: `CheckpointStore.cancelAndRemoveAboveBlock(target)` cancels and removes every
|
|
115
|
+
`CheckpointProver` whose last block is above the prune target, the moment a `chain-pruned`
|
|
116
|
+
event arrives.
|
|
117
|
+
- **Expired**: `CheckpointStore.reapExpired(expiredEpoch)` drops any `CheckpointProver` whose
|
|
118
|
+
epoch is at or below the supplied expired epoch. Once an epoch's proof-submission window has
|
|
119
|
+
closed, its proof can no longer be accepted on L1, so the `CheckpointProver` is no longer needed.
|
|
120
|
+
- **Shutdown**: `CheckpointStore.stop()` cancels every remaining prover and awaits its teardown
|
|
121
|
+
(including teardowns still in flight for provers already removed by a prune or reap).
|
|
121
122
|
|
|
122
123
|
### Eager tx gathering
|
|
123
124
|
|
|
@@ -155,9 +156,9 @@ stateDiagram-v2
|
|
|
155
156
|
awaiting_root --> publishing_proof: epoch proof ready, submit to L1
|
|
156
157
|
publishing_proof --> completed: publish succeeds
|
|
157
158
|
publishing_proof --> superseded: longer same-epoch candidate wins
|
|
158
|
-
publishing_proof --> failed: L1 submission errored
|
|
159
|
-
awaiting_checkpoints -->
|
|
160
|
-
awaiting_root --> failed: top-tree prove errored
|
|
159
|
+
publishing_proof --> failed: L1 submission errored (provers healthy)
|
|
160
|
+
awaiting_checkpoints --> stopped: a checkpoint prover failed
|
|
161
|
+
awaiting_root --> failed: top-tree prove errored (provers healthy)
|
|
161
162
|
initialized --> timed_out: deadline
|
|
162
163
|
awaiting_checkpoints --> timed_out: deadline (EpochSession or candidate)
|
|
163
164
|
awaiting_root --> timed_out: deadline (EpochSession or candidate)
|
|
@@ -167,9 +168,22 @@ stateDiagram-v2
|
|
|
167
168
|
superseded --> [*]
|
|
168
169
|
cancelled --> [*]
|
|
169
170
|
timed_out --> [*]
|
|
171
|
+
stopped --> [*]
|
|
170
172
|
failed --> [*]
|
|
171
173
|
```
|
|
172
174
|
|
|
175
|
+
A fault settles the `EpochSession` in one of two terminal states:
|
|
176
|
+
|
|
177
|
+
- **`stopped`** — a `CheckpointProver` under the session failed (its block proofs rejected). This may
|
|
178
|
+
be a prune-induced fork fault, so it is *not* a verdict on the epoch and is *not* uploaded. The
|
|
179
|
+
reconciler drops the session and, guarded by `isFailed()`, does not rebuild over the failed prover;
|
|
180
|
+
a prune/re-add installs a fresh prover and the epoch is retried.
|
|
181
|
+
- **`failed`** — the session's own work (top-tree prove or L1 submit) failed while *every* checkpoint
|
|
182
|
+
prover succeeded. Because healthy provers rule out a prune, this is a genuine, race-free failure
|
|
183
|
+
(`EpochSession.hasFailed()`). The reconciler retains such a full session (so the tick does not
|
|
184
|
+
re-prove a deterministically-failing epoch) and uploads a post-mortem exactly once. No prune/fault
|
|
185
|
+
classification is needed — the two states carry it.
|
|
186
|
+
|
|
173
187
|
The non-terminal states track the window between `start()` and the L1 submission:
|
|
174
188
|
|
|
175
189
|
- `awaiting-checkpoints` — a `TopTreeJob` is awaiting each checkpoint's sub-tree result
|
|
@@ -288,8 +302,8 @@ sequenceDiagram
|
|
|
288
302
|
alt content key new
|
|
289
303
|
CS->>CP: new CheckpointProver(args)
|
|
290
304
|
CP->>CP: eager gather + sub-tree start
|
|
291
|
-
else content key
|
|
292
|
-
CS->>CP:
|
|
305
|
+
else content key already live
|
|
306
|
+
CS->>CP: reuse existing prover
|
|
293
307
|
end
|
|
294
308
|
PN->>SM: onCheckpointAdded(epoch)
|
|
295
309
|
SM->>SM: queue reconcile({kind:'checkpoint', epoch})
|
|
@@ -306,9 +320,9 @@ sequenceDiagram
|
|
|
306
320
|
participant CS as CheckpointStore
|
|
307
321
|
participant SM as SessionManager
|
|
308
322
|
|
|
309
|
-
L2->>PN: chain-pruned{
|
|
310
|
-
PN->>CS:
|
|
311
|
-
CS->>CS:
|
|
323
|
+
L2->>PN: chain-pruned{block}
|
|
324
|
+
PN->>CS: cancelAndRemoveAboveBlock(prunedToBlock)
|
|
325
|
+
CS->>CS: cancel + remove every CheckpointProver whose last block is above the target
|
|
312
326
|
PN->>SM: onPrune(affectedEpochs)
|
|
313
327
|
SM->>SM: queue reconcile({kind:'prune', affectedEpochs})
|
|
314
328
|
SM->>SM: walk EpochSessions, cancel-and-recreate those with shifted content
|
|
@@ -328,73 +342,84 @@ sequenceDiagram
|
|
|
328
342
|
PS->>PS: drain reads proven afresh, re-checks eligibility
|
|
329
343
|
```
|
|
330
344
|
|
|
331
|
-
###
|
|
345
|
+
### Periodic expiry sweep
|
|
332
346
|
|
|
333
347
|
```mermaid
|
|
334
348
|
sequenceDiagram
|
|
335
|
-
participant
|
|
349
|
+
participant T as expiryTicker (RunningPromise)
|
|
336
350
|
participant PN as ProverNode
|
|
351
|
+
participant L2 as L2BlockStream
|
|
337
352
|
participant CC as ChonkCache
|
|
338
353
|
participant CS as CheckpointStore
|
|
339
354
|
|
|
340
|
-
|
|
355
|
+
T->>PN: checkEpochExpiry() (every poll interval)
|
|
341
356
|
PN->>L2: getSyncedL2SlotNumber()
|
|
342
357
|
PN->>PN: latestEpoch = getEpochAtSlot(latestSlot)
|
|
343
358
|
PN->>PN: newlyExpiredUpTo = latestEpoch - (proofSubmissionEpochs + 1)
|
|
344
359
|
loop for each newly-expired epoch
|
|
345
|
-
PN->>L2:
|
|
360
|
+
PN->>L2: getBlocks({epoch, onlyCheckpointed})
|
|
346
361
|
PN->>CC: releaseForBlocks(blocks)
|
|
347
362
|
PN->>CS: reapExpired(epoch)
|
|
348
363
|
end
|
|
349
364
|
```
|
|
350
365
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
`
|
|
357
|
-
|
|
366
|
+
The sweep is driven solely by the `expiryTicker` (a `RunningPromise` armed in `start()`), which
|
|
367
|
+
calls `checkEpochExpiry()` every poll interval whether or not block-stream events arrive. It is
|
|
368
|
+
deliberately **not** run from `handleBlockStreamEvent` — expiry is a background sweep keyed off the
|
|
369
|
+
archiver's synced slot, not part of event processing — and because a `RunningPromise` never overlaps
|
|
370
|
+
its own runs, no two sweeps can race. An epoch `E` is expired once the chain reaches the start of epoch
|
|
371
|
+
`E + proofSubmissionEpochs + 1` — the deadline beyond which an L1 submission for `E` would be rejected.
|
|
372
|
+
Expiry only releases the chonk cache and reaps `E`'s provers.
|
|
373
|
+
A monotonic high-water mark (`lastExpiredEpoch`) makes the sweep cheap: it only
|
|
374
|
+
advances after a sweep completes and never revisits an epoch. It is seeded at `start()` from the last
|
|
375
|
+
fully-proven epoch (`resolveLastFullyProvenEpoch`), so on a restart we never re-sweep epochs that already
|
|
376
|
+
reached L1.
|
|
358
377
|
|
|
359
378
|
### Periodic tick
|
|
360
379
|
|
|
361
380
|
`SessionManager.start()` arms a `RunningPromise` that fires
|
|
362
381
|
`reconcile({ kind: 'tick' })` every `tickIntervalMs`. The tick picks up epochs that
|
|
363
382
|
became complete by time alone (no fresh checkpoint event) and advances to the
|
|
364
|
-
next unproven epoch once the previous one lands on L1.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
383
|
+
next unproven epoch once the previous one lands on L1. The tick is not gated by any per-epoch
|
|
384
|
+
bookkeeping; two things keep it from re-proving a doomed epoch:
|
|
385
|
+
|
|
386
|
+
- `openFullSessionIfReady` refuses to build a session when any `CheckpointProver` in the set has
|
|
387
|
+
**failed** (`isFailed()`). So a prover fault (possibly a prune) skips the epoch cheaply each tick —
|
|
388
|
+
no session, no proving — until a prune/re-add installs a fresh prover.
|
|
389
|
+
- A session that failed on its own account (`hasFailed()` — top-tree/submit failed with every prover
|
|
390
|
+
healthy) is **retained** by `recreateInvalidSessions` rather than deleted, so the tick's
|
|
391
|
+
`fullSessions.has(epoch)` check skips it. It is replaced only when its canonical content changes (a
|
|
392
|
+
re-add), so a deterministically-failing epoch is not re-proved every tick.
|
|
393
|
+
|
|
394
|
+
Transient blockers (max-pending-jobs reached, archiver still indexing) create no session and no failed
|
|
395
|
+
prover, so the next tick simply tries again.
|
|
369
396
|
|
|
370
397
|
## Walkthroughs
|
|
371
398
|
|
|
372
399
|
### checkpoint-added → prune → checkpoint-added (reorg resilience)
|
|
373
400
|
|
|
374
|
-
State: epoch N has checkpoints c1..c4
|
|
375
|
-
|
|
376
|
-
checkpoints `[c1, c2, c3, c4]`.
|
|
401
|
+
State: epoch N has checkpoints c1..c4 (slots s1..s4). `fullSessions[N]` holds `EpochSession`
|
|
402
|
+
**A** with spec `{kind:'full', N, fromSlot:s1, toSlot:s4}`, referencing `[c1, c2, c3, c4]`.
|
|
377
403
|
|
|
378
|
-
1. **chain-pruned arrives, target c3.**
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
changed')`. Epoch N still complete on L1 → reconcile constructs
|
|
382
|
-
the same spec `{full, N, s1, s4}` but checkpoints `[c1, c2, c3]`.
|
|
404
|
+
1. **chain-pruned arrives, target c3.** The store cancels and removes c4 (its last block is
|
|
405
|
+
above the prune target). Reconcile fires: `EpochSession` A's frozen set `[c1, c2, c3, c4]`
|
|
406
|
+
includes the now-cancelled c4, so it no longer matches the store's `[c1, c2, c3]` →
|
|
407
|
+
`A.cancel('canonical content changed')`. Epoch N still complete on L1 → reconcile constructs
|
|
408
|
+
`EpochSession` **B** with the same spec `{full, N, s1, s4}` but checkpoints `[c1, c2, c3]`.
|
|
383
409
|
|
|
384
410
|
2. **`EpochSession` B starts top-tree proving over [c1, c2, c3].**
|
|
385
411
|
|
|
386
|
-
3. **chain-checkpointed arrives, target c4_re (same content key as old c4).** The
|
|
387
|
-
|
|
388
|
-
and
|
|
389
|
-
|
|
390
|
-
root and so get a fresh `CheckpointProver` instead.)
|
|
412
|
+
3. **chain-checkpointed arrives, target c4_re (same content key as old c4).** The old c4
|
|
413
|
+
prover was removed on the prune, so the store constructs a **fresh** `CheckpointProver` for
|
|
414
|
+
c4_re and starts its sub-tree work from scratch. (Its block-rollup jobs are content-addressed
|
|
415
|
+
in the proving broker, so any the old c4 already completed are reused rather than re-proved.)
|
|
391
416
|
|
|
392
|
-
4. **Reconcile fires.** `EpochSession` B's
|
|
393
|
-
|
|
394
|
-
|
|
417
|
+
4. **Reconcile fires.** `EpochSession` B's content for `(s1, s4)` is now `[c1, c2, c3, c4_re]`,
|
|
418
|
+
doesn't match its frozen `[c1, c2, c3]` → `B.cancel(...)`. Construct `EpochSession` **C** with
|
|
419
|
+
the same spec but checkpoints `[c1, c2, c3, c4_re]`.
|
|
395
420
|
|
|
396
|
-
5. **`EpochSession` C reuses the long-lived c1..
|
|
397
|
-
|
|
421
|
+
5. **`EpochSession` C reuses the long-lived c1..c3 `CheckpointProver` instances and the fresh
|
|
422
|
+
c4_re.** Only c4_re's witnesses are regenerated; the top-tree is recomputed. The chonk cache
|
|
398
423
|
survived the reorg because no epoch in this range has expired yet.
|
|
399
424
|
|
|
400
425
|
|
|
@@ -466,23 +491,37 @@ Keying by tx hash makes the cache survive any reorg up to finality; releasing on
|
|
|
466
491
|
finality means we don't grow the cache indefinitely while still keeping every
|
|
467
492
|
reorg-relevant proof.
|
|
468
493
|
|
|
469
|
-
### Why
|
|
494
|
+
### Why is a pruned `CheckpointProver` removed rather than kept for a possible re-add?
|
|
495
|
+
|
|
496
|
+
A prover's sub-tree work forks world-state per block, and an L1 prune of a base block faults
|
|
497
|
+
those in-flight reads — so the work cannot survive the prune, and there is nothing to preserve
|
|
498
|
+
by keeping the prover around. Removing it on prune and rebuilding on re-add is therefore both
|
|
499
|
+
correct and simpler; the expensive block-rollup proofs are still reused when content re-appears,
|
|
500
|
+
because the proving broker is content-addressed independently of the prover's lifetime.
|
|
501
|
+
|
|
502
|
+
### How is a genuine failure told apart from a prune, without a racy classification?
|
|
470
503
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
504
|
+
Knowledge of an L1 reorg reaches the prover-node on two causally unordered channels: the control
|
|
505
|
+
plane (`chain-pruned` → `onPrune`) and the data plane (world-state unwinds, and an in-flight fork
|
|
506
|
+
read faults inside a `CheckpointProver` mid-proof). If the `EpochSession` reacted to a fault by asking
|
|
507
|
+
"was this a prune or a genuine failure?", every answer would sample control-plane state that lags the
|
|
508
|
+
data-plane unwind — racy by construction. We remove the question by pushing the fact down to where it
|
|
509
|
+
is unambiguous: a `CheckpointProver` records `isFailed()` when *its* block proofs reject. The session
|
|
510
|
+
then reads a clean signal: if any prover failed, the fault might be a prune (`stopped`) — don't upload,
|
|
511
|
+
don't rebuild over it, recover on re-add; if *no* prover failed yet the session's own top-tree/submit
|
|
512
|
+
work failed, that is definitively **not** a prune (`failed`) — a genuine, race-free failure that is
|
|
513
|
+
retained (so a deterministic failure isn't re-proved every tick) and uploaded once. No control-plane
|
|
514
|
+
sampling, no timing race — the two prover-level and session-level facts carry the whole decision.
|
|
476
515
|
|
|
477
516
|
## Configuration
|
|
478
517
|
|
|
479
518
|
| Env var | Description |
|
|
480
519
|
|---|---|
|
|
481
|
-
| `PROVER_NODE_POLLING_INTERVAL_MS` | Polling interval for the L2BlockStream
|
|
520
|
+
| `PROVER_NODE_POLLING_INTERVAL_MS` | Polling interval for the L2BlockStream and the SessionManager periodic tick. Default 1000 ms. |
|
|
482
521
|
| `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. |
|
|
483
522
|
| `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. |
|
|
484
523
|
| `TX_GATHERING_TIMEOUT_MS` | Per-block tx gather deadline used by each `CheckpointProver`. |
|
|
485
|
-
| `PROVER_NODE_FAILED_EPOCH_STORE` | If set,
|
|
524
|
+
| `PROVER_NODE_FAILED_EPOCH_STORE` | If set, a full session that fails on its own account (top-tree/submit, provers healthy) uploads its proving data (every `CheckpointProver`'s txs + register-time data, regardless of sub-tree completion) to this file store. |
|
|
486
525
|
| `PROVER_NODE_DISABLE_PROOF_PUBLISH` | If true, the publishing service runs `analyzeEpochProofSubmission` (estimates L1 fees) instead of actually submitting. |
|
|
487
526
|
|
|
488
527
|
## Failure handling and observability
|
|
@@ -500,14 +539,29 @@ Loggers:
|
|
|
500
539
|
- `prover-node:checkpoint-prover` — sub-tree pipeline (gather, block processing).
|
|
501
540
|
- `prover-client:chonk-cache` — chonk-verifier cache enqueue / release events.
|
|
502
541
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
`
|
|
509
|
-
|
|
510
|
-
|
|
542
|
+
Failed proving data is uploaded to the configured file store (`PROVER_NODE_FAILED_EPOCH_STORE`) at two
|
|
543
|
+
levels, and re-proved offline by the matching downloader/rerunner: an epoch job via
|
|
544
|
+
`downloadEpochProvingJob` + `rerunEpochProvingJob`, and a single checkpoint via the same download plus
|
|
545
|
+
`rerunCheckpointProvingJob` (which rebuilds just that checkpoint's sub-tree prover and awaits its block
|
|
546
|
+
proofs — no epoch top-tree or L1 submit). Both upload levels build an `EpochProvingJobData` snapshot with
|
|
547
|
+
`SessionManager.buildProvingData(...)` — every `CheckpointProver`'s txs and register-time data, even if
|
|
548
|
+
its sub-tree never reached `isCompleted()` — and ship it via `uploadEpochProofFailure` alongside a
|
|
549
|
+
world-state + archiver backup:
|
|
550
|
+
|
|
551
|
+
- **Per checkpoint.** When a `CheckpointProver`'s block proofs reject for a non-cancel reason (a sub-tree
|
|
552
|
+
fault or a prune-induced fork fault), it fires its `onFailed` callback and `ProverNode` uploads that one
|
|
553
|
+
checkpoint via `tryUploadCheckpointFailure(prover)`. This fires for prune-induced faults too — that is
|
|
554
|
+
intentional; a prune-caused checkpoint snapshot is harmless, and *not* trying to tell prune from genuine
|
|
555
|
+
failure is exactly what keeps this race-free. A cancelled prover (control-plane prune / shutdown) is not
|
|
556
|
+
a failure and does not upload.
|
|
557
|
+
- **Per session.** When a full session ends in its own genuine failure (`EpochSession.hasFailed()` —
|
|
558
|
+
top-tree/submit failed with every prover healthy), the session manager fires `onSessionFailed` and
|
|
559
|
+
`ProverNode` uploads all the session's checkpoints via `tryUploadEpochFailure(...)`. This is race-free
|
|
560
|
+
(healthy provers rule out a prune) and fires once, since the failed session is retained and never re-run.
|
|
561
|
+
|
|
562
|
+
A `stopped` session (a prover under it failed) does not upload at the session level — its failed checkpoint
|
|
563
|
+
already uploaded itself. Each backup is a full world-state + archiver snapshot, so a prune that faults
|
|
564
|
+
several in-flight provers produces several uploads; this is accepted for the diagnostic value.
|
|
511
565
|
|
|
512
566
|
Metrics emitted by `EpochSession`s:
|
|
513
567
|
|
|
@@ -4,10 +4,19 @@ import { type ProverClientConfig } from '@aztec/prover-client';
|
|
|
4
4
|
import { ProverBrokerConfig } from '@aztec/prover-client/broker';
|
|
5
5
|
import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
|
|
6
6
|
import type { GenesisData } from '@aztec/stdlib/world-state';
|
|
7
|
+
type RerunConfig = DataStoreConfig & ProverBrokerConfig & ProverClientConfig & Pick<L1ContractsConfig, 'aztecEpochDuration'>;
|
|
7
8
|
/**
|
|
8
9
|
* Given a local folder where `downloadEpochProvingJob` was called, creates a new archiver and world state
|
|
9
10
|
* using the state snapshots, and creates a new epoch proving session to prove the downloaded proving job.
|
|
10
11
|
* Proving is done with a local proving broker and agents as specified by the config.
|
|
11
12
|
*/
|
|
12
|
-
export declare function rerunEpochProvingJob(localPath: string, log: Logger, config:
|
|
13
|
-
|
|
13
|
+
export declare function rerunEpochProvingJob(localPath: string, log: Logger, config: RerunConfig, genesis?: GenesisData): Promise<"awaiting-checkpoints" | "awaiting-predecessor" | "awaiting-root" | "cancelled" | "completed" | "failed" | "initialized" | "publishing-proof" | "stopped" | "superseded" | "timed-out">;
|
|
14
|
+
/**
|
|
15
|
+
* Re-proves a single downloaded checkpoint proving job (as uploaded by a `CheckpointProver` failure).
|
|
16
|
+
* Reconstructs just that checkpoint's sub-tree prover from the snapshot and awaits its block proofs — no
|
|
17
|
+
* epoch top-tree, no L1 submission — so a checkpoint-level failure can be reproduced offline in isolation.
|
|
18
|
+
* Returns the block-rollup proof outputs on success; throws if the checkpoint fails to prove again.
|
|
19
|
+
*/
|
|
20
|
+
export declare function rerunCheckpointProvingJob(localPath: string, log: Logger, config: RerunConfig, genesis?: GenesisData): Promise<import("@aztec/stdlib/interfaces/server").PublicInputsAndRecursiveProof<import("@aztec/stdlib/rollup").BlockRollupPublicInputs, 480>[]>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVydW4tZXBvY2gtcHJvdmluZy1qb2IuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hY3Rpb25zL3JlcnVuLWVwb2NoLXByb3Zpbmctam9iLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFaEUsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFcEQsT0FBTyxFQUFFLEtBQUssa0JBQWtCLEVBQXNCLE1BQU0sc0JBQXNCLENBQUM7QUFDbkYsT0FBTyxFQUFFLGtCQUFrQixFQUErQixNQUFNLDZCQUE2QixDQUFDO0FBTzlGLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRzlELE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBVzdELEtBQUssV0FBVyxHQUFHLGVBQWUsR0FDaEMsa0JBQWtCLEdBQ2xCLGtCQUFrQixHQUNsQixJQUFJLENBQUMsaUJBQWlCLEVBQUUsb0JBQW9CLENBQUMsQ0FBQztBQUVoRDs7OztHQUlHO0FBQ0gsd0JBQXNCLG9CQUFvQixDQUFDLFNBQVMsRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsV0FBVyxFQUFFLE9BQU8sQ0FBQyxFQUFFLFdBQVcsbU1Bc0NwSDtBQUVEOzs7OztHQUtHO0FBQ0gsd0JBQXNCLHlCQUF5QixDQUM3QyxTQUFTLEVBQUUsTUFBTSxFQUNqQixHQUFHLEVBQUUsTUFBTSxFQUNYLE1BQU0sRUFBRSxXQUFXLEVBQ25CLE9BQU8sQ0FBQyxFQUFFLFdBQVcsbUpBaUJ0QiJ9
|
|
@@ -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;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,
|
|
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,KAAK,WAAW,GAAG,eAAe,GAChC,kBAAkB,GAClB,kBAAkB,GAClB,IAAI,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,CAAC;AAEhD;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,WAAW,mMAsCpH;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE,WAAW,mJAiBtB"}
|
|
@@ -91,56 +91,19 @@ import { ProverNodeJobMetrics } from '../metrics.js';
|
|
|
91
91
|
hasError: false
|
|
92
92
|
};
|
|
93
93
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const avmSimulator = _ts_add_disposable_resource(env, await AvmSimulatorPool.spawn({
|
|
102
|
-
wsdbIpcPath: worldState.getIpcPath()
|
|
103
|
-
}), true);
|
|
104
|
-
const publicProcessorFactory = new PublicProcessorFactory(createContractDataSource(archiver), avmSimulator, undefined, undefined, log.getBindings());
|
|
94
|
+
const ctx = _ts_add_disposable_resource(env, await createRerunContext(localPath, log, config, genesis), true);
|
|
95
|
+
const { jobData, prover, metrics } = ctx;
|
|
96
|
+
log.info(`Rerunning epoch proving for epoch ${jobData.epochNumber}`);
|
|
97
|
+
const provers = [];
|
|
98
|
+
for(let i = 0; i < jobData.checkpoints.length; i++){
|
|
99
|
+
provers.push(await buildCheckpointProver(ctx, i, log));
|
|
100
|
+
}
|
|
105
101
|
// Local rerun never publishes — stub the service so submit() always resolves 'published'
|
|
106
102
|
// and withdraw is a no-op.
|
|
107
103
|
const publishingService = {
|
|
108
104
|
submit: ()=>Promise.resolve('published'),
|
|
109
105
|
withdraw: ()=>{}
|
|
110
106
|
};
|
|
111
|
-
const broker = await createAndStartProvingBroker(config, telemetry);
|
|
112
|
-
const prover = await createProverClient(config, worldState, broker, telemetry);
|
|
113
|
-
const chonkCache = new ChonkCache(log.getBindings());
|
|
114
|
-
const txProvider = makeReplayingTxProvider(jobData.txs);
|
|
115
|
-
log.info(`Rerunning epoch proving for epoch ${jobData.epochNumber}`);
|
|
116
|
-
const provers = [];
|
|
117
|
-
for(let i = 0; i < jobData.checkpoints.length; i++){
|
|
118
|
-
const checkpoint = jobData.checkpoints[i];
|
|
119
|
-
const previousBlockHeader = i === 0 ? jobData.previousBlockHeader : jobData.checkpoints[i - 1].blocks.at(-1).header;
|
|
120
|
-
const l1ToL2Messages = jobData.l1ToL2Messages[checkpoint.number] ?? [];
|
|
121
|
-
const previousArchiveSiblingPath = await getLastSiblingPath(MerkleTreeId.ARCHIVE, worldState.getSnapshot(BlockNumber(checkpoint.blocks[0].number - 1)));
|
|
122
|
-
const attestations = checkpoint.number === jobData.checkpoints.at(-1).number ? jobData.attestations : [];
|
|
123
|
-
provers.push(new CheckpointProver({
|
|
124
|
-
checkpoint,
|
|
125
|
-
epochNumber: jobData.epochNumber,
|
|
126
|
-
attestations,
|
|
127
|
-
previousBlockHeader,
|
|
128
|
-
l1ToL2Messages,
|
|
129
|
-
previousArchiveSiblingPath
|
|
130
|
-
}, {
|
|
131
|
-
proverFactory: prover,
|
|
132
|
-
chonkCache,
|
|
133
|
-
publicProcessorFactory,
|
|
134
|
-
dbProvider: worldState,
|
|
135
|
-
txProvider,
|
|
136
|
-
dateProvider: new DateProvider(),
|
|
137
|
-
proverId: prover.getProverId(),
|
|
138
|
-
metrics,
|
|
139
|
-
txGatheringTimeoutMs: 120_000,
|
|
140
|
-
deadline: undefined,
|
|
141
|
-
log
|
|
142
|
-
}));
|
|
143
|
-
}
|
|
144
107
|
const l1Constants = {
|
|
145
108
|
epochDuration: config.aztecEpochDuration
|
|
146
109
|
};
|
|
@@ -174,6 +137,102 @@ import { ProverNodeJobMetrics } from '../metrics.js';
|
|
|
174
137
|
if (result) await result;
|
|
175
138
|
}
|
|
176
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Re-proves a single downloaded checkpoint proving job (as uploaded by a `CheckpointProver` failure).
|
|
142
|
+
* Reconstructs just that checkpoint's sub-tree prover from the snapshot and awaits its block proofs — no
|
|
143
|
+
* epoch top-tree, no L1 submission — so a checkpoint-level failure can be reproduced offline in isolation.
|
|
144
|
+
* Returns the block-rollup proof outputs on success; throws if the checkpoint fails to prove again.
|
|
145
|
+
*/ export async function rerunCheckpointProvingJob(localPath, log, config, genesis) {
|
|
146
|
+
const env = {
|
|
147
|
+
stack: [],
|
|
148
|
+
error: void 0,
|
|
149
|
+
hasError: false
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
const ctx = _ts_add_disposable_resource(env, await createRerunContext(localPath, log, config, genesis), true);
|
|
153
|
+
const { jobData } = ctx;
|
|
154
|
+
const checkpointNumber = jobData.checkpoints[0].number;
|
|
155
|
+
log.info(`Rerunning checkpoint proving for checkpoint ${checkpointNumber} (epoch ${jobData.epochNumber})`);
|
|
156
|
+
const prover = await buildCheckpointProver(ctx, 0, log);
|
|
157
|
+
try {
|
|
158
|
+
const blockProofs = await prover.whenBlockProofsReady();
|
|
159
|
+
log.info(`Completed proving for checkpoint ${checkpointNumber} with ${blockProofs.length} block proof(s)`);
|
|
160
|
+
return blockProofs;
|
|
161
|
+
} finally{
|
|
162
|
+
prover.cancel({
|
|
163
|
+
routine: true
|
|
164
|
+
});
|
|
165
|
+
await prover.whenDone();
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {
|
|
168
|
+
env.error = e;
|
|
169
|
+
env.hasError = true;
|
|
170
|
+
} finally{
|
|
171
|
+
const result = _ts_dispose_resources(env);
|
|
172
|
+
if (result) await result;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Rebuilds the offline proving environment from a downloaded job: world state + archiver from the
|
|
177
|
+
* snapshots, a local proving broker + client, a chonk cache, and a tx provider that replays the job's txs.
|
|
178
|
+
*/ async function createRerunContext(localPath, log, config, genesis) {
|
|
179
|
+
const jobData = deserializeEpochProvingJobData(readFileSync(localPath));
|
|
180
|
+
log.info(`Loaded proving job data for epoch ${jobData.epochNumber}`);
|
|
181
|
+
const telemetry = getTelemetryClient();
|
|
182
|
+
const metrics = new ProverNodeJobMetrics(telemetry.getMeter('prover-job'), telemetry.getTracer('prover-job'));
|
|
183
|
+
const worldState = await createWorldState(config, genesis);
|
|
184
|
+
const initialBlockHash = await worldState.getInitialHeader().hash();
|
|
185
|
+
const archiver = await createArchiverStore(config, initialBlockHash);
|
|
186
|
+
const avmSimulator = await AvmSimulatorPool.spawn({
|
|
187
|
+
wsdbIpcPath: worldState.getIpcPath()
|
|
188
|
+
});
|
|
189
|
+
const publicProcessorFactory = new PublicProcessorFactory(createContractDataSource(archiver), avmSimulator, undefined, undefined, log.getBindings());
|
|
190
|
+
const broker = await createAndStartProvingBroker(config, telemetry);
|
|
191
|
+
const prover = await createProverClient(config, worldState, broker, telemetry);
|
|
192
|
+
const chonkCache = new ChonkCache(log.getBindings());
|
|
193
|
+
const txProvider = makeReplayingTxProvider(jobData.txs);
|
|
194
|
+
return {
|
|
195
|
+
jobData,
|
|
196
|
+
metrics,
|
|
197
|
+
worldState,
|
|
198
|
+
publicProcessorFactory,
|
|
199
|
+
prover,
|
|
200
|
+
chonkCache,
|
|
201
|
+
txProvider,
|
|
202
|
+
async [Symbol.asyncDispose] () {
|
|
203
|
+
await avmSimulator[Symbol.asyncDispose]();
|
|
204
|
+
await worldState[Symbol.asyncDispose]();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/** Reconstructs the `CheckpointProver` for the checkpoint at `index` in the job, ready to prove. */ async function buildCheckpointProver(ctx, index, log) {
|
|
209
|
+
const { jobData, worldState, prover, chonkCache, publicProcessorFactory, txProvider, metrics } = ctx;
|
|
210
|
+
const checkpoint = jobData.checkpoints[index];
|
|
211
|
+
const previousBlockHeader = index === 0 ? jobData.previousBlockHeader : jobData.checkpoints[index - 1].blocks.at(-1).header;
|
|
212
|
+
const l1ToL2Messages = jobData.l1ToL2Messages[checkpoint.number] ?? [];
|
|
213
|
+
const previousArchiveSiblingPath = await getLastSiblingPath(MerkleTreeId.ARCHIVE, worldState.getSnapshot(BlockNumber(checkpoint.blocks[0].number - 1)));
|
|
214
|
+
const attestations = checkpoint.number === jobData.checkpoints.at(-1).number ? jobData.attestations : [];
|
|
215
|
+
return new CheckpointProver({
|
|
216
|
+
checkpoint,
|
|
217
|
+
epochNumber: jobData.epochNumber,
|
|
218
|
+
attestations,
|
|
219
|
+
previousBlockHeader,
|
|
220
|
+
l1ToL2Messages,
|
|
221
|
+
previousArchiveSiblingPath
|
|
222
|
+
}, {
|
|
223
|
+
proverFactory: prover,
|
|
224
|
+
chonkCache,
|
|
225
|
+
publicProcessorFactory,
|
|
226
|
+
dbProvider: worldState,
|
|
227
|
+
txProvider,
|
|
228
|
+
dateProvider: new DateProvider(),
|
|
229
|
+
proverId: prover.getProverId(),
|
|
230
|
+
metrics,
|
|
231
|
+
txGatheringTimeoutMs: 120_000,
|
|
232
|
+
deadline: undefined,
|
|
233
|
+
log
|
|
234
|
+
});
|
|
235
|
+
}
|
|
177
236
|
/** Build a synthetic ITxProvider that returns the supplied txs map by lookup. */ function makeReplayingTxProvider(txs) {
|
|
178
237
|
const lookup = (hashes)=>{
|
|
179
238
|
const found = [];
|