@aztec/prover-node 0.0.1-commit.f504929 → 0.0.1-commit.f5a9928

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.
Files changed (76) hide show
  1. package/README.md +572 -0
  2. package/dest/actions/download-epoch-proving-job.js +1 -1
  3. package/dest/actions/rerun-epoch-proving-job.d.ts +14 -4
  4. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
  5. package/dest/actions/rerun-epoch-proving-job.js +146 -24
  6. package/dest/actions/upload-epoch-proof-failure.d.ts +2 -2
  7. package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -1
  8. package/dest/bin/run-failed-epoch.js +1 -3
  9. package/dest/checkpoint-store.d.ts +95 -0
  10. package/dest/checkpoint-store.d.ts.map +1 -0
  11. package/dest/checkpoint-store.js +178 -0
  12. package/dest/config.d.ts +4 -2
  13. package/dest/config.d.ts.map +1 -1
  14. package/dest/config.js +10 -3
  15. package/dest/factory.d.ts +1 -1
  16. package/dest/factory.d.ts.map +1 -1
  17. package/dest/factory.js +28 -12
  18. package/dest/index.d.ts +2 -1
  19. package/dest/index.d.ts.map +1 -1
  20. package/dest/index.js +1 -0
  21. package/dest/job/checkpoint-prover.d.ts +165 -0
  22. package/dest/job/checkpoint-prover.d.ts.map +1 -0
  23. package/dest/job/checkpoint-prover.js +405 -0
  24. package/dest/job/epoch-session.d.ts +160 -0
  25. package/dest/job/epoch-session.d.ts.map +1 -0
  26. package/dest/job/epoch-session.js +744 -0
  27. package/dest/job/top-tree-job.d.ts +82 -0
  28. package/dest/job/top-tree-job.d.ts.map +1 -0
  29. package/dest/job/top-tree-job.js +152 -0
  30. package/dest/metrics.d.ts +40 -3
  31. package/dest/metrics.d.ts.map +1 -1
  32. package/dest/metrics.js +101 -4
  33. package/dest/monitors/epoch-monitor.d.ts +1 -1
  34. package/dest/monitors/epoch-monitor.d.ts.map +1 -1
  35. package/dest/monitors/epoch-monitor.js +11 -9
  36. package/dest/proof-publishing-service.d.ts +161 -0
  37. package/dest/proof-publishing-service.d.ts.map +1 -0
  38. package/dest/proof-publishing-service.js +335 -0
  39. package/dest/prover-node-publisher.d.ts +25 -15
  40. package/dest/prover-node-publisher.d.ts.map +1 -1
  41. package/dest/prover-node-publisher.js +201 -62
  42. package/dest/prover-node.d.ts +132 -67
  43. package/dest/prover-node.d.ts.map +1 -1
  44. package/dest/prover-node.js +537 -218
  45. package/dest/prover-publisher-factory.d.ts +4 -2
  46. package/dest/prover-publisher-factory.d.ts.map +1 -1
  47. package/dest/prover-publisher-factory.js +4 -3
  48. package/dest/session-manager.d.ts +158 -0
  49. package/dest/session-manager.d.ts.map +1 -0
  50. package/dest/session-manager.js +492 -0
  51. package/dest/test/index.d.ts +7 -6
  52. package/dest/test/index.d.ts.map +1 -1
  53. package/package.json +23 -22
  54. package/src/actions/download-epoch-proving-job.ts +1 -1
  55. package/src/actions/rerun-epoch-proving-job.ts +178 -33
  56. package/src/actions/upload-epoch-proof-failure.ts +1 -1
  57. package/src/bin/run-failed-epoch.ts +1 -2
  58. package/src/checkpoint-store.ts +212 -0
  59. package/src/config.ts +14 -3
  60. package/src/factory.ts +22 -12
  61. package/src/index.ts +1 -0
  62. package/src/job/checkpoint-prover.ts +538 -0
  63. package/src/job/epoch-session.ts +462 -0
  64. package/src/job/top-tree-job.ts +227 -0
  65. package/src/metrics.ts +123 -10
  66. package/src/monitors/epoch-monitor.ts +5 -6
  67. package/src/proof-publishing-service.ts +427 -0
  68. package/src/prover-node-publisher.ts +236 -78
  69. package/src/prover-node.ts +619 -242
  70. package/src/prover-publisher-factory.ts +6 -3
  71. package/src/session-manager.ts +592 -0
  72. package/src/test/index.ts +6 -6
  73. package/dest/job/epoch-proving-job.d.ts +0 -63
  74. package/dest/job/epoch-proving-job.d.ts.map +0 -1
  75. package/dest/job/epoch-proving-job.js +0 -752
  76. package/src/job/epoch-proving-job.ts +0 -449
