@aztec/prover-node 0.0.0-test.1 → 0.0.1-commit.5daedc8

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 (82) 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 +13 -10
  17. package/dest/config.d.ts.map +1 -1
  18. package/dest/config.js +81 -14
  19. package/dest/factory.d.ts +13 -9
  20. package/dest/factory.d.ts.map +1 -1
  21. package/dest/factory.js +95 -31
  22. package/dest/index.d.ts +2 -2
  23. package/dest/index.d.ts.map +1 -1
  24. package/dest/index.js +1 -1
  25. package/dest/job/epoch-proving-job-data.d.ts +17 -0
  26. package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
  27. package/dest/job/epoch-proving-job-data.js +53 -0
  28. package/dest/job/epoch-proving-job.d.ts +25 -17
  29. package/dest/job/epoch-proving-job.d.ts.map +1 -1
  30. package/dest/job/epoch-proving-job.js +155 -51
  31. package/dest/metrics.d.ts +29 -5
  32. package/dest/metrics.d.ts.map +1 -1
  33. package/dest/metrics.js +141 -35
  34. package/dest/monitors/epoch-monitor.d.ts +5 -2
  35. package/dest/monitors/epoch-monitor.d.ts.map +1 -1
  36. package/dest/monitors/epoch-monitor.js +15 -2
  37. package/dest/monitors/index.d.ts +1 -1
  38. package/dest/prover-node-publisher.d.ts +10 -12
  39. package/dest/prover-node-publisher.d.ts.map +1 -1
  40. package/dest/prover-node-publisher.js +62 -62
  41. package/dest/prover-node.d.ts +48 -43
  42. package/dest/prover-node.d.ts.map +1 -1
  43. package/dest/prover-node.js +175 -105
  44. package/dest/prover-publisher-factory.d.ts +21 -0
  45. package/dest/prover-publisher-factory.d.ts.map +1 -0
  46. package/dest/prover-publisher-factory.js +26 -0
  47. package/dest/test/index.d.ts +5 -3
  48. package/dest/test/index.d.ts.map +1 -1
  49. package/dest/test/index.js +1 -3
  50. package/package.json +39 -33
  51. package/src/actions/download-epoch-proving-job.ts +44 -0
  52. package/src/actions/index.ts +2 -0
  53. package/src/actions/rerun-epoch-proving-job.ts +61 -0
  54. package/src/actions/upload-epoch-proof-failure.ts +88 -0
  55. package/src/bin/run-failed-epoch.ts +77 -0
  56. package/src/config.ts +108 -24
  57. package/src/factory.ts +163 -43
  58. package/src/index.ts +1 -1
  59. package/src/job/epoch-proving-job-data.ts +77 -0
  60. package/src/job/epoch-proving-job.ts +222 -52
  61. package/src/metrics.ts +135 -37
  62. package/src/monitors/epoch-monitor.ts +18 -6
  63. package/src/prover-node-publisher.ts +96 -88
  64. package/src/prover-node.ts +213 -136
  65. package/src/prover-publisher-factory.ts +37 -0
  66. package/src/test/index.ts +7 -4
  67. package/dest/http.d.ts +0 -8
  68. package/dest/http.d.ts.map +0 -1
  69. package/dest/http.js +0 -9
  70. package/dest/prover-coordination/config.d.ts +0 -7
  71. package/dest/prover-coordination/config.d.ts.map +0 -1
  72. package/dest/prover-coordination/config.js +0 -11
  73. package/dest/prover-coordination/factory.d.ts +0 -22
  74. package/dest/prover-coordination/factory.d.ts.map +0 -1
  75. package/dest/prover-coordination/factory.js +0 -42
  76. package/dest/prover-coordination/index.d.ts +0 -3
  77. package/dest/prover-coordination/index.d.ts.map +0 -1
  78. package/dest/prover-coordination/index.js +0 -2
  79. package/src/http.ts +0 -13
  80. package/src/prover-coordination/config.ts +0 -17
  81. package/src/prover-coordination/factory.ts +0 -72
  82. package/src/prover-coordination/index.ts +0 -2
@@ -1,8 +1,16 @@
1
+ import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
1
2
  import { asyncPool } from '@aztec/foundation/async-pool';
3
+ import { EpochNumber } from '@aztec/foundation/branded-types';
4
+ import { padArrayEnd } from '@aztec/foundation/collection';
5
+ import { Fr } from '@aztec/foundation/fields';
2
6
  import { createLogger } from '@aztec/foundation/log';
