@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.
- package/dest/actions/download-epoch-proving-job.d.ts +18 -0
- package/dest/actions/download-epoch-proving-job.d.ts.map +1 -0
- package/dest/actions/download-epoch-proving-job.js +37 -0
- package/dest/actions/index.d.ts +3 -0
- package/dest/actions/index.d.ts.map +1 -0
- package/dest/actions/index.js +2 -0
- package/dest/actions/rerun-epoch-proving-job.d.ts +11 -0
- package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -0
- package/dest/actions/rerun-epoch-proving-job.js +40 -0
- package/dest/actions/upload-epoch-proof-failure.d.ts +15 -0
- package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -0
- package/dest/actions/upload-epoch-proof-failure.js +78 -0
- package/dest/bin/run-failed-epoch.d.ts +2 -0
- package/dest/bin/run-failed-epoch.d.ts.map +1 -0
- package/dest/bin/run-failed-epoch.js +67 -0
- package/dest/config.d.ts +13 -10
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +81 -14
- package/dest/factory.d.ts +13 -9
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +95 -31
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/job/epoch-proving-job-data.d.ts +17 -0
- package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
- package/dest/job/epoch-proving-job-data.js +53 -0
- package/dest/job/epoch-proving-job.d.ts +25 -17
- package/dest/job/epoch-proving-job.d.ts.map +1 -1
- package/dest/job/epoch-proving-job.js +155 -51
- package/dest/metrics.d.ts +29 -5
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +141 -35
- package/dest/monitors/epoch-monitor.d.ts +5 -2
- package/dest/monitors/epoch-monitor.d.ts.map +1 -1
- package/dest/monitors/epoch-monitor.js +15 -2
- package/dest/monitors/index.d.ts +1 -1
- package/dest/prover-node-publisher.d.ts +10 -12
- package/dest/prover-node-publisher.d.ts.map +1 -1
- package/dest/prover-node-publisher.js +62 -62
- package/dest/prover-node.d.ts +48 -43
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +175 -105
- package/dest/prover-publisher-factory.d.ts +21 -0
- package/dest/prover-publisher-factory.d.ts.map +1 -0
- package/dest/prover-publisher-factory.js +26 -0
- package/dest/test/index.d.ts +5 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +1 -3
- package/package.json +39 -33
- package/src/actions/download-epoch-proving-job.ts +44 -0
- package/src/actions/index.ts +2 -0
- package/src/actions/rerun-epoch-proving-job.ts +61 -0
- package/src/actions/upload-epoch-proof-failure.ts +88 -0
- package/src/bin/run-failed-epoch.ts +77 -0
- package/src/config.ts +108 -24
- package/src/factory.ts +163 -43
- package/src/index.ts +1 -1
- package/src/job/epoch-proving-job-data.ts +77 -0
- package/src/job/epoch-proving-job.ts +222 -52
- package/src/metrics.ts +135 -37
- package/src/monitors/epoch-monitor.ts +18 -6
- package/src/prover-node-publisher.ts +96 -88
- package/src/prover-node.ts +213 -136
- package/src/prover-publisher-factory.ts +37 -0
- package/src/test/index.ts +7 -4
- package/dest/http.d.ts +0 -8
- package/dest/http.d.ts.map +0 -1
- package/dest/http.js +0 -9
- package/dest/prover-coordination/config.d.ts +0 -7
- package/dest/prover-coordination/config.d.ts.map +0 -1
- package/dest/prover-coordination/config.js +0 -11
- package/dest/prover-coordination/factory.d.ts +0 -22
- package/dest/prover-coordination/factory.d.ts.map +0 -1
- package/dest/prover-coordination/factory.js +0 -42
- package/dest/prover-coordination/index.d.ts +0 -3
- package/dest/prover-coordination/index.d.ts.map +0 -1
- package/dest/prover-coordination/index.js +0 -2
- package/src/http.ts +0 -13
- package/src/prover-coordination/config.ts +0 -17
- package/src/prover-coordination/factory.ts +0 -72
- 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
|
|
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 {
|
|
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
|
|
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
|
|
39
|
-
private
|
|
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
|
|
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:
|
|
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.
|
|
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():
|
|
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]:
|
|
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
|
|
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.
|
|
96
|
-
await
|
|
137
|
+
const blobFieldsPerCheckpoint = this.blocks.map(block => block.getCheckpointBlobFields());
|
|
138
|
+
const finalBlobBatchingChallenges = await buildFinalBlobChallenges(blobFieldsPerCheckpoint);
|
|
97
139
|
|
|
98
|
-
|
|
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 =
|
|
103
|
-
const l1ToL2Messages =
|
|
104
|
-
const previousHeader =
|
|
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(
|
|
192
|
+
await this.prover.startNewBlock(block.number, globalVariables.timestamp, txs.length);
|
|
120
193
|
|
|
121
194
|
// Process public fns
|
|
122
|
-
const db = await this.
|
|
123
|
-
const
|
|
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
|
-
|
|
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.
|
|
141
|
-
this.log.info(`
|
|
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
|
-
|
|
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`, {
|
|
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
|
|
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.
|
|
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:
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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
|
|
219
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
28
|
+
private meter: Meter,
|
|
29
|
+
public readonly tracer: Tracer,
|
|
35
30
|
private logger = createLogger('prover-node:publisher:metrics'),
|
|
36
31
|
) {
|
|
37
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
}
|