@aztec/prover-node 5.0.0-private.20260318 → 5.0.0-rc.1

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 (71) hide show
  1. package/README.md +506 -0
  2. package/dest/actions/download-epoch-proving-job.js +1 -1
  3. package/dest/actions/rerun-epoch-proving-job.d.ts +4 -3
  4. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
  5. package/dest/actions/rerun-epoch-proving-job.js +103 -21
  6. package/dest/bin/run-failed-epoch.js +1 -3
  7. package/dest/checkpoint-store.d.ts +83 -0
  8. package/dest/checkpoint-store.d.ts.map +1 -0
  9. package/dest/checkpoint-store.js +181 -0
  10. package/dest/config.d.ts +1 -1
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +1 -1
  13. package/dest/factory.d.ts +1 -1
  14. package/dest/factory.d.ts.map +1 -1
  15. package/dest/factory.js +22 -8
  16. package/dest/index.d.ts +2 -1
  17. package/dest/index.d.ts.map +1 -1
  18. package/dest/index.js +1 -0
  19. package/dest/job/checkpoint-prover.d.ts +134 -0
  20. package/dest/job/checkpoint-prover.d.ts.map +1 -0
  21. package/dest/job/checkpoint-prover.js +350 -0
  22. package/dest/job/epoch-session.d.ts +146 -0
  23. package/dest/job/epoch-session.d.ts.map +1 -0
  24. package/dest/job/epoch-session.js +709 -0
  25. package/dest/job/top-tree-job.d.ts +82 -0
  26. package/dest/job/top-tree-job.d.ts.map +1 -0
  27. package/dest/job/top-tree-job.js +152 -0
  28. package/dest/metrics.d.ts +29 -5
  29. package/dest/metrics.d.ts.map +1 -1
  30. package/dest/metrics.js +73 -9
  31. package/dest/monitors/epoch-monitor.js +6 -2
  32. package/dest/proof-publishing-service.d.ts +159 -0
  33. package/dest/proof-publishing-service.d.ts.map +1 -0
  34. package/dest/proof-publishing-service.js +334 -0
  35. package/dest/prover-node-publisher.d.ts +18 -11
  36. package/dest/prover-node-publisher.d.ts.map +1 -1
  37. package/dest/prover-node-publisher.js +195 -57
  38. package/dest/prover-node.d.ts +96 -68
  39. package/dest/prover-node.d.ts.map +1 -1
  40. package/dest/prover-node.js +382 -227
  41. package/dest/prover-publisher-factory.d.ts +2 -2
  42. package/dest/prover-publisher-factory.d.ts.map +1 -1
  43. package/dest/prover-publisher-factory.js +3 -3
  44. package/dest/session-manager.d.ts +158 -0
  45. package/dest/session-manager.d.ts.map +1 -0
  46. package/dest/session-manager.js +452 -0
  47. package/dest/test/index.d.ts +7 -6
  48. package/dest/test/index.d.ts.map +1 -1
  49. package/package.json +23 -23
  50. package/src/actions/download-epoch-proving-job.ts +1 -1
  51. package/src/actions/rerun-epoch-proving-job.ts +114 -28
  52. package/src/bin/run-failed-epoch.ts +1 -2
  53. package/src/checkpoint-store.ts +213 -0
  54. package/src/config.ts +2 -1
  55. package/src/factory.ts +18 -10
  56. package/src/index.ts +1 -0
  57. package/src/job/checkpoint-prover.ts +465 -0
  58. package/src/job/epoch-session.ts +424 -0
  59. package/src/job/top-tree-job.ts +227 -0
  60. package/src/metrics.ts +88 -12
  61. package/src/monitors/epoch-monitor.ts +2 -2
  62. package/src/proof-publishing-service.ts +424 -0
  63. package/src/prover-node-publisher.ts +220 -67
  64. package/src/prover-node.ts +439 -249
  65. package/src/prover-publisher-factory.ts +3 -3
  66. package/src/session-manager.ts +552 -0
  67. package/src/test/index.ts +6 -6
  68. package/dest/job/epoch-proving-job.d.ts +0 -63
  69. package/dest/job/epoch-proving-job.d.ts.map +0 -1
  70. package/dest/job/epoch-proving-job.js +0 -762
  71. package/src/job/epoch-proving-job.ts +0 -465