@@ -1,63 +1,208 @@
1
- import { createArchiverStore } from '@aztec/archiver';
1
+ import { createArchiverStore, createContractDataSource } from '@aztec/archiver';
2
2
  import type { L1ContractsConfig } from '@aztec/ethereum/config';
3
+ import { BlockNumber } from '@aztec/foundation/branded-types';
3
4
  import type { Logger } from '@aztec/foundation/log';
4
- import type { DataStoreConfig } from '@aztec/kv-store/config';
5
+ import { DateProvider } from '@aztec/foundation/timer';
5
6
  import { type ProverClientConfig, createProverClient } from '@aztec/prover-client';
6
7
  import { ProverBrokerConfig, createAndStartProvingBroker } from '@aztec/prover-client/broker';
8
+ import { getLastSiblingPath } from '@aztec/prover-client/helpers';
9
+ import { ChonkCache } from '@aztec/prover-client/orchestrator';
7
10
  import { PublicProcessorFactory } from '@aztec/simulator/server';
11
+ import type { L2Block } from '@aztec/stdlib/block';
12
+ import { getEpochAtSlot, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
13
+ import type { ITxProvider } from '@aztec/stdlib/interfaces/server';
14
+ import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
15
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
16
+ import type { Tx, TxHash } from '@aztec/stdlib/tx';
17
+ import type { GenesisData } from '@aztec/stdlib/world-state';
8
18
  import { getTelemetryClient } from '@aztec/telemetry-client';
9
19
  import { createWorldState } from '@aztec/world-state';
10
20
 
11
21
  import { readFileSync } from 'fs';
12
22
 
13
- import { deserializeEpochProvingJobData } from '../job/epoch-proving-job-data.js';
14
- import { EpochProvingJob } from '../job/epoch-proving-job.js';
23
+ import { CheckpointProver } from '../job/checkpoint-prover.js';
24
+ import { type EpochProvingJobData, deserializeEpochProvingJobData } from '../job/epoch-proving-job-data.js';
25
+ import { EpochSession, type SessionSpec } from '../job/epoch-session.js';
15
26
  import { ProverNodeJobMetrics } from '../metrics.js';
16
27
 
28
+ type RerunConfig = DataStoreConfig &
29
+ ProverBrokerConfig &
30
+ ProverClientConfig &
31
+ Pick<L1ContractsConfig, 'aztecEpochDuration'>;
32
+
17
33
  /**
18
34
  * Given a local folder where `downloadEpochProvingJob` was called, creates a new archiver and world state
19
- * using the state snapshots, and creates a new epoch proving job to prove the downloaded proving job.
35
+ * using the state snapshots, and creates a new epoch proving session to prove the downloaded proving job.
20
36
  * Proving is done with a local proving broker and agents as specified by the config.
21
37
  */
22
- export async function rerunEpochProvingJob(
38
+ export async function rerunEpochProvingJob(localPath: string, log: Logger, config: RerunConfig, genesis?: GenesisData) {
39
+ const ctx = await createRerunContext(localPath, log, config, genesis);
40
+ const { jobData, prover, metrics } = ctx;
41
+
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
+ }
48
+
49
+ // Local rerun never publishes — stub the service so submit() always resolves 'published'
50
+ // and withdraw is a no-op.
51
+ const publishingService = {
52
+ submit: () => Promise.resolve('published' as const),
53
+ withdraw: () => {},
54
+ };
55
+
56
+ const l1Constants = { epochDuration: config.aztecEpochDuration };
57
+ const [fromSlot, toSlot] = getSlotRangeForEpoch(jobData.epochNumber, l1Constants);
58
+ const spec: SessionSpec = { kind: 'full', epochNumber: jobData.epochNumber, fromSlot, toSlot };
59
+
60
+ const session = new EpochSession(spec, provers, {
61
+ proverFactory: prover,
62
+ proverId: prover.getProverId(),
63
+ publishingService,
64
+ metrics,
65
+ dateProvider: new DateProvider(),
66
+ deadline: undefined,
67
+ config: {},
68
+ bindings: log.getBindings(),
69
+ });
70
+
71
+ const finalState = await session.start();
72
+ log.info(`Completed proving for epoch ${jobData.epochNumber} with status ${finalState}`, {
73
+ derivedEpoch: getEpochAtSlot(provers[0].slotNumber, l1Constants),
74
+ });
75
+ return finalState;
76
+ }
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(
23
85
  localPath: string,
24
86
  log: Logger,
25
- config: DataStoreConfig & ProverBrokerConfig & ProverClientConfig & Pick<L1ContractsConfig, 'aztecEpochDuration'>,
87
+ config: RerunConfig,
88
+ genesis?: GenesisData,
26
89
  ) {
90
+ const 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
+ };
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> {
27
128
  const jobData = deserializeEpochProvingJobData(readFileSync(localPath));
28
129
  log.info(`Loaded proving job data for epoch ${jobData.epochNumber}`);
29
130
 
30
131
  const telemetry = getTelemetryClient();
31
132
  const metrics = new ProverNodeJobMetrics(telemetry.getMeter('prover-job'), telemetry.getTracer('prover-job'));
32
- const worldState = await createWorldState(config);
33
- const archiver = await createArchiverStore(config, { epochDuration: config.aztecEpochDuration });
34
- const publicProcessorFactory = new PublicProcessorFactory(archiver, undefined, undefined, log.getBindings());
35
-
36
- const publisher = { submitEpochProof: () => Promise.resolve(true) };
37
- const l2BlockSourceForReorgDetection = undefined;
38
- const deadline = undefined;
39
-
40
- // This starts a local proving broker that does not get exposed as a service. This should be good enough for
41
- // smallish epochs to be proven if we run on a large machine, but as epochs grow larger, we may want to switch
42
- // this out for a live proving broker with multiple agents that we can connect to.
133
+ const worldState = await createWorldState(config, genesis);
134
+ const initialBlockHash = await worldState.getInitialHeader().hash();
135
+ const archiver = await createArchiverStore(config, initialBlockHash);
136
+ const publicProcessorFactory = new PublicProcessorFactory(
137
+ createContractDataSource(archiver),
138
+ undefined,
139
+ undefined,
140
+ log.getBindings(),
141
+ );
43
142
  const broker = await createAndStartProvingBroker(config, telemetry);
44
143
  const prover = await createProverClient(config, worldState, broker, telemetry);
144
+ const chonkCache = new ChonkCache(log.getBindings());
145
+ const txProvider = makeReplayingTxProvider(jobData.txs);
45
146
 
46
- const provingJob = new EpochProvingJob(
47
- jobData,
48
- worldState,
49
- prover.createEpochProver(),
50
- publicProcessorFactory,
51
- publisher,
52
- l2BlockSourceForReorgDetection,
53
- metrics,
54
- deadline,
55
- { skipEpochCheck: true },
56
- log.getBindings(),
147
+ return { jobData, metrics, worldState, publicProcessorFactory, prover, chonkCache, txProvider };
148
+ }
149
+
150
+ /** Reconstructs the `CheckpointProver` for the checkpoint at `index` in the job, ready to prove. */
151
+ async function buildCheckpointProver(ctx: RerunContext, index: number, log: Logger): Promise<CheckpointProver> {
152
+ const { jobData, worldState, prover, chonkCache, publicProcessorFactory, txProvider, metrics } = ctx;
153
+ const checkpoint = jobData.checkpoints[index];
154
+ const previousBlockHeader =
155
+ index === 0 ? jobData.previousBlockHeader : jobData.checkpoints[index - 1].blocks.at(-1)!.header;
156
+ const l1ToL2Messages = jobData.l1ToL2Messages[checkpoint.number] ?? [];
157
+ const previousArchiveSiblingPath = await getLastSiblingPath(
158
+ MerkleTreeId.ARCHIVE,
159
+ worldState.getSnapshot(BlockNumber(checkpoint.blocks[0].number - 1)),
160
+ );
161
+ const attestations = checkpoint.number === jobData.checkpoints.at(-1)!.number ? jobData.attestations : [];
162
+ return new CheckpointProver(
163
+ {
164
+ checkpoint,
165
+ epochNumber: jobData.epochNumber,
166
+ attestations,
167
+ previousBlockHeader,
168
+ l1ToL2Messages,
169
+ previousArchiveSiblingPath,
170
+ },
171
+ {
172
+ proverFactory: prover,
173
+ chonkCache,
174
+ publicProcessorFactory,
175
+ dbProvider: worldState,
176
+ txProvider,
177
+ dateProvider: new DateProvider(),
178
+ proverId: prover.getProverId(),
179
+ metrics,
180
+ txGatheringTimeoutMs: 120_000,
181
+ deadline: undefined,
182
+ log,
183
+ },
57
184
  );
185
+ }
58
186
 
59
- log.info(`Rerunning epoch proving job for epoch ${jobData.epochNumber}`);
60
- await provingJob.run();
61
- log.info(`Completed job for epoch ${jobData.epochNumber} with status ${provingJob.getState()}`);
62
- return provingJob.getState();
187
+ /** Build a synthetic ITxProvider that returns the supplied txs map by lookup. */
188
+ function makeReplayingTxProvider(txs: Map<string, Tx>): ITxProvider {
189
+ const lookup = (hashes: TxHash[]) => {
190
+ const found: Tx[] = [];
191
+ const missing: TxHash[] = [];
192
+ for (const hash of hashes) {
193
+ const tx = txs.get(hash.toString());
194
+ if (tx) {
195
+ found.push(tx);
196
+ } else {
197
+ missing.push(hash);
198
+ }
199
+ }
200
+ return { txs: found, missingTxs: missing };
201
+ };
202
+ return {
203
+ getAvailableTxs: hashes => Promise.resolve(lookup(hashes)),
204
+ hasTxs: hashes => Promise.resolve(hashes.map(h => txs.has(h.toString()))),
205
+ getTxsForBlockProposal: () => Promise.resolve({ txs: [], missingTxs: [] }),
206
+ getTxsForBlock: (block: L2Block) => Promise.resolve(lookup(block.body.txEffects.map(e => e.txHash))),
207
+ };
63
208
  }
@@ -3,11 +3,11 @@ import { tryRmDir } from '@aztec/foundation/fs';
3
3
  import { jsonStringify } from '@aztec/foundation/json-rpc';
4
4
  import type { Logger } from '@aztec/foundation/log';
5
5
  import { isoDate } from '@aztec/foundation/string';
6
- import type { DataStoreConfig } from '@aztec/kv-store/config';
7
6
  import { buildSnapshotMetadata, createBackups } from '@aztec/node-lib/actions';
8
7
  import type { ChainConfig } from '@aztec/stdlib/config';
9
8
  import { type FileStore, createFileStore } from '@aztec/stdlib/file-store';
10
9
  import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
10
+ import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
11
11
  import { type UploadSnapshotMetadata, getBasePath, uploadSnapshotData } from '@aztec/stdlib/snapshots';
12
12
  import { WORLD_STATE_DB_VERSION } from '@aztec/world-state';
13
13
 
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable no-console */
2
2
  import { getL1ContractsConfigEnvVars } from '@aztec/ethereum/config';
3
- import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
4
3
  import { EthAddress } from '@aztec/foundation/eth-address';
5
4
  import { jsonParseWithSchema, jsonStringify } from '@aztec/foundation/json-rpc';
6
5
  import { createLogger } from '@aztec/foundation/log';
@@ -50,7 +49,7 @@ async function rerunFailedEpoch(provingJobUrl: string, baseLocalDir: string) {
50
49
  logger.info(`Rerunning proving job from ${jobPath} with state from ${dataDir}`, metadata);
51
50
  const result = await rerunEpochProvingJob(jobPath, logger, {
52
51
  ...config,
53
- l1Contracts: { rollupAddress: metadata.rollupAddress } as L1ContractAddresses,
52
+ rollupAddress: metadata.rollupAddress,
54
53
  rollupVersion: metadata.rollupVersion,
55
54
  });
56
55
 
@@ -0,0 +1,212 @@
1
+ import type { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
3
+ import type { L2BlockSource } from '@aztec/stdlib/block';
4
+ import type { Checkpoint } from '@aztec/stdlib/checkpoint';
5
+ import { type L1RollupConstants, getEpochAtSlot, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
6
+
7
+ import {
8
+ CheckpointProver,
9
+ type CheckpointProverArgs,
10
+ type CheckpointProverDeps,
11
+ type CheckpointProverTestHooks,
12
+ } from './job/checkpoint-prover.js';
13
+
14
+ /** Register-time data needed to construct a `CheckpointProver` (everything except the checkpoint + epoch). */
15
+ export type RegisterCheckpointData = Omit<CheckpointProverArgs, 'checkpoint' | 'epochNumber'>;
16
+
17
+ /** Factory used by the store to construct new provers. Tests can inject a stub. */
18
+ export type CheckpointProverFactory = (args: CheckpointProverArgs, deps: CheckpointProverDeps) => CheckpointProver;
19
+
20
+ /**
21
+ * Prover-node-wide registry of `CheckpointProver` instances, content-addressed by
22
+ * `(checkpoint number, slot, checkpoint archive root)`.
23
+ *
24
+ * The store survives every epoch / session boundary. A prover lives from its first
25
+ * `addOrUpdate` call until either:
26
+ * - its checkpoint is pruned by an L1 reorg (`cancelAndRemoveAboveBlock`), or
27
+ * - its epoch's proof-submission window has closed (`reapExpired`), so the proof could no
28
+ * longer be accepted on L1 even if produced.
29
+ *
30
+ * A prover's sub-tree work forks world-state per block and does not survive a prune of a base
31
+ * block, so there is nothing to preserve across a reorg: a pruned prover is cancelled and
32
+ * dropped, and a re-add (even of identical content) constructs a fresh prover.
33
+ */
34
+ export class CheckpointStore {
35
+ private readonly provers = new Map<string, CheckpointProver>();
36
+ /**
37
+ * Teardowns of provers already removed from `provers` (by prune or reap), awaited on `stop()`.
38
+ * Keyed by a monotonic id rather than the prover's content id: a prune-then-re-add can leave two
39
+ * teardowns for the same content id in flight at once, which a content-id key would clobber.
40
+ */
41
+ private readonly pendingTeardowns = new Map<number, Promise<void>>();
42
+ private nextTeardownId = 0;
43
+ /** Test-only hooks injected into every prover this store constructs. */
44
+ private testHooks: CheckpointProverTestHooks = {};
45
+ private readonly log: Logger;
46
+
47
+ constructor(
48
+ private readonly l2BlockSource: Pick<L2BlockSource, 'getL1Constants'>,
49
+ private readonly proverDeps: Omit<CheckpointProverDeps, 'log'>,
50
+ bindings?: LoggerBindings,
51
+ private readonly proverFactoryFn: CheckpointProverFactory = (args, deps) => new CheckpointProver(args, deps),
52
+ ) {
53
+ this.log = createLogger('prover-node:checkpoint-store', bindings);
54
+ }
55
+
56
+ public start(): Promise<void> {
57
+ return Promise.resolve();
58
+ }
59
+
60
+ public async stop(): Promise<void> {
61
+ // Cancel every live prover, then await both their teardown and any still in flight for provers
62
+ // already removed by a prune or reap.
63
+ const provers = Array.from(this.provers.values());
64
+ this.provers.clear();
65
+ for (const prover of provers) {
66
+ prover.cancel();
67
+ }
68
+ await Promise.allSettled([...provers.map(p => p.whenDone()), ...this.pendingTeardowns.values()]);
69
+ }
70
+
71
+ /**
72
+ * Tracks the teardown of a prover just removed from the store so `stop()` can await it. The entry
73
+ * removes itself once teardown settles, so the map stays bounded by the number in flight.
74
+ */
75
+ private trackTeardown(prover: CheckpointProver): void {
76
+ const id = this.nextTeardownId++;
77
+ const done = prover.whenDone();
78
+ this.pendingTeardowns.set(id, done);
79
+ void done.finally(() => this.pendingTeardowns.delete(id));
80
+ }
81
+
82
+ /**
83
+ * Registers a checkpoint with the store. If a prover already exists for the
84
+ * `(number, slot, archive root)` content key it is reused (an at-least-once re-registration of
85
+ * still-canonical content); otherwise a new prover is constructed.
86
+ */
87
+ public async addOrUpdate(checkpoint: Checkpoint, data: RegisterCheckpointData): Promise<CheckpointProver> {
88
+ const l1Constants = await this.l2BlockSource.getL1Constants();
89
+ const epochNumber = getEpochAtSlot(checkpoint.header.slotNumber, l1Constants);
90
+ const id = CheckpointProver.idFor(checkpoint);
91
+
92
+ const existing = this.provers.get(id);
93
+ if (existing) {
94
+ return existing;
95
+ }
96
+
97
+ // At most one canonical checkpoint per slot. A different checkpoint at the same slot means the
98
+ // caller forgot to prune the old chain before adding the replacement — surface it rather than
99
+ // silently creating a parallel canonical chain. A pruned checkpoint has already been removed,
100
+ // so every prover still in the store is canonical.
101
+ for (const prover of this.provers.values()) {
102
+ if (prover.slotNumber === checkpoint.header.slotNumber) {
103
+ throw new Error(
104
+ `Cannot add checkpoint ${checkpoint.number} (archive ${checkpoint.archive.root}) at slot ${checkpoint.header.slotNumber}: ` +
105
+ `a different checkpoint already occupies this slot. Prune it first.`,
106
+ );
107
+ }
108
+ }
109
+
110
+ const prover = this.proverFactoryFn(
111
+ { ...data, checkpoint, epochNumber },
112
+ { ...this.proverDeps, checkpointProveOverride: this.testHooks.checkpointProveOverride, log: this.log },
113
+ );
114
+ this.provers.set(id, prover);
115
+ return prover;
116
+ }
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
+
126
+ /**
127
+ * Cancels and removes every prover that holds a block above the prune target. A checkpoint is orphaned by a prune to
128
+ * block `targetBlockNumber` iff its last block sits above the target — including a checkpoint whose range straddles
129
+ * the target (partially orphaned), which block-range marking catches without boundary ambiguity. Keying off the
130
+ * surviving block number (rather than a checkpoint number) is correct even when the source has already
131
+ * re-checkpointed past the divergence: the prune event reports the highest surviving block, which by construction
132
+ * survives on the source, whereas the source's current checkpointed tip can sit above the prune target.
133
+ *
134
+ * The prover's in-flight sub-tree work forks world-state per block and faults once its base block is pruned, so it
135
+ * cannot be reused; it is cancelled (aborting the fork reads) and dropped. A subsequent re-add constructs a fresh
136
+ * prover. Returns the removed provers.
137
+ */
138
+ public cancelAndRemoveAboveBlock(targetBlockNumber: BlockNumber): CheckpointProver[] {
139
+ const affected: CheckpointProver[] = [];
140
+ for (const [id, prover] of Array.from(this.provers.entries())) {
141
+ const lastBlockNumber = prover.checkpoint.blocks.at(-1)!.number;
142
+ if (lastBlockNumber > targetBlockNumber) {
143
+ prover.cancel();
144
+ this.trackTeardown(prover);
145
+ this.provers.delete(id);
146
+ affected.push(prover);
147
+ }
148
+ }
149
+ return affected;
150
+ }
151
+
152
+ /**
153
+ * Drops provers whose epoch is at or below the supplied expired epoch. Once an epoch's
154
+ * proof-submission window has closed, its proof can no longer be accepted on L1, so the
155
+ * prover is no longer needed.
156
+ */
157
+ public reapExpired(expiredEpoch: EpochNumber): void {
158
+ const reaped: { id: string; checkpointNumber: CheckpointNumber; epochNumber: EpochNumber }[] = [];
159
+ for (const [id, prover] of Array.from(this.provers.entries())) {
160
+ if (prover.epochNumber <= expiredEpoch) {
161
+ reaped.push({ id, checkpointNumber: prover.checkpoint.number, epochNumber: prover.epochNumber });
162
+ prover.cancel({ routine: true });
163
+ this.trackTeardown(prover);
164
+ this.provers.delete(id);
165
+ }
166
+ }
167
+ if (reaped.length > 0) {
168
+ this.log.info(`Reaped ${reaped.length} expired CheckpointProver(s) for expiredEpoch ${expiredEpoch}`, {
169
+ expiredEpoch,
170
+ reapedCount: reaped.length,
171
+ reaped,
172
+ });
173
+ }
174
+ }
175
+
176
+ /** Returns the prover with the supplied id, or undefined. */
177
+ public get(id: string): CheckpointProver | undefined {
178
+ return this.provers.get(id);
179
+ }
180
+
181
+ /** Returns the prover for the supplied checkpoint (by its content-addressed id), or undefined. */
182
+ public getByCheckpoint(checkpoint: Checkpoint): CheckpointProver | undefined {
183
+ return this.provers.get(CheckpointProver.idFor(checkpoint));
184
+ }
185
+
186
+ /** Every prover currently in the store, in insertion order. */
187
+ public listAll(): CheckpointProver[] {
188
+ return Array.from(this.provers.values());
189
+ }
190
+
191
+ /** Provers in the store, sorted by checkpoint number. */
192
+ public list(): CheckpointProver[] {
193
+ return Array.from(this.provers.values()).sort((a, b) => a.checkpoint.number - b.checkpoint.number);
194
+ }
195
+
196
+ /**
197
+ * Provers whose slot is in the supplied epoch's slot range, sorted by checkpoint number.
198
+ */
199
+ public async listForEpoch(epoch: EpochNumber): Promise<CheckpointProver[]> {
200
+ const l1Constants = await this.l2BlockSource.getL1Constants();
201
+ const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, l1Constants);
202
+ return this.listInSlotRange(fromSlot, toSlot);
203
+ }
204
+
205
+ /** Provers whose slot falls within `[fromSlot, toSlot]`, sorted by checkpoint number. */
206
+ public listInSlotRange(fromSlot: SlotNumber, toSlot: SlotNumber): CheckpointProver[] {
207
+ return this.list().filter(p => p.slotNumber >= fromSlot && p.slotNumber <= toSlot);
208
+ }
209
+ }
210
+
211
+ /** Sub-set of `L1RollupConstants` actually consumed by the store's slot helpers. */
212
+ export type CheckpointStoreL1Constants = Pick<L1RollupConstants, 'epochDuration'>;
package/src/config.ts CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  numberConfigHelper,
7
7
  pickConfigMappings,
8
8
  } from '@aztec/foundation/config';
9
- import { type DataStoreConfig, dataConfigMappings } from '@aztec/kv-store/config';
9
+ import { EthAddress } from '@aztec/foundation/eth-address';
10
10
  import { type KeyStoreConfig, keyStoreConfigMappings } from '@aztec/node-keystore/config';
11
11
  import { ethPrivateKeySchema } from '@aztec/node-keystore/schemas';
12
12
  import type { KeyStore } from '@aztec/node-keystore/types';
@@ -24,6 +24,7 @@ import {
24
24
  proverPublisherConfigMappings,
25
25
  proverTxSenderConfigMappings,
26
26
  } from '@aztec/sequencer-client/config';
27
+ import { type DataStoreConfig, dataConfigMappings } from '@aztec/stdlib/kv-store';
27
28
 
28
29
  export type ProverNodeConfig = ProverClientUserConfig &
29
30
  ProverPublisherConfig &
@@ -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> = {
@@ -60,7 +62,7 @@ export const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProver
60
62
  proverNodeMaxParallelBlocksPerEpoch: {
61
63
  env: 'PROVER_NODE_MAX_PARALLEL_BLOCKS_PER_EPOCH',
62
64
  description: 'The Maximum number of blocks to process in parallel while proving an epoch',
63
- ...numberConfigHelper(32),
65
+ ...numberConfigHelper(0),
64
66
  },
65
67
  proverNodeFailedEpochStore: {
66
68
  env: 'PROVER_NODE_FAILED_EPOCH_STORE',
@@ -68,7 +70,8 @@ export const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProver
68
70
  defaultValue: undefined,
69
71
  },
70
72
  proverNodeEpochProvingDelayMs: {
71
- description: 'Optional delay in milliseconds to wait before proving a new epoch',
73
+ description:
74
+ 'Optional delay in milliseconds to wait for late-arriving events (e.g. reorgs) to settle before starting top-tree proving for an epoch',
72
75
  defaultValue: undefined,
73
76
  },
74
77
  txGatheringIntervalMs: {
@@ -96,6 +99,14 @@ export const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProver
96
99
  description: 'Whether the prover node skips publishing proofs to L1',
97
100
  ...booleanConfigHelper(false),
98
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
+ },
99
110
  };
100
111
 
101
112
  export const proverNodeConfigMappings: ConfigMappingsType<ProverNodeConfig> = {
package/src/factory.ts CHANGED
@@ -3,6 +3,7 @@ import type { BlobClientInterface } from '@aztec/blob-client/client';
3
3
  import { Blob } from '@aztec/blob-lib';
4
4
  import type { EpochCacheInterface } from '@aztec/epoch-cache';
5
5
  import { createEthereumChain } from '@aztec/ethereum/chain';
6
+ import { makeL1HttpTransport } from '@aztec/ethereum/client';
6
7
  import { RollupContract } from '@aztec/ethereum/contracts';
7
8
  import { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
8
9
  import { PublisherManager } from '@aztec/ethereum/publisher-manager';
@@ -27,10 +28,9 @@ import type {
27
28
  } from '@aztec/stdlib/interfaces/server';
28
29
  import { L1Metrics, type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
29
30
 
30
- import { createPublicClient, fallback, http } from 'viem';
31
+ import { createPublicClient } from 'viem';
31
32
 
32
33
  import type { SpecificProverNodeConfig } from './config.js';
33
- import { EpochMonitor } from './monitors/epoch-monitor.js';
34
34
  import { ProverNode } from './prover-node.js';
35
35
  import { ProverPublisherFactory } from './prover-publisher-factory.js';
36
36
 
@@ -95,11 +95,11 @@ export async function createProverNode(
95
95
 
96
96
  const publicClient = createPublicClient({
97
97
  chain: chain.chainInfo,
98
- transport: fallback(config.l1RpcUrls.map((url: string) => http(url, { batch: false }))),
98
+ transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }),
99
99
  pollingInterval: config.viemPollingIntervalMS,
100
100
  });
101
101
 
102
- const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
102
+ const rollupContract = new RollupContract(publicClient, config.rollupAddress.toString());
103
103
 
104
104
  const l1TxUtils = deps.l1TxUtils
105
105
  ? [deps.l1TxUtils]
@@ -118,11 +118,28 @@ export async function createProverNode(
118
118
  { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
119
119
  );
120
120
 
121
+ // Create a funder L1TxUtils from the keystore funding account (if configured)
122
+ const fundingSigner = keyStoreManager?.createFundingSigner();
123
+ let funderL1TxUtils: L1TxUtils | undefined;
124
+ if (fundingSigner) {
125
+ const [funder] = await createL1TxUtilsFromSigners(
126
+ publicClient,
127
+ [fundingSigner],
128
+ { ...config, scope: 'prover' },
129
+ { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider },
130
+ );
131
+ funderL1TxUtils = funder;
132
+ }
133
+
121
134
  const publisherFactory =
122
135
  deps.publisherFactory ??
123
136
  new ProverPublisherFactory(config, {
124
137
  rollupContract,
125
- publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), log.getBindings()),
138
+ proofSubmissionTarget: config.proofSubmissionTargetAddress,
139
+ publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), {
140
+ bindings: log.getBindings(),
141
+ funder: funderL1TxUtils,
142
+ }),
126
143
  telemetry,
127
144
  });
128
145
 
@@ -144,12 +161,6 @@ export async function createProverNode(
144
161
  ),
145
162
  };
146
163
 
147
- const epochMonitor = await EpochMonitor.create(
148
- archiver,
149
- { pollingIntervalMs: config.proverNodePollingIntervalMs, provingDelayMs: config.proverNodeEpochProvingDelayMs },
150
- telemetry,
151
- );
152
-
153
164
  const l1Metrics = new L1Metrics(
154
165
  telemetry.getMeter('ProverNodeL1Metrics'),
155
166
  publicClient,
@@ -167,7 +178,6 @@ export async function createProverNode(
167
178
  archiver,
168
179
  worldStateSynchronizer,
169
180
  p2pClient,
170
- epochMonitor,
171
181
  rollupContract,
172
182
  l1Metrics,
173
183
  proverNodeConfig,
package/src/index.ts CHANGED
@@ -2,5 +2,6 @@ export * from './actions/index.js';
2
2
  export * from './config.js';
3
3
  export * from './factory.js';
4
4
  export * from './monitors/index.js';
5
+ export * from './proof-publishing-service.js';
5
6
  export * from './prover-node-publisher.js';
6
7
  export * from './prover-node.js';