@aztec/prover-node 0.86.0 → 0.87.0

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 (59) hide show
  1. package/dest/actions/download-epoch-proving-job.d.ts +18 -0
  2. package/dest/actions/download-epoch-proving-job.d.ts.map +1 -0
  3. package/dest/actions/download-epoch-proving-job.js +37 -0
  4. package/dest/actions/index.d.ts +3 -0
  5. package/dest/actions/index.d.ts.map +1 -0
  6. package/dest/actions/index.js +2 -0
  7. package/dest/actions/rerun-epoch-proving-job.d.ts +11 -0
  8. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -0
  9. package/dest/actions/rerun-epoch-proving-job.js +40 -0
  10. package/dest/actions/upload-epoch-proof-failure.d.ts +15 -0
  11. package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -0
  12. package/dest/actions/upload-epoch-proof-failure.js +78 -0
  13. package/dest/bin/run-failed-epoch.d.ts +2 -0
  14. package/dest/bin/run-failed-epoch.d.ts.map +1 -0
  15. package/dest/bin/run-failed-epoch.js +67 -0
  16. package/dest/config.d.ts +2 -2
  17. package/dest/config.d.ts.map +1 -1
  18. package/dest/config.js +5 -0
  19. package/dest/factory.d.ts.map +1 -1
  20. package/dest/factory.js +5 -5
  21. package/dest/index.d.ts +1 -0
  22. package/dest/index.d.ts.map +1 -1
  23. package/dest/index.js +1 -0
  24. package/dest/job/epoch-proving-job-data.d.ts +15 -0
  25. package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
  26. package/dest/job/epoch-proving-job-data.js +45 -0
  27. package/dest/job/epoch-proving-job.d.ts +10 -9
  28. package/dest/job/epoch-proving-job.d.ts.map +1 -1
  29. package/dest/job/epoch-proving-job.js +41 -24
  30. package/dest/metrics.js +2 -2
  31. package/dest/prover-coordination/combined-prover-coordination.d.ts.map +1 -1
  32. package/dest/prover-coordination/combined-prover-coordination.js +7 -4
  33. package/dest/prover-coordination/config.d.ts.map +1 -1
  34. package/dest/prover-coordination/config.js +2 -1
  35. package/dest/prover-coordination/factory.d.ts.map +1 -1
  36. package/dest/prover-coordination/factory.js +8 -4
  37. package/dest/prover-node.d.ts +23 -18
  38. package/dest/prover-node.d.ts.map +1 -1
  39. package/dest/prover-node.js +88 -37
  40. package/dest/test/index.d.ts +4 -2
  41. package/dest/test/index.d.ts.map +1 -1
  42. package/dest/test/index.js +1 -1
  43. package/package.json +25 -24
  44. package/src/actions/download-epoch-proving-job.ts +44 -0
  45. package/src/actions/index.ts +2 -0
  46. package/src/actions/rerun-epoch-proving-job.ts +61 -0
  47. package/src/actions/upload-epoch-proof-failure.ts +88 -0
  48. package/src/bin/run-failed-epoch.ts +77 -0
  49. package/src/config.ts +7 -1
  50. package/src/factory.ts +21 -7
  51. package/src/index.ts +1 -0
  52. package/src/job/epoch-proving-job-data.ts +68 -0
  53. package/src/job/epoch-proving-job.ts +55 -23
  54. package/src/metrics.ts +2 -2
  55. package/src/prover-coordination/combined-prover-coordination.ts +9 -6
  56. package/src/prover-coordination/config.ts +1 -0
  57. package/src/prover-coordination/factory.ts +7 -4
  58. package/src/prover-node.ts +120 -53
  59. package/src/test/index.ts +7 -4
@@ -10,7 +10,6 @@ import {
10
10
  EpochProvingJobTerminalState,
11
11
  type ForkMerkleTreeOperations,
12
12
  } from '@aztec/stdlib/interfaces/server';
13
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
14
13
  import type { ProcessedTx, Tx } from '@aztec/stdlib/tx';
15
14
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
16
15
 
@@ -18,6 +17,7 @@ import * as crypto from 'node:crypto';
18
17
 
19
18
  import type { ProverNodeJobMetrics } from '../metrics.js';
20
19
  import type { ProverNodePublisher } from '../prover-node-publisher.js';
20
+ import { type EpochProvingJobData, validateEpochProvingJobData } from './epoch-proving-job-data.js';
21
21
 