@@ -1,12 +1,13 @@
1
+ import type { EpochProverFactory } from '@aztec/prover-client';
1
2
  import type { EpochProverManager } from '@aztec/stdlib/interfaces/server';
2
- import type { EpochProvingJob } from '../job/epoch-proving-job.js';
3
- import type { ProverNodePublisher } from '../prover-node-publisher.js';
3
+ import type { ProofPublishingService } from '../proof-publishing-service.js';
4
4
  import { ProverNode } from '../prover-node.js';
5
+ import type { SessionManager } from '../session-manager.js';
5
6
  declare abstract class TestProverNodeClass extends ProverNode {
6
- prover: EpochProverManager;
7
- publisher: ProverNodePublisher;
8
- abstract tryUploadEpochFailure(job: EpochProvingJob): Promise<string | undefined>;
7
+ prover: EpochProverManager & EpochProverFactory;
8
+ publishingService: ProofPublishingService;
9
+ sessionManager: SessionManager;
9
10
  }
10
11
  export type TestProverNode = TestProverNodeClass;
11
12
  export {};
12
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLGtCQUFrQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFMUUsT0FBTyxLQUFLLEVBQUUsZUFBZSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDbkUsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUN2RSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFL0MsdUJBQWUsbUJBQW9CLFNBQVEsVUFBVTtJQUNwQyxNQUFNLEVBQUUsa0JBQWtCLENBQUM7SUFDM0IsU0FBUyxFQUFFLG1CQUFtQixDQUFDO0lBRTlDLFNBQXlCLHFCQUFxQixDQUFDLEdBQUcsRUFBRSxlQUFlLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsQ0FBQztDQUNuRztBQUVELE1BQU0sTUFBTSxjQUFjLEdBQUcsbUJBQW1CLENBQUMifQ==
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDL0QsT0FBTyxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUUxRSxPQUFPLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQzdFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUMvQyxPQUFPLEtBQUssRUFBRSxjQUFjLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUU1RCx1QkFBZSxtQkFBb0IsU0FBUSxVQUFVO0lBQ3BDLE1BQU0sRUFBRSxrQkFBa0IsR0FBRyxrQkFBa0IsQ0FBQztJQUNoRCxpQkFBaUIsRUFBRSxzQkFBc0IsQ0FBQztJQUMxQyxjQUFjLEVBQUUsY0FBYyxDQUFDO0NBQy9DO0FBRUQsTUFBTSxNQUFNLGNBQWMsR0FBRyxtQkFBbUIsQ0FBQyJ9
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,uBAAe,mBAAoB,SAAQ,UAAU;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,SAAS,EAAE,mBAAmB,CAAC;IAE9C,SAAyB,qBAAqB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACnG;AAED,MAAM,MAAM,cAAc,GAAG,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,uBAAe,mBAAoB,SAAQ,UAAU;IACpC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAAC;IAChD,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,cAAc,EAAE,cAAc,CAAC;CAC/C;AAED,MAAM,MAAM,cAAc,GAAG,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/prover-node",
3
- "version": "5.0.0-private.20260318",
3
+ "version": "5.0.0-rc.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -56,28 +56,28 @@
56
56
  ]
57
57
  },
