@aztec/prover-node 5.0.0-nightly.20260709 → 5.0.0-nightly.20260711

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 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
@@ -56,8 +55,9 @@ The prover-node splits responsibility between four classes:
56
55
  - **`CheckpointStore`** — a registry of `CheckpointProver` instances keyed by
57
56
  `(checkpointNumber, slot, archiveRoot)`. Each `CheckpointProver` runs its own sub-tree pipeline
58
57
  (tx gather → block processing → block-rollup proofs), starting eagerly the moment a
59
- checkpoint is registered. The store is the single source of canonical-vs-pruned
60
- checkpoint content that `EpochSession`s query when assembling their subsets.
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
61
  - **`SessionManager`** — owns every live `EpochSession`, the serial reconcile queue,
62
62
  the periodic tick, and all `EpochSession` lifecycle decisions. `ProverNode` calls into it
63
63
  via `onCheckpointAdded`, `onPrune`, and `startProof`. Every trigger it receives is
@@ -81,43 +81,38 @@ A `CheckpointProver` is content-addressed by `(checkpoint.number, slot, archiveR
81
81
  where `archiveRoot` is the checkpoint's own archive root (its post-state). Keying on the
82
82
  post-state makes the identity precise: two checkpoints are "the same" iff they produce
83
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`, while an
85
- identical re-add collapses to the same `CheckpointProver` and reuses its in-flight work.
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).
86
88
 
87
89
  ```mermaid
88
90
  stateDiagram-v2
89
91
  [*] --> Created
90
92
  Created --> Proving: gather + execute
91
93
  Proving --> Proven: sub-tree resolves blockProofs
92
- Proving --> Cancelled: cancel()
94
+ Proving --> Cancelled: cancel() (prune / shutdown / epoch-session error)
95
+ Proven --> Cancelled: cancel() (prune / reap)
93
96
  Proven --> Reaped: reapExpired(epoch)
94
97
  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
98
  ```
102
99
 
103
- The **`Pruned`** state is a side flag, not a place in the main lifecycle: sub-tree
104
- proving keeps running underneath, so a brief reorg that prunes and immediately
105
- re-adds the same checkpoint avoids any re-proving. The flag only gates *eligibility*
106
- to be included in an `EpochSession` `EpochSession`s ask the store for *canonical* (non-pruned)
107
- checkpoints when assembling their subsets.
108
-
109
- ### Reaping rules
110
-
111
- - **Pruned**: the `SlotWatcher` (a `RunningPromise` polling
112
- `l2BlockSource.getSyncedL2SlotNumber`) reaps a pruned `CheckpointProver` when the chain's
113
- synced slot has moved past the `CheckpointProver`'s slot. Once the chain is past that slot,
114
- a re-add with the same content is impossible.
115
- - **Canonical**: `CheckpointStore.reapExpired(expiredEpoch)` drops any canonical
116
- `CheckpointProver` whose epoch is at or below the supplied expired epoch. Once an epoch's
117
- proof-submission window has closed, its proof can no longer be accepted on L1,
118
- so the `CheckpointProver` is no longer needed.
119
- - **Cancelled**: removed immediately by whichever path called `cancel()` (store
120
- shutdown, prune past-slot, `EpochSession` error).
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).
121
116
 
122
117
  ### Eager tx gathering
123
118
 
@@ -288,8 +283,8 @@ sequenceDiagram
288
283
  alt content key new
289
284
  CS->>CP: new CheckpointProver(args)
290
285
  CP->>CP: eager gather + sub-tree start
291
- else content key matches
292
- CS->>CP: markCanonical()
286
+ else content key already live
287
+ CS->>CP: reuse existing prover
293
288
  end
294
289
  PN->>SM: onCheckpointAdded(epoch)
295
290
  SM->>SM: queue reconcile({kind:'checkpoint', epoch})
@@ -306,9 +301,9 @@ sequenceDiagram
306
301
  participant CS as CheckpointStore
307
302
  participant SM as SessionManager
308
303
 
309
- L2->>PN: chain-pruned{checkpoint}
310
- PN->>CS: markPrunedAfter(checkpoint.number)
311
- CS->>CS: flip every CheckpointProver above threshold to pruned (sub-tree keeps running)
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
312
307
  PN->>SM: onPrune(affectedEpochs)
313
308
  SM->>SM: queue reconcile({kind:'prune', affectedEpochs})
314
309
  SM->>SM: walk EpochSessions, cancel-and-recreate those with shifted content
@@ -371,30 +366,28 @@ indexing) leave the mark in place and the next tick retries.
371
366
 
372
367
  ### checkpoint-added → prune → checkpoint-added (reorg resilience)
373
368
 
