@aztec/prover-node 0.0.1-commit.3100065 → 0.0.1-commit.321f6a9
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 +34 -4
- package/dest/job/checkpoint-prover.d.ts.map +1 -1
- package/dest/job/checkpoint-prover.js +41 -8
- 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 +67 -63
- 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 +61 -7
- 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 +73 -67
package/dest/session-manager.js
CHANGED
|
@@ -24,14 +24,6 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
24
24
|
* freshly-constructed session.
|
|
25
25
|
*/ reconcileQueue;
|
|
26
26
|
/** Cached L1 constants, populated on first read. */ cachedL1Constants;
|
|
27
|
-
/**
|
|
28
|
-
* Highest epoch for which the periodic tick has successfully created a full session.
|
|
29
|
-
* Monotonic high-water mark: once the tick observes a session for epoch X, it stops
|
|
30
|
-
* trying to open one — even if that session subsequently fails (only a new checkpoint
|
|
31
|
-
* event reopens it). Crucially, the mark only advances when a session actually exists
|
|
32
|
-
* post-open, so transient blockers (atMaxSessionLimit, archiver still indexing) leave
|
|
33
|
-
* the mark in place and the next tick retries.
|
|
34
|
-
*/ lastTickEpoch;
|
|
35
27
|
/** Test-only hooks applied to every session this manager constructs. */ sessionHooks;
|
|
36
28
|
/** Periodic tick that nudges reconcile to pick up newly-complete epochs. Started by `start()`. */ epochTicker;
|
|
37
29
|
constructor(deps){
|
|
@@ -150,7 +142,11 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
150
142
|
await this.epochTicker?.stop();
|
|
151
143
|
await this.reconcileQueue.cancel();
|
|
152
144
|
const sessions = this.allSessions();
|
|
153
|
-
|
|
145
|
+
// A clean shutdown is just a restart, so preserve the in-flight broker jobs (abortJobs: false)
|
|
146
|
+
// for the restarted node to reuse rather than re-proving the epoch from scratch.
|
|
147
|
+
await Promise.allSettled(sessions.map((s)=>s.cancel('prover-node stopping', {
|
|
148
|
+
abortJobs: false
|
|
149
|
+
})));
|
|
154
150
|
}
|
|
155
151
|
// ---------------- reconcile ----------------
|
|
156
152
|
scheduleReconcile(trigger) {
|
|
@@ -165,31 +161,34 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
165
161
|
for (const epoch of implicatedEpochs){
|
|
166
162
|
await this.openFullSessionIfReady(epoch);
|
|
167
163
|
}
|
|
168
|
-
// Advance the tick high-water mark only once a session actually exists for the epoch.
|
|
169
|
-
// `openFullSessionIfReady` can early-return without creating one (atMaxSessionLimit,
|
|
170
|
-
// archiver still indexing, etc.); in those cases we want the next tick to try again
|
|
171
|
-
// rather than skip the epoch forever.
|
|
172
|
-
if (trigger.kind === 'tick' && implicatedEpochs.length === 1) {
|
|
173
|
-
const epoch = implicatedEpochs[0];
|
|
174
|
-
if (this.fullSessions.has(epoch)) {
|
|
175
|
-
this.lastTickEpoch = epoch;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
164
|
if (trigger.kind === 'start-proof') {
|
|
179
165
|
this.openPartialSession(trigger.spec);
|
|
180
166
|
}
|
|
181
167
|
}
|
|
182
168
|
recreateInvalidSessions() {
|
|
183
169
|
for (const [key, session] of Array.from(this.fullSessions.entries())){
|
|
170
|
+
const canonical = this.checkpointsForSpec(session.getSpec());
|
|
171
|
+
const contentChanged = !this.checkpointsMatch(session.getCheckpoints(), canonical);
|
|
184
172
|
if (session.isTerminal()) {
|
|
173
|
+
// A full session that failed on its own account is retained as a "do not re-prove" marker while
|
|
174
|
+
// its content is unchanged — this is what stops the tick re-proving a deterministically-failing
|
|
175
|
+
// epoch. When the content changes (a re-add), it is replaced so the epoch retries over the new
|
|
176
|
+
// provers. Any other terminal full session is simply dropped.
|
|
177
|
+
if (session.hasFailed() && !contentChanged) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
185
180
|
this.fullSessions.delete(key);
|
|
181
|
+
if (contentChanged && this.canBuildOver(canonical)) {
|
|
182
|
+
const newSession = this.constructSession(session.getSpec(), canonical);
|
|
183
|
+
this.fullSessions.set(key, newSession);
|
|
184
|
+
void this.runSession(newSession);
|
|
185
|
+
}
|
|
186
186
|
continue;
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
if (!this.checkpointsMatch(session.getCheckpoints(), canonical)) {
|
|
188
|
+
if (contentChanged) {
|
|
190
189
|
this.fireAndForgetCancel(session, 'canonical content changed');
|
|
191
190
|
this.fullSessions.delete(key);
|
|
192
|
-
if (canonical
|
|
191
|
+
if (this.canBuildOver(canonical)) {
|
|
193
192
|
const newSession = this.constructSession(session.getSpec(), canonical);
|
|
194
193
|
this.fullSessions.set(key, newSession);
|
|
195
194
|
void this.runSession(newSession);
|
|
@@ -205,7 +204,7 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
205
204
|
if (!this.checkpointsMatch(session.getCheckpoints(), canonical)) {
|
|
206
205
|
this.fireAndForgetCancel(session, 'canonical content changed');
|
|
207
206
|
this.partialSessions.delete(key);
|
|
208
|
-
if (canonical
|
|
207
|
+
if (this.canBuildOver(canonical)) {
|
|
209
208
|
const newSession = this.constructSession(session.getSpec(), canonical);
|
|
210
209
|
this.partialSessions.set(key, newSession);
|
|
211
210
|
void this.runSession(newSession);
|
|
@@ -213,9 +212,13 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
213
212
|
}
|
|
214
213
|
}
|
|
215
214
|
}
|
|
215
|
+
/** A session may be built over a checkpoint set only when it is non-empty and contains no failed prover. */ canBuildOver(canonical) {
|
|
216
|
+
return canonical.length > 0 && !this.hasFailedProver(canonical);
|
|
217
|
+
}
|
|
216
218
|
async openFullSessionIfReady(epoch) {
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
+
// A session present here already covers the epoch: either live, or a retained genuinely-failed
|
|
220
|
+
// session kept by `recreateInvalidSessions` as a "do not re-prove" marker. Either way, don't open
|
|
221
|
+
// another — the retained-failed one is replaced only when its canonical content changes.
|
|
219
222
|
if (this.fullSessions.has(epoch)) {
|
|
220
223
|
return;
|
|
221
224
|
}
|
|
@@ -242,6 +245,15 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
242
245
|
});
|
|
243
246
|
return;
|
|
244
247
|
}
|
|
248
|
+
if (this.hasFailedProver(canonical)) {
|
|
249
|
+
// A checkpoint prover in the set has failed (a sub-tree fault or a prune-induced fork fault), so a
|
|
250
|
+
// session over it would fail immediately. Don't re-create it every tick — it recovers when a
|
|
251
|
+
// prune/re-add replaces the failed prover with a fresh one, or fails for good at expiry.
|
|
252
|
+
this.log.debug(`Skipping full-session open for epoch ${epoch}: a checkpoint prover has failed`, {
|
|
253
|
+
epoch
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
245
257
|
const spec = {
|
|
246
258
|
kind: 'full',
|
|
247
259
|
epochNumber: epoch,
|
|
@@ -254,7 +266,7 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
254
266
|
}
|
|
255
267
|
openPartialSession(spec) {
|
|
256
268
|
const canonical = this.deps.checkpointStore.listInSlotRange(spec.fromSlot, spec.toSlot);
|
|
257
|
-
if (canonical.length === 0) {
|
|
269
|
+
if (canonical.length === 0 || this.hasFailedProver(canonical)) {
|
|
258
270
|
return;
|
|
259
271
|
}
|
|
260
272
|
// Reuse a live partial session for this epoch whose checkpoint set already matches the
|
|
@@ -312,31 +324,27 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
312
324
|
}
|
|
313
325
|
const state = await session.start();
|
|
314
326
|
this.log.info(`Session ${session.getId()} exited with state ${state}`);
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (!this.checkpointsMatch(session.getCheckpoints(), this.checkpointsForSpec(session.getSpec()))) {
|
|
322
|
-
this.log.info(`Skipping failure upload for session ${session.getId()}: canonical content changed`, {
|
|
323
|
-
...session.getSpec()
|
|
324
|
-
});
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
+
// A full session that failed on its own account (top-tree/submit failed with every prover healthy)
|
|
328
|
+
// is a genuine, race-free failure: upload its post-mortem once. `recreateInvalidSessions` retains
|
|
329
|
+
// the terminal session so this fires exactly once (it is never re-run over the same content). A
|
|
330
|
+
// `stopped` session (a prover under it failed) is not uploaded — it may be a prune, and recovers on
|
|
331
|
+
// re-add.
|
|
332
|
+
if (session.getKind() === 'full' && session.hasFailed() && this.deps.onSessionFailed) {
|
|
327
333
|
try {
|
|
328
334
|
await this.deps.onSessionFailed(session);
|
|
329
335
|
} catch (err) {
|
|
330
|
-
this.log.error(`Error in onSessionFailed callback for ${session.
|
|
336
|
+
this.log.error(`Error in onSessionFailed callback for epoch ${session.getEpochNumber()}`, err);
|
|
331
337
|
}
|
|
332
338
|
}
|
|
333
339
|
}
|
|
334
340
|
/**
|
|
335
|
-
* Builds the EpochProvingJobData snapshot for failure upload
|
|
336
|
-
*
|
|
337
|
-
* partial state is still useful for post-mortem analysis.
|
|
338
|
-
*/ static
|
|
339
|
-
|
|
341
|
+
* Builds the EpochProvingJobData snapshot for a post-mortem failure upload from a set of
|
|
342
|
+
* checkpoint provers. Includes every checkpoint regardless of whether sub-tree proving
|
|
343
|
+
* completed — partial state is still useful for post-mortem analysis.
|
|
344
|
+
*/ static buildProvingData(checkpoints) {
|
|
345
|
+
if (checkpoints.length === 0) {
|
|
346
|
+
throw new Error('Cannot build proving data from an empty checkpoint set');
|
|
347
|
+
}
|
|
340
348
|
const txs = new Map();
|
|
341
349
|
const l1ToL2Messages = {};
|
|
342
350
|
for (const c of checkpoints){
|
|
@@ -346,7 +354,7 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
346
354
|
l1ToL2Messages[c.checkpoint.number] = c.l1ToL2Messages;
|
|
347
355
|
}
|
|
348
356
|
return {
|
|
349
|
-
epochNumber:
|
|
357
|
+
epochNumber: checkpoints[0].epochNumber,
|
|
350
358
|
checkpoints: checkpoints.map((c)=>c.checkpoint),
|
|
351
359
|
txs,
|
|
352
360
|
l1ToL2Messages,
|
|
@@ -366,20 +374,12 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
366
374
|
/**
|
|
367
375
|
* Maps a reconcile trigger to the epochs whose full session should be (re)opened.
|
|
368
376
|
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* - `checkpoint` and `prune` are deliberately NOT gated. They only fire when the epoch's canonical
|
|
376
|
-
* content actually changes — a checkpoint arrives, or a reorg prunes/replaces one — which is
|
|
377
|
-
* exactly when re-attempting is correct.
|
|
378
|
-
*
|
|
379
|
-
* A genuine proving failure produces no content change, hence no checkpoint/prune event, so only
|
|
380
|
-
* the gated tick could reopen it — and it won't. A prune + re-add fires ungated events, so the
|
|
381
|
-
* epoch is reopened through this path (and `openFullSessionIfReady` rebuilds over the fresh
|
|
382
|
-
* provers). See the "onTick does not retry ... but recovers ... re-added" test.
|
|
377
|
+
* The periodic `tick` returns the next unproven epoch every time; it does not track prior attempts.
|
|
378
|
+
* `openFullSessionIfReady` is what keeps this from re-proving a doomed epoch: it refuses to build a
|
|
379
|
+
* session when any checkpoint prover in the set has failed, so a stuck epoch is cheaply skipped each
|
|
380
|
+
* tick rather than re-proved. `checkpoint` and `prune` fire when an epoch's canonical content changes
|
|
381
|
+
* (a checkpoint arrives, or a reorg prunes/replaces one) — which is what installs a fresh prover in
|
|
382
|
+
* place of a failed one, letting the next open succeed and recovering a pruned-then-re-added epoch.
|
|
383
383
|
*/ async epochsForTrigger(trigger) {
|
|
384
384
|
switch(trigger.kind){
|
|
385
385
|
case 'checkpoint':
|
|
@@ -391,10 +391,7 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
391
391
|
case 'tick':
|
|
392
392
|
{
|
|
393
393
|
const epoch = await this.nextUnprovenEpoch();
|
|
394
|
-
|
|
395
|
-
return [];
|
|
396
|
-
}
|
|
397
|
-
return [
|
|
394
|
+
return epoch === undefined ? [] : [
|
|
398
395
|
epoch
|
|
399
396
|
];
|
|
400
397
|
}
|
|
@@ -423,6 +420,13 @@ import { EpochSession, specKey } from './job/epoch-session.js';
|
|
|
423
420
|
checkpointsForSpec(spec) {
|
|
424
421
|
return this.deps.checkpointStore.listInSlotRange(spec.fromSlot, spec.toSlot);
|
|
425
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* True if any prover in the set has failed. The epoch cannot be proven over a failed prover (it can
|
|
425
|
+
* never produce its block proofs), so a session must not be built or rebuilt over it until a prune/re-add
|
|
426
|
+
* has replaced it with a fresh prover.
|
|
427
|
+
*/ hasFailedProver(checkpoints) {
|
|
428
|
+
return checkpoints.some((c)=>c.isFailed());
|
|
429
|
+
}
|
|
426
430
|
fireAndForgetCancel(session, reason) {
|
|
427
431
|
void session.cancel(reason).catch((err)=>this.log.warn(`Error cancelling session ${session.getId()}`, err));
|
|
428
432
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/prover-node",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.321f6a9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -56,28 +56,29 @@
|
|
|
56
56
|
]
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@aztec/archiver": "0.0.1-commit.
|
|
60
|
-
"@aztec/bb-prover": "0.0.1-commit.
|
|
61
|
-
"@aztec/
|
|
62
|
-
"@aztec/blob-
|
|
63
|
-
"@aztec/
|
|
64
|
-
"@aztec/
|
|
65
|
-
"@aztec/
|
|
66
|
-
"@aztec/
|
|
67
|
-
"@aztec/
|
|
68
|
-
"@aztec/
|
|
69
|
-
"@aztec/
|
|
70
|
-
"@aztec/
|
|
71
|
-
"@aztec/node-
|
|
72
|
-
"@aztec/
|
|
73
|
-
"@aztec/
|
|
74
|
-
"@aztec/
|
|
75
|
-
"@aztec/
|
|
76
|
-
"@aztec/
|
|
77
|
-
"@aztec/
|
|
78
|
-
"@aztec/
|
|
79
|
-
"@aztec/
|
|
80
|
-
"@aztec/
|
|
59
|
+
"@aztec/archiver": "0.0.1-commit.321f6a9",
|
|
60
|
+
"@aztec/bb-prover": "0.0.1-commit.321f6a9",
|
|
61
|
+
"@aztec/bb.js": "0.0.1-commit.321f6a9",
|
|
62
|
+
"@aztec/blob-client": "0.0.1-commit.321f6a9",
|
|
63
|
+
"@aztec/blob-lib": "0.0.1-commit.321f6a9",
|
|
64
|
+
"@aztec/constants": "0.0.1-commit.321f6a9",
|
|
65
|
+
"@aztec/epoch-cache": "0.0.1-commit.321f6a9",
|
|
66
|
+
"@aztec/ethereum": "0.0.1-commit.321f6a9",
|
|
67
|
+
"@aztec/foundation": "0.0.1-commit.321f6a9",
|
|
68
|
+
"@aztec/kv-store": "0.0.1-commit.321f6a9",
|
|
69
|
+
"@aztec/l1-artifacts": "0.0.1-commit.321f6a9",
|
|
70
|
+
"@aztec/native": "0.0.1-commit.321f6a9",
|
|
71
|
+
"@aztec/node-keystore": "0.0.1-commit.321f6a9",
|
|
72
|
+
"@aztec/node-lib": "0.0.1-commit.321f6a9",
|
|
73
|
+
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.321f6a9",
|
|
74
|
+
"@aztec/p2p": "0.0.1-commit.321f6a9",
|
|
75
|
+
"@aztec/protocol-contracts": "0.0.1-commit.321f6a9",
|
|
76
|
+
"@aztec/prover-client": "0.0.1-commit.321f6a9",
|
|
77
|
+
"@aztec/sequencer-client": "0.0.1-commit.321f6a9",
|
|
78
|
+
"@aztec/simulator": "0.0.1-commit.321f6a9",
|
|
79
|
+
"@aztec/stdlib": "0.0.1-commit.321f6a9",
|
|
80
|
+
"@aztec/telemetry-client": "0.0.1-commit.321f6a9",
|
|
81
|
+
"@aztec/world-state": "0.0.1-commit.321f6a9",
|
|
81
82
|
"source-map-support": "^0.5.21",
|
|
82
83
|
"tslib": "^2.4.0",
|
|
83
84
|
"viem": "npm:@aztec/viem@2.38.2"
|
|
@@ -7,7 +7,7 @@ import { type ProverClientConfig, createProverClient } from '@aztec/prover-clien
|
|
|
7
7
|
import { ProverBrokerConfig, createAndStartProvingBroker } from '@aztec/prover-client/broker';
|
|
8
8
|
import { getLastSiblingPath } from '@aztec/prover-client/helpers';
|
|
9
9
|
import { ChonkCache } from '@aztec/prover-client/orchestrator';
|
|
10
|
-
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
10
|
+
import { AvmSimulatorPool, PublicProcessorFactory } from '@aztec/simulator/server';
|
|
11
11
|
import type { L2Block } from '@aztec/stdlib/block';
|
|
12
12
|
import { getEpochAtSlot, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
|
|
13
13
|
import type { ITxProvider } from '@aztec/stdlib/interfaces/server';
|
|
@@ -21,35 +21,30 @@ import { createWorldState } from '@aztec/world-state';
|
|
|
21
21
|
import { readFileSync } from 'fs';
|
|
22
22
|
|
|
23
23
|
import { CheckpointProver } from '../job/checkpoint-prover.js';
|
|
24
|
-
import { deserializeEpochProvingJobData } from '../job/epoch-proving-job-data.js';
|
|
24
|
+
import { type EpochProvingJobData, deserializeEpochProvingJobData } from '../job/epoch-proving-job-data.js';
|
|
25
25
|
import { EpochSession, type SessionSpec } from '../job/epoch-session.js';
|
|
26
26
|
import { ProverNodeJobMetrics } from '../metrics.js';
|
|
27
27
|
|
|
28
|
+
type RerunConfig = DataStoreConfig &
|
|
29
|
+
ProverBrokerConfig &
|
|
30
|
+
ProverClientConfig &
|
|
31
|
+
Pick<L1ContractsConfig, 'aztecEpochDuration'>;
|
|
32
|
+
|
|
28
33
|
/**
|
|
29
34
|
* Given a local folder where `downloadEpochProvingJob` was called, creates a new archiver and world state
|
|
30
35
|
* using the state snapshots, and creates a new epoch proving session to prove the downloaded proving job.
|
|
31
36
|
* Proving is done with a local proving broker and agents as specified by the config.
|
|
32
37
|
*/
|
|
33
|
-
export async function rerunEpochProvingJob(
|
|
34
|
-
localPath
|
|
35
|
-
|
|
36
|
-
config: DataStoreConfig & ProverBrokerConfig & ProverClientConfig & Pick<L1ContractsConfig, 'aztecEpochDuration'>,
|
|
37
|
-
genesis?: GenesisData,
|
|
38
|
-
) {
|
|
39
|
-
const jobData = deserializeEpochProvingJobData(readFileSync(localPath));
|
|
40
|
-
log.info(`Loaded proving job data for epoch ${jobData.epochNumber}`);
|
|
38
|
+
export async function rerunEpochProvingJob(localPath: string, log: Logger, config: RerunConfig, genesis?: GenesisData) {
|
|
39
|
+
await using ctx = await createRerunContext(localPath, log, config, genesis);
|
|
40
|
+
const { jobData, prover, metrics } = ctx;
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
createContractDataSource(archiver),
|
|
49
|
-
undefined,
|
|
50
|
-
undefined,
|
|
51
|
-
log.getBindings(),
|
|
52
|
-
);
|
|
42
|
+
log.info(`Rerunning epoch proving for epoch ${jobData.epochNumber}`);
|
|
43
|
+
|
|
44
|
+
const provers: CheckpointProver[] = [];
|
|
45
|
+
for (let i = 0; i < jobData.checkpoints.length; i++) {
|
|
46
|
+
provers.push(await buildCheckpointProver(ctx, i, log));
|
|
47
|
+
}
|
|
53
48
|
|
|
54
49
|
// Local rerun never publishes — stub the service so submit() always resolves 'published'
|
|
55
50
|
// and withdraw is a no-op.
|
|
@@ -57,51 +52,6 @@ export async function rerunEpochProvingJob(
|
|
|
57
52
|
submit: () => Promise.resolve('published' as const),
|
|
58
53
|
withdraw: () => {},
|
|
59
54
|
};
|
|
60
|
-
const broker = await createAndStartProvingBroker(config, telemetry);
|
|
61
|
-
const prover = await createProverClient(config, worldState, broker, telemetry);
|
|
62
|
-
const chonkCache = new ChonkCache(log.getBindings());
|
|
63
|
-
|
|
64
|
-
const txProvider = makeReplayingTxProvider(jobData.txs);
|
|
65
|
-
|
|
66
|
-
log.info(`Rerunning epoch proving for epoch ${jobData.epochNumber}`);
|
|
67
|
-
|
|
68
|
-
const provers: CheckpointProver[] = [];
|
|
69
|
-
for (let i = 0; i < jobData.checkpoints.length; i++) {
|
|
70
|
-
const checkpoint = jobData.checkpoints[i];
|
|
71
|
-
const previousBlockHeader =
|
|
72
|
-
i === 0 ? jobData.previousBlockHeader : jobData.checkpoints[i - 1].blocks.at(-1)!.header;
|
|
73
|
-
const l1ToL2Messages = jobData.l1ToL2Messages[checkpoint.number] ?? [];
|
|
74
|
-
const previousArchiveSiblingPath = await getLastSiblingPath(
|
|
75
|
-
MerkleTreeId.ARCHIVE,
|
|
76
|
-
worldState.getSnapshot(BlockNumber(checkpoint.blocks[0].number - 1)),
|
|
77
|
-
);
|
|
78
|
-
const attestations = checkpoint.number === jobData.checkpoints.at(-1)!.number ? jobData.attestations : [];
|
|
79
|
-
provers.push(
|
|
80
|
-
new CheckpointProver(
|
|
81
|
-
{
|
|
82
|
-
checkpoint,
|
|
83
|
-
epochNumber: jobData.epochNumber,
|
|
84
|
-
attestations,
|
|
85
|
-
previousBlockHeader,
|
|
86
|
-
l1ToL2Messages,
|
|
87
|
-
previousArchiveSiblingPath,
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
proverFactory: prover,
|
|
91
|
-
chonkCache,
|
|
92
|
-
publicProcessorFactory,
|
|
93
|
-
dbProvider: worldState,
|
|
94
|
-
txProvider,
|
|
95
|
-
dateProvider: new DateProvider(),
|
|
96
|
-
proverId: prover.getProverId(),
|
|
97
|
-
metrics,
|
|
98
|
-
txGatheringTimeoutMs: 120_000,
|
|
99
|
-
deadline: undefined,
|
|
100
|
-
log,
|
|
101
|
-
},
|
|
102
|
-
),
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
55
|
|
|
106
56
|
const l1Constants = { epochDuration: config.aztecEpochDuration };
|
|
107
57
|
const [fromSlot, toSlot] = getSlotRangeForEpoch(jobData.epochNumber, l1Constants);
|
|
@@ -125,6 +75,129 @@ export async function rerunEpochProvingJob(
|
|
|
125
75
|
return finalState;
|
|
126
76
|
}
|
|
127
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Re-proves a single downloaded checkpoint proving job (as uploaded by a `CheckpointProver` failure).
|
|
80
|
+
* Reconstructs just that checkpoint's sub-tree prover from the snapshot and awaits its block proofs — no
|
|
81
|
+
* epoch top-tree, no L1 submission — so a checkpoint-level failure can be reproduced offline in isolation.
|
|
82
|
+
* Returns the block-rollup proof outputs on success; throws if the checkpoint fails to prove again.
|
|
83
|
+
*/
|
|
84
|
+
export async function rerunCheckpointProvingJob(
|
|
85
|
+
localPath: string,
|
|
86
|
+
log: Logger,
|
|
87
|
+
config: RerunConfig,
|
|
88
|
+
genesis?: GenesisData,
|
|
89
|
+
) {
|
|
90
|
+
await using ctx = await createRerunContext(localPath, log, config, genesis);
|
|
91
|
+
const { jobData } = ctx;
|
|
92
|
+
const checkpointNumber = jobData.checkpoints[0].number;
|
|
93
|
+
|
|
94
|
+
log.info(`Rerunning checkpoint proving for checkpoint ${checkpointNumber} (epoch ${jobData.epochNumber})`);
|
|
95
|
+
|
|
96
|
+
const prover = await buildCheckpointProver(ctx, 0, log);
|
|
97
|
+
try {
|
|
98
|
+
const blockProofs = await prover.whenBlockProofsReady();
|
|
99
|
+
log.info(`Completed proving for checkpoint ${checkpointNumber} with ${blockProofs.length} block proof(s)`);
|
|
100
|
+
return blockProofs;
|
|
101
|
+
} finally {
|
|
102
|
+
prover.cancel({ routine: true });
|
|
103
|
+
await prover.whenDone();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Everything a rerun needs, reconstructed from the downloaded snapshot + job data. */
|
|
108
|
+
type RerunContext = {
|
|
109
|
+
jobData: EpochProvingJobData;
|
|
110
|
+
metrics: ProverNodeJobMetrics;
|
|
111
|
+
worldState: Awaited<ReturnType<typeof createWorldState>>;
|
|
112
|
+
publicProcessorFactory: PublicProcessorFactory;
|
|
113
|
+
prover: Awaited<ReturnType<typeof createProverClient>>;
|
|
114
|
+
chonkCache: ChonkCache;
|
|
115
|
+
txProvider: ITxProvider;
|
|
116
|
+
} & AsyncDisposable;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Rebuilds the offline proving environment from a downloaded job: world state + archiver from the
|
|
120
|
+
* snapshots, a local proving broker + client, a chonk cache, and a tx provider that replays the job's txs.
|
|
121
|
+
*/
|
|
122
|
+
async function createRerunContext(
|
|
123
|
+
localPath: string,
|
|
124
|
+
log: Logger,
|
|
125
|
+
config: RerunConfig,
|
|
126
|
+
genesis?: GenesisData,
|
|
127
|
+
): Promise<RerunContext> {
|
|
128
|
+
const jobData = deserializeEpochProvingJobData(readFileSync(localPath));
|
|
129
|
+
log.info(`Loaded proving job data for epoch ${jobData.epochNumber}`);
|
|
130
|
+
|
|
131
|
+
const telemetry = getTelemetryClient();
|
|
132
|
+
const metrics = new ProverNodeJobMetrics(telemetry.getMeter('prover-job'), telemetry.getTracer('prover-job'));
|
|
133
|
+
const worldState = await createWorldState(config, genesis);
|
|
134
|
+
const initialBlockHash = await worldState.getInitialHeader().hash();
|
|
135
|
+
const archiver = await createArchiverStore(config, initialBlockHash);
|
|
136
|
+
const avmSimulator = await AvmSimulatorPool.spawn({ wsdbIpcPath: worldState.getIpcPath() });
|
|
137
|
+
const publicProcessorFactory = new PublicProcessorFactory(
|
|
138
|
+
createContractDataSource(archiver),
|
|
139
|
+
avmSimulator,
|
|
140
|
+
undefined,
|
|
141
|
+
undefined,
|
|
142
|
+
log.getBindings(),
|
|
143
|
+
);
|
|
144
|
+
const broker = await createAndStartProvingBroker(config, telemetry);
|
|
145
|
+
const prover = await createProverClient(config, worldState, broker, telemetry);
|
|
146
|
+
const chonkCache = new ChonkCache(log.getBindings());
|
|
147
|
+
const txProvider = makeReplayingTxProvider(jobData.txs);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
jobData,
|
|
151
|
+
metrics,
|
|
152
|
+
worldState,
|
|
153
|
+
publicProcessorFactory,
|
|
154
|
+
prover,
|
|
155
|
+
chonkCache,
|
|
156
|
+
txProvider,
|
|
157
|
+
async [Symbol.asyncDispose]() {
|
|
158
|
+
await avmSimulator[Symbol.asyncDispose]();
|
|
159
|
+
await worldState[Symbol.asyncDispose]();
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Reconstructs the `CheckpointProver` for the checkpoint at `index` in the job, ready to prove. */
|
|
165
|
+
async function buildCheckpointProver(ctx: RerunContext, index: number, log: Logger): Promise<CheckpointProver> {
|
|
166
|
+
const { jobData, worldState, prover, chonkCache, publicProcessorFactory, txProvider, metrics } = ctx;
|
|
167
|
+
const checkpoint = jobData.checkpoints[index];
|
|
168
|
+
const previousBlockHeader =
|
|
169
|
+
index === 0 ? jobData.previousBlockHeader : jobData.checkpoints[index - 1].blocks.at(-1)!.header;
|
|
170
|
+
const l1ToL2Messages = jobData.l1ToL2Messages[checkpoint.number] ?? [];
|
|
171
|
+
const previousArchiveSiblingPath = await getLastSiblingPath(
|
|
172
|
+
MerkleTreeId.ARCHIVE,
|
|
173
|
+
worldState.getSnapshot(BlockNumber(checkpoint.blocks[0].number - 1)),
|
|
174
|
+
);
|
|
175
|
+
const attestations = checkpoint.number === jobData.checkpoints.at(-1)!.number ? jobData.attestations : [];
|
|
176
|
+
return new CheckpointProver(
|
|
177
|
+
{
|
|
178
|
+
checkpoint,
|
|
179
|
+
epochNumber: jobData.epochNumber,
|
|
180
|
+
attestations,
|
|
181
|
+
previousBlockHeader,
|
|
182
|
+
l1ToL2Messages,
|
|
183
|
+
previousArchiveSiblingPath,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
proverFactory: prover,
|
|
187
|
+
chonkCache,
|
|
188
|
+
publicProcessorFactory,
|
|
189
|
+
dbProvider: worldState,
|
|
190
|
+
txProvider,
|
|
191
|
+
dateProvider: new DateProvider(),
|
|
192
|
+
proverId: prover.getProverId(),
|
|
193
|
+
metrics,
|
|
194
|
+
txGatheringTimeoutMs: 120_000,
|
|
195
|
+
deadline: undefined,
|
|
196
|
+
log,
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
128
201
|
/** Build a synthetic ITxProvider that returns the supplied txs map by lookup. */
|
|
129
202
|
function makeReplayingTxProvider(txs: Map<string, Tx>): ITxProvider {
|
|
130
203
|
const lookup = (hashes: TxHash[]) => {
|
package/src/checkpoint-store.ts
CHANGED
|
@@ -4,7 +4,12 @@ import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
|
4
4
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
5
5
|
import { type L1RollupConstants, getEpochAtSlot, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
CheckpointProver,
|
|
9
|
+
type CheckpointProverArgs,
|
|
10
|
+
type CheckpointProverDeps,
|
|
11
|
+
type CheckpointProverTestHooks,
|
|
12
|
+
} from './job/checkpoint-prover.js';
|
|
8
13
|
|
|
9
14
|
/** Register-time data needed to construct a `CheckpointProver` (everything except the checkpoint + epoch). */
|
|
10
15
|
export type RegisterCheckpointData = Omit<CheckpointProverArgs, 'checkpoint' | 'epochNumber'>;
|
|
@@ -35,6 +40,8 @@ export class CheckpointStore {
|
|
|
35
40
|
*/
|
|
36
41
|
private readonly pendingTeardowns = new Map<number, Promise<void>>();
|
|
37
42
|
private nextTeardownId = 0;
|
|
43
|
+
/** Test-only hooks injected into every prover this store constructs. */
|
|
44
|
+
private testHooks: CheckpointProverTestHooks = {};
|
|
38
45
|
private readonly log: Logger;
|
|
39
46
|
|
|
40
47
|
constructor(
|
|
@@ -100,11 +107,22 @@ export class CheckpointStore {
|
|
|
100
107
|
}
|
|
101
108
|
}
|
|
102
109
|
|
|
103
|
-
const prover = this.proverFactoryFn(
|
|
110
|
+
const prover = this.proverFactoryFn(
|
|
111
|
+
{ ...data, checkpoint, epochNumber },
|
|
112
|
+
{ ...this.proverDeps, checkpointProveOverride: this.testHooks.checkpointProveOverride, log: this.log },
|
|
113
|
+
);
|
|
104
114
|
this.provers.set(id, prover);
|
|
105
115
|
return prover;
|
|
106
116
|
}
|
|
107
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Installs test-only hooks applied to every prover constructed from now on. Used by the e2e harness to
|
|
120
|
+
* force a checkpoint sub-tree failure without monkey-patching the prover factory.
|
|
121
|
+
*/
|
|
122
|
+
public setTestHooks(hooks: CheckpointProverTestHooks): void {
|
|
123
|
+
this.testHooks = hooks;
|
|
124
|
+
}
|
|
125
|
+
|
|
108
126
|
/**
|
|
109
127
|
* Cancels and removes every prover that holds a block above the prune target. A checkpoint is orphaned by a prune to
|
|
110
128
|
* block `targetBlockNumber` iff its last block sits above the target — including a checkpoint whose range straddles
|
package/src/config.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
numberConfigHelper,
|
|
7
7
|
pickConfigMappings,
|
|
8
8
|
} from '@aztec/foundation/config';
|
|
9
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
10
|
import { type KeyStoreConfig, keyStoreConfigMappings } from '@aztec/node-keystore/config';
|
|
10
11
|
import { ethPrivateKeySchema } from '@aztec/node-keystore/schemas';
|
|
11
12
|
import type { KeyStore } from '@aztec/node-keystore/types';
|
|
@@ -44,6 +45,7 @@ export type SpecificProverNodeConfig = {
|
|
|
44
45
|
txGatheringIntervalMs: number;
|
|
45
46
|
txGatheringBatchSize: number;
|
|
46
47
|
txGatheringMaxParallelRequestsPerNode: number;
|
|
48
|
+
proofSubmissionTargetAddress?: EthAddress;
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
export const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeConfig> = {
|
|
@@ -97,6 +99,14 @@ export const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProver
|
|
|
97
99
|
description: 'Whether the prover node skips publishing proofs to L1',
|
|
98
100
|
...booleanConfigHelper(false),
|
|
99
101
|
},
|
|
102
|
+
proofSubmissionTargetAddress: {
|
|
103
|
+
env: 'PROVER_NODE_PROOF_SUBMISSION_TARGET_ADDRESS',
|
|
104
|
+
description:
|
|
105
|
+
'Optional L1 address the submitEpochRootProof tx is sent to. Must expose the identical submitEpochRootProof ABI ' +
|
|
106
|
+
'and forward to the rollup. Defaults to the rollup address.',
|
|
107
|
+
parseEnv: (val: string) => EthAddress.fromString(val),
|
|
108
|
+
defaultValue: undefined,
|
|
109
|
+
},
|
|
100
110
|
};
|
|
101
111
|
|
|
102
112
|
export const proverNodeConfigMappings: ConfigMappingsType<ProverNodeConfig> = {
|
package/src/factory.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type ProverTxSenderConfig,
|
|
20
20
|
getPublisherConfigFromProverConfig,
|
|
21
21
|
} from '@aztec/sequencer-client';
|
|
22
|
+
import type { AvmSimulator } from '@aztec/simulator/server';
|
|
22
23
|
import type {
|
|
23
24
|
ITxProvider,
|
|
24
25
|
ProverConfig,
|
|
@@ -47,6 +48,8 @@ export type ProverNodeDeps = {
|
|
|
47
48
|
epochCache: EpochCacheInterface;
|
|
48
49
|
blobClient: BlobClientInterface;
|
|
49
50
|
keyStoreManager?: KeystoreManager;
|
|
51
|
+
/** AVM execution backend (simulator pool + CDB server) for public simulation. */
|
|
52
|
+
avmSimulator: AvmSimulator;
|
|
50
53
|
};
|
|
51
54
|
|
|
52
55
|
/** Creates a new prover node subsystem given a config and dependencies */
|
|
@@ -135,6 +138,7 @@ export async function createProverNode(
|
|
|
135
138
|
deps.publisherFactory ??
|
|
136
139
|
new ProverPublisherFactory(config, {
|
|
137
140
|
rollupContract,
|
|
141
|
+
proofSubmissionTarget: config.proofSubmissionTargetAddress,
|
|
138
142
|
publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), {
|
|
139
143
|
bindings: log.getBindings(),
|
|
140
144
|
funder: funderL1TxUtils,
|
|
@@ -179,6 +183,7 @@ export async function createProverNode(
|
|
|
179
183
|
p2pClient,
|
|
180
184
|
rollupContract,
|
|
181
185
|
l1Metrics,
|
|
186
|
+
deps.avmSimulator,
|
|
182
187
|
proverNodeConfig,
|
|
183
188
|
telemetry,
|
|
184
189
|
delayer,
|