@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 +53 -60
- package/dest/checkpoint-store.d.ts +39 -38
- package/dest/checkpoint-store.d.ts.map +1 -1
- package/dest/checkpoint-store.js +62 -79
- package/dest/job/checkpoint-prover.d.ts +6 -16
- package/dest/job/checkpoint-prover.d.ts.map +1 -1
- package/dest/job/checkpoint-prover.js +11 -34
- package/dest/metrics.js +1 -1
- package/dest/prover-node.d.ts +1 -1
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +3 -5
- package/dest/session-manager.d.ts +2 -2
- package/dest/session-manager.d.ts.map +1 -1
- package/dest/session-manager.js +38 -8
- package/package.json +23 -23
- package/src/checkpoint-store.ts +62 -86
- package/src/job/checkpoint-prover.ts +13 -39
- package/src/metrics.ts +1 -1
- package/src/prover-node.ts +3 -4
- package/src/session-manager.ts +38 -7
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
|
|
60
|
-
|
|
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
|
|
85
|
-
|
|
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
|
-
|
|
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).
|
|
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
|
|
292
|
-
CS->>CP:
|
|
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{
|
|
310
|
-
PN->>CS:
|
|
311
|
-
CS->>CS:
|
|
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
|
|
375
|
-
|
|
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.**
|
|
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]`.
|
|
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
|
-
|
|
388
|
-
and
|
|
389
|
-
|
|
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
|
|
393
|
-
|
|
394
|
-
|
|
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..
|
|
397
|
-
|
|
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
|
|
462
|
+
### Why is a pruned `CheckpointProver` removed rather than kept for a possible re-add?
|
|
470
463
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
|
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
|
-
* -
|
|
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
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
-
|
|
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, '
|
|
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
|
|
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
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
64
|
+
cancelAndRemoveAboveBlock(targetBlockNumber: BlockNumber): CheckpointProver[];
|
|
54
65
|
/**
|
|
55
|
-
* Drops
|
|
56
|
-
*
|
|
57
|
-
*
|
|
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
|
|
75
|
+
/** Every prover currently in the store, in insertion order. */
|
|
65
76
|
listAll(): CheckpointProver[];
|
|
66
|
-
/**
|
|
67
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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,
|
|
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;
|
|
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"}
|
package/dest/checkpoint-store.js
CHANGED
|
@@ -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
|
-
* -
|
|
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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
-
|
|
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,
|
|
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
|
|
41
|
-
//
|
|
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(
|
|
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
|
|
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
|
|
63
|
-
//
|
|
64
|
-
//
|
|
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
|
|
67
|
-
throw new Error(`Cannot add checkpoint ${checkpoint.number} (archive ${checkpoint.archive.root}) at slot ${checkpoint.header.slotNumber}: ` + `a different
|
|
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
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
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
|
-
*
|
|
89
|
-
|
|
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.
|
|
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
|
|
94
|
-
prover.
|
|
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
|
|
102
|
-
*
|
|
103
|
-
*
|
|
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
|
-
|
|
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
|
|
153
|
+
/** Every prover currently in the store, in insertion order. */ listAll() {
|
|
138
154
|
return Array.from(this.provers.values());
|
|
139
155
|
}
|
|
140
|
-
/**
|
|
141
|
-
return Array.from(this.provers.values()).
|
|
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
|
-
*
|
|
145
|
-
|
|
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.
|
|
164
|
+
return this.listInSlotRange(fromSlot, toSlot);
|
|
150
165
|
}
|
|
151
|
-
/**
|
|
152
|
-
return this.
|
|
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
|
}
|