374
- State: epoch N has checkpoints c1..c4 all canonical (slots s1..s4). `fullSessions[N]`
375
- holds `EpochSession` **A** with spec `{kind:'full', N, fromSlot:s1, toSlot:s4}`, referencing
376
- checkpoints `[c1, c2, c3, c4]`.
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]`.
377
371
 
378
- 1. **chain-pruned arrives, target c3.** Store flips c4 to pruned. Reconcile fires:
379
- for `EpochSession` A, canonical content for `(s1, s4)` is now `[c1, c2, c3]` (c4 pruned).
380
- The frozen set `[c1, c2, c3, c4]` no longer matches `A.cancel('canonical content
381
- changed')`. Epoch N still complete on L1 → reconcile constructs `EpochSession` **B** with
382
- the same spec `{full, N, s1, s4}` but checkpoints `[c1, c2, c3]`.
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]`.
383
377
 
384
378
  2. **`EpochSession` B starts top-tree proving over [c1, c2, c3].**
385
379
 
386
- 3. **chain-checkpointed arrives, target c4_re (same content key as old c4).** The
387
- store finds the existing `CheckpointProver` at `(c4.number, s4, c4.archive.root)`
388
- and calls `markCanonical()`. The sub-tree work that never stopped is visible to
389
- `EpochSession`s again. (A re-add with *different* content would have a different archive
390
- root and so get a fresh `CheckpointProver` instead.)
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.)
391
384
 
392
- 4. **Reconcile fires.** `EpochSession` B's canonical content for `(s1, s4)` is now `[c1, c2,
393
- c3, c4]`, doesn't match its frozen `[c1, c2, c3]` → `B.cancel(...)`. Construct
394
- `EpochSession` **C** with same spec but checkpoints `[c1, c2, c3, c4]`.
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]`.
395
388
 
396
- 5. **`EpochSession` C reuses the long-lived c1..c4 `CheckpointProver` instances.** Sub-tree
397
- work may already be complete; only the top-tree is recomputed. The chonk cache
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
398
391
  survived the reorg because no epoch in this range has expired yet.
399
392
 
400
393
 
@@ -466,19 +459,19 @@ Keying by tx hash makes the cache survive any reorg up to finality; releasing on
466
459
  finality means we don't grow the cache indefinitely while still keeping every
467
460
  reorg-relevant proof.
468
461
 
469
- ### Why does the slot watcher only reap pruned `CheckpointProver`s?
462
+ ### Why is a pruned `CheckpointProver` removed rather than kept for a possible re-add?
470
463
 
471
- Canonical `CheckpointProver`s can't be reaped on a slot heuristic they're still part of the
472
- proven-chain story. Pruned `CheckpointProver`s, on the other hand, are only kept around in
473
- case the chain re-adds the same content; once the synced slot has moved past, that
474
- re-add is impossible, and the `CheckpointProver` can go. Finality is the right signal for
475
- canonical reaping, because finality is the only state that rules out future reorgs.
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.
476
469
 
477
470
  ## Configuration
478
471
 
479
472
  | Env var | Description |
480
473
  |---|---|
481
- | `PROVER_NODE_POLLING_INTERVAL_MS` | Polling interval for the L2BlockStream, the checkpoint-store slot watcher, and the SessionManager periodic tick. Default 1000 ms. |
474
+ | `PROVER_NODE_POLLING_INTERVAL_MS` | Polling interval for the L2BlockStream and the SessionManager periodic tick. Default 1000 ms. |
482
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. |
483
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. |
484
477
  | `TX_GATHERING_TIMEOUT_MS` | Per-block tx gather deadline used by each `CheckpointProver`. |