22
22
  /**
23
23
  * Job that grabs a range of blocks from the unfinalised chain from L1, gets their txs given their hashes,
@@ -36,19 +36,17 @@ export class EpochProvingJob implements Traceable {
36
36
  public readonly tracer: Tracer;
37
37
 
38
38
  constructor(
39
- private dbProvider: ForkMerkleTreeOperations,
40
- private epochNumber: bigint,
41
- private blocks: L2Block[],
42
- private txs: Tx[],
39
+ private data: EpochProvingJobData,
40
+ private dbProvider: Pick<ForkMerkleTreeOperations, 'fork'>,
43
41
  private prover: EpochProver,
44
42
  private publicProcessorFactory: PublicProcessorFactory,
45
- private publisher: ProverNodePublisher,
46
- private l2BlockSource: L2BlockSource,
47
- private l1ToL2MessageSource: L1ToL2MessageSource,
43
+ private publisher: Pick<ProverNodePublisher, 'submitEpochProof'>,
44
+ private l2BlockSource: L2BlockSource | undefined,
48
45
  private metrics: ProverNodeJobMetrics,
49
46
  private deadline: Date | undefined,
50
- private config: { parallelBlockLimit: number } = { parallelBlockLimit: 32 },
47
+ private config: { parallelBlockLimit?: number; skipEpochCheck?: boolean },
51
48
  ) {
49
+ validateEpochProvingJobData(data);
52
50
  this.uuid = crypto.randomUUID();
53
51
  this.tracer = metrics.tracer;
54
52
  }
@@ -62,22 +60,40 @@ export class EpochProvingJob implements Traceable {
62
60
  }
63
61
 
64
62
  public getEpochNumber(): bigint {
65
- return this.epochNumber;
63
+ return this.data.epochNumber;
66
64
  }
67
65
 
68
66
  public getDeadline(): Date | undefined {
69
67
  return this.deadline;
70
68
  }
71
69
 
70
+ public getProvingData(): EpochProvingJobData {
71
+ return this.data;
72
+ }
73
+
74
+ private get epochNumber() {
75
+ return this.data.epochNumber;
76
+ }
77
+
78
+ private get blocks() {
79
+ return this.data.blocks;
80
+ }
81
+
82
+ private get txs() {
83
+ return this.data.txs;
84
+ }
85
+
72
86
  /**
73
87
  * Proves the given epoch and submits the proof to L1.
74
88
  */
75
89
  @trackSpan('EpochProvingJob.run', function () {
76
- return { [Attributes.EPOCH_NUMBER]: Number(this.epochNumber) };
90
+ return { [Attributes.EPOCH_NUMBER]: Number(this.data.epochNumber) };
77
91
  })
