@aztec/prover-node 0.0.1-commit.b655e406 → 0.0.1-commit.b8a057fa

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 (84) hide show
  1. package/README.md +518 -0
  2. package/dest/actions/download-epoch-proving-job.d.ts +4 -4
  3. package/dest/actions/download-epoch-proving-job.js +1 -1
  4. package/dest/actions/index.d.ts +1 -1
  5. package/dest/actions/rerun-epoch-proving-job.d.ts +6 -4
  6. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
  7. package/dest/actions/rerun-epoch-proving-job.js +191 -28
  8. package/dest/actions/upload-epoch-proof-failure.d.ts +2 -2
  9. package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -1
  10. package/dest/bin/run-failed-epoch.d.ts +1 -1
  11. package/dest/bin/run-failed-epoch.js +6 -5
  12. package/dest/checkpoint-store.d.ts +87 -0
  13. package/dest/checkpoint-store.d.ts.map +1 -0
  14. package/dest/checkpoint-store.js +186 -0
  15. package/dest/config.d.ts +10 -10
  16. package/dest/config.d.ts.map +1 -1
  17. package/dest/config.js +27 -22
  18. package/dest/factory.d.ts +23 -16
  19. package/dest/factory.d.ts.map +1 -1
  20. package/dest/factory.js +49 -68
  21. package/dest/index.d.ts +3 -1
  22. package/dest/index.d.ts.map +1 -1
  23. package/dest/index.js +2 -0
  24. package/dest/job/checkpoint-prover.d.ts +134 -0
  25. package/dest/job/checkpoint-prover.d.ts.map +1 -0
  26. package/dest/job/checkpoint-prover.js +353 -0
  27. package/dest/job/epoch-proving-job-data.d.ts +8 -6
  28. package/dest/job/epoch-proving-job-data.d.ts.map +1 -1
  29. package/dest/job/epoch-proving-job-data.js +25 -18
  30. package/dest/job/epoch-session.d.ts +146 -0
  31. package/dest/job/epoch-session.d.ts.map +1 -0
  32. package/dest/job/epoch-session.js +720 -0
  33. package/dest/job/top-tree-job.d.ts +82 -0
  34. package/dest/job/top-tree-job.d.ts.map +1 -0
  35. package/dest/job/top-tree-job.js +152 -0
  36. package/dest/metrics.d.ts +42 -4
  37. package/dest/metrics.d.ts.map +1 -1
  38. package/dest/metrics.js +128 -99
  39. package/dest/monitors/epoch-monitor.d.ts +3 -2
  40. package/dest/monitors/epoch-monitor.d.ts.map +1 -1
  41. package/dest/monitors/epoch-monitor.js +14 -20
  42. package/dest/monitors/index.d.ts +1 -1
  43. package/dest/proof-publishing-service.d.ts +161 -0
  44. package/dest/proof-publishing-service.d.ts.map +1 -0
  45. package/dest/proof-publishing-service.js +335 -0
  46. package/dest/prover-node-publisher.d.ts +36 -23
  47. package/dest/prover-node-publisher.d.ts.map +1 -1
  48. package/dest/prover-node-publisher.js +244 -98
  49. package/dest/prover-node.d.ts +122 -71
  50. package/dest/prover-node.d.ts.map +1 -1
  51. package/dest/prover-node.js +872 -226
  52. package/dest/prover-publisher-factory.d.ts +12 -6
  53. package/dest/prover-publisher-factory.d.ts.map +1 -1
  54. package/dest/prover-publisher-factory.js +8 -5
  55. package/dest/session-manager.d.ts +158 -0
  56. package/dest/session-manager.d.ts.map +1 -0
  57. package/dest/session-manager.js +452 -0
  58. package/dest/test/index.d.ts +7 -6
  59. package/dest/test/index.d.ts.map +1 -1
  60. package/package.json +28 -25
  61. package/src/actions/download-epoch-proving-job.ts +1 -1
  62. package/src/actions/rerun-epoch-proving-job.ts +120 -30
  63. package/src/actions/upload-epoch-proof-failure.ts +1 -1
  64. package/src/bin/run-failed-epoch.ts +5 -3
  65. package/src/checkpoint-store.ts +218 -0
  66. package/src/config.ts +39 -34
  67. package/src/factory.ts +86 -111
  68. package/src/index.ts +2 -0
  69. package/src/job/checkpoint-prover.ts +468 -0
  70. package/src/job/epoch-proving-job-data.ts +31 -25
  71. package/src/job/epoch-session.ts +436 -0
  72. package/src/job/top-tree-job.ts +227 -0
  73. package/src/metrics.ts +146 -84
  74. package/src/monitors/epoch-monitor.ts +10 -17
  75. package/src/proof-publishing-service.ts +427 -0
  76. package/src/prover-node-publisher.ts +300 -129
  77. package/src/prover-node.ts +569 -254
  78. package/src/prover-publisher-factory.ts +22 -11
  79. package/src/session-manager.ts +552 -0
  80. package/src/test/index.ts +6 -6
  81. package/dest/job/epoch-proving-job.d.ts +0 -69
  82. package/dest/job/epoch-proving-job.d.ts.map +0 -1
  83. package/dest/job/epoch-proving-job.js +0 -354
  84. package/src/job/epoch-proving-job.ts +0 -420