@@ -14,74 +14,75 @@ export type CheckpointProverFactory = (args: CheckpointProverArgs, deps: Checkpo
14
14
  *
15
15
  * The store survives every epoch / session boundary. A prover lives from its first
16
16
  * `addOrUpdate` call until either:
17
- * - it has been pruned and the L2 chain has moved past its slot (no re-add possible), or
17
+ * - its checkpoint is pruned by an L1 reorg (`cancelAndRemoveAboveBlock`), or
18
18
  * - its epoch's proof-submission window has closed (`reapExpired`), so the proof could no
19
19
  * longer be accepted on L1 even if produced.
20
20
  *
21
- * A re-add of a checkpoint that matches an existing prover's content key reuses the
22
- * existing prover (and flips it back to canonical); the in-flight sub-tree work never
23
- * stops, so a prune-then-re-add of the same content avoids re-proving entirely.
21
+ * A prover's sub-tree work forks world-state per block and does not survive a prune of a base
22
+ * block, so there is nothing to preserve across a reorg: a pruned prover is cancelled and
23
+ * dropped, and a re-add (even of identical content) constructs a fresh prover.
24
24
  */
25
25
  export declare class CheckpointStore {
26
26
  private readonly l2BlockSource;
27
27
  private readonly proverDeps;
28
- private readonly options;
29
28
  private readonly proverFactoryFn;
30
29
  private readonly provers;
31
- private readonly slotWatcher;
30
+ /**
31
+ * Teardowns of provers already removed from `provers` (by prune or reap), awaited on `stop()`.
32
+ * Keyed by a monotonic id rather than the prover's content id: a prune-then-re-add can leave two
33
+ * teardowns for the same content id in flight at once, which a content-id key would clobber.
34
+ */
35
+ private readonly pendingTeardowns;
36
+ private nextTeardownId;
32
37
  private readonly log;
33
- constructor(l2BlockSource: Pick<L2BlockSource, 'getSyncedL2SlotNumber' | 'getL1Constants'>, proverDeps: Omit<CheckpointProverDeps, 'log'>, options: {
34
- slotWatcherPollIntervalMs: number;
35
- }, bindings?: LoggerBindings, proverFactoryFn?: CheckpointProverFactory);
38
+ constructor(l2BlockSource: Pick<L2BlockSource, 'getL1Constants'>, proverDeps: Omit<CheckpointProverDeps, 'log'>, bindings?: LoggerBindings, proverFactoryFn?: CheckpointProverFactory);
36
39
  start(): Promise<void>;
37
40
  stop(): Promise<void>;
41
+ /**
42
+ * Tracks the teardown of a prover just removed from the store so `stop()` can await it. The entry
43
+ * removes itself once teardown settles, so the map stays bounded by the number in flight.
44
+ */
45
+ private trackTeardown;
38
46
  /**
39
47
  * Registers a checkpoint with the store. If a prover already exists for the
40
- * `(number, slot, archive root)` content key, it is reused and marked canonical;
41
- * otherwise a new prover is constructed.
48
+ * `(number, slot, archive root)` content key it is reused (an at-least-once re-registration of
49
+ * still-canonical content); otherwise a new prover is constructed.
42
50
  */
43
51
  addOrUpdate(checkpoint: Checkpoint, data: RegisterCheckpointData): Promise<CheckpointProver>;
44
52
  /**
45
- * Marks every canonical prover that holds a block above the prune target as pruned. A checkpoint is orphaned by a
46
- * prune to block `targetBlockNumber` iff its last block sits above the target — including a checkpoint whose range
47
- * straddles the target (partially orphaned), which block-range marking catches without boundary ambiguity. Keying
48
- * off the surviving block number (rather than a checkpoint number) is correct even when the source has already
53
+ * Cancels and removes every prover that holds a block above the prune target. A checkpoint is orphaned by a prune to
54
+ * block `targetBlockNumber` iff its last block sits above the target — including a checkpoint whose range straddles
55
+ * the target (partially orphaned), which block-range marking catches without boundary ambiguity. Keying off the
56
+ * surviving block number (rather than a checkpoint number) is correct even when the source has already
49
57
  * re-checkpointed past the divergence: the prune event reports the highest surviving block, which by construction
50
58
  * survives on the source, whereas the source's current checkpointed tip can sit above the prune target.
51
- * Sub-tree work keeps running so a re-add of the same content can pick it up. Returns the affected provers.
59
+ *
60
+ * The prover's in-flight sub-tree work forks world-state per block and faults once its base block is pruned, so it
61
+ * cannot be reused; it is cancelled (aborting the fork reads) and dropped. A subsequent re-add constructs a fresh
62
+ * prover. Returns the removed provers.
52
63
  */
53
- markPrunedAboveBlock(targetBlockNumber: BlockNumber): CheckpointProver[];
64
+ cancelAndRemoveAboveBlock(targetBlockNumber: BlockNumber): CheckpointProver[];
54
65
  /**
55
- * Drops canonical (non-pruned) provers whose epoch is at or below the supplied expired
56
- * epoch. Once an epoch's proof-submission window has closed, its proof can no longer be
57
- * accepted on L1, so the prover is no longer needed.
66
+ * Drops provers whose epoch is at or below the supplied expired epoch. Once an epoch's
67
+ * proof-submission window has closed, its proof can no longer be accepted on L1, so the
68
+ * prover is no longer needed.
58
69
  */
59
70
  reapExpired(expiredEpoch: EpochNumber): void;
60
71
  /** Returns the prover with the supplied id, or undefined. */
61
72
  get(id: string): CheckpointProver | undefined;
62
73
  /** Returns the prover for the supplied checkpoint (by its content-addressed id), or undefined. */
63
74
  getByCheckpoint(checkpoint: Checkpoint): CheckpointProver | undefined;
64
- /** Every prover currently in the store (canonical and pruned), in insertion order. */
75
+ /** Every prover currently in the store, in insertion order. */
65
76
  listAll(): CheckpointProver[];
66
- /** Canonical (non-pruned) provers in the store, sorted by checkpoint number. */
67
- listCanonical(): CheckpointProver[];
68
- /**
69
- * Canonical provers whose slot is in the supplied epoch's slot range, sorted by
70
- * checkpoint number.
71
- */
72
- listCanonicalForEpoch(epoch: EpochNumber): Promise<CheckpointProver[]>;
73
- /** Canonical provers whose slot falls within `[fromSlot, toSlot]`, sorted by checkpoint number. */
74
- listCanonicalInSlotRange(fromSlot: SlotNumber, toSlot: SlotNumber): CheckpointProver[];
77
+ /** Provers in the store, sorted by checkpoint number. */
78
+ list(): CheckpointProver[];
75
79
  /**
76
- * SlotWatcher tick: reap pruned provers whose slot has passed the chain's synced
77
- * slot. Once the chain has moved past, no re-add can revive the prover and its
78
- * content key is unique enough that an actual re-add would create a new entry.
79
- *
80
- * Protected so unit tests can drive a single tick without spinning up the
81
- * `RunningPromise` and waiting on its interval.
80
+ * Provers whose slot is in the supplied epoch's slot range, sorted by checkpoint number.
82
81
  */
83
- protected reapPrunedPastSlot(): Promise<void>;
82
+ listForEpoch(epoch: EpochNumber): Promise<CheckpointProver[]>;
83
+ /** Provers whose slot falls within `[fromSlot, toSlot]`, sorted by checkpoint number. */
84
+ listInSlotRange(fromSlot: SlotNumber, toSlot: SlotNumber): CheckpointProver[];
84
85
  }
85
86
  /** Sub-set of `L1RollupConstants` actually consumed by the store's slot helpers. */
86
87
  export type CheckpointStoreL1Constants = Pick<L1RollupConstants, 'epochDuration'>;
87
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludC1zdG9yZS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2NoZWNrcG9pbnQtc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsV0FBVyxFQUFvQixXQUFXLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDOUcsT0FBTyxFQUFlLEtBQUssY0FBYyxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBRXZGLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzNELE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUF3QyxNQUFNLDZCQUE2QixDQUFDO0FBRTNHLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxLQUFLLG9CQUFvQixFQUFFLEtBQUssb0JBQW9CLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUVwSCw4R0FBOEc7QUFDOUcsTUFBTSxNQUFNLHNCQUFzQixHQUFHLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxZQUFZLEdBQUcsYUFBYSxDQUFDLENBQUM7QUFFOUYsbUZBQW1GO0FBQ25GLE1BQU0sTUFBTSx1QkFBdUIsR0FBRyxDQUFDLElBQUksRUFBRSxvQkFBb0IsRUFBRSxJQUFJLEVBQUUsb0JBQW9CLEtBQUssZ0JBQWdCLENBQUM7QUFFbkg7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILHFCQUFhLGVBQWU7SUFNeEIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxhQUFhO0lBQzlCLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVTtJQUMzQixPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU87SUFFeEIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxlQUFlO0lBVGxDLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUF1QztJQUMvRCxPQUFPLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBaUI7SUFDN0MsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQVM7SUFFN0IsWUFDbUIsYUFBYSxFQUFFLElBQUksQ0FBQyxhQUFhLEVBQUUsdUJBQXVCLEdBQUcsZ0JBQWdCLENBQUMsRUFDOUUsVUFBVSxFQUFFLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxLQUFLLENBQUMsRUFDN0MsT0FBTyxFQUFFO1FBQUUseUJBQXlCLEVBQUUsTUFBTSxDQUFBO0tBQUUsRUFDL0QsUUFBUSxDQUFDLEVBQUUsY0FBYyxFQUNSLGVBQWUsR0FBRSx1QkFBMEUsRUFRN0c7SUFFTSxLQUFLLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUc1QjtJQUVZLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBU2pDO0lBRUQ7Ozs7T0FJRztJQUNVLFdBQVcsQ0FBQyxVQUFVLEVBQUUsVUFBVSxFQUFFLElBQUksRUFBRSxzQkFBc0IsR0FBRyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0EwQnhHO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSSxvQkFBb0IsQ0FBQyxpQkFBaUIsRUFBRSxXQUFXLEdBQUcsZ0JBQWdCLEVBQUUsQ0FVOUU7SUFFRDs7OztPQUlHO0lBQ0ksV0FBVyxDQUFDLFlBQVksRUFBRSxXQUFXLEdBQUcsSUFBSSxDQW9CbEQ7SUFFRCw2REFBNkQ7SUFDdEQsR0FBRyxDQUFDLEVBQUUsRUFBRSxNQUFNLEdBQUcsZ0JBQWdCLEdBQUcsU0FBUyxDQUVuRDtJQUVELGtHQUFrRztJQUMzRixlQUFlLENBQUMsVUFBVSxFQUFFLFVBQVUsR0FBRyxnQkFBZ0IsR0FBRyxTQUFTLENBRTNFO0lBRUQsc0ZBQXNGO0lBQy9FLE9BQU8sSUFBSSxnQkFBZ0IsRUFBRSxDQUVuQztJQUVELGdGQUFnRjtJQUN6RSxhQUFhLElBQUksZ0JBQWdCLEVBQUUsQ0FJekM7SUFFRDs7O09BR0c7SUFDVSxxQkFBcUIsQ0FBQyxLQUFLLEVBQUUsV0FBVyxHQUFHLE9BQU8sQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLENBSWxGO0lBRUQsbUdBQW1HO0lBQzVGLHdCQUF3QixDQUFDLFFBQVEsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLFVBQVUsR0FBRyxnQkFBZ0IsRUFBRSxDQUU1RjtJQUVEOzs7Ozs7O09BT0c7SUFDSCxVQUFnQixrQkFBa0IsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBc0JsRDtDQUNGO0FBRUQsb0ZBQW9GO0FBQ3BGLE1BQU0sTUFBTSwwQkFBMEIsR0FBRyxJQUFJLENBQUMsaUJBQWlCLEVBQUUsZUFBZSxDQUFDLENBQUMifQ==
88
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2twb2ludC1zdG9yZS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2NoZWNrcG9pbnQtc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsV0FBVyxFQUFvQixXQUFXLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDOUcsT0FBTyxFQUFlLEtBQUssY0FBYyxFQUFnQixNQUFNLHVCQUF1QixDQUFDO0FBQ3ZGLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzNELE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUF3QyxNQUFNLDZCQUE2QixDQUFDO0FBRTNHLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxLQUFLLG9CQUFvQixFQUFFLEtBQUssb0JBQW9CLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUVwSCw4R0FBOEc7QUFDOUcsTUFBTSxNQUFNLHNCQUFzQixHQUFHLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxZQUFZLEdBQUcsYUFBYSxDQUFDLENBQUM7QUFFOUYsbUZBQW1GO0FBQ25GLE1BQU0sTUFBTSx1QkFBdUIsR0FBRyxDQUFDLElBQUksRUFBRSxvQkFBb0IsRUFBRSxJQUFJLEVBQUUsb0JBQW9CLEtBQUssZ0JBQWdCLENBQUM7QUFFbkg7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILHFCQUFhLGVBQWU7SUFZeEIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxhQUFhO0lBQzlCLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVTtJQUUzQixPQUFPLENBQUMsUUFBUSxDQUFDLGVBQWU7SUFkbEMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQXVDO0lBQy9EOzs7O09BSUc7SUFDSCxPQUFPLENBQUMsUUFBUSxDQUFDLGdCQUFnQixDQUFvQztJQUNyRSxPQUFPLENBQUMsY0FBYyxDQUFLO0lBQzNCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBRTdCLFlBQ21CLGFBQWEsRUFBRSxJQUFJLENBQUMsYUFBYSxFQUFFLGdCQUFnQixDQUFDLEVBQ3BELFVBQVUsRUFBRSxJQUFJLENBQUMsb0JBQW9CLEVBQUUsS0FBSyxDQUFDLEVBQzlELFFBQVEsQ0FBQyxFQUFFLGNBQWMsRUFDUixlQUFlLEdBQUUsdUJBQTBFLEVBRzdHO0lBRU0sS0FBSyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FFNUI7SUFFWSxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQVNqQztJQUVEOzs7T0FHRztJQUNILE9BQU8sQ0FBQyxhQUFhO0lBT3JCOzs7O09BSUc7SUFDVSxXQUFXLENBQUMsVUFBVSxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsc0JBQXNCLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixDQUFDLENBMEJ4RztJQUVEOzs7Ozs7Ozs7OztPQVdHO0lBQ0kseUJBQXlCLENBQUMsaUJBQWlCLEVBQUUsV0FBVyxHQUFHLGdCQUFnQixFQUFFLENBWW5GO0lBRUQ7Ozs7T0FJRztJQUNJLFdBQVcsQ0FBQyxZQUFZLEVBQUUsV0FBVyxHQUFHLElBQUksQ0FpQmxEO0lBRUQsNkRBQTZEO0lBQ3RELEdBQUcsQ0FBQyxFQUFFLEVBQUUsTUFBTSxHQUFHLGdCQUFnQixHQUFHLFNBQVMsQ0FFbkQ7SUFFRCxrR0FBa0c7SUFDM0YsZUFBZSxDQUFDLFVBQVUsRUFBRSxVQUFVLEdBQUcsZ0JBQWdCLEdBQUcsU0FBUyxDQUUzRTtJQUVELCtEQUErRDtJQUN4RCxPQUFPLElBQUksZ0JBQWdCLEVBQUUsQ0FFbkM7SUFFRCx5REFBeUQ7SUFDbEQsSUFBSSxJQUFJLGdCQUFnQixFQUFFLENBRWhDO0lBRUQ7O09BRUc7SUFDVSxZQUFZLENBQUMsS0FBSyxFQUFFLFdBQVcsR0FBRyxPQUFPLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQyxDQUl6RTtJQUVELHlGQUF5RjtJQUNsRixlQUFlLENBQUMsUUFBUSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsVUFBVSxHQUFHLGdCQUFnQixFQUFFLENBRW5GO0NBQ0Y7QUFFRCxvRkFBb0Y7QUFDcEYsTUFBTSxNQUFNLDBCQUEwQixHQUFHLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxlQUFlLENBQUMsQ0FBQyJ9
@@ -1 +1 @@
1
- {"version":3,"file":"checkpoint-store.d.ts","sourceRoot":"","sources":["../src/checkpoint-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAoB,WAAW,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC9G,OAAO,EAAe,KAAK,cAAc,EAAgB,MAAM,uBAAuB,CAAC;AAEvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,KAAK,iBAAiB,EAAwC,MAAM,6BAA6B,CAAC;AAE3G,OAAO,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEpH,8GAA8G;AAC9G,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,oBAAoB,EAAE,YAAY,GAAG,aAAa,CAAC,CAAC;AAE9F,mFAAmF;AACnF,MAAM,MAAM,uBAAuB,GAAG,CAAC,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,oBAAoB,KAAK,gBAAgB,CAAC;AAEnH;;;;;;;;;;;;;GAaG;AACH,qBAAa,eAAe;IAMxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAExB,OAAO,CAAC,QAAQ,CAAC,eAAe;IATlC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiB;IAC7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,YACmB,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,uBAAuB,GAAG,gBAAgB,CAAC,EAC9E,UAAU,EAAE,IAAI,CAAC,oBAAoB,EAAE,KAAK,CAAC,EAC7C,OAAO,EAAE;QAAE,yBAAyB,EAAE,MAAM,CAAA;KAAE,EAC/D,QAAQ,CAAC,EAAE,cAAc,EACR,eAAe,GAAE,uBAA0E,EAQ7G;IAEM,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAG5B;IAEY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CASjC;IAED;;;;OAIG;IACU,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0BxG;IAED;;;;;;;;OAQG;IACI,oBAAoB,CAAC,iBAAiB,EAAE,WAAW,GAAG,gBAAgB,EAAE,CAU9E;IAED;;;;OAIG;IACI,WAAW,CAAC,YAAY,EAAE,WAAW,GAAG,IAAI,CAoBlD;IAED,6DAA6D;IACtD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEnD;IAED,kGAAkG;IAC3F,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,gBAAgB,GAAG,SAAS,CAE3E;IAED,sFAAsF;IAC/E,OAAO,IAAI,gBAAgB,EAAE,CAEnC;IAED,gFAAgF;IACzE,aAAa,IAAI,gBAAgB,EAAE,CAIzC;IAED;;;OAGG;IACU,qBAAqB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAIlF;IAED,mGAAmG;IAC5F,wBAAwB,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,GAAG,gBAAgB,EAAE,CAE5F;IAED;;;;;;;OAOG;IACH,UAAgB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAsBlD;CACF;AAED,oFAAoF;AACpF,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"checkpoint-store.d.ts","sourceRoot":"","sources":["../src/checkpoint-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAoB,WAAW,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC9G,OAAO,EAAe,KAAK,cAAc,EAAgB,MAAM,uBAAuB,CAAC;AACvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,KAAK,iBAAiB,EAAwC,MAAM,6BAA6B,CAAC;AAE3G,OAAO,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEpH,8GAA8G;AAC9G,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,oBAAoB,EAAE,YAAY,GAAG,aAAa,CAAC,CAAC;AAE9F,mFAAmF;AACnF,MAAM,MAAM,uBAAuB,GAAG,CAAC,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,oBAAoB,KAAK,gBAAgB,CAAC;AAEnH;;;;;;;;;;;;;GAaG;AACH,qBAAa,eAAe;IAYxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAE3B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAdlC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoC;IACrE,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAE7B,YACmB,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,gBAAgB,CAAC,EACpD,UAAU,EAAE,IAAI,CAAC,oBAAoB,EAAE,KAAK,CAAC,EAC9D,QAAQ,CAAC,EAAE,cAAc,EACR,eAAe,GAAE,uBAA0E,EAG7G;IAEM,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5B;IAEY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CASjC;IAED;;;OAGG;IACH,OAAO,CAAC,aAAa;IAOrB;;;;OAIG;IACU,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0BxG;IAED;;;;;;;;;;;OAWG;IACI,yBAAyB,CAAC,iBAAiB,EAAE,WAAW,GAAG,gBAAgB,EAAE,CAYnF;IAED;;;;OAIG;IACI,WAAW,CAAC,YAAY,EAAE,WAAW,GAAG,IAAI,CAiBlD;IAED,6DAA6D;IACtD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEnD;IAED,kGAAkG;IAC3F,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,gBAAgB,GAAG,SAAS,CAE3E;IAED,+DAA+D;IACxD,OAAO,IAAI,gBAAgB,EAAE,CAEnC;IAED,yDAAyD;IAClD,IAAI,IAAI,gBAAgB,EAAE,CAEhC;IAED;;OAEG;IACU,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAIzE;IAED,yFAAyF;IAClF,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,GAAG,gBAAgB,EAAE,CAEnF;CACF;AAED,oFAAoF;AACpF,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import { createLogger } from '@aztec/foundation/log';
2
- import { RunningPromise } from '@aztec/foundation/promise';
3
2
  import { getEpochAtSlot, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
4
3
  import { CheckpointProver } from './job/checkpoint-prover.js';
5
4
  /**
@@ -8,63 +7,78 @@ import { CheckpointProver } from './job/checkpoint-prover.js';
8
7
  *
9
8
  * The store survives every epoch / session boundary. A prover lives from its first
10
9
  * `addOrUpdate` call until either:
11
- * - it has been pruned and the L2 chain has moved past its slot (no re-add possible), or
10
+ * - its checkpoint is pruned by an L1 reorg (`cancelAndRemoveAboveBlock`), or
12
11
  * - its epoch's proof-submission window has closed (`reapExpired`), so the proof could no
13
12
  * longer be accepted on L1 even if produced.
14
13
  *
15
- * A re-add of a checkpoint that matches an existing prover's content key reuses the
16
- * existing prover (and flips it back to canonical); the in-flight sub-tree work never
17
- * stops, so a prune-then-re-add of the same content avoids re-proving entirely.
14
+ * A prover's sub-tree work forks world-state per block and does not survive a prune of a base
15
+ * block, so there is nothing to preserve across a reorg: a pruned prover is cancelled and
16
+ * dropped, and a re-add (even of identical content) constructs a fresh prover.
18
17
  */ export class CheckpointStore {
19
18
  l2BlockSource;
20
19
  proverDeps;
21
- options;
22
20
  proverFactoryFn;
23
21
  provers;
24
- slotWatcher;
22
+ /**
23
+ * Teardowns of provers already removed from `provers` (by prune or reap), awaited on `stop()`.
24
+ * Keyed by a monotonic id rather than the prover's content id: a prune-then-re-add can leave two
25
+ * teardowns for the same content id in flight at once, which a content-id key would clobber.
26
+ */ pendingTeardowns;
27
+ nextTeardownId;
25
28
  log;
26
- constructor(l2BlockSource, proverDeps, options, bindings, proverFactoryFn = (args, deps)=>new CheckpointProver(args, deps)){
29
+ constructor(l2BlockSource, proverDeps, bindings, proverFactoryFn = (args, deps)=>new CheckpointProver(args, deps)){
27
30
  this.l2BlockSource = l2BlockSource;
28
31
  this.proverDeps = proverDeps;
29
- this.options = options;
30
32
  this.proverFactoryFn = proverFactoryFn;
31
33
  this.provers = new Map();
34
+ this.pendingTeardowns = new Map();
35
+ this.nextTeardownId = 0;
32
36
  this.log = createLogger('prover-node:checkpoint-store', bindings);
33
- this.slotWatcher = new RunningPromise(()=>this.reapPrunedPastSlot(), this.log, this.options.slotWatcherPollIntervalMs);
34
37
  }
35
38
  start() {
36
- this.slotWatcher.start();
37
39
  return Promise.resolve();
38
40
  }
39
41
  async stop() {
40
- await this.slotWatcher.stop();
41
- // Cancel every live prover; await teardown.
42
+ // Cancel every live prover, then await both their teardown and any still in flight for provers
43
+ // already removed by a prune or reap.
42
44
  const provers = Array.from(this.provers.values());
43
45
  this.provers.clear();
44
46
  for (const prover of provers){
45
47
  prover.cancel();
46
48
  }
47
- await Promise.allSettled(provers.map((p)=>p.whenDone()));
49
+ await Promise.allSettled([
50
+ ...provers.map((p)=>p.whenDone()),
51
+ ...this.pendingTeardowns.values()
52
+ ]);
53
+ }
54
+ /**
55
+ * Tracks the teardown of a prover just removed from the store so `stop()` can await it. The entry
56
+ * removes itself once teardown settles, so the map stays bounded by the number in flight.
57
+ */ trackTeardown(prover) {
58
+ const id = this.nextTeardownId++;
59
+ const done = prover.whenDone();
60
+ this.pendingTeardowns.set(id, done);
61
+ void done.finally(()=>this.pendingTeardowns.delete(id));
48
62
  }
49
63
  /**
50
64
  * Registers a checkpoint with the store. If a prover already exists for the
51
- * `(number, slot, archive root)` content key, it is reused and marked canonical;
52
- * otherwise a new prover is constructed.
65
+ * `(number, slot, archive root)` content key it is reused (an at-least-once re-registration of
66
+ * still-canonical content); otherwise a new prover is constructed.
53
67
  */ async addOrUpdate(checkpoint, data) {
54
68
  const l1Constants = await this.l2BlockSource.getL1Constants();
55
69
  const epochNumber = getEpochAtSlot(checkpoint.header.slotNumber, l1Constants);
56
70
  const id = CheckpointProver.idFor(checkpoint);
57
71
  const existing = this.provers.get(id);
58
72
  if (existing) {
59
- existing.markCanonical();
60
73
  return existing;
61
74
  }
62
- // At most one canonical checkpoint per slot. A different canonical checkpoint at the
63
- // same slot means the caller forgot to prune the old chain before adding the replacement
64
- // — surface it rather than silently creating a parallel canonical chain.
75
+ // At most one canonical checkpoint per slot. A different checkpoint at the same slot means the
76
+ // caller forgot to prune the old chain before adding the replacement — surface it rather than
77
+ // silently creating a parallel canonical chain. A pruned checkpoint has already been removed,
78
+ // so every prover still in the store is canonical.
65
79
  for (const prover of this.provers.values()){
66
- if (prover.slotNumber === checkpoint.header.slotNumber && !prover.isPruned()) {
67
- throw new Error(`Cannot add checkpoint ${checkpoint.number} (archive ${checkpoint.archive.root}) at slot ${checkpoint.header.slotNumber}: ` + `a different canonical checkpoint already occupies this slot. Prune it first.`);
80
+ if (prover.slotNumber === checkpoint.header.slotNumber) {
81
+ throw new Error(`Cannot add checkpoint ${checkpoint.number} (archive ${checkpoint.archive.root}) at slot ${checkpoint.header.slotNumber}: ` + `a different checkpoint already occupies this slot. Prune it first.`);
68
82
  }
69
83
  }
70
84
  const prover = this.proverFactoryFn({
@@ -79,34 +93,36 @@ import { CheckpointProver } from './job/checkpoint-prover.js';
79
93
  return prover;
80
94
  }
81
95
  /**
82
- * Marks every canonical prover that holds a block above the prune target as pruned. A checkpoint is orphaned by a
83
- * prune to block `targetBlockNumber` iff its last block sits above the target — including a checkpoint whose range
84
- * straddles the target (partially orphaned), which block-range marking catches without boundary ambiguity. Keying
85
- * off the surviving block number (rather than a checkpoint number) is correct even when the source has already
96
+ * Cancels and removes every prover that holds a block above the prune target. A checkpoint is orphaned by a prune to
97
+ * block `targetBlockNumber` iff its last block sits above the target — including a checkpoint whose range straddles
98
+ * the target (partially orphaned), which block-range marking catches without boundary ambiguity. Keying off the
99
+ * surviving block number (rather than a checkpoint number) is correct even when the source has already
86
100
  * re-checkpointed past the divergence: the prune event reports the highest surviving block, which by construction
87
101
  * survives on the source, whereas the source's current checkpointed tip can sit above the prune target.
88
- * Sub-tree work keeps running so a re-add of the same content can pick it up. Returns the affected provers.
89
- */ markPrunedAboveBlock(targetBlockNumber) {
102
+ *
103
+ * The prover's in-flight sub-tree work forks world-state per block and faults once its base block is pruned, so it
104
+ * cannot be reused; it is cancelled (aborting the fork reads) and dropped. A subsequent re-add constructs a fresh
105
+ * prover. Returns the removed provers.
106
+ */ cancelAndRemoveAboveBlock(targetBlockNumber) {
90
107
  const affected = [];
91
- for (const prover of this.provers.values()){
108
+ for (const [id, prover] of Array.from(this.provers.entries())){
92
109
  const lastBlockNumber = prover.checkpoint.blocks.at(-1).number;
93
- if (lastBlockNumber > targetBlockNumber && !prover.isPruned()) {
94
- prover.markPruned();
110
+ if (lastBlockNumber > targetBlockNumber) {
111
+ prover.cancel();
112
+ this.trackTeardown(prover);
113
+ this.provers.delete(id);
95
114
  affected.push(prover);
96
115
  }
97
116
  }
98
117
  return affected;
99
118
  }
100
119
  /**
101
- * Drops canonical (non-pruned) provers whose epoch is at or below the supplied expired
102
- * epoch. Once an epoch's proof-submission window has closed, its proof can no longer be
103
- * accepted on L1, so the prover is no longer needed.
120
+ * Drops provers whose epoch is at or below the supplied expired epoch. Once an epoch's
121
+ * proof-submission window has closed, its proof can no longer be accepted on L1, so the
122
+ * prover is no longer needed.
104
123
  */ reapExpired(expiredEpoch) {
105
124
  const reaped = [];
106
125
  for (const [id, prover] of Array.from(this.provers.entries())){
107
- if (prover.isPruned()) {
108
- continue;
109
- }
110
126
  if (prover.epochNumber <= expiredEpoch) {
111
127
  reaped.push({
112
128
  id,
@@ -116,7 +132,7 @@ import { CheckpointProver } from './job/checkpoint-prover.js';
116
132
  prover.cancel({
117
133
  routine: true
118
134
  });
119
- void prover.whenDone();
135
+ this.trackTeardown(prover);
120
136
  this.provers.delete(id);
121
137
  }
122
138
  }
@@ -134,53 +150,20 @@ import { CheckpointProver } from './job/checkpoint-prover.js';
134
150
  /** Returns the prover for the supplied checkpoint (by its content-addressed id), or undefined. */ getByCheckpoint(checkpoint) {
135
151
  return this.provers.get(CheckpointProver.idFor(checkpoint));
136
152
  }
137
- /** Every prover currently in the store (canonical and pruned), in insertion order. */ listAll() {
153
+ /** Every prover currently in the store, in insertion order. */ listAll() {
138
154
  return Array.from(this.provers.values());
139
155
  }
140
- /** Canonical (non-pruned) provers in the store, sorted by checkpoint number. */ listCanonical() {
141
- return Array.from(this.provers.values()).filter((p)=>!p.isPruned()).sort((a, b)=>a.checkpoint.number - b.checkpoint.number);
156
+ /** Provers in the store, sorted by checkpoint number. */ list() {
157
+ return Array.from(this.provers.values()).sort((a, b)=>a.checkpoint.number - b.checkpoint.number);
142
158
  }
143
159
  /**
144
- * Canonical provers whose slot is in the supplied epoch's slot range, sorted by
145
- * checkpoint number.
146
- */ async listCanonicalForEpoch(epoch) {
160
+ * Provers whose slot is in the supplied epoch's slot range, sorted by checkpoint number.
161
+ */ async listForEpoch(epoch) {
147
162
  const l1Constants = await this.l2BlockSource.getL1Constants();
148
163
  const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, l1Constants);
149
- return this.listCanonicalInSlotRange(fromSlot, toSlot);
164
+ return this.listInSlotRange(fromSlot, toSlot);
150
165
  }
151
- /** Canonical provers whose slot falls within `[fromSlot, toSlot]`, sorted by checkpoint number. */ listCanonicalInSlotRange(fromSlot, toSlot) {
152
- return this.listCanonical().filter((p)=>p.slotNumber >= fromSlot && p.slotNumber <= toSlot);
153
- }
154
- /**
155
- * SlotWatcher tick: reap pruned provers whose slot has passed the chain's synced
156
- * slot. Once the chain has moved past, no re-add can revive the prover and its
157
- * content key is unique enough that an actual re-add would create a new entry.
158
- *
159
- * Protected so unit tests can drive a single tick without spinning up the
160
- * `RunningPromise` and waiting on its interval.
161
- */ async reapPrunedPastSlot() {
162
- let syncedSlot;
163
- try {
164
- syncedSlot = await this.l2BlockSource.getSyncedL2SlotNumber();
165
- } catch (err) {
166
- this.log.debug(`SlotWatcher could not read synced slot`, {
167
- error: `${err}`
168
- });
169
- return;
170
- }
171
- if (syncedSlot === undefined) {
172
- return;
173
- }
174
- for (const [id, prover] of Array.from(this.provers.entries())){
175
- if (prover.isPruned() && prover.slotNumber < syncedSlot) {
176
- this.log.info(`Reaping pruned CheckpointProver ${id}: slot ${prover.slotNumber} < synced ${syncedSlot}`, {
177
- checkpointNumber: prover.checkpoint.number,
178
- slotNumber: prover.slotNumber
179
- });
180
- prover.cancel();
181
- void prover.whenDone();
182
- this.provers.delete(id);
183
- }
184
- }
166
+ /** Provers whose slot falls within `[fromSlot, toSlot]`, sorted by checkpoint number. */ listInSlotRange(fromSlot, toSlot) {
167
+ return this.list().filter((p)=>p.slotNumber >= fromSlot && p.slotNumber <= toSlot);
185
168
  }
186
169
  }