78
92
  public async run() {
79
93
  this.scheduleDeadlineStop();
80
- await this.scheduleEpochCheck();
94
+ if (!this.config.skipEpochCheck) {
95
+ await this.scheduleEpochCheck();
96
+ }
81
97
 
82
98
  const epochNumber = Number(this.epochNumber);
83
99
  const epochSizeBlocks = this.blocks.length;
@@ -100,13 +116,13 @@ export class EpochProvingJob implements Traceable {
100
116
  this.prover.startNewEpoch(epochNumber, fromBlock, epochSizeBlocks);
101
117
  await this.prover.startTubeCircuits(this.txs);
102
118
 
103
- await asyncPool(this.config.parallelBlockLimit, this.blocks, async block => {
119
+ await asyncPool(this.config.parallelBlockLimit ?? 32, this.blocks, async block => {
104
120
  this.checkState();
105
121
 
106
122
  const globalVariables = block.header.globalVariables;
107
123
  const txs = await this.getTxs(block);
108
- const l1ToL2Messages = await this.getL1ToL2Messages(block);
109
- const previousHeader = (await this.getBlockHeader(block.number - 1))!;
124
+ const l1ToL2Messages = this.getL1ToL2Messages(block);
125
+ const previousHeader = this.getBlockHeader(block.number - 1)!;
110
126
 
111
127
  this.log.verbose(`Starting processing block ${block.number}`, {
112
128
  number: block.number,
@@ -221,10 +237,16 @@ export class EpochProvingJob implements Traceable {
221
237
  * If those change, stops the proving job with a `rerun` state, so the node re-enqueues it.
222
238
  */
223
239
  private async scheduleEpochCheck() {
224
- const intervalMs = Math.ceil((await this.l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000;
240
+ const l2BlockSource = this.l2BlockSource;
241
+ if (!l2BlockSource) {
242
+ this.log.warn(`No L2 block source available, skipping epoch check`);
243
+ return;
244
+ }
245
+
246
+ const intervalMs = Math.ceil((await l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000;
225
247
  this.epochCheckPromise = new RunningPromise(
226
248
  async () => {
227
- const blocks = await this.l2BlockSource.getBlockHeadersForEpoch(this.epochNumber);
249
+ const blocks = await l2BlockSource.getBlockHeadersForEpoch(this.epochNumber);
228
250
  const blockHashes = await Promise.all(blocks.map(block => block.hash()));
229
251
  const thisBlockHashes = await Promise.all(this.blocks.map(block => block.hash()));
230
252
  if (
@@ -246,12 +268,22 @@ export class EpochProvingJob implements Traceable {
246
268
  this.log.verbose(`Scheduled epoch check for epoch ${this.epochNumber} every ${intervalMs}ms`);
247
269
  }
248
270
 
249
- /* Returns the header for the given block number, or the genesis block for block zero. */
250
- private async getBlockHeader(blockNumber: number) {
251
- if (blockNumber === 0) {
252
- return (await this.dbProvider.fork()).getInitialHeader();
271
+ /* Returns the header for the given block number based on the epoch proving job data. */
272
+ private getBlockHeader(blockNumber: number) {
273
+ const block = this.blocks.find(b => b.number === blockNumber);
274
+ if (block) {
275
+ return block.header;
253
276
  }
254
- return this.l2BlockSource.getBlockHeader(blockNumber);
277
+
278
+ if (blockNumber === Number(this.data.previousBlockHeader.getBlockNumber())) {
279
+ return this.data.previousBlockHeader;
280
+ }
281
+
282
+ throw new Error(
283
+ `Block header not found for block number ${blockNumber} (got ${this.blocks
284
+ .map(b => b.number)
285
+ .join(', ')} and previous header ${this.data.previousBlockHeader.getBlockNumber()})`,
286
+ );
255
287
  }
256
288
 
257
289
  private async getTxs(block: L2Block): Promise<Tx[]> {
@@ -263,7 +295,7 @@ export class EpochProvingJob implements Traceable {
263
295
  }
264
296
 
265
297
  private getL1ToL2Messages(block: L2Block) {
266
- return this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(block.number));
298
+ return this.data.l1ToL2Messages[block.number];
267
299
  }
268
300
 
269
301
  private async processTxs(publicProcessor: PublicProcessor, txs: Tx[]): Promise<ProcessedTx[]> {
package/src/metrics.ts CHANGED
@@ -242,7 +242,7 @@ export class ProverNodePublisherMetrics {
242
242
 
243
243
  try {
244
244
  this.gasPrice.record(parseInt(formatEther(stats.gasPrice, 'gwei'), 10));
245
- } catch (e) {
245
+ } catch {
246
246
  // ignore
247
247
  }
248
248
 
@@ -252,7 +252,7 @@ export class ProverNodePublisherMetrics {
252
252
 
253
253
  try {
254
254
  this.txTotalFee.record(parseFloat(formatEther(totalFee)));
255
- } catch (e) {
255
+ } catch {
256
256
  // ignore
257
257
  }
258
258
  }
@@ -94,7 +94,7 @@ export class CombinedProverCoordination implements ProverCoordination {
94
94
  const notFound = txHashes.filter((_, index) => !availability[index]);
95
95
  const txsToFind = new Set(notFound.map(tx => tx.toString()));
96
96
  if (txsToFind.size === 0) {
97
- this.log.info(`Check for ${txHashes.length} txs found all in the pool}`);
97
+ this.log.info(`Check for ${txHashes.length} txs found all in the pool`);
98
98
  return;
99
99
  }
100
100
  this.log.info(`Check for ${txHashes.length} txs found ${txsToFind.size} missing. Will gather from nodes and p2p`);
@@ -107,16 +107,19 @@ export class CombinedProverCoordination implements ProverCoordination {
107
107
  const toFindFromP2P = txsToFind.size;
108
108
  this.log.verbose(`Gathering ${toFindFromP2P} txs from p2p network`);
109
109
  const foundFromP2P = await pool.getTxsByHash([...txsToFind].map(tx => TxHash.fromString(tx)));
110
+
111
+ // TODO(!!): test for this
112
+ // getTxsByHash returns undefined for transactions that are not found, so it must be filtered to find the true length
113
+ const foundFromP2PLength = foundFromP2P.filter(tx => !!tx).length;
114
+
110
115
  const numFoundFromNodes = originalToFind - toFindFromP2P;
111
- const numNotFound = toFindFromP2P - foundFromP2P.length;
116
+ const numNotFound = toFindFromP2P - foundFromP2PLength;
112
117
  if (numNotFound === 0) {
113
- this.log.info(
114
- `Found all ${originalToFind} txs. ${numFoundFromNodes} from nodes, ${foundFromP2P.length} from p2p`,
115
- );
118
+ this.log.info(`Found all ${originalToFind} txs. ${numFoundFromNodes} from nodes, ${foundFromP2PLength} from p2p`);
116
119
  return;
117
120
  }
118
121
  this.log.warn(
119
- `Failed to find ${numNotFound} txs from any source. Found ${foundFromP2P.length} from p2p and ${numFoundFromNodes} from nodes`,
122
+ `Failed to find ${numNotFound} txs from any source. Found ${foundFromP2PLength} from p2p and ${numFoundFromNodes} from nodes`,
120
123
  );
121
124
  }
122
125
 
@@ -9,6 +9,7 @@ export const proverCoordinationConfigMappings: ConfigMappingsType<ProverCoordina
9
9
  env: 'PROVER_COORDINATION_NODE_URLS',
10
10
  description: 'The URLs of the tx provider nodes',
11
11
  parseEnv: (val: string) => val.split(',').map(url => url.trim().replace(/\/$/, '')),
12
+ defaultValue: [],
12
13
  },
13
14
  };
14
15
 
@@ -9,6 +9,7 @@ import { protocolContractTreeRoot } from '@aztec/protocol-contracts';
9
9
  import { type AztecNode, createAztecNodeClient } from '@aztec/stdlib/interfaces/client';
10
10
  import type { ProverCoordination, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
11
11
  import { P2PClientType } from '@aztec/stdlib/p2p';
12
+ import { getPackageVersion } from '@aztec/stdlib/update-checker';
12
13
  import { getComponentsVersionsFromConfig } from '@aztec/stdlib/versioning';
13
14
  import { type TelemetryClient, makeTracedFetch } from '@aztec/telemetry-client';
14
15
 
@@ -59,12 +60,13 @@ export async function createProverCoordination(
59
60
  }
60
61
 
61
62
  let nodes: AztecNode[] = [];
62
- if (config.proverCoordinationNodeUrls.length > 0) {
63
+ if (config.proverCoordinationNodeUrls && config.proverCoordinationNodeUrls.length > 0) {
63
64
  log.info('Using prover coordination via node urls');
64
65
  const versions = getComponentsVersionsFromConfig(config, protocolContractTreeRoot, getVKTreeRoot());
65
- nodes = config.proverCoordinationNodeUrls.map(url =>
66
- createAztecNodeClient(url, versions, makeTracedFetch([1, 2, 3], false)),
67
- );
66
+ nodes = config.proverCoordinationNodeUrls.map(url => {
67
+ log.info(`Creating aztec node client for prover coordination with url: ${url}`);
68
+ return createAztecNodeClient(url, versions, makeTracedFetch([1, 2, 3], false));
69
+ });
68
70
  }
69
71
 
70
72
  const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier();
@@ -75,6 +77,7 @@ export async function createProverCoordination(
75
77
  proofVerifier,
76
78
  deps.worldStateSynchronizer,
77
79
  deps.epochCache,
80
+ getPackageVersion() ?? '',
78
81
  deps.telemetry,
79
82
  );
80
83
  await p2pClient.start();
@@ -1,13 +1,16 @@
1
+ import type { Archiver } from '@aztec/archiver';
1
2
  import type { ViemPublicClient } from '@aztec/ethereum';
2
- import { compact } from '@aztec/foundation/collection';
3
+ import { assertRequired, compact, pick, sum } from '@aztec/foundation/collection';
3
4
  import { memoize } from '@aztec/foundation/decorators';
4
5
  import { EthAddress } from '@aztec/foundation/eth-address';
6
+ import type { Fr } from '@aztec/foundation/fields';
5
7
  import { createLogger } from '@aztec/foundation/log';
6
8
  import { RunningPromise } from '@aztec/foundation/running-promise';
7
9
  import { DateProvider } from '@aztec/foundation/timer';
8
- import type { Maybe } from '@aztec/foundation/types';
10
+ import type { DataStoreConfig } from '@aztec/kv-store/config';
9
11
  import { PublicProcessorFactory } from '@aztec/simulator/server';
10
12
  import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
13
+ import type { ChainConfig } from '@aztec/stdlib/config';
11
14
  import type { ContractDataSource } from '@aztec/stdlib/contract';
12
15
  import { getProofSubmissionDeadlineTimestamp } from '@aztec/stdlib/epoch-helpers';
13
16
  import {
@@ -21,7 +24,7 @@ import {
21
24
  tryStop,
22
25
  } from '@aztec/stdlib/interfaces/server';
23
26
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
24
- import type { Tx, TxHash } from '@aztec/stdlib/tx';
27
+ import type { TxHash } from '@aztec/stdlib/tx';
25
28
  import {
26
29
  Attributes,
27
30
  L1Metrics,
@@ -32,22 +35,20 @@ import {
32
35
  trackSpan,
33
36
  } from '@aztec/telemetry-client';
34
37
 
38
+ import { uploadEpochProofFailure } from './actions/upload-epoch-proof-failure.js';
39
+ import type { SpecificProverNodeConfig } from './config.js';
40
+ import type { EpochProvingJobData } from './job/epoch-proving-job-data.js';
35
41
  import { EpochProvingJob, type EpochProvingJobState } from './job/epoch-proving-job.js';
36
42
  import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
37
43
  import type { EpochMonitor, EpochMonitorHandler } from './monitors/epoch-monitor.js';
38
44
  import type { ProverNodePublisher } from './prover-node-publisher.js';
39
45
 
40
- export type ProverNodeOptions = {
41
- pollingIntervalMs: number;
42
- maxPendingJobs: number;
43
- maxParallelBlocksPerEpoch: number;
44
- txGatheringIntervalMs: number;
45
- };
46
+ type ProverNodeOptions = SpecificProverNodeConfig & Partial<DataStoreOptions>;
47
+ type DataStoreOptions = Pick<DataStoreConfig, 'dataDirectory'> & Pick<ChainConfig, 'l1ChainId' | 'rollupVersion'>;
46
48
 
47
49
  /**
48
- * An Aztec Prover Node is a standalone process that monitors the unfinalised chain on L1 for unproven blocks,
49
- * submits bids for proving them, and monitors if they are accepted. If so, the prover node fetches the txs
50
- * from a tx source in the p2p network or an external node, re-executes their public functions, creates a rollup
50
+ * An Aztec Prover Node is a standalone process that monitors the unfinalised chain on L1 for unproven epochs,
51
+ * fetches their txs from the p2p network or external nodes, re-executes their public functions, creates a rollup
51
52
  * proof for the epoch, and submits it to L1.
52
53
  */
53
54
  export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable {
@@ -55,7 +56,7 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
55
56
  private dateProvider = new DateProvider();
56
57
 
57
58
  private jobs: Map<string, EpochProvingJob> = new Map();
58
- private options: ProverNodeOptions;
59
+ private config: ProverNodeOptions;
59
60
  private jobMetrics: ProverNodeJobMetrics;
60
61
  private rewardsMetrics: ProverNodeRewardsMetrics;
61
62
  private l1Metrics: L1Metrics;
@@ -68,13 +69,13 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
68
69
  constructor(
69
70
  protected readonly prover: EpochProverManager,
70
71
  protected readonly publisher: ProverNodePublisher,
71
- protected readonly l2BlockSource: L2BlockSource & Maybe<Service>,
72
+ protected readonly l2BlockSource: L2BlockSource & Partial<Service>,
72
73
  protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
73
74
  protected readonly contractDataSource: ContractDataSource,
74
75
  protected readonly worldState: WorldStateSynchronizer,
75
- protected readonly coordination: ProverCoordination,
76
+ protected readonly coordination: ProverCoordination & Partial<Service>,
76
77
  protected readonly epochsMonitor: EpochMonitor,
77
- options: Partial<ProverNodeOptions> = {},
78
+ config: Partial<ProverNodeOptions> = {},
78
79
  protected readonly telemetryClient: TelemetryClient = getTelemetryClient(),
79
80
  ) {
80
81
  this.l1Metrics = new L1Metrics(
@@ -83,14 +84,19 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
83
84
  [publisher.getSenderAddress()],
84
85
  );
85
86
 
86
- this.options = {
87
- pollingIntervalMs: 1_000,
88
- maxPendingJobs: 100,
89
- maxParallelBlocksPerEpoch: 32,
87
+ this.config = {
88
+ proverNodePollingIntervalMs: 1_000,
89
+ proverNodeMaxPendingJobs: 100,
90
+ proverNodeMaxParallelBlocksPerEpoch: 32,
90
91
  txGatheringIntervalMs: 1_000,
91
- ...compact(options),
92
+ txGatheringBatchSize: 10,
93
+ txGatheringMaxParallelRequestsPerNode: 100,
94
+ proverNodeFailedEpochStore: undefined,
95
+ ...compact(config),
92
96
  };
93
97
 
98
+ this.validateConfig();
99
+
94
100
  const meter = telemetryClient.getMeter('ProverNode');
95
101
  this.tracer = telemetryClient.getTracer('ProverNode');
96
102
 
@@ -102,7 +108,7 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
102
108
  this.publisher.getRollupContract(),
103
109
  );
104
110
 
105
- this.txFetcher = new RunningPromise(() => this.checkForTxs(), this.log, this.options.txGatheringIntervalMs);
111
+ this.txFetcher = new RunningPromise(() => this.checkForTxs(), this.log, this.config.txGatheringIntervalMs);
106
112
  }
107
113
 
108
114
  public getProverId() {
@@ -151,7 +157,7 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
151
157
  this.epochsMonitor.start(this);
152
158
  this.l1Metrics.start();
153
159
  await this.rewardsMetrics.start();
154
- this.log.info(`Started Prover Node with prover id ${this.prover.getProverId().toString()}`, this.options);
160
+ this.log.info(`Started Prover Node with prover id ${this.prover.getProverId().toString()}`, this.config);
155
161
  }
156
162
 
157
163
  /**
@@ -188,28 +194,49 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
188
194
  * Starts a proving process and returns immediately.
189
195
  */
190
196
  public async startProof(epochNumber: number | bigint) {
191
- const job = await this.createProvingJob(BigInt(epochNumber));
197
+ const job = await this.createProvingJob(BigInt(epochNumber), { skipEpochCheck: true });
192
198
  void this.runJob(job);
193
199
  }
194
200
 
195
201
  private async runJob(job: EpochProvingJob) {
196
- const ctx = { id: job.getId(), epochNumber: job.getEpochNumber() };
202
+ const epochNumber = job.getEpochNumber();
203
+ const ctx = { id: job.getId(), epochNumber, state: undefined as EpochProvingJobState | undefined };
204
+
197
205
  try {
198
206
  await job.run();
199
207
  const state = job.getState();
208
+ ctx.state = state;
209
+
200
210
  if (state === 'reorg') {
201
- this.log.warn(`Running new job for epoch ${job.getEpochNumber()} due to reorg`, ctx);
202
- await this.createProvingJob(job.getEpochNumber());
211
+ this.log.warn(`Running new job for epoch ${epochNumber} due to reorg`, ctx);
212
+ await this.createProvingJob(epochNumber);
213
+ } else if (state === 'failed') {
214
+ this.log.error(`Job for ${epochNumber} exited with state ${state}`, ctx);
215
+ await this.tryUploadEpochFailure(job);
203
216
  } else {
204
- this.log.verbose(`Job for ${job.getEpochNumber()} exited with state ${state}`, ctx);
217
+ this.log.verbose(`Job for ${epochNumber} exited with state ${state}`, ctx);
205
218
  }
206
219
  } catch (err) {
207
- this.log.error(`Error proving epoch ${job.getEpochNumber()}`, err, ctx);
220
+ this.log.error(`Error proving epoch ${epochNumber}`, err, ctx);
208
221
  } finally {
209
222
  this.jobs.delete(job.getId());
210
223
  }
211
224
  }
212
225
 
226
+ protected async tryUploadEpochFailure(job: EpochProvingJob) {
227
+ if (this.config.proverNodeFailedEpochStore) {
228
+ return await uploadEpochProofFailure(
229
+ this.config.proverNodeFailedEpochStore,
230
+ job.getId(),
231
+ job.getProvingData(),
232
+ this.l2BlockSource as Archiver,
233
+ this.worldState,
234
+ assertRequired(pick(this.config, 'l1ChainId', 'rollupVersion', 'dataDirectory')),
235
+ this.log,
236
+ );
237
+ }
238
+ }
239
+
213
240
  /**
214
241
  * Returns the prover instance.
215
242
  */
@@ -239,36 +266,38 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
239
266
  }
240
267
 
241
268
  private checkMaximumPendingJobs() {
242
- const { maxPendingJobs } = this.options;
243
- return maxPendingJobs === 0 || this.jobs.size < maxPendingJobs;
269
+ const { proverNodeMaxPendingJobs: maxPendingJobs } = this.config;
270
+ if (maxPendingJobs > 0 && this.jobs.size >= maxPendingJobs) {
271
+ throw new Error(`Maximum pending proving jobs ${maxPendingJobs} reached. Cannot create new job.`);
272
+ }
244
273
  }
245
274
 
246
275
  @trackSpan('ProverNode.createProvingJob', epochNumber => ({ [Attributes.EPOCH_NUMBER]: Number(epochNumber) }))
247
- private async createProvingJob(epochNumber: bigint) {
248
- if (!this.checkMaximumPendingJobs()) {
249
- throw new Error(`Maximum pending proving jobs ${this.options.maxPendingJobs} reached. Cannot create new job.`);
250
- }
276
+ private async createProvingJob(epochNumber: bigint, opts: { skipEpochCheck?: boolean } = {}) {
277
+ this.checkMaximumPendingJobs();
251
278
 
252
- // Gather blocks for this epoch
253
- const { blocks, txs } = await this.gatherEpochData(epochNumber);
279
+ // Gather all data for this epoch
280
+ const epochData = await this.gatherEpochData(epochNumber);
254
281
 
255
- const fromBlock = blocks[0].number;
256
- const toBlock = blocks.at(-1)!.number;
282
+ const fromBlock = epochData.blocks[0].number;
283
+ const toBlock = epochData.blocks.at(-1)!.number;
284
+ this.log.verbose(`Creating proving job for epoch ${epochNumber} for block range ${fromBlock} to ${toBlock}`);
257
285
 
258
286
  // Fast forward world state to right before the target block and get a fork
259
- this.log.verbose(`Creating proving job for epoch ${epochNumber} for block range ${fromBlock} to ${toBlock}`);
260
287
  await this.worldState.syncImmediate(toBlock);
261
288
 
262
- // Create a processor using the forked world state
289
+ // Create a processor factory
263
290
  const publicProcessorFactory = new PublicProcessorFactory(
264
291
  this.contractDataSource,
265
292
  this.dateProvider,
266
293
  this.telemetryClient,
267
294
  );
268
295
 
296
+ // Set deadline for this job to run. It will abort if it takes too long.
269
297
  const deadlineTs = getProofSubmissionDeadlineTimestamp(epochNumber, await this.getL1Constants());
270
298
  const deadline = new Date(Number(deadlineTs) * 1000);
271
- const job = this.doCreateEpochProvingJob(epochNumber, deadline, blocks, txs, publicProcessorFactory);
299
+
300
+ const job = this.doCreateEpochProvingJob(epochData, deadline, publicProcessorFactory, opts);
272
301
  this.jobs.set(job.getId(), job);
273
302
  return job;
274
303
  }
@@ -295,12 +324,13 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
295
324
  }
296
325
 
297
326
  @trackSpan('ProverNode.gatherEpochData', epochNumber => ({ [Attributes.EPOCH_NUMBER]: Number(epochNumber) }))
298
- private async gatherEpochData(epochNumber: bigint) {
299
- // Gather blocks for this epoch and their txs
327
+ private async gatherEpochData(epochNumber: bigint): Promise<EpochProvingJobData> {
300
328
  const blocks = await this.gatherBlocks(epochNumber);
301
329
  const txs = await this.gatherTxs(epochNumber, blocks);
330
+ const l1ToL2Messages = await this.gatherMessages(epochNumber, blocks);
331
+ const previousBlockHeader = await this.gatherPreviousBlockHeader(epochNumber, blocks[0]);
302
332
 
303
- return { blocks, txs };
333
+ return { blocks, txs, l1ToL2Messages, epochNumber, previousBlockHeader };
304
334
  }
305
335
 
306
336
  private async gatherBlocks(epochNumber: bigint) {
@@ -328,27 +358,49 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
328
358
  throw new Error(`Txs not found for epoch ${epochNumber}: ${missingTxHashes}`);
329
359
  }
330
360
 
361
+ private async gatherMessages(epochNumber: bigint, blocks: L2Block[]) {
362
+ const messages = await Promise.all(blocks.map(b => this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(b.number))));
363
+ const messageCount = sum(messages.map(m => m.length));
364
+ this.log.verbose(`Gathered all ${messageCount} messages for epoch ${epochNumber}`, { epochNumber });
365
+ const messagesByBlock: Record<number, Fr[]> = {};
366
+ for (let i = 0; i < blocks.length; i++) {
367
+ messagesByBlock[blocks[i].number] = messages[i];
368
+ }
369
+ return messagesByBlock;
370
+ }
371
+
372
+ private async gatherPreviousBlockHeader(epochNumber: bigint, initialBlock: L2Block) {
373
+ const previousBlockNumber = initialBlock.number - 1;
374
+ const header = await (previousBlockNumber === 0
375
+ ? this.worldState.getCommitted().getInitialHeader()
376
+ : this.l2BlockSource.getBlockHeader(previousBlockNumber));
377
+
378
+ if (!header) {
379
+ throw new Error(`Previous block header ${initialBlock.number} not found for proving epoch ${epochNumber}`);
380
+ }
381
+
382
+ this.log.verbose(`Gathered previous block header ${header.getBlockNumber()} for epoch ${epochNumber}`);
383
+ return header;
384
+ }
385
+
331
386
  /** Extracted for testing purposes. */
332
387
  protected doCreateEpochProvingJob(
333
- epochNumber: bigint,
388
+ data: EpochProvingJobData,
334
389
  deadline: Date | undefined,
335
- blocks: L2Block[],
336
- txs: Tx[],
337
390
  publicProcessorFactory: PublicProcessorFactory,
391
+ opts: { skipEpochCheck?: boolean } = {},
338
392
  ) {
393
+ const { proverNodeMaxParallelBlocksPerEpoch: parallelBlockLimit } = this.config;
339
394
  return new EpochProvingJob(
395
+ data,
340
396
  this.worldState,
341
- epochNumber,
342
- blocks,
343
- txs,
344
397
  this.prover.createEpochProver(),
345
398
  publicProcessorFactory,
346
399
  this.publisher,
347
400
  this.l2BlockSource,
348
- this.l1ToL2MessageSource,
349
401
  this.jobMetrics,
350
402
  deadline,
351
- { parallelBlockLimit: this.options.maxParallelBlocksPerEpoch },
403
+ { parallelBlockLimit, ...opts },
352
404
  );
353
405
  }
354
406
 
@@ -356,6 +408,21 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
356
408
  protected async triggerMonitors() {
357
409
  await this.epochsMonitor.work();
358
410
  }
411
+
412
+ private validateConfig() {
413
+ if (
414
+ this.config.proverNodeFailedEpochStore &&
415
+ (!this.config.dataDirectory || !this.config.l1ChainId || !this.config.rollupVersion)
416
+ ) {
417
+ this.log.warn(
418
+ `Invalid prover-node config (missing dataDirectory, l1ChainId, or rollupVersion)`,
419
+ pick(this.config, 'proverNodeFailedEpochStore', 'dataDirectory', 'l1ChainId', 'rollupVersion'),
420
+ );
421
+ throw new Error(
422
+ 'All of dataDirectory, l1ChainId, and rollupVersion are required if proverNodeFailedEpochStore is set.',
423
+ );
424
+ }
425
+ }
359
426
  }
360
427
 
361
428
  class EmptyEpochError extends Error {
package/src/test/index.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import type { EpochProverManager } from '@aztec/stdlib/interfaces/server';
2
2
 
3
+ import type { EpochProvingJob } from '../job/epoch-proving-job.js';
3
4
  import type { ProverNodePublisher } from '../prover-node-publisher.js';
4
5
  import { ProverNode } from '../prover-node.js';
5
6
 
6
- class TestProverNode_ extends ProverNode {
7
- public declare prover: EpochProverManager;
8
- public declare publisher: ProverNodePublisher;
7
+ abstract class TestProverNodeClass extends ProverNode {
8
+ declare public prover: EpochProverManager;
9
+ declare public publisher: ProverNodePublisher;
10
+
11
+ public abstract override tryUploadEpochFailure(job: EpochProvingJob): Promise<string | undefined>;
9
12
  }
10
13
 
11
- export type TestProverNode = TestProverNode_;
14
+ export type TestProverNode = TestProverNodeClass;