@@ -1,61 +1,151 @@
1
- import { createArchiverStore } from '@aztec/archiver';
1
+ import { createArchiverStore, createContractDataSource } from '@aztec/archiver';
2
+ import type { L1ContractsConfig } from '@aztec/ethereum/config';
3
+ import { BlockNumber } from '@aztec/foundation/branded-types';
2
4
  import type { Logger } from '@aztec/foundation/log';
3
- import type { DataStoreConfig } from '@aztec/kv-store/config';
5
+ import { DateProvider } from '@aztec/foundation/timer';
4
6
  import { type ProverClientConfig, createProverClient } from '@aztec/prover-client';
5
7
  import { ProverBrokerConfig, createAndStartProvingBroker } from '@aztec/prover-client/broker';
6
- import { PublicProcessorFactory } from '@aztec/simulator/server';
8
+ import { getLastSiblingPath } from '@aztec/prover-client/helpers';
9
+ import { ChonkCache } from '@aztec/prover-client/orchestrator';
10
+ import { AvmSimulatorPool, 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';
7
18
  import { getTelemetryClient } from '@aztec/telemetry-client';
8
19
  import { createWorldState } from '@aztec/world-state';
9
20
 
10
21
  import { readFileSync } from 'fs';
11
22
 
23
+ import { CheckpointProver } from '../job/checkpoint-prover.js';
12
24
  import { deserializeEpochProvingJobData } from '../job/epoch-proving-job-data.js';
13
- import { EpochProvingJob } from '../job/epoch-proving-job.js';
25
+ import { EpochSession, type SessionSpec } from '../job/epoch-session.js';
14
26
  import { ProverNodeJobMetrics } from '../metrics.js';
15
27
 
16
28
  /**
17
29
  * Given a local folder where `downloadEpochProvingJob` was called, creates a new archiver and world state
18
- * using the state snapshots, and creates a new epoch proving job to prove the downloaded proving job.
30
+ * using the state snapshots, and creates a new epoch proving session to prove the downloaded proving job.
19
31
  * Proving is done with a local proving broker and agents as specified by the config.
20
32
  */
21
33
  export async function rerunEpochProvingJob(
22
34
  localPath: string,
23
35
  log: Logger,
24
- config: DataStoreConfig & ProverBrokerConfig & ProverClientConfig,
36
+ config: DataStoreConfig & ProverBrokerConfig & ProverClientConfig & Pick<L1ContractsConfig, 'aztecEpochDuration'>,
37
+ genesis?: GenesisData,
25
38
  ) {
26
39
  const jobData = deserializeEpochProvingJobData(readFileSync(localPath));
27
40
  log.info(`Loaded proving job data for epoch ${jobData.epochNumber}`);
28
41
 
29
42
  const telemetry = getTelemetryClient();
30
43
  const metrics = new ProverNodeJobMetrics(telemetry.getMeter('prover-job'), telemetry.getTracer('prover-job'));
31
- const worldState = await createWorldState(config);
32
- const archiver = await createArchiverStore(config);
33
- const publicProcessorFactory = new PublicProcessorFactory(archiver);
34
-
35
- const publisher = { submitEpochProof: () => Promise.resolve(true) };
36
- const l2BlockSourceForReorgDetection = undefined;
37
- const deadline = undefined;
44
+ await using worldState = await createWorldState(config, genesis);
45
+ const initialBlockHash = await worldState.getInitialHeader().hash();
46
+ const archiver = await createArchiverStore(config, initialBlockHash);
47
+ await using avmSimulator = await AvmSimulatorPool.spawn({ wsdbIpcPath: worldState.getIpcPath() });
48
+ const publicProcessorFactory = new PublicProcessorFactory(
49
+ createContractDataSource(archiver),
50
+ avmSimulator,
51
+ undefined,
52
+ undefined,
53
+ log.getBindings(),
54
+ );
38
55
 
39
- // This starts a local proving broker that does not get exposed as a service. This should be good enough for
40
- // smallish epochs to be proven if we run on a large machine, but as epochs grow larger, we may want to switch
41
- // this out for a live proving broker with multiple agents that we can connect to.
56
+ // Local rerun never publishes stub the service so submit() always resolves 'published'
57
+ // and withdraw is a no-op.
58
+ const publishingService = {
59
+ submit: () => Promise.resolve('published' as const),
60
+ withdraw: () => {},
61
+ };
42
62
  const broker = await createAndStartProvingBroker(config, telemetry);
43
63
  const prover = await createProverClient(config, worldState, broker, telemetry);
64
+ const chonkCache = new ChonkCache(log.getBindings());
65
+
66
+ const txProvider = makeReplayingTxProvider(jobData.txs);
67
+
68
+ log.info(`Rerunning epoch proving for epoch ${jobData.epochNumber}`);
69
+
70
+ const provers: CheckpointProver[] = [];
71
+ for (let i = 0; i < jobData.checkpoints.length; i++) {
72
+ const checkpoint = jobData.checkpoints[i];
73
+ const previousBlockHeader =
74
+ i === 0 ? jobData.previousBlockHeader : jobData.checkpoints[i - 1].blocks.at(-1)!.header;
75
+ const l1ToL2Messages = jobData.l1ToL2Messages[checkpoint.number] ?? [];
76
+ const previousArchiveSiblingPath = await getLastSiblingPath(
77
+ MerkleTreeId.ARCHIVE,
78
+ worldState.getSnapshot(BlockNumber(checkpoint.blocks[0].number - 1)),
79
+ );
80
+ const attestations = checkpoint.number === jobData.checkpoints.at(-1)!.number ? jobData.attestations : [];
81
+ provers.push(
82
+ new CheckpointProver(
83
+ {
84
+ checkpoint,
85
+ epochNumber: jobData.epochNumber,
86
+ attestations,
87
+ previousBlockHeader,
88
+ l1ToL2Messages,
89
+ previousArchiveSiblingPath,
90
+ },
91
+ {
92
+ proverFactory: prover,
93
+ chonkCache,
94
+ publicProcessorFactory,
95
+ dbProvider: worldState,
96
+ txProvider,
97
+ dateProvider: new DateProvider(),
98
+ proverId: prover.getProverId(),
99
+ metrics,
100
+ txGatheringTimeoutMs: 120_000,
101
+ deadline: undefined,
102
+ log,
103
+ },
104
+ ),
105
+ );
106
+ }
44
107
 
45
- const provingJob = new EpochProvingJob(
46
- jobData,
47
- worldState,
48
- prover.createEpochProver(),
49
- publicProcessorFactory,
50
- publisher,
51
- l2BlockSourceForReorgDetection,
108
+ const l1Constants = { epochDuration: config.aztecEpochDuration };
109
+ const [fromSlot, toSlot] = getSlotRangeForEpoch(jobData.epochNumber, l1Constants);
110
+ const spec: SessionSpec = { kind: 'full', epochNumber: jobData.epochNumber, fromSlot, toSlot };
111
+
112
+ const session = new EpochSession(spec, provers, {
113
+ proverFactory: prover,
114
+ proverId: prover.getProverId(),
115
+ publishingService,
52
116
  metrics,
53
- deadline,
54
- { skipEpochCheck: true },
55
- );
117
+ dateProvider: new DateProvider(),
118
+ deadline: undefined,
119
+ config: {},
120
+ bindings: log.getBindings(),
121
+ });
122
+
123
+ const finalState = await session.start();
124
+ log.info(`Completed proving for epoch ${jobData.epochNumber} with status ${finalState}`, {
125
+ derivedEpoch: getEpochAtSlot(provers[0].slotNumber, l1Constants),
126
+ });
127
+ return finalState;
128
+ }
56
129
 
57
- log.info(`Rerunning epoch proving job for epoch ${jobData.epochNumber}`);
58
- await provingJob.run();
59
- log.info(`Completed job for epoch ${jobData.epochNumber} with status ${provingJob.getState()}`);
60
- return provingJob.getState();
130
+ /** Build a synthetic ITxProvider that returns the supplied txs map by lookup. */
131
+ function makeReplayingTxProvider(txs: Map<string, Tx>): ITxProvider {
132
+ const lookup = (hashes: TxHash[]) => {
133
+ const found: Tx[] = [];
134
+ const missing: TxHash[] = [];
135
+ for (const hash of hashes) {
136
+ const tx = txs.get(hash.toString());
137
+ if (tx) {
138
+ found.push(tx);
139
+ } else {
140
+ missing.push(hash);
141
+ }
142
+ }
143
+ return { txs: found, missingTxs: missing };
144
+ };
145
+ return {
146
+ getAvailableTxs: hashes => Promise.resolve(lookup(hashes)),
147
+ hasTxs: hashes => Promise.resolve(hashes.map(h => txs.has(h.toString()))),
148
+ getTxsForBlockProposal: () => Promise.resolve({ txs: [], missingTxs: [] }),
149
+ getTxsForBlock: (block: L2Block) => Promise.resolve(lookup(block.body.txEffects.map(e => e.txHash))),
150
+ };
61
151
  }
@@ -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,5 +1,5 @@
1
1
  /* eslint-disable no-console */
2
- import type { L1ContractAddresses } from '@aztec/ethereum';
2
+ import { getL1ContractsConfigEnvVars } from '@aztec/ethereum/config';
3
3
  import { EthAddress } from '@aztec/foundation/eth-address';
4
4
  import { jsonParseWithSchema, jsonStringify } from '@aztec/foundation/json-rpc';
5
5
  import { createLogger } from '@aztec/foundation/log';
@@ -22,8 +22,10 @@ async function rerunFailedEpoch(provingJobUrl: string, baseLocalDir: string) {
22
22
  const dataDir = join(localDir, 'state');
23
23
 
24
24
  const env = getProverNodeConfigFromEnv();
25
+ const l1Config = getL1ContractsConfigEnvVars();
25
26
  const config = {
26
- ...getProverNodeConfigFromEnv(),
27
+ ...env,
28
+ ...l1Config,
27
29
  dataDirectory: dataDir,
28
30
  dataStoreMapSizeKb: env.dataStoreMapSizeKb ?? 1024 * 1024,
29
31
  proverId: env.proverId ?? EthAddress.random(),
@@ -47,7 +49,7 @@ async function rerunFailedEpoch(provingJobUrl: string, baseLocalDir: string) {
47
49
  logger.info(`Rerunning proving job from ${jobPath} with state from ${dataDir}`, metadata);
48
50
  const result = await rerunEpochProvingJob(jobPath, logger, {
49
51
  ...config,
50
- l1Contracts: { rollupAddress: metadata.rollupAddress } as L1ContractAddresses,
52
+ rollupAddress: metadata.rollupAddress,
51
53
  rollupVersion: metadata.rollupVersion,
52
54
  });
53
55
 
@@ -0,0 +1,218 @@
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 { RunningPromise } from '@aztec/foundation/promise';
4
+ import type { L2BlockSource } from '@aztec/stdlib/block';
5
+ import type { Checkpoint } from '@aztec/stdlib/checkpoint';
6
+ import { type L1RollupConstants, getEpochAtSlot, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
7
+
8
+ import { CheckpointProver, type CheckpointProverArgs, type CheckpointProverDeps } from './job/checkpoint-prover.js';
9
+
10
+ /** Register-time data needed to construct a `CheckpointProver` (everything except the checkpoint + epoch). */
11
+ export type RegisterCheckpointData = Omit<CheckpointProverArgs, 'checkpoint' | 'epochNumber'>;
12
+
13
+ /** Factory used by the store to construct new provers. Tests can inject a stub. */
14
+ export type CheckpointProverFactory = (args: CheckpointProverArgs, deps: CheckpointProverDeps) => CheckpointProver;
15
+
16
+ /**
17
+ * Prover-node-wide registry of `CheckpointProver` instances, content-addressed by
18
+ * `(checkpoint number, slot, checkpoint archive root)`.
19
+ *
20
+ * The store survives every epoch / session boundary. A prover lives from its first
21
+ * `addOrUpdate` call until either:
22
+ * - it has been pruned and the L2 chain has moved past its slot (no re-add possible), or
23
+ * - its epoch's proof-submission window has closed (`reapExpired`), so the proof could no
24
+ * longer be accepted on L1 even if produced.
25
+ *
26
+ * A re-add of a checkpoint that matches an existing prover's content key reuses the
27
+ * existing prover (and flips it back to canonical); the in-flight sub-tree work never
28
+ * stops, so a prune-then-re-add of the same content avoids re-proving entirely.
29
+ */
30
+ export class CheckpointStore {
31
+ private readonly provers = new Map<string, CheckpointProver>();
32
+ private readonly slotWatcher: RunningPromise;
33
+ private readonly log: Logger;
34
+
35
+ constructor(
36
+ private readonly l2BlockSource: Pick<L2BlockSource, 'getSyncedL2SlotNumber' | 'getL1Constants'>,
37
+ private readonly proverDeps: Omit<CheckpointProverDeps, 'log'>,
38
+ private readonly options: { slotWatcherPollIntervalMs: number },
39
+ bindings?: LoggerBindings,
40
+ private readonly proverFactoryFn: CheckpointProverFactory = (args, deps) => new CheckpointProver(args, deps),
41
+ ) {
42
+ this.log = createLogger('prover-node:checkpoint-store', bindings);
43
+ this.slotWatcher = new RunningPromise(
44
+ () => this.reapPrunedPastSlot(),
45
+ this.log,
46
+ this.options.slotWatcherPollIntervalMs,
47
+ );
48
+ }
49
+
50
+ public start(): Promise<void> {
51
+ this.slotWatcher.start();
52
+ return Promise.resolve();
53
+ }
54
+
55
+ public async stop(): Promise<void> {
56
+ await this.slotWatcher.stop();
57
+ // Cancel every live prover; await teardown.
58
+ const provers = Array.from(this.provers.values());
59
+ this.provers.clear();
60
+ for (const prover of provers) {
61
+ prover.cancel();
62
+ }
63
+ await Promise.allSettled(provers.map(p => p.whenDone()));
64
+ }
65
+
66
+ /**
67
+ * Registers a checkpoint with the store. If a prover already exists for the
68
+ * `(number, slot, archive root)` content key, it is reused and marked canonical;
69
+ * otherwise a new prover is constructed.
70
+ */
71
+ public async addOrUpdate(checkpoint: Checkpoint, data: RegisterCheckpointData): Promise<CheckpointProver> {
72
+ const l1Constants = await this.l2BlockSource.getL1Constants();
73
+ const epochNumber = getEpochAtSlot(checkpoint.header.slotNumber, l1Constants);
74
+ const id = CheckpointProver.idFor(checkpoint);
75
+
76
+ const existing = this.provers.get(id);
77
+ if (existing) {
78
+ existing.markCanonical();
79
+ return existing;
80
+ }
81
+
82
+ // At most one canonical checkpoint per slot. A different canonical checkpoint at the
83
+ // same slot means the caller forgot to prune the old chain before adding the replacement
84
+ // — surface it rather than silently creating a parallel canonical chain.
85
+ for (const prover of this.provers.values()) {
86
+ if (prover.slotNumber === checkpoint.header.slotNumber && !prover.isPruned()) {
87
+ throw new Error(
88
+ `Cannot add checkpoint ${checkpoint.number} (archive ${checkpoint.archive.root}) at slot ${checkpoint.header.slotNumber}: ` +
89
+ `a different canonical checkpoint already occupies this slot. Prune it first.`,
90
+ );
91
+ }
92
+ }
93
+
94
+ const prover = this.proverFactoryFn({ ...data, checkpoint, epochNumber }, { ...this.proverDeps, log: this.log });
95
+ this.provers.set(id, prover);
96
+ return prover;
97
+ }
98
+
99
+ /**
100
+ * Marks every canonical prover that holds a block above the prune target as pruned. A checkpoint is orphaned by a
101
+ * prune to block `targetBlockNumber` iff its last block sits above the target — including a checkpoint whose range
102
+ * straddles the target (partially orphaned), which block-range marking catches without boundary ambiguity. Keying
103
+ * off the surviving block number (rather than a checkpoint number) is correct even when the source has already
104
+ * re-checkpointed past the divergence: the prune event reports the highest surviving block, which by construction
105
+ * survives on the source, whereas the source's current checkpointed tip can sit above the prune target.
106
+ * Sub-tree work keeps running so a re-add of the same content can pick it up. Returns the affected provers.
107
+ */
108
+ public markPrunedAboveBlock(targetBlockNumber: BlockNumber): CheckpointProver[] {
109
+ const affected: CheckpointProver[] = [];
110
+ for (const prover of this.provers.values()) {
111
+ const lastBlockNumber = prover.checkpoint.blocks.at(-1)!.number;
112
+ if (lastBlockNumber > targetBlockNumber && !prover.isPruned()) {
113
+ prover.markPruned();
114
+ affected.push(prover);
115
+ }
116
+ }
117
+ return affected;
118
+ }
119
+
120
+ /**
121
+ * Drops canonical (non-pruned) provers whose epoch is at or below the supplied expired
122
+ * epoch. Once an epoch's proof-submission window has closed, its proof can no longer be
123
+ * accepted on L1, so the prover is no longer needed.
124
+ */
125
+ public reapExpired(expiredEpoch: EpochNumber): void {
126
+ const reaped: { id: string; checkpointNumber: CheckpointNumber; epochNumber: EpochNumber }[] = [];
127
+ for (const [id, prover] of Array.from(this.provers.entries())) {
128
+ if (prover.isPruned()) {
129
+ continue;
130
+ }
131
+ if (prover.epochNumber <= expiredEpoch) {
132
+ reaped.push({ id, checkpointNumber: prover.checkpoint.number, epochNumber: prover.epochNumber });
133
+ prover.cancel({ routine: true });
134
+ void prover.whenDone();
135
+ this.provers.delete(id);
136
+ }
137
+ }
138
+ if (reaped.length > 0) {
139
+ this.log.info(`Reaped ${reaped.length} expired CheckpointProver(s) for expiredEpoch ${expiredEpoch}`, {
140
+ expiredEpoch,
141
+ reapedCount: reaped.length,
142
+ reaped,
143
+ });
144
+ }
145
+ }
146
+
147
+ /** Returns the prover with the supplied id, or undefined. */
148
+ public get(id: string): CheckpointProver | undefined {
149
+ return this.provers.get(id);
150
+ }
151
+
152
+ /** Returns the prover for the supplied checkpoint (by its content-addressed id), or undefined. */
153
+ public getByCheckpoint(checkpoint: Checkpoint): CheckpointProver | undefined {
154
+ return this.provers.get(CheckpointProver.idFor(checkpoint));
155
+ }
156
+
157
+ /** Every prover currently in the store (canonical and pruned), in insertion order. */
158
+ public listAll(): CheckpointProver[] {
159
+ return Array.from(this.provers.values());
160
+ }
161
+
162
+ /** Canonical (non-pruned) provers in the store, sorted by checkpoint number. */
163
+ public listCanonical(): CheckpointProver[] {
164
+ return Array.from(this.provers.values())
165
+ .filter(p => !p.isPruned())
166
+ .sort((a, b) => a.checkpoint.number - b.checkpoint.number);
167
+ }
168
+
169
+ /**
170
+ * Canonical provers whose slot is in the supplied epoch's slot range, sorted by
171
+ * checkpoint number.
172
+ */
173
+ public async listCanonicalForEpoch(epoch: EpochNumber): Promise<CheckpointProver[]> {
174
+ const l1Constants = await this.l2BlockSource.getL1Constants();
175
+ const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, l1Constants);
176
+ return this.listCanonicalInSlotRange(fromSlot, toSlot);
177
+ }
178
+
179
+ /** Canonical provers whose slot falls within `[fromSlot, toSlot]`, sorted by checkpoint number. */
180
+ public listCanonicalInSlotRange(fromSlot: SlotNumber, toSlot: SlotNumber): CheckpointProver[] {
181
+ return this.listCanonical().filter(p => p.slotNumber >= fromSlot && p.slotNumber <= toSlot);
182
+ }
183
+
184
+ /**
185
+ * SlotWatcher tick: reap pruned provers whose slot has passed the chain's synced
186
+ * slot. Once the chain has moved past, no re-add can revive the prover and its
187
+ * content key is unique enough that an actual re-add would create a new entry.
188
+ *
189
+ * Protected so unit tests can drive a single tick without spinning up the
190
+ * `RunningPromise` and waiting on its interval.
191
+ */
192
+ protected async reapPrunedPastSlot(): Promise<void> {
193
+ let syncedSlot: SlotNumber | undefined;
194
+ try {
195
+ syncedSlot = await this.l2BlockSource.getSyncedL2SlotNumber();
196
+ } catch (err) {
197
+ this.log.debug(`SlotWatcher could not read synced slot`, { error: `${err}` });
198
+ return;
199
+ }
200
+ if (syncedSlot === undefined) {
201
+ return;
202
+ }
203
+ for (const [id, prover] of Array.from(this.provers.entries())) {
204
+ if (prover.isPruned() && prover.slotNumber < syncedSlot) {
205
+ this.log.info(`Reaping pruned CheckpointProver ${id}: slot ${prover.slotNumber} < synced ${syncedSlot}`, {
206
+ checkpointNumber: prover.checkpoint.number,
207
+ slotNumber: prover.slotNumber,
208
+ });
209
+ prover.cancel();
210
+ void prover.whenDone();
211
+ this.provers.delete(id);
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ /** Sub-set of `L1RollupConstants` actually consumed by the store's slot helpers. */
218
+ export type CheckpointStoreL1Constants = Pick<L1RollupConstants, 'epochDuration'>;
package/src/config.ts CHANGED
@@ -1,42 +1,38 @@
1
- import { type ArchiverConfig, archiverConfigMappings } from '@aztec/archiver/config';
2
1
  import type { ACVMConfig, BBConfig } from '@aztec/bb-prover/config';
3
- import { type GenesisStateConfig, genesisStateConfigMappings } from '@aztec/ethereum';
4
2
  import {
5
3
  type ConfigMappingsType,
6
4
  booleanConfigHelper,
7
5
  getConfigFromMappings,
8
6
  numberConfigHelper,
7
+ pickConfigMappings,
9
8
  } from '@aztec/foundation/config';
10
- import { type DataStoreConfig, dataConfigMappings } from '@aztec/kv-store/config';
11
- import { type KeyStore, type KeyStoreConfig, ethPrivateKeySchema, keyStoreConfigMappings } from '@aztec/node-keystore';
9
+ import { EthAddress } from '@aztec/foundation/eth-address';
10
+ import { type KeyStoreConfig, keyStoreConfigMappings } from '@aztec/node-keystore/config';
11
+ import { ethPrivateKeySchema } from '@aztec/node-keystore/schemas';
12
+ import type { KeyStore } from '@aztec/node-keystore/types';
12
13
  import { type SharedNodeConfig, sharedNodeConfigMappings } from '@aztec/node-lib/config';
13
- import { type P2PConfig, p2pConfigMappings } from '@aztec/p2p/config';
14
14
  import {
15
15
  type ProverAgentConfig,
16
16
  type ProverBrokerConfig,
17
17
  proverAgentConfigMappings,
18
18
  proverBrokerConfigMappings,
19
- } from '@aztec/prover-client/broker';
19
+ } from '@aztec/prover-client/broker/config';
20
20
  import { type ProverClientUserConfig, bbConfigMappings, proverClientConfigMappings } from '@aztec/prover-client/config';
21
21
  import {
22
- type PublisherConfig,
23
- type TxSenderConfig,
24
- getPublisherConfigMappings,
25
- getTxSenderConfigMappings,
22
+ type ProverPublisherConfig,
23
+ type ProverTxSenderConfig,
24
+ proverPublisherConfigMappings,
25
+ proverTxSenderConfigMappings,
26
26
  } from '@aztec/sequencer-client/config';
27
- import { type WorldStateConfig, worldStateConfigMappings } from '@aztec/world-state/config';
28
-
29
- export type ProverNodeConfig = ArchiverConfig &
30
- ProverClientUserConfig &
31
- P2PConfig &
32
- WorldStateConfig &
33
- PublisherConfig &
34
- TxSenderConfig &
27
+ import { type DataStoreConfig, dataConfigMappings } from '@aztec/stdlib/kv-store';
28
+
29
+ export type ProverNodeConfig = ProverClientUserConfig &
30
+ ProverPublisherConfig &
31
+ ProverTxSenderConfig &
35
32
  DataStoreConfig &
36
33
  KeyStoreConfig &
37
- SharedNodeConfig &
38
34
  SpecificProverNodeConfig &
39
- GenesisStateConfig;
35
+ Pick<SharedNodeConfig, 'web3SignerUrl'>;
40
36
 
41
37
  export type SpecificProverNodeConfig = {
42
38
  proverNodeMaxPendingJobs: number;
@@ -49,9 +45,10 @@ export type SpecificProverNodeConfig = {
49
45
  txGatheringIntervalMs: number;
50
46
  txGatheringBatchSize: number;
51
47
  txGatheringMaxParallelRequestsPerNode: number;
48
+ proofSubmissionTargetAddress?: EthAddress;
52
49
  };
53
50
 
54
- const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeConfig> = {
51
+ export const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeConfig> = {
55
52
  proverNodeMaxPendingJobs: {
56
53
  env: 'PROVER_NODE_MAX_PENDING_JOBS',
57
54
  description: 'The maximum number of pending jobs for the prover node',
@@ -65,7 +62,7 @@ const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeCon
65
62
  proverNodeMaxParallelBlocksPerEpoch: {
66
63
  env: 'PROVER_NODE_MAX_PARALLEL_BLOCKS_PER_EPOCH',
67
64
  description: 'The Maximum number of blocks to process in parallel while proving an epoch',
68
- ...numberConfigHelper(32),
65
+ ...numberConfigHelper(0),
69
66
  },
70
67
  proverNodeFailedEpochStore: {
71
68
  env: 'PROVER_NODE_FAILED_EPOCH_STORE',
@@ -73,7 +70,8 @@ const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeCon
73
70
  defaultValue: undefined,
74
71
  },
75
72
  proverNodeEpochProvingDelayMs: {
76
- 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',
77
75
  defaultValue: undefined,
78
76
  },
79
77
  txGatheringIntervalMs: {
@@ -101,20 +99,24 @@ const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeCon
101
99
  description: 'Whether the prover node skips publishing proofs to L1',
102
100
  ...booleanConfigHelper(false),
103
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
+ },
104
110
  };
105
111
 
106
112
  export const proverNodeConfigMappings: ConfigMappingsType<ProverNodeConfig> = {
107
113
  ...dataConfigMappings,
108
114
  ...keyStoreConfigMappings,
109
- ...archiverConfigMappings,
110
115
  ...proverClientConfigMappings,
111
- ...p2pConfigMappings,
112
- ...worldStateConfigMappings,
113
- ...getPublisherConfigMappings('PROVER'),
114
- ...getTxSenderConfigMappings('PROVER'),
116
+ ...proverPublisherConfigMappings,
117
+ ...proverTxSenderConfigMappings,
115
118
  ...specificProverNodeConfigMappings,
116
- ...genesisStateConfigMappings,
117
- ...sharedNodeConfigMappings,
119
+ ...pickConfigMappings(sharedNodeConfigMappings, ['web3SignerUrl']),
118
120
  };
119
121
 
120
122
  export function getProverNodeConfigFromEnv(): ProverNodeConfig {
@@ -141,7 +143,7 @@ function createKeyStoreFromWeb3Signer(config: ProverNodeConfig): KeyStore | unde
141
143
  }
142
144
 
143
145
  // Also, we need at least one publisher address.
144
- const publishers = config.publisherAddresses ?? [];
146
+ const publishers = config.proverPublisherAddresses ?? [];
145
147
 
146
148
  if (publishers.length === 0) {
147
149
  return undefined;
@@ -162,8 +164,8 @@ function createKeyStoreFromWeb3Signer(config: ProverNodeConfig): KeyStore | unde
162
164
 
163
165
  function createKeyStoreFromPublisherKeys(config: ProverNodeConfig): KeyStore | undefined {
164
166
  // Extract the publisher keys from the provided config.
165
- const publisherKeys = config.publisherPrivateKeys
166
- ? config.publisherPrivateKeys.map(k => ethPrivateKeySchema.parse(k.getValue()))
167
+ const publisherKeys = config.proverPublisherPrivateKeys
168
+ ? config.proverPublisherPrivateKeys.map((k: { getValue: () => string }) => ethPrivateKeySchema.parse(k.getValue()))
167
169
  : [];
168
170
 
169
171
  // There must be at least 1.
@@ -192,7 +194,10 @@ function createKeyStoreFromPublisherKeys(config: ProverNodeConfig): KeyStore | u
192
194
 
193
195
  export function createKeyStoreForProver(config: ProverNodeConfig): KeyStore | undefined {
194
196
  if (config.web3SignerUrl !== undefined && config.web3SignerUrl.length > 0) {
195
- return createKeyStoreFromWeb3Signer(config);
197
+ const keyStore = createKeyStoreFromWeb3Signer(config);
198
+ if (keyStore) {
199
+ return keyStore;
200
+ }
196
201
  }
197
202
 
198
203
  return createKeyStoreFromPublisherKeys(config);