3
- import { promiseWithResolvers } from '@aztec/foundation/promise';
7
+ import { RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise';
4
8
  import { Timer } from '@aztec/foundation/timer';
9
+ import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
10
+ import { protocolContractsHash } from '@aztec/protocol-contracts';
11
+ import { buildFinalBlobChallenges } from '@aztec/prover-client/helpers';
5
12
  import type { PublicProcessor, PublicProcessorFactory } from '@aztec/simulator/server';
13
+ import { PublicSimulatorConfig } from '@aztec/stdlib/avm';
6
14
  import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
7
15
  import {
8
16
  type EpochProver,
@@ -10,17 +18,25 @@ import {
10
18
  EpochProvingJobTerminalState,
11
19
  type ForkMerkleTreeOperations,
12
20
  } from '@aztec/stdlib/interfaces/server';
13
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
21
+ import { CheckpointConstantData } from '@aztec/stdlib/rollup';
22
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
14
23
  import type { ProcessedTx, Tx } from '@aztec/stdlib/tx';
15
24
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
16
25
 
17
26
  import * as crypto from 'node:crypto';
18
27
 
19
- import type { ProverNodeMetrics } from '../metrics.js';
28
+ import type { ProverNodeJobMetrics } from '../metrics.js';
20
29
  import type { ProverNodePublisher } from '../prover-node-publisher.js';
30
+ import { type EpochProvingJobData, validateEpochProvingJobData } from './epoch-proving-job-data.js';
31
+
32
+ export type EpochProvingJobOptions = {
33
+ parallelBlockLimit?: number;
34
+ skipEpochCheck?: boolean;
35
+ skipSubmitProof?: boolean;
36
+ };
21
37
 
22
38
  /**
23
- * Job that grabs a range of blocks from the unfinalised chain from L1, gets their txs given their hashes,
39
+ * Job that grabs a range of blocks from the unfinalized chain from L1, gets their txs given their hashes,
24
40
  * re-executes their public calls, generates a rollup proof, and submits it to L1. This job will update the
25
41
  * world state as part of public call execution via the public processor.
26
42
  */
@@ -30,27 +46,25 @@ export class EpochProvingJob implements Traceable {
30
46
  private uuid: string;
31
47
 
32
48
  private runPromise: Promise<void> | undefined;
49
+ private epochCheckPromise: RunningPromise | undefined;
33
50
  private deadlineTimeoutHandler: NodeJS.Timeout | undefined;
34
51
 
35
52
  public readonly tracer: Tracer;
36
53
 
37
54
  constructor(
38
- private dbProvider: ForkMerkleTreeOperations,
39
- private epochNumber: bigint,
40
- private blocks: L2Block[],
41
- private txs: Tx[],
55
+ private data: EpochProvingJobData,
56
+ private dbProvider: Pick<ForkMerkleTreeOperations, 'fork'>,
42
57
  private prover: EpochProver,
43
58
  private publicProcessorFactory: PublicProcessorFactory,
44
- private publisher: ProverNodePublisher,
45
- private l2BlockSource: L2BlockSource,
46
- private l1ToL2MessageSource: L1ToL2MessageSource,
47
- private metrics: ProverNodeMetrics,
59
+ private publisher: Pick<ProverNodePublisher, 'submitEpochProof'>,
60
+ private l2BlockSource: L2BlockSource | undefined,
61
+ private metrics: ProverNodeJobMetrics,
48
62
  private deadline: Date | undefined,
49
- private config: { parallelBlockLimit: number } = { parallelBlockLimit: 32 },
50
- private cleanUp: (job: EpochProvingJob) => Promise<void> = () => Promise.resolve(),
63
+ private config: EpochProvingJobOptions,
51
64
  ) {
65
+ validateEpochProvingJobData(data);
52
66
  this.uuid = crypto.randomUUID();
53
- this.tracer = metrics.client.getTracer('EpochProvingJob');
67
+ this.tracer = metrics.tracer;
54
68
  }
55
69
 
56
70
  public getId(): string {
@@ -61,20 +75,48 @@ export class EpochProvingJob implements Traceable {
61
75
  return this.state;
62
76
  }
63
77
 
64
- public getEpochNumber(): bigint {
65
- return this.epochNumber;
78
+ public getEpochNumber(): EpochNumber {
79
+ return this.data.epochNumber;
80
+ }
81
+
82
+ public getDeadline(): Date | undefined {
83
+ return this.deadline;
84
+ }
85
+
86
+ public getProvingData(): EpochProvingJobData {
87
+ return this.data;
88
+ }
89
+
90
+ private get epochNumber() {
91
+ return this.data.epochNumber;
92
+ }
93
+
94
+ private get blocks() {
95
+ return this.data.blocks;
96
+ }
97
+
98
+ private get txs() {
99
+ return this.data.txs;
100
+ }
101
+
102
+ private get attestations() {
103
+ return this.data.attestations;
66
104
  }
67
105
 
68
106
  /**
69
107
  * Proves the given epoch and submits the proof to L1.
70
108
  */
71
109
  @trackSpan('EpochProvingJob.run', function () {
72
- return { [Attributes.EPOCH_NUMBER]: Number(this.epochNumber) };
110
+ return { [Attributes.EPOCH_NUMBER]: this.data.epochNumber };
73
111
  })
74
112
  public async run() {
75
113
  this.scheduleDeadlineStop();
114
+ if (!this.config.skipEpochCheck) {
115
+ await this.scheduleEpochCheck();
116
+ }
76
117
 
77
- const epochNumber = Number(this.epochNumber);
118
+ const attestations = this.attestations.map(attestation => attestation.toViem());
119
+ const epochNumber = this.epochNumber;
78
120
  const epochSizeBlocks = this.blocks.length;
79
121
  const epochSizeTxs = this.blocks.reduce((total, current) => total + current.body.txEffects.length, 0);
80
122
  const [fromBlock, toBlock] = [this.blocks[0].number, this.blocks.at(-1)!.number];
@@ -92,16 +134,23 @@ export class EpochProvingJob implements Traceable {
92
134
  this.runPromise = promise;
93
135
 
94
136
  try {
95
- this.prover.startNewEpoch(epochNumber, fromBlock, epochSizeBlocks);
96
- await this.prover.startTubeCircuits(this.txs);
137
+ const blobFieldsPerCheckpoint = this.blocks.map(block => block.getCheckpointBlobFields());
138
+ const finalBlobBatchingChallenges = await buildFinalBlobChallenges(blobFieldsPerCheckpoint);
97
139
 
98
- await asyncPool(this.config.parallelBlockLimit, this.blocks, async block => {
140
+ // TODO(#17027): Enable multiple blocks per checkpoint.
141
+ // Total number of checkpoints equals number of blocks because we currently build a checkpoint with only one block.
142
+ const totalNumCheckpoints = epochSizeBlocks;
143
+
144
+ this.prover.startNewEpoch(epochNumber, totalNumCheckpoints, finalBlobBatchingChallenges);
145
+ await this.prover.startChonkVerifierCircuits(Array.from(this.txs.values()));
146
+
147
+ await asyncPool(this.config.parallelBlockLimit ?? 32, this.blocks, async block => {
99
148
  this.checkState();
100
149
 
101
150
  const globalVariables = block.header.globalVariables;
102
- const txs = await this.getTxs(block);
103
- const l1ToL2Messages = await this.getL1ToL2Messages(block);
104
- const previousHeader = (await this.getBlockHeader(block.number - 1))!;
151
+ const txs = this.getTxs(block);
152
+ const l1ToL2Messages = this.getL1ToL2Messages(block);
153
+ const previousHeader = this.getBlockHeader(block.number - 1)!;
105
154
 
106
155
  this.log.verbose(`Starting processing block ${block.number}`, {
107
156
  number: block.number,
@@ -115,12 +164,44 @@ export class EpochProvingJob implements Traceable {
115
164
  ...globalVariables,
116
165
  });
117
166
 
167
+ const checkpointConstants = CheckpointConstantData.from({
168
+ chainId: globalVariables.chainId,
169
+ version: globalVariables.version,
170
+ vkTreeRoot: getVKTreeRoot(),
171
+ protocolContractsHash: protocolContractsHash,
172
+ proverId: this.prover.getProverId().toField(),
173
+ slotNumber: globalVariables.slotNumber,
174
+ coinbase: globalVariables.coinbase,
175
+ feeRecipient: globalVariables.feeRecipient,
176
+ gasFees: globalVariables.gasFees,
177
+ });
178
+
179
+ // TODO(#17027): Enable multiple blocks per checkpoint.
180
+ // Each checkpoint has only one block.
181
+ const totalNumBlocks = 1;
182
+ const checkpointIndex = block.number - fromBlock;
183
+ await this.prover.startNewCheckpoint(
184
+ checkpointIndex,
185
+ checkpointConstants,
186
+ l1ToL2Messages,
187
+ totalNumBlocks,
188
+ previousHeader,
189
+ );
190
+
118
191
  // Start block proving
119
- await this.prover.startNewBlock(globalVariables, l1ToL2Messages, previousHeader);
192
+ await this.prover.startNewBlock(block.number, globalVariables.timestamp, txs.length);
120
193
 
121
194
  // Process public fns
122
- const db = await this.dbProvider.fork(block.number - 1);
123
- const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, true);
195
+ const db = await this.createFork(block.number - 1, l1ToL2Messages);
196
+ const config = PublicSimulatorConfig.from({
197
+ proverId: this.prover.getProverId().toField(),
198
+ skipFeeEnforcement: false,
199
+ collectDebugLogs: false,
200
+ collectHints: true,
201
+ maxDebugLogMemoryReads: 0,
202
+ collectStatistics: false,
203
+ });
204
+ const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, config);
124
205
  const processed = await this.processTxs(publicProcessor, txs);
125
206
  await this.prover.addTxs(processed);
126
207
  await db.close();
@@ -131,17 +212,36 @@ export class EpochProvingJob implements Traceable {
131
212
  });
132
213
 
133
214
  // Mark block as completed to pad it
134
- await this.prover.setBlockCompleted(block.number, block.header);
215
+ const expectedBlockHeader = block.getBlockHeader();
216
+ await this.prover.setBlockCompleted(block.number, expectedBlockHeader);
135
217
  });
136
218
 
137
219
  const executionTime = timer.ms();
138
220
 
139
221
  this.progressState('awaiting-prover');
140
- const { publicInputs, proof } = await this.prover.finaliseEpoch();
141
- this.log.info(`Finalised proof for epoch ${epochNumber}`, { epochNumber, uuid: this.uuid, duration: timer.ms() });
222
+ const { publicInputs, proof, batchedBlobInputs } = await this.prover.finalizeEpoch();
223
+ this.log.info(`Finalized proof for epoch ${epochNumber}`, { epochNumber, uuid: this.uuid, duration: timer.ms() });
142
224
 
143
225
  this.progressState('publishing-proof');
144
- const success = await this.publisher.submitEpochProof({ fromBlock, toBlock, epochNumber, publicInputs, proof });
226
+
227
+ if (this.config.skipSubmitProof) {
228
+ this.log.info(
229
+ `Proof publishing is disabled. Dropping valid proof for epoch ${epochNumber} (blocks ${fromBlock} to ${toBlock})`,
230
+ );
231
+ this.state = 'completed';
232
+ this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeBlocks, epochSizeTxs);
233
+ return;
234
+ }
235
+
236
+ const success = await this.publisher.submitEpochProof({
237
+ fromBlock,
238
+ toBlock,
239
+ epochNumber,
240
+ publicInputs,
241
+ proof,
242
+ batchedBlobInputs,
243
+ attestations,
244
+ });
145
245
  if (!success) {
146
246
  throw new Error('Failed to submit epoch proof to L1');
147
247
  }
@@ -154,34 +254,59 @@ export class EpochProvingJob implements Traceable {
154
254
  this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeBlocks, epochSizeTxs);
155
255
  } catch (err: any) {
156
256
  if (err && err.name === 'HaltExecutionError') {
157
- this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, { uuid: this.uuid, epochNumber });
257
+ this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, {
258
+ uuid: this.uuid,
259
+ epochNumber,
260
+ details: err.message,
261
+ });
158
262
  return;
159
263
  }
160
264
  this.log.error(`Error running epoch ${epochNumber} prover job`, err, { uuid: this.uuid, epochNumber });
161
- this.state = 'failed';
265
+ if (this.state === 'processing' || this.state === 'awaiting-prover' || this.state === 'publishing-proof') {
266
+ this.state = 'failed';
267
+ }
162
268
  } finally {
163
269
  clearTimeout(this.deadlineTimeoutHandler);
164
- await this.cleanUp(this);
270
+ await this.epochCheckPromise?.stop();
165
271
  await this.prover.stop();
166
272
  resolve();
167
273
  }
168
274
  }
169
275
 
276
+ /**
277
+ * Create a new db fork for tx processing, inserting all L1 to L2.
278
+ * REFACTOR: The prover already spawns a db fork of its own for each block, so we may be able to do away with just one fork.
279
+ */
280
+ private async createFork(blockNumber: number, l1ToL2Messages: Fr[]) {
281
+ const db = await this.dbProvider.fork(blockNumber);
282
+ const l1ToL2MessagesPadded = padArrayEnd<Fr, number>(
283
+ l1ToL2Messages,
284
+ Fr.ZERO,
285
+ NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
286
+ 'Too many L1 to L2 messages',
287
+ );
288
+ this.log.verbose(`Creating fork at ${blockNumber} with ${l1ToL2Messages.length} L1 to L2 messages`, {
289
+ blockNumber,
290
+ l1ToL2Messages: l1ToL2Messages.map(m => m.toString()),
291
+ });
292
+ await db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
293
+ return db;
294
+ }
295
+
170
296
  private progressState(state: EpochProvingJobState) {
171
297
  this.checkState();
172
298
  this.state = state;
173
299
  }
174
300
 
175
301
  private checkState() {
176
- if (this.state === 'timed-out' || this.state === 'stopped' || this.state === 'failed') {
302
+ if (this.state === 'timed-out' || this.state === 'stopped' || this.state === 'failed' || this.state === 'reorg') {
177
303
  throw new HaltExecutionError(this.state);
178
304
  }
179
305
  }
180
306
 
181
- public async stop(state: EpochProvingJobState = 'stopped') {
307
+ public async stop(state: EpochProvingJobTerminalState = 'stopped') {
182
308
  this.state = state;
183
309
  this.prover.cancel();
184
- // TODO(palla/prover): Stop the publisher as well
185
310
  if (this.runPromise) {
186
311
  await this.runPromise;
187
312
  }
@@ -207,24 +332,66 @@ export class EpochProvingJob implements Traceable {
207
332
  }
208
333
  }
209
334
 
210
- /* Returns the header for the given block number, or the genesis block for block zero. */
211
- private async getBlockHeader(blockNumber: number) {
212
- if (blockNumber === 0) {
213
- return (await this.dbProvider.fork()).getInitialHeader();
335
+ /**
336
+ * Kicks off a running promise that queries the archiver for the set of L2 blocks of the current epoch.
337
+ * If those change, stops the proving job with a `rerun` state, so the node re-enqueues it.
338
+ */
339
+ private async scheduleEpochCheck() {
340
+ const l2BlockSource = this.l2BlockSource;
341
+ if (!l2BlockSource) {
342
+ this.log.warn(`No L2 block source available, skipping epoch check`);
343
+ return;
214
344
  }
215
- return this.l2BlockSource.getBlockHeader(blockNumber);
345
+
346
+ const intervalMs = Math.ceil((await l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000;
347
+ this.epochCheckPromise = new RunningPromise(
348
+ async () => {
349
+ const blocks = await l2BlockSource.getBlockHeadersForEpoch(this.epochNumber);
350
+ const blockHashes = await Promise.all(blocks.map(block => block.hash()));
351
+ const thisBlockHashes = await Promise.all(this.blocks.map(block => block.hash()));
352
+ if (
353
+ blocks.length !== this.blocks.length ||
354
+ !blockHashes.every((block, i) => block.equals(thisBlockHashes[i]))
355
+ ) {
356
+ this.log.warn('Epoch blocks changed underfoot', {
357
+ uuid: this.uuid,
358
+ epochNumber: this.epochNumber,
359
+ oldBlockHashes: thisBlockHashes,
360
+ newBlockHashes: blockHashes,
361
+ });
362
+ void this.stop('reorg');
363
+ }
364
+ },
365
+ this.log,
366
+ intervalMs,
367
+ ).start();
368
+ this.log.verbose(`Scheduled epoch check for epoch ${this.epochNumber} every ${intervalMs}ms`);
369
+ }
370
+
371
+ /* Returns the header for the given block number based on the epoch proving job data. */
372
+ private getBlockHeader(blockNumber: number) {
373
+ const block = this.blocks.find(b => b.number === blockNumber);
374
+ if (block) {
375
+ return block.getBlockHeader();
376
+ }
377
+
378
+ if (blockNumber === Number(this.data.previousBlockHeader.getBlockNumber())) {
379
+ return this.data.previousBlockHeader;
380
+ }
381
+
382
+ throw new Error(
383
+ `Block header not found for block number ${blockNumber} (got ${this.blocks
384
+ .map(b => b.number)
385
+ .join(', ')} and previous header ${this.data.previousBlockHeader.getBlockNumber()})`,
386
+ );
216
387
  }
217
388
 
218
- private async getTxs(block: L2Block): Promise<Tx[]> {
219
- const txHashes = block.body.txEffects.map(tx => tx.txHash.toBigInt());
220
- const txsAndHashes = await Promise.all(this.txs.map(async tx => ({ tx, hash: await tx.getTxHash() })));
221
- return txsAndHashes
222
- .filter(txAndHash => txHashes.includes(txAndHash.hash.toBigInt()))
223
- .map(txAndHash => txAndHash.tx);
389
+ private getTxs(block: L2Block): Tx[] {
390
+ return block.body.txEffects.map(txEffect => this.txs.get(txEffect.txHash.toString())!);
224
391
  }
225
392
 
226
393
  private getL1ToL2Messages(block: L2Block) {
227
- return this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(block.number));
394
+ return this.data.l1ToL2Messages[block.number];
228
395
  }
229
396
 
230
397
  private async processTxs(publicProcessor: PublicProcessor, txs: Tx[]): Promise<ProcessedTx[]> {
@@ -232,8 +399,11 @@ export class EpochProvingJob implements Traceable {
232
399
  const [processedTxs, failedTxs] = await publicProcessor.process(txs, { deadline });
233
400
 
234
401
  if (failedTxs.length) {
402
+ const failedTxHashes = await Promise.all(failedTxs.map(({ tx }) => tx.getTxHash()));
235
403
  throw new Error(
236
- `Txs failed processing: ${failedTxs.map(({ tx, error }) => `${tx.getTxHash()} (${error})`).join(', ')}`,
404
+ `Txs failed processing: ${failedTxs
405
+ .map(({ error }, index) => `${failedTxHashes[index]} (${error})`)
406
+ .join(', ')}`,
237
407
  );
238
408
  }
239
409
 
@@ -246,7 +416,7 @@ export class EpochProvingJob implements Traceable {
246
416
  }
247
417
 
248
418
  class HaltExecutionError extends Error {
249
- constructor(state: EpochProvingJobState) {
419
+ constructor(public readonly state: EpochProvingJobState) {
250
420
  super(`Halted execution due to state ${state}`);
251
421
  this.name = 'HaltExecutionError';
252
422
  }
package/src/metrics.ts CHANGED
@@ -1,106 +1,201 @@
1
+ import type { RollupContract } from '@aztec/ethereum';
2
+ import type { EthAddress } from '@aztec/foundation/eth-address';
1
3
  import { createLogger } from '@aztec/foundation/log';
2
4
  import type { L1PublishProofStats, L1PublishStats } from '@aztec/stdlib/stats';
3
5
  import {
4
6
  Attributes,
7
+ type BatchObservableResult,
5
8
  type Gauge,
6
9
  type Histogram,
10
+ type Meter,
7
11
  Metrics,
12
+ type ObservableGauge,
8
13
  type TelemetryClient,
14
+ type Tracer,
9
15
  type UpDownCounter,
10
16
  ValueType,
11
17
  } from '@aztec/telemetry-client';
12
18
 
13
- import { formatEther } from 'viem';
19
+ import { formatEther, formatUnits } from 'viem';
14
20
 
15
- export class ProverNodeMetrics {
21
+ export class ProverNodeJobMetrics {
16
22
  proverEpochExecutionDuration: Histogram;
17
23
  provingJobDuration: Histogram;
18
24
  provingJobBlocks: Gauge;
19
25
  provingJobTransactions: Gauge;
20
26
 
21
- gasPrice: Histogram;
22
- txCount: UpDownCounter;
23
- txDuration: Histogram;
24
- txGas: Histogram;
25
- txCalldataSize: Histogram;
26
- txCalldataGas: Histogram;
27
- txBlobDataGasUsed: Histogram;
28
- txBlobDataGasCost: Histogram;
29
-
30
- private senderBalance: Gauge;
31
-
32
27
  constructor(
33
- public readonly client: TelemetryClient,
34
- name = 'ProverNode',
28
+ private meter: Meter,
29
+ public readonly tracer: Tracer,
35
30
  private logger = createLogger('prover-node:publisher:metrics'),
36
31
  ) {
37
- const meter = client.getMeter(name);
38
- this.proverEpochExecutionDuration = meter.createHistogram(Metrics.PROVER_NODE_EXECUTION_DURATION, {
32
+ this.proverEpochExecutionDuration = this.meter.createHistogram(Metrics.PROVER_NODE_EXECUTION_DURATION, {
39
33
  description: 'Duration of execution of an epoch by the prover',
40
34
  unit: 'ms',
41
35
  valueType: ValueType.INT,
42
36
  });
43
- this.provingJobDuration = meter.createHistogram(Metrics.PROVER_NODE_JOB_DURATION, {
37
+ this.provingJobDuration = this.meter.createHistogram(Metrics.PROVER_NODE_JOB_DURATION, {
44
38
  description: 'Duration of proving job',
45
39
  unit: 's',
46
40
  valueType: ValueType.DOUBLE,
47
41
  });
48
- this.provingJobBlocks = meter.createGauge(Metrics.PROVER_NODE_JOB_BLOCKS, {
42
+ this.provingJobBlocks = this.meter.createGauge(Metrics.PROVER_NODE_JOB_BLOCKS, {
49
43
  description: 'Number of blocks in a proven epoch',
50
44
  valueType: ValueType.INT,
51
45
  });
52
- this.provingJobTransactions = meter.createGauge(Metrics.PROVER_NODE_JOB_TRANSACTIONS, {
46
+ this.provingJobTransactions = this.meter.createGauge(Metrics.PROVER_NODE_JOB_TRANSACTIONS, {
53
47
  description: 'Number of transactions in a proven epoch',
54
48
  valueType: ValueType.INT,
55
49
  });
50
+ }
51
+
52
+ public recordProvingJob(executionTimeMs: number, totalTimeMs: number, numBlocks: number, numTxs: number) {
53
+ this.proverEpochExecutionDuration.record(Math.ceil(executionTimeMs));
54
+ this.provingJobDuration.record(totalTimeMs / 1000);
55
+ this.provingJobBlocks.record(Math.floor(numBlocks));
56
+ this.provingJobTransactions.record(Math.floor(numTxs));
57
+ }
58
+ }
59
+
60
+ export class ProverNodeRewardsMetrics {
61
+ private rewards: ObservableGauge;
62
+ private accumulatedRewards: UpDownCounter;
63
+ private prevEpoch = -1n;
64
+ private proofSubmissionEpochs = 0;
65
+
66
+ constructor(
67
+ private meter: Meter,
68
+ private coinbase: EthAddress,
69
+ private rollup: RollupContract,
70
+ private logger = createLogger('prover-node:publisher:metrics'),
71
+ ) {
72
+ this.rewards = this.meter.createObservableGauge(Metrics.PROVER_NODE_REWARDS_PER_EPOCH, {
73
+ valueType: ValueType.DOUBLE,
74
+ description: 'The rewards earned',
75
+ });
76
+
77
+ this.accumulatedRewards = this.meter.createUpDownCounter(Metrics.PROVER_NODE_REWARDS_TOTAL, {
78
+ valueType: ValueType.DOUBLE,
79
+ description: 'The rewards earned (total)',
80
+ });
81
+ }
82
+
83
+ public async start() {
84
+ const proofSubmissionEpochs = await this.rollup.getProofSubmissionEpochs();
85
+ this.proofSubmissionEpochs = Number(proofSubmissionEpochs);
86
+ this.meter.addBatchObservableCallback(this.observe, [this.rewards]);
87
+ }
88
+
89
+ public stop() {
90
+ this.meter.removeBatchObservableCallback(this.observe, [this.rewards]);
91
+ }
92
+
93
+ private observe = async (observer: BatchObservableResult): Promise<void> => {
94
+ const epoch = await this.rollup.getCurrentEpochNumber();
95
+
96
+ if (epoch > this.proofSubmissionEpochs) {
97
+ // look at the prev epoch so that we get an accurate value, after proof submission window has closed
98
+ // For example, if proof submission window is 1 epoch, and we are in epoch 2, we should be looking at epoch 0.
99
+ // Similarly, if the proof submission window is 0, and we are in epoch 1, we should be looking at epoch 0.
100
+ const closedEpoch = BigInt(epoch) - BigInt(this.proofSubmissionEpochs) - 1n;
101
+ const rewards = await this.rollup.getSpecificProverRewardsForEpoch(closedEpoch, this.coinbase);
56
102
 
57
- this.gasPrice = meter.createHistogram(Metrics.L1_PUBLISHER_GAS_PRICE, {
103
+ const fmt = parseFloat(formatUnits(rewards, 18));
104
+
105
+ observer.observe(this.rewards, fmt, {
106
+ [Attributes.COINBASE]: this.coinbase.toString(),
107
+ });
108
+
109
+ // only accumulate once per epoch
110
+ if (closedEpoch > this.prevEpoch) {
111
+ this.prevEpoch = closedEpoch;
112
+ this.accumulatedRewards.add(fmt, {
113
+ [Attributes.COINBASE]: this.coinbase.toString(),
114
+ });
115
+ }
116
+ }
117
+ };
118
+ }
119
+
120
+ export class ProverNodePublisherMetrics {
121
+ gasPrice: Histogram;
122
+ txCount: UpDownCounter;
123
+ txDuration: Histogram;
124
+ txGas: Histogram;
125
+ txCalldataSize: Histogram;
126
+ txCalldataGas: Histogram;
127
+ txBlobDataGasUsed: Histogram;
128
+ txBlobDataGasCost: Histogram;
129
+ txTotalFee: Histogram;
130
+
131
+ private senderBalance: Gauge;
132
+ private meter: Meter;
133
+
134
+ constructor(
135
+ public readonly client: TelemetryClient,
136
+ name = 'ProverNode',
137
+ private logger = createLogger('prover-node:publisher:metrics'),
138
+ ) {
139
+ this.meter = client.getMeter(name);
140
+
141
+ this.gasPrice = this.meter.createHistogram(Metrics.L1_PUBLISHER_GAS_PRICE, {
58
142
  description: 'The gas price used for transactions',
59
143
  unit: 'gwei',
60
144
  valueType: ValueType.DOUBLE,
61
145
  });
62
146
 
63
- this.txCount = meter.createUpDownCounter(Metrics.L1_PUBLISHER_TX_COUNT, {
147
+ this.txCount = this.meter.createUpDownCounter(Metrics.L1_PUBLISHER_TX_COUNT, {
64
148
  description: 'The number of transactions processed',
65
149
  });
66
150
 
67
- this.txDuration = meter.createHistogram(Metrics.L1_PUBLISHER_TX_DURATION, {
151
+ this.txDuration = this.meter.createHistogram(Metrics.L1_PUBLISHER_TX_DURATION, {
68
152
  description: 'The duration of transaction processing',
69
153
  unit: 'ms',
70
154
  valueType: ValueType.INT,
71
155
  });
72
156
 
73
- this.txGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_GAS, {
157
+ this.txGas = this.meter.createHistogram(Metrics.L1_PUBLISHER_TX_GAS, {
74
158
  description: 'The gas consumed by transactions',
75
159
  unit: 'gas',
76
160
  valueType: ValueType.INT,
77
161
  });
78
162
 
79
- this.txCalldataSize = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_SIZE, {
163
+ this.txCalldataSize = this.meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_SIZE, {
80
164
  description: 'The size of the calldata in transactions',
81
165
  unit: 'By',
82
166
  valueType: ValueType.INT,
83
167
  });
84
168
 
85
- this.txCalldataGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_GAS, {
169
+ this.txCalldataGas = this.meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_GAS, {
86
170
  description: 'The gas consumed by the calldata in transactions',
87
171
  unit: 'gas',
88
172
  valueType: ValueType.INT,
89
173
  });
90
174
 
91
- this.txBlobDataGasUsed = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_USED, {
175
+ this.txBlobDataGasUsed = this.meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_USED, {
92
176
  description: 'The amount of blob gas used in transactions',
93
177
  unit: 'gas',
94
178
  valueType: ValueType.INT,
95
179
  });
96
180
 
97
- this.txBlobDataGasCost = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_COST, {
181
+ this.txBlobDataGasCost = this.meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_COST, {
98
182
  description: 'The gas cost of blobs in transactions',
99
183
  unit: 'gwei',
100
184
  valueType: ValueType.INT,
101
185
  });
102
186
 
103
- this.senderBalance = meter.createGauge(Metrics.L1_PUBLISHER_BALANCE, {
187
+ this.txTotalFee = this.meter.createHistogram(Metrics.L1_PUBLISHER_TX_TOTAL_FEE, {
188
+ description: 'How much L1 tx costs',
189
+ unit: 'gwei',
190
+ valueType: ValueType.DOUBLE,
191
+ advice: {
192
+ explicitBucketBoundaries: [
193
+ 0.001, 0.002, 0.004, 0.008, 0.01, 0.02, 0.04, 0.08, 0.1, 0.2, 0.4, 0.8, 1, 1.2, 1.4, 1.8, 2,
194
+ ],
195
+ },
196
+ });
197
+
198
+ this.senderBalance = this.meter.createGauge(Metrics.L1_PUBLISHER_BALANCE, {
104
199
  unit: 'eth',
105
200
  description: 'The balance of the sender address',
106
201
  valueType: ValueType.DOUBLE,
@@ -118,13 +213,6 @@ export class ProverNodeMetrics {
118
213
  this.recordTx(durationMs, stats);
119
214
  }
120
215
 
121
- public recordProvingJob(executionTimeMs: number, totalTimeMs: number, numBlocks: number, numTxs: number) {
122
- this.proverEpochExecutionDuration.record(Math.ceil(executionTimeMs));
123
- this.provingJobDuration.record(totalTimeMs / 1000);
124
- this.provingJobBlocks.record(Math.floor(numBlocks));
125
- this.provingJobTransactions.record(Math.floor(numTxs));
126
- }
127
-
128
216
  public recordSenderBalance(wei: bigint, senderAddress: string) {
129
217
  const eth = parseFloat(formatEther(wei, 'wei'));
130
218
  this.senderBalance.record(eth, {
@@ -157,7 +245,17 @@ export class ProverNodeMetrics {
157
245
 
158
246
  try {
159
247
  this.gasPrice.record(parseInt(formatEther(stats.gasPrice, 'gwei'), 10));
160
- } catch (e) {
248
+ } catch {
249
+ // ignore
250
+ }
251
+
252
+ const executionFee = stats.gasUsed * stats.gasPrice;
253
+ const blobFee = stats.blobGasUsed * stats.blobDataGas;
254
+ const totalFee = executionFee + blobFee;
255
+
256
+ try {
257
+ this.txTotalFee.record(parseFloat(formatEther(totalFee)));
258
+ } catch {
161
259
  // ignore
162
260
  }
163
261
  }