@aztec/prover-node 0.0.1-commit.3100065 → 0.0.1-commit.330febf
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 +95 -34
- 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 +195 -55
- package/dest/checkpoint-store.d.ts +9 -2
- package/dest/checkpoint-store.d.ts.map +1 -1
- package/dest/checkpoint-store.js +9 -0
- package/dest/config.d.ts +3 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +7 -0
- package/dest/factory.d.ts +4 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +2 -1
- package/dest/job/checkpoint-prover.d.ts +47 -6
- package/dest/job/checkpoint-prover.d.ts.map +1 -1
- package/dest/job/checkpoint-prover.js +94 -19
- 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/prover-node-publisher.d.ts +4 -1
- package/dest/prover-node-publisher.d.ts.map +1 -1
- package/dest/prover-node-publisher.js +5 -3
- package/dest/prover-node.d.ts +37 -8
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +82 -19
- package/dest/prover-publisher-factory.d.ts +3 -1
- package/dest/prover-publisher-factory.d.ts.map +1 -1
- package/dest/prover-publisher-factory.js +1 -0
- package/dest/session-manager.d.ts +16 -16
- package/dest/session-manager.d.ts.map +1 -1
- package/dest/session-manager.js +75 -65
- package/package.json +24 -23
- package/src/actions/rerun-epoch-proving-job.ts +139 -66
- package/src/checkpoint-store.ts +20 -2
- package/src/config.ts +10 -0
- package/src/factory.ts +5 -0
- package/src/job/checkpoint-prover.ts +113 -17
- package/src/job/epoch-session.ts +30 -4
- package/src/job/top-tree-job.ts +4 -4
- package/src/prover-node-publisher.ts +7 -3
- package/src/prover-node.ts +97 -20
- package/src/prover-publisher-factory.ts +3 -0
- package/src/session-manager.ts +78 -69
package/src/session-manager.ts
CHANGED
|
@@ -60,8 +60,9 @@ export type SessionManagerDeps = {
|
|
|
60
60
|
dateProvider: DateProvider;
|
|
61
61
|
config: SessionManagerConfig;
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
63
|
+
* Fired once when a full session ends in its own genuine failure (`EpochSession.hasFailed()` — top-tree
|
|
64
|
+
* or submit failed with every prover healthy). The owner uploads a post-mortem here. Not fired for a
|
|
65
|
+
* `stopped` session (a prover under it failed — possibly a prune), which is recovered on re-add instead.
|
|
65
66
|
*/
|
|
66
67
|
onSessionFailed?: (session: EpochSession) => Promise<void>;
|
|
67
68
|
bindings?: LoggerBindings;
|
|
@@ -88,15 +89,6 @@ export class SessionManager {
|
|
|
88
89
|
private readonly reconcileQueue = new SerialQueue();
|
|
89
90
|
/** Cached L1 constants, populated on first read. */
|
|
90
91
|
private cachedL1Constants: L1RollupConstants | undefined;
|
|
91
|
-
/**
|
|
92
|
-
* Highest epoch for which the periodic tick has successfully created a full session.
|
|
93
|
-
* Monotonic high-water mark: once the tick observes a session for epoch X, it stops
|
|
94
|
-
* trying to open one — even if that session subsequently fails (only a new checkpoint
|
|
95
|
-
* event reopens it). Crucially, the mark only advances when a session actually exists
|
|
96
|
-
* post-open, so transient blockers (atMaxSessionLimit, archiver still indexing) leave
|
|
97
|
-
* the mark in place and the next tick retries.
|
|
98
|
-
*/
|
|
99
|
-
private lastTickEpoch: EpochNumber | undefined;
|
|
100
92
|
/** Test-only hooks applied to every session this manager constructs. */
|
|
101
93
|
private sessionHooks: EpochSessionHooks | undefined;
|
|
102
94
|
/** Periodic tick that nudges reconcile to pick up newly-complete epochs. Started by `start()`. */
|
|
@@ -227,7 +219,9 @@ export class SessionManager {
|
|
|
227
219
|
await this.epochTicker?.stop();
|
|
228
220
|
await this.reconcileQueue.cancel();
|
|
229
221
|
const sessions = this.allSessions();
|
|
230
|
-
|
|
222
|
+
// A clean shutdown is just a restart, so preserve the in-flight broker jobs (abortJobs: false)
|
|
223
|
+
// for the restarted node to reuse rather than re-proving the epoch from scratch.
|
|
224
|
+
await Promise.allSettled(sessions.map(s => s.cancel('prover-node stopping', { abortJobs: false })));
|
|
231
225
|
}
|
|
232
226
|
|
|
233
227
|
// ---------------- reconcile ----------------
|
|
@@ -246,17 +240,6 @@ export class SessionManager {
|
|
|
246
240
|
await this.openFullSessionIfReady(epoch);
|
|
247
241
|
}
|
|
248
242
|
|
|
249
|
-
// Advance the tick high-water mark only once a session actually exists for the epoch.
|
|
250
|
-
// `openFullSessionIfReady` can early-return without creating one (atMaxSessionLimit,
|
|
251
|
-
// archiver still indexing, etc.); in those cases we want the next tick to try again
|
|
252
|
-
// rather than skip the epoch forever.
|
|
253
|
-
if (trigger.kind === 'tick' && implicatedEpochs.length === 1) {
|
|
254
|
-
const epoch = implicatedEpochs[0];
|
|
255
|
-
if (this.fullSessions.has(epoch)) {
|
|
256
|
-
this.lastTickEpoch = epoch;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
243
|
if (trigger.kind === 'start-proof') {
|
|
261
244
|
this.openPartialSession(trigger.spec);
|
|
262
245
|
}
|
|
@@ -264,15 +247,30 @@ export class SessionManager {
|
|
|
264
247
|
|
|
265
248
|
private recreateInvalidSessions(): void {
|
|
266
249
|
for (const [key, session] of Array.from(this.fullSessions.entries())) {
|
|
250
|
+
const canonical = this.checkpointsForSpec(session.getSpec());
|
|
251
|
+
const contentChanged = !this.checkpointsMatch(session.getCheckpoints(), canonical);
|
|
252
|
+
|
|
267
253
|
if (session.isTerminal()) {
|
|
254
|
+
// A full session that failed on its own account is retained as a "do not re-prove" marker while
|
|
255
|
+
// its content is unchanged — this is what stops the tick re-proving a deterministically-failing
|
|
256
|
+
// epoch. When the content changes (a re-add), it is replaced so the epoch retries over the new
|
|
257
|
+
// provers. Any other terminal full session is simply dropped.
|
|
258
|
+
if (session.hasFailed() && !contentChanged) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
268
261
|
this.fullSessions.delete(key);
|
|
262
|
+
if (contentChanged && this.canBuildOver(canonical)) {
|
|
263
|
+
const newSession = this.constructSession(session.getSpec(), canonical);
|
|
264
|
+
this.fullSessions.set(key, newSession);
|
|
265
|
+
void this.runSession(newSession);
|
|
266
|
+
}
|
|
269
267
|
continue;
|
|
270
268
|
}
|
|
271
|
-
|
|
272
|
-
if (
|
|
269
|
+
|
|
270
|
+
if (contentChanged) {
|
|
273
271
|
this.fireAndForgetCancel(session, 'canonical content changed');
|
|
274
272
|
this.fullSessions.delete(key);
|
|
275
|
-
if (canonical
|
|
273
|
+
if (this.canBuildOver(canonical)) {
|
|
276
274
|
const newSession = this.constructSession(session.getSpec(), canonical);
|
|
277
275
|
this.fullSessions.set(key, newSession);
|
|
278
276
|
void this.runSession(newSession);
|
|
@@ -288,7 +286,7 @@ export class SessionManager {
|
|
|
288
286
|
if (!this.checkpointsMatch(session.getCheckpoints(), canonical)) {
|
|
289
287
|
this.fireAndForgetCancel(session, 'canonical content changed');
|
|
290
288
|
this.partialSessions.delete(key);
|
|
291
|
-
if (canonical
|
|
289
|
+
if (this.canBuildOver(canonical)) {
|
|
292
290
|
const newSession = this.constructSession(session.getSpec(), canonical);
|
|
293
291
|
this.partialSessions.set(key, newSession);
|
|
294
292
|
void this.runSession(newSession);
|
|
@@ -297,9 +295,15 @@ export class SessionManager {
|
|
|
297
295
|
}
|
|
298
296
|
}
|
|
299
297
|
|
|
298
|
+
/** A session may be built over a checkpoint set only when it is non-empty and contains no failed prover. */
|
|
299
|
+
private canBuildOver(canonical: readonly CheckpointProver[]): boolean {
|
|
300
|
+
return canonical.length > 0 && !this.hasFailedProver(canonical);
|
|
301
|
+
}
|
|
302
|
+
|
|
300
303
|
private async openFullSessionIfReady(epoch: EpochNumber): Promise<void> {
|
|
301
|
-
//
|
|
302
|
-
//
|
|
304
|
+
// A session present here already covers the epoch: either live, or a retained genuinely-failed
|
|
305
|
+
// session kept by `recreateInvalidSessions` as a "do not re-prove" marker. Either way, don't open
|
|
306
|
+
// another — the retained-failed one is replaced only when its canonical content changes.
|
|
303
307
|
if (this.fullSessions.has(epoch)) {
|
|
304
308
|
return;
|
|
305
309
|
}
|
|
@@ -324,6 +328,13 @@ export class SessionManager {
|
|
|
324
328
|
});
|
|
325
329
|
return;
|
|
326
330
|
}
|
|
331
|
+
if (this.hasFailedProver(canonical)) {
|
|
332
|
+
// A checkpoint prover in the set has failed (a sub-tree fault or a prune-induced fork fault), so a
|
|
333
|
+
// session over it would fail immediately. Don't re-create it every tick — it recovers when a
|
|
334
|
+
// prune/re-add replaces the failed prover with a fresh one, or fails for good at expiry.
|
|
335
|
+
this.log.debug(`Skipping full-session open for epoch ${epoch}: a checkpoint prover has failed`, { epoch });
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
327
338
|
const spec: SessionSpec = { kind: 'full', epochNumber: epoch, fromSlot, toSlot };
|
|
328
339
|
const session = this.constructSession(spec, canonical);
|
|
329
340
|
this.fullSessions.set(epoch, session);
|
|
@@ -332,7 +343,7 @@ export class SessionManager {
|
|
|
332
343
|
|
|
333
344
|
private openPartialSession(spec: SessionSpec): void {
|
|
334
345
|
const canonical = this.deps.checkpointStore.listInSlotRange(spec.fromSlot, spec.toSlot);
|
|
335
|
-
if (canonical.length === 0) {
|
|
346
|
+
if (canonical.length === 0 || this.hasFailedProver(canonical)) {
|
|
336
347
|
return;
|
|
337
348
|
}
|
|
338
349
|
// Reuse a live partial session for this epoch whose checkpoint set already matches the
|
|
@@ -404,43 +415,43 @@ export class SessionManager {
|
|
|
404
415
|
}
|
|
405
416
|
const state = await session.start();
|
|
406
417
|
this.log.info(`Session ${session.getId()} exited with state ${state}`);
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
this.log.info(`Skipping failure upload for session ${session.getId()}: canonical content changed`, {
|
|
415
|
-
...session.getSpec(),
|
|
416
|
-
});
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
418
|
+
|
|
419
|
+
// A full session that failed on its own account (top-tree/submit failed with every prover healthy)
|
|
420
|
+
// is a genuine, race-free failure: upload its post-mortem once. `recreateInvalidSessions` retains
|
|
421
|
+
// the terminal session so this fires exactly once (it is never re-run over the same content). A
|
|
422
|
+
// `stopped` session (a prover under it failed) is not uploaded — it may be a prune, and recovers on
|
|
423
|
+
// re-add.
|
|
424
|
+
if (session.getKind() === 'full' && session.hasFailed() && this.deps.onSessionFailed) {
|
|
419
425
|
try {
|
|
420
426
|
await this.deps.onSessionFailed(session);
|
|
421
427
|
} catch (err) {
|
|
422
|
-
this.log.error(`Error in onSessionFailed callback for ${session.
|
|
428
|
+
this.log.error(`Error in onSessionFailed callback for epoch ${session.getEpochNumber()}`, err);
|
|
423
429
|
}
|
|
424
430
|
}
|
|
425
431
|
}
|
|
426
432
|
|
|
427
433
|
/**
|
|
428
|
-
* Builds the EpochProvingJobData snapshot for failure upload
|
|
429
|
-
*
|
|
430
|
-
* partial state is still useful for post-mortem analysis.
|
|
434
|
+
* Builds the EpochProvingJobData snapshot for a post-mortem failure upload from a set of
|
|
435
|
+
* checkpoint provers. Includes every checkpoint regardless of whether sub-tree proving
|
|
436
|
+
* completed — partial state is still useful for post-mortem analysis.
|
|
431
437
|
*/
|
|
432
|
-
public static
|
|
433
|
-
|
|
438
|
+
public static async buildProvingData(checkpoints: readonly CheckpointProver[]): Promise<EpochProvingJobData> {
|
|
439
|
+
if (checkpoints.length === 0) {
|
|
440
|
+
throw new Error('Cannot build proving data from an empty checkpoint set');
|
|
441
|
+
}
|
|
442
|
+
// Provers no longer cache their txs; re-fetch each checkpoint's txs from the tx pool (concurrently)
|
|
443
|
+
// for the snapshot. The pool retains them past the proving window (A-1274), so this is durable.
|
|
444
|
+
const perCheckpoint = await Promise.all(checkpoints.map(async c => ({ c, txs: await c.getTxsForUpload() })));
|
|
434
445
|
const txs = new Map();
|
|
435
446
|
const l1ToL2Messages: Record<number, Fr[]> = {};
|
|
436
|
-
for (const c of
|
|
437
|
-
for (const [hash, tx] of
|
|
447
|
+
for (const { c, txs: checkpointTxs } of perCheckpoint) {
|
|
448
|
+
for (const [hash, tx] of checkpointTxs) {
|
|
438
449
|
txs.set(hash, tx);
|
|
439
450
|
}
|
|
440
451
|
l1ToL2Messages[c.checkpoint.number] = c.l1ToL2Messages;
|
|
441
452
|
}
|
|
442
453
|
return {
|
|
443
|
-
epochNumber:
|
|
454
|
+
epochNumber: checkpoints[0].epochNumber,
|
|
444
455
|
checkpoints: checkpoints.map(c => c.checkpoint),
|
|
445
456
|
txs,
|
|
446
457
|
l1ToL2Messages,
|
|
@@ -463,20 +474,12 @@ export class SessionManager {
|
|
|
463
474
|
/**
|
|
464
475
|
* Maps a reconcile trigger to the epochs whose full session should be (re)opened.
|
|
465
476
|
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
* - `checkpoint` and `prune` are deliberately NOT gated. They only fire when the epoch's canonical
|
|
473
|
-
* content actually changes — a checkpoint arrives, or a reorg prunes/replaces one — which is
|
|
474
|
-
* exactly when re-attempting is correct.
|
|
475
|
-
*
|
|
476
|
-
* A genuine proving failure produces no content change, hence no checkpoint/prune event, so only
|
|
477
|
-
* the gated tick could reopen it — and it won't. A prune + re-add fires ungated events, so the
|
|
478
|
-
* epoch is reopened through this path (and `openFullSessionIfReady` rebuilds over the fresh
|
|
479
|
-
* provers). See the "onTick does not retry ... but recovers ... re-added" test.
|
|
477
|
+
* The periodic `tick` returns the next unproven epoch every time; it does not track prior attempts.
|
|
478
|
+
* `openFullSessionIfReady` is what keeps this from re-proving a doomed epoch: it refuses to build a
|
|
479
|
+
* session when any checkpoint prover in the set has failed, so a stuck epoch is cheaply skipped each
|
|
480
|
+
* tick rather than re-proved. `checkpoint` and `prune` fire when an epoch's canonical content changes
|
|
481
|
+
* (a checkpoint arrives, or a reorg prunes/replaces one) — which is what installs a fresh prover in
|
|
482
|
+
* place of a failed one, letting the next open succeed and recovering a pruned-then-re-added epoch.
|
|
480
483
|
*/
|
|
481
484
|
private async epochsForTrigger(trigger: ReconcileTrigger): Promise<EpochNumber[]> {
|
|
482
485
|
switch (trigger.kind) {
|
|
@@ -486,10 +489,7 @@ export class SessionManager {
|
|
|
486
489
|
return trigger.affectedEpochs;
|
|
487
490
|
case 'tick': {
|
|
488
491
|
const epoch = await this.nextUnprovenEpoch();
|
|
489
|
-
|
|
490
|
-
return [];
|
|
491
|
-
}
|
|
492
|
-
return [epoch];
|
|
492
|
+
return epoch === undefined ? [] : [epoch];
|
|
493
493
|
}
|
|
494
494
|
case 'start-proof':
|
|
495
495
|
return [];
|
|
@@ -516,6 +516,15 @@ export class SessionManager {
|
|
|
516
516
|
return this.deps.checkpointStore.listInSlotRange(spec.fromSlot, spec.toSlot);
|
|
517
517
|
}
|
|
518
518
|
|
|
519
|
+
/**
|
|
520
|
+
* True if any prover in the set has failed. The epoch cannot be proven over a failed prover (it can
|
|
521
|
+
* never produce its block proofs), so a session must not be built or rebuilt over it until a prune/re-add
|
|
522
|
+
* has replaced it with a fresh prover.
|
|
523
|
+
*/
|
|
524
|
+
private hasFailedProver(checkpoints: readonly CheckpointProver[]): boolean {
|
|
525
|
+
return checkpoints.some(c => c.isFailed());
|
|
526
|
+
}
|
|
527
|
+
|
|
519
528
|
private fireAndForgetCancel(session: EpochSession, reason: string): void {
|
|
520
529
|
void session.cancel(reason).catch(err => this.log.warn(`Error cancelling session ${session.getId()}`, err));
|
|
521
530
|
}
|