58
58
  "dependencies": {
59
- "@aztec/archiver": "5.0.0-private.20260318",
60
- "@aztec/bb-prover": "5.0.0-private.20260318",
61
- "@aztec/blob-client": "5.0.0-private.20260318",
62
- "@aztec/blob-lib": "5.0.0-private.20260318",
63
- "@aztec/constants": "5.0.0-private.20260318",
64
- "@aztec/epoch-cache": "5.0.0-private.20260318",
65
- "@aztec/ethereum": "5.0.0-private.20260318",
66
- "@aztec/foundation": "5.0.0-private.20260318",
67
- "@aztec/kv-store": "5.0.0-private.20260318",
68
- "@aztec/l1-artifacts": "5.0.0-private.20260318",
69
- "@aztec/native": "5.0.0-private.20260318",
70
- "@aztec/node-keystore": "5.0.0-private.20260318",
71
- "@aztec/node-lib": "5.0.0-private.20260318",
72
- "@aztec/noir-protocol-circuits-types": "5.0.0-private.20260318",
73
- "@aztec/p2p": "5.0.0-private.20260318",
74
- "@aztec/protocol-contracts": "5.0.0-private.20260318",
75
- "@aztec/prover-client": "5.0.0-private.20260318",
76
- "@aztec/sequencer-client": "5.0.0-private.20260318",
77
- "@aztec/simulator": "5.0.0-private.20260318",
78
- "@aztec/stdlib": "5.0.0-private.20260318",
79
- "@aztec/telemetry-client": "5.0.0-private.20260318",
80
- "@aztec/world-state": "5.0.0-private.20260318",
59
+ "@aztec/archiver": "5.0.0-rc.1",
60
+ "@aztec/bb-prover": "5.0.0-rc.1",
61
+ "@aztec/blob-client": "5.0.0-rc.1",
62
+ "@aztec/blob-lib": "5.0.0-rc.1",
63
+ "@aztec/constants": "5.0.0-rc.1",
64
+ "@aztec/epoch-cache": "5.0.0-rc.1",
65
+ "@aztec/ethereum": "5.0.0-rc.1",
66
+ "@aztec/foundation": "5.0.0-rc.1",
67
+ "@aztec/kv-store": "5.0.0-rc.1",
68
+ "@aztec/l1-artifacts": "5.0.0-rc.1",
69
+ "@aztec/native": "5.0.0-rc.1",
70
+ "@aztec/node-keystore": "5.0.0-rc.1",
71
+ "@aztec/node-lib": "5.0.0-rc.1",
72
+ "@aztec/noir-protocol-circuits-types": "5.0.0-rc.1",
73
+ "@aztec/p2p": "5.0.0-rc.1",
74
+ "@aztec/protocol-contracts": "5.0.0-rc.1",
75
+ "@aztec/prover-client": "5.0.0-rc.1",
76
+ "@aztec/sequencer-client": "5.0.0-rc.1",
77
+ "@aztec/simulator": "5.0.0-rc.1",
78
+ "@aztec/stdlib": "5.0.0-rc.1",
79
+ "@aztec/telemetry-client": "5.0.0-rc.1",
80
+ "@aztec/world-state": "5.0.0-rc.1",
81
81
  "source-map-support": "^0.5.21",
82
82
  "tslib": "^2.4.0",
83
83
  "viem": "npm:@aztec/viem@2.38.2"
@@ -30,7 +30,7 @@ export async function downloadEpochProvingJob(
30
30
 
31
31
  const dataUrls = makeSnapshotPaths(location);
32
32
  log.info(`Downloading state snapshot from ${location} to local data directory`, { metadata, dataUrls });
33
- await snapshotSync({ dataUrls }, log, { ...config, ...metadata, snapshotsUrl: location });
33
+ await snapshotSync({ dataUrls }, log, { ...config, ...metadata, fileStore });
34
34
 
35
35
  const dataPath = urlJoin(location, 'data.bin');
36
36
  const localPath = config.jobDataDownloadPath;
@@ -1,63 +1,149 @@
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';
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';
8
+ import { getLastSiblingPath } from '@aztec/prover-client/helpers';
9
+ import { ChonkCache } from '@aztec/prover-client/orchestrator';
6
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';
7
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
 
23
+ import { CheckpointProver } from '../job/checkpoint-prover.js';
13
24
  import { deserializeEpochProvingJobData } from '../job/epoch-proving-job-data.js';
14
- import { EpochProvingJob } from '../job/epoch-proving-job.js';
25
+ import { EpochSession, type SessionSpec } from '../job/epoch-session.js';
15
26
  import { ProverNodeJobMetrics } from '../metrics.js';
16
27
 
17
28
  /**
18
29
  * 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.
30
+ * using the state snapshots, and creates a new epoch proving session to prove the downloaded proving job.
20
31
  * Proving is done with a local proving broker and agents as specified by the config.
21
32
  */
22
33
  export async function rerunEpochProvingJob(
23
34
  localPath: string,
24
35
  log: Logger,
25
36
  config: DataStoreConfig & ProverBrokerConfig & ProverClientConfig & Pick<L1ContractsConfig, 'aztecEpochDuration'>,
37
+ genesis?: GenesisData,
26
38
  ) {
27
39
  const jobData = deserializeEpochProvingJobData(readFileSync(localPath));
28
40
  log.info(`Loaded proving job data for epoch ${jobData.epochNumber}`);
29
41
 
30
42
  const telemetry = getTelemetryClient();
31
43
  const metrics = new ProverNodeJobMetrics(telemetry.getMeter('prover-job'), telemetry.getTracer('prover-job'));
32
- const worldState = await createWorldState(config);
33
- const archiver = await createArchiverStore(config);
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;
44
+ const worldState = await createWorldState(config, genesis);
45
+ const initialBlockHash = await worldState.getInitialHeader().hash();
46
+ const archiver = await createArchiverStore(config, initialBlockHash);
47
+ const publicProcessorFactory = new PublicProcessorFactory(
48
+ createContractDataSource(archiver),
49
+ undefined,
50
+ undefined,
51
+ log.getBindings(),
52
+ );
39
53
 
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.
54
+ // Local rerun never publishes stub the service so submit() always resolves 'published'
55
+ // and withdraw is a no-op.
56
+ const publishingService = {
57
+ submit: () => Promise.resolve('published' as const),
58
+ withdraw: () => {},
59
+ };
43
60
  const broker = await createAndStartProvingBroker(config, telemetry);
44
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
+ }
45
105
 
46
- const provingJob = new EpochProvingJob(
47
- jobData,
48
- worldState,
49
- prover.createEpochProver(),
50
- publicProcessorFactory,
51
- publisher,
52
- l2BlockSourceForReorgDetection,
106
+ const l1Constants = { epochDuration: config.aztecEpochDuration };
107
+ const [fromSlot, toSlot] = getSlotRangeForEpoch(jobData.epochNumber, l1Constants);
108
+ const spec: SessionSpec = { kind: 'full', epochNumber: jobData.epochNumber, fromSlot, toSlot };
109
+
110
+ const session = new EpochSession(spec, provers, {
111
+ proverFactory: prover,
112
+ proverId: prover.getProverId(),
113
+ publishingService,
53
114
  metrics,
54
- deadline,
55
- { skipEpochCheck: true },
56
- log.getBindings(),
57
- );
115
+ dateProvider: new DateProvider(),
116
+ deadline: undefined,
117
+ config: {},
118
+ bindings: log.getBindings(),
119
+ });
120
+
121
+ const finalState = await session.start();
122
+ log.info(`Completed proving for epoch ${jobData.epochNumber} with status ${finalState}`, {
123
+ derivedEpoch: getEpochAtSlot(provers[0].slotNumber, l1Constants),
124
+ });
125
+ return finalState;
126
+ }
58
127
 
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();
128
+ /** Build a synthetic ITxProvider that returns the supplied txs map by lookup. */
129
+ function makeReplayingTxProvider(txs: Map<string, Tx>): ITxProvider {
130
+ const lookup = (hashes: TxHash[]) => {
131
+ const found: Tx[] = [];
132
+ const missing: TxHash[] = [];
133
+ for (const hash of hashes) {
134
+ const tx = txs.get(hash.toString());
135
+ if (tx) {
136
+ found.push(tx);
137
+ } else {
138
+ missing.push(hash);
139
+ }
140
+ }
141
+ return { txs: found, missingTxs: missing };
142
+ };
143
+ return {
144
+ getAvailableTxs: hashes => Promise.resolve(lookup(hashes)),
145
+ hasTxs: hashes => Promise.resolve(hashes.map(h => txs.has(h.toString()))),
146
+ getTxsForBlockProposal: () => Promise.resolve({ txs: [], missingTxs: [] }),
147
+ getTxsForBlock: (block: L2Block) => Promise.resolve(lookup(block.body.txEffects.map(e => e.txHash))),
148
+ };
63
149
  }
@@ -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,213 @@
1
+ import type { 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 whose checkpoint number is strictly greater than
101
+ * `prunedNumber` as pruned. Sub-tree work keeps running so a re-add of the same
102
+ * content can pick it up. Returns the affected provers.
103
+ */
104
+ public markPrunedAfter(prunedNumber: CheckpointNumber): CheckpointProver[] {
105
+ const affected: CheckpointProver[] = [];
106
+ for (const prover of this.provers.values()) {
107
+ if (prover.checkpoint.number > prunedNumber && !prover.isPruned()) {
108
+ prover.markPruned();
109
+ affected.push(prover);
110
+ }
111
+ }
112
+ return affected;
113
+ }
114
+
115
+ /**
116
+ * Drops canonical (non-pruned) provers whose epoch is at or below the supplied expired
117
+ * epoch. Once an epoch's proof-submission window has closed, its proof can no longer be
118
+ * accepted on L1, so the prover is no longer needed.
119
+ */
120
+ public reapExpired(expiredEpoch: EpochNumber): void {
121
+ const reaped: { id: string; checkpointNumber: CheckpointNumber; epochNumber: EpochNumber }[] = [];
122
+ for (const [id, prover] of Array.from(this.provers.entries())) {
123
+ if (prover.isPruned()) {
124
+ continue;
125
+ }
126
+ if (prover.epochNumber <= expiredEpoch) {
127
+ reaped.push({ id, checkpointNumber: prover.checkpoint.number, epochNumber: prover.epochNumber });
128
+ prover.cancel({ routine: true });
129
+ void prover.whenDone();
130
+ this.provers.delete(id);
131
+ }
132
+ }
133
+ if (reaped.length > 0) {
134
+ this.log.info(`Reaped ${reaped.length} expired CheckpointProver(s) for expiredEpoch ${expiredEpoch}`, {
135
+ expiredEpoch,
136
+ reapedCount: reaped.length,
137
+ reaped,
138
+ });
139
+ }
140
+ }
141
+
142
+ /** Returns the prover with the supplied id, or undefined. */
143
+ public get(id: string): CheckpointProver | undefined {
144
+ return this.provers.get(id);
145
+ }
146
+
147
+ /** Returns the prover for the supplied checkpoint (by its content-addressed id), or undefined. */
148
+ public getByCheckpoint(checkpoint: Checkpoint): CheckpointProver | undefined {
149
+ return this.provers.get(CheckpointProver.idFor(checkpoint));
150
+ }
151
+
152
+ /** Every prover currently in the store (canonical and pruned), in insertion order. */
153
+ public listAll(): CheckpointProver[] {
154
+ return Array.from(this.provers.values());
155
+ }
156
+
157
+ /** Canonical (non-pruned) provers in the store, sorted by checkpoint number. */
158
+ public listCanonical(): CheckpointProver[] {
159
+ return Array.from(this.provers.values())
160
+ .filter(p => !p.isPruned())
161
+ .sort((a, b) => a.checkpoint.number - b.checkpoint.number);
162
+ }
163
+
164
+ /**
165
+ * Canonical provers whose slot is in the supplied epoch's slot range, sorted by
166
+ * checkpoint number.
167
+ */
168
+ public async listCanonicalForEpoch(epoch: EpochNumber): Promise<CheckpointProver[]> {
169
+ const l1Constants = await this.l2BlockSource.getL1Constants();
170
+ const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, l1Constants);
171
+ return this.listCanonicalInSlotRange(fromSlot, toSlot);
172
+ }
173
+
174
+ /** Canonical provers whose slot falls within `[fromSlot, toSlot]`, sorted by checkpoint number. */
175
+ public listCanonicalInSlotRange(fromSlot: SlotNumber, toSlot: SlotNumber): CheckpointProver[] {
176
+ return this.listCanonical().filter(p => p.slotNumber >= fromSlot && p.slotNumber <= toSlot);
177
+ }
178
+
179
+ /**
180
+ * SlotWatcher tick: reap pruned provers whose slot has passed the chain's synced
181
+ * slot. Once the chain has moved past, no re-add can revive the prover and its
182
+ * content key is unique enough that an actual re-add would create a new entry.
183
+ *
184
+ * Protected so unit tests can drive a single tick without spinning up the
185
+ * `RunningPromise` and waiting on its interval.
186
+ */
187
+ protected async reapPrunedPastSlot(): Promise<void> {
188
+ let syncedSlot: SlotNumber | undefined;
189
+ try {
190
+ syncedSlot = await this.l2BlockSource.getSyncedL2SlotNumber();
191
+ } catch (err) {
192
+ this.log.debug(`SlotWatcher could not read synced slot`, { error: `${err}` });
193
+ return;
194
+ }
195
+ if (syncedSlot === undefined) {
196
+ return;
197
+ }
198
+ for (const [id, prover] of Array.from(this.provers.entries())) {
199
+ if (prover.isPruned() && prover.slotNumber < syncedSlot) {
200
+ this.log.info(`Reaping pruned CheckpointProver ${id}: slot ${prover.slotNumber} < synced ${syncedSlot}`, {
201
+ checkpointNumber: prover.checkpoint.number,
202
+ slotNumber: prover.slotNumber,
203
+ });
204
+ prover.cancel();
205
+ void prover.whenDone();
206
+ this.provers.delete(id);
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ /** Sub-set of `L1RollupConstants` actually consumed by the store's slot helpers. */
213
+ export type CheckpointStoreL1Constants = Pick<L1RollupConstants, 'epochDuration'>;
package/src/config.ts CHANGED
@@ -68,7 +68,8 @@ export const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProver
68
68
  defaultValue: undefined,
69
69
  },
70
70
  proverNodeEpochProvingDelayMs: {
71
- description: 'Optional delay in milliseconds to wait before proving a new epoch',
71
+ description:
72
+ 'Optional delay in milliseconds to wait for late-arriving events (e.g. reorgs) to settle before starting top-tree proving for an epoch',
72
73
  defaultValue: undefined,
73
74
  },
74
75
  txGatheringIntervalMs: {
package/src/factory.ts CHANGED
@@ -31,7 +31,6 @@ import { L1Metrics, type TelemetryClient, getTelemetryClient } from '@aztec/tele
31
31
  import { createPublicClient } from 'viem';
32
32
 
33
33
  import type { SpecificProverNodeConfig } from './config.js';
34
- import { EpochMonitor } from './monitors/epoch-monitor.js';
35
34
  import { ProverNode } from './prover-node.js';
36
35
  import { ProverPublisherFactory } from './prover-publisher-factory.js';
37
36
 
@@ -100,7 +99,7 @@ export async function createProverNode(
100
99
  pollingInterval: config.viemPollingIntervalMS,
101
100
  });
102
101
 
103
- const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
102
+ const rollupContract = new RollupContract(publicClient, config.rollupAddress.toString());
104
103
 
105
104
  const l1TxUtils = deps.l1TxUtils
106
105
  ? [deps.l1TxUtils]
@@ -119,11 +118,27 @@ export async function createProverNode(
119
118
  { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
120
119
  );
121
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
+
122
134
  const publisherFactory =
123
135
  deps.publisherFactory ??
124
136
  new ProverPublisherFactory(config, {
125
137
  rollupContract,
126
- publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), log.getBindings()),
138
+ publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), {
139
+ bindings: log.getBindings(),
140
+ funder: funderL1TxUtils,
141
+ }),
127
142
  telemetry,
128
143
  });
129
144
 
@@ -145,12 +160,6 @@ export async function createProverNode(
145
160
  ),
146
161
  };
147
162
 
148
- const epochMonitor = await EpochMonitor.create(
149
- archiver,
150
- { pollingIntervalMs: config.proverNodePollingIntervalMs, provingDelayMs: config.proverNodeEpochProvingDelayMs },
151
- telemetry,
152
- );
153
-
154
163
  const l1Metrics = new L1Metrics(
155
164
  telemetry.getMeter('ProverNodeL1Metrics'),
156
165
  publicClient,
@@ -168,7 +177,6 @@ export async function createProverNode(
168
177
  archiver,
169
178
  worldStateSynchronizer,
170
179
  p2pClient,
171
- epochMonitor,
172
180
  rollupContract,
173
181
  l1Metrics,
174
182
  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';