@aztec/prover-node 0.0.0-test.1 → 0.0.1-commit.b655e406
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 +12 -9
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +81 -14
- package/dest/factory.d.ts +12 -8
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +95 -31
- package/dest/index.d.ts +1 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/job/epoch-proving-job-data.d.ts +16 -0
- package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
- package/dest/job/epoch-proving-job-data.js +52 -0
- package/dest/job/epoch-proving-job.d.ts +30 -15
- package/dest/job/epoch-proving-job.d.ts.map +1 -1
- package/dest/job/epoch-proving-job.js +149 -50
- package/dest/metrics.d.ts +28 -4
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +141 -35
- package/dest/monitors/epoch-monitor.d.ts +3 -1
- package/dest/monitors/epoch-monitor.d.ts.map +1 -1
- package/dest/monitors/epoch-monitor.js +15 -2
- package/dest/prover-node-publisher.d.ts +7 -10
- package/dest/prover-node-publisher.d.ts.map +1 -1
- package/dest/prover-node-publisher.js +59 -60
- package/dest/prover-node.d.ts +43 -39
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +171 -100
- 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 +4 -2
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +1 -3
- package/package.json +36 -31
- 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 +161 -43
- package/src/index.ts +1 -1
- package/src/job/epoch-proving-job-data.ts +76 -0
- package/src/job/epoch-proving-job.ts +215 -50
- package/src/metrics.ts +135 -37
- package/src/monitors/epoch-monitor.ts +16 -5
- package/src/prover-node-publisher.ts +93 -86
- package/src/prover-node.ts +203 -126
- 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
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 = 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
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
2
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
3
|
+
import { sleep } from '@aztec/foundation/sleep';
|
|
3
4
|
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
4
5
|
import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
5
6
|
import {
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
} from '@aztec/telemetry-client';
|
|
12
13
|
|
|
13
14
|
export interface EpochMonitorHandler {
|
|
14
|
-
handleEpochReadyToProve(epochNumber: bigint): Promise<
|
|
15
|
+
handleEpochReadyToProve(epochNumber: bigint): Promise<boolean>;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -36,16 +37,19 @@ export class EpochMonitor implements Traceable {
|
|
|
36
37
|
constructor(
|
|
37
38
|
private readonly l2BlockSource: L2BlockSource,
|
|
38
39
|
private readonly l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
39
|
-
private options: { pollingIntervalMs: number },
|
|
40
|
+
private options: { pollingIntervalMs: number; provingDelayMs?: number },
|
|
40
41
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
41
42
|
) {
|
|
42
43
|
this.tracer = telemetry.getTracer('EpochMonitor');
|
|
43
44
|
this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.options.pollingIntervalMs);
|
|
45
|
+
if (this.options.provingDelayMs) {
|
|
46
|
+
this.log.warn(`Prover node epoch monitor running with delay of ${this.options.provingDelayMs}ms`);
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
public static async create(
|
|
47
51
|
l2BlockSource: L2BlockSource,
|
|
48
|
-
options: { pollingIntervalMs: number },
|
|
52
|
+
options: { pollingIntervalMs: number; provingDelayMs?: number },
|
|
49
53
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
50
54
|
): Promise<EpochMonitor> {
|
|
51
55
|
const l1Constants = await l2BlockSource.getL1Constants();
|
|
@@ -71,6 +75,7 @@ export class EpochMonitor implements Traceable {
|
|
|
71
75
|
@trackSpan('EpochMonitor.work')
|
|
72
76
|
public async work() {
|
|
73
77
|
const { epochToProve, blockNumber, slotNumber } = await this.getEpochNumberToProve();
|
|
78
|
+
this.log.debug(`Epoch to prove: ${epochToProve}`, { blockNumber, slotNumber });
|
|
74
79
|
if (epochToProve === undefined) {
|
|
75
80
|
this.log.trace(`Next block to prove ${blockNumber} not yet mined`, { blockNumber });
|
|
76
81
|
return;
|
|
@@ -86,9 +91,15 @@ export class EpochMonitor implements Traceable {
|
|
|
86
91
|
return;
|
|
87
92
|
}
|
|
88
93
|
|
|
94
|
+
if (this.options.provingDelayMs) {
|
|
95
|
+
this.log.debug(`Waiting ${this.options.provingDelayMs}ms before proving epoch ${epochToProve}`);
|
|
96
|
+
await sleep(this.options.provingDelayMs);
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
this.log.debug(`Epoch ${epochToProve} is ready to be proven`);
|
|
90
|
-
await this.handler?.handleEpochReadyToProve(epochToProve)
|
|
91
|
-
|
|
100
|
+
if (await this.handler?.handleEpochReadyToProve(epochToProve)) {
|
|
101
|
+
this.latestEpochNumber = epochToProve;
|
|
102
|
+
}
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
private async getEpochNumberToProve() {
|
|
@@ -1,34 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { BatchedBlob } from '@aztec/blob-lib';
|
|
2
|
+
import { AZTEC_MAX_EPOCH_DURATION } from '@aztec/constants';
|
|
3
|
+
import type { L1TxUtils, RollupContract, ViemCommitteeAttestation } from '@aztec/ethereum';
|
|
3
4
|
import { makeTuple } from '@aztec/foundation/array';
|
|
4
|
-
import { areArraysEqual
|
|
5
|
+
import { areArraysEqual } from '@aztec/foundation/collection';
|
|
5
6
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
7
|
import { Fr } from '@aztec/foundation/fields';
|
|
7
8
|
import { createLogger } from '@aztec/foundation/log';
|
|
8
|
-
import {
|
|
9
|
-
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
9
|
+
import type { Tuple } from '@aztec/foundation/serialize';
|
|
10
10
|
import { Timer } from '@aztec/foundation/timer';
|
|
11
11
|
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
12
12
|
import type { PublisherConfig, TxSenderConfig } from '@aztec/sequencer-client';
|
|
13
|
+
import { CommitteeAttestation, CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
13
14
|
import type { Proof } from '@aztec/stdlib/proofs';
|
|
14
15
|
import type { FeeRecipient, RootRollupPublicInputs } from '@aztec/stdlib/rollup';
|
|
15
16
|
import type { L1PublishProofStats } from '@aztec/stdlib/stats';
|
|
16
17
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
17
18
|
|
|
19
|
+
import { inspect } from 'util';
|
|
18
20
|
import { type Hex, type TransactionReceipt, encodeFunctionData } from 'viem';
|
|
19
21
|
|
|
20
|
-
import {
|
|
22
|
+
import { ProverNodePublisherMetrics } from './metrics.js';
|
|
21
23
|
|
|
22
|
-
/**
|
|
23
|
-
* Stats for a sent transaction.
|
|
24
|
-
*/
|
|
25
24
|
/** Arguments to the submitEpochProof method of the rollup contract */
|
|
26
25
|
export type L1SubmitEpochProofArgs = {
|
|
27
26
|
epochSize: number;
|
|
28
27
|
previousArchive: Fr;
|
|
29
28
|
endArchive: Fr;
|
|
30
|
-
previousBlockHash: Fr;
|
|
31
|
-
endBlockHash: Fr;
|
|
32
29
|
endTimestamp: Fr;
|
|
33
30
|
outHash: Fr;
|
|
34
31
|
proverId: Fr;
|
|
@@ -37,10 +34,8 @@ export type L1SubmitEpochProofArgs = {
|
|
|
37
34
|
};
|
|
38
35
|
|
|
39
36
|
export class ProverNodePublisher {
|
|
40
|
-
private interruptibleSleep = new InterruptibleSleep();
|
|
41
|
-
private sleepTimeMs: number;
|
|
42
37
|
private interrupted = false;
|
|
43
|
-
private metrics:
|
|
38
|
+
private metrics: ProverNodePublisherMetrics;
|
|
44
39
|
|
|
45
40
|
protected log = createLogger('prover-node:l1-tx-publisher');
|
|
46
41
|
|
|
@@ -56,16 +51,18 @@ export class ProverNodePublisher {
|
|
|
56
51
|
telemetry?: TelemetryClient;
|
|
57
52
|
},
|
|
58
53
|
) {
|
|
59
|
-
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
|
|
60
|
-
|
|
61
54
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
62
55
|
|
|
63
|
-
this.metrics = new
|
|
56
|
+
this.metrics = new ProverNodePublisherMetrics(telemetry, 'ProverNode');
|
|
64
57
|
|
|
65
58
|
this.rollupContract = deps.rollupContract;
|
|
66
59
|
this.l1TxUtils = deps.l1TxUtils;
|
|
67
60
|
}
|
|
68
61
|
|
|
62
|
+
public getRollupContract() {
|
|
63
|
+
return this.rollupContract;
|
|
64
|
+
}
|
|
65
|
+
|
|
69
66
|
/**
|
|
70
67
|
* Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
|
|
71
68
|
* Be warned, the call may return false even if the tx subsequently gets successfully mined.
|
|
@@ -74,16 +71,17 @@ export class ProverNodePublisher {
|
|
|
74
71
|
*/
|
|
75
72
|
public interrupt() {
|
|
76
73
|
this.interrupted = true;
|
|
77
|
-
this.
|
|
74
|
+
this.l1TxUtils.interrupt();
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
/** Restarts the publisher after calling `interrupt`. */
|
|
81
78
|
public restart() {
|
|
82
79
|
this.interrupted = false;
|
|
80
|
+
this.l1TxUtils.restart();
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
public getSenderAddress() {
|
|
86
|
-
return
|
|
84
|
+
return this.l1TxUtils.getSenderAddress();
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
public async submitEpochProof(args: {
|
|
@@ -92,12 +90,14 @@ export class ProverNodePublisher {
|
|
|
92
90
|
toBlock: number;
|
|
93
91
|
publicInputs: RootRollupPublicInputs;
|
|
94
92
|
proof: Proof;
|
|
93
|
+
batchedBlobInputs: BatchedBlob;
|
|
94
|
+
attestations: ViemCommitteeAttestation[];
|
|
95
95
|
}): Promise<boolean> {
|
|
96
96
|
const { epochNumber, fromBlock, toBlock } = args;
|
|
97
97
|
const ctx = { epochNumber, fromBlock, toBlock };
|
|
98
|
+
|
|
98
99
|
if (!this.interrupted) {
|
|
99
100
|
const timer = new Timer();
|
|
100
|
-
|
|
101
101
|
// Validate epoch proof range and hashes are correct before submitting
|
|
102
102
|
await this.validateEpochProofSubmission(args);
|
|
103
103
|
|
|
@@ -107,13 +107,16 @@ export class ProverNodePublisher {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
try {
|
|
110
|
-
this.metrics.recordSenderBalance(
|
|
110
|
+
this.metrics.recordSenderBalance(
|
|
111
|
+
await this.l1TxUtils.getSenderBalance(),
|
|
112
|
+
this.l1TxUtils.getSenderAddress().toString(),
|
|
113
|
+
);
|
|
111
114
|
} catch (err) {
|
|
112
115
|
this.log.warn(`Failed to record the ETH balance of the prover node: ${err}`);
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
// Tx was mined successfully
|
|
116
|
-
if (txReceipt.status) {
|
|
119
|
+
if (txReceipt.status === 'success') {
|
|
117
120
|
const tx = await this.l1TxUtils.getTransactionStats(txReceipt.transactionHash);
|
|
118
121
|
const stats: L1PublishProofStats = {
|
|
119
122
|
gasPrice: txReceipt.effectiveGasPrice,
|
|
@@ -132,11 +135,10 @@ export class ProverNodePublisher {
|
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
this.metrics.recordFailedTx();
|
|
135
|
-
this.log.error(`Rollup.submitEpochProof tx status failed
|
|
136
|
-
await this.sleepOrInterrupted();
|
|
138
|
+
this.log.error(`Rollup.submitEpochProof tx status failed ${txReceipt.transactionHash}`, undefined, ctx);
|
|
137
139
|
}
|
|
138
140
|
|
|
139
|
-
this.log.verbose('L2 block data syncing interrupted
|
|
141
|
+
this.log.verbose('L2 block data syncing interrupted', ctx);
|
|
140
142
|
return false;
|
|
141
143
|
}
|
|
142
144
|
|
|
@@ -145,49 +147,51 @@ export class ProverNodePublisher {
|
|
|
145
147
|
toBlock: number;
|
|
146
148
|
publicInputs: RootRollupPublicInputs;
|
|
147
149
|
proof: Proof;
|
|
150
|
+
batchedBlobInputs: BatchedBlob;
|
|
151
|
+
attestations: ViemCommitteeAttestation[];
|
|
148
152
|
}) {
|
|
149
|
-
const { fromBlock, toBlock, publicInputs,
|
|
153
|
+
const { fromBlock, toBlock, publicInputs, batchedBlobInputs } = args;
|
|
150
154
|
|
|
151
155
|
// Check that the block numbers match the expected epoch to be proven
|
|
152
156
|
const { pendingBlockNumber: pending, provenBlockNumber: proven } = await this.rollupContract.getTips();
|
|
153
|
-
if
|
|
157
|
+
// Don't publish if proven is beyond our toBlock, pointless to do so
|
|
158
|
+
if (proven > BigInt(toBlock)) {
|
|
154
159
|
throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as proven block is ${proven}`);
|
|
155
160
|
}
|
|
161
|
+
// toBlock can't be greater than pending
|
|
156
162
|
if (toBlock > pending) {
|
|
157
163
|
throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as pending block is ${pending}`);
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
// Check the
|
|
161
|
-
const blockLog = await this.rollupContract.getBlock(
|
|
162
|
-
if (publicInputs.
|
|
166
|
+
// Check the archive for the immediate block before the epoch
|
|
167
|
+
const blockLog = await this.rollupContract.getBlock(BigInt(fromBlock - 1));
|
|
168
|
+
if (publicInputs.previousArchiveRoot.toString() !== blockLog.archive) {
|
|
163
169
|
throw new Error(
|
|
164
|
-
`Previous archive root mismatch: ${publicInputs.
|
|
170
|
+
`Previous archive root mismatch: ${publicInputs.previousArchiveRoot.toString()} !== ${blockLog.archive}`,
|
|
165
171
|
);
|
|
166
172
|
}
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
|
|
174
|
+
// Check the archive for the last block in the epoch
|
|
175
|
+
const endBlockLog = await this.rollupContract.getBlock(BigInt(toBlock));
|
|
176
|
+
if (publicInputs.endArchiveRoot.toString() !== endBlockLog.archive) {
|
|
169
177
|
throw new Error(
|
|
170
|
-
`
|
|
178
|
+
`End archive root mismatch: ${publicInputs.endArchiveRoot.toString()} !== ${endBlockLog.archive}`,
|
|
171
179
|
);
|
|
172
180
|
}
|
|
173
181
|
|
|
174
|
-
// Check the
|
|
175
|
-
const
|
|
176
|
-
if (publicInputs.
|
|
182
|
+
// Check the batched blob inputs from the root rollup against the batched blob computed in ts
|
|
183
|
+
const finalBlobAccumulator = batchedBlobInputs.toFinalBlobAccumulator();
|
|
184
|
+
if (!publicInputs.blobPublicInputs.equals(finalBlobAccumulator)) {
|
|
177
185
|
throw new Error(
|
|
178
|
-
`
|
|
186
|
+
`Batched blob mismatch: ${inspect(publicInputs.blobPublicInputs)} !== ${inspect(finalBlobAccumulator)}`,
|
|
179
187
|
);
|
|
180
188
|
}
|
|
181
|
-
if (publicInputs.endBlockHash.toString() !== endBlockLog.blockHash) {
|
|
182
|
-
throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockLog.blockHash}`);
|
|
183
|
-
}
|
|
184
189
|
|
|
185
190
|
// Compare the public inputs computed by the contract with the ones injected
|
|
186
|
-
const rollupPublicInputs = await this.rollupContract.getEpochProofPublicInputs(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const argsPublicInputs = [...publicInputs.toFields(), ...aggregationObject];
|
|
191
|
+
const rollupPublicInputs = await this.rollupContract.getEpochProofPublicInputs(
|
|
192
|
+
this.getEpochProofPublicInputsArgs(args),
|
|
193
|
+
);
|
|
194
|
+
const argsPublicInputs = [...publicInputs.toFields()];
|
|
191
195
|
|
|
192
196
|
if (!areArraysEqual(rollupPublicInputs.map(Fr.fromHexString), argsPublicInputs, (a, b) => a.equals(b))) {
|
|
193
197
|
const fmt = (inputs: Fr[] | readonly string[]) => inputs.map(x => x.toString()).join(', ');
|
|
@@ -202,34 +206,23 @@ export class ProverNodePublisher {
|
|
|
202
206
|
toBlock: number;
|
|
203
207
|
publicInputs: RootRollupPublicInputs;
|
|
204
208
|
proof: Proof;
|
|
209
|
+
batchedBlobInputs: BatchedBlob;
|
|
210
|
+
attestations: ViemCommitteeAttestation[];
|
|
205
211
|
}): Promise<TransactionReceipt | undefined> {
|
|
206
|
-
const
|
|
207
|
-
const argsArray = this.getSubmitEpochProofArgs(args);
|
|
208
|
-
|
|
209
|
-
const txArgs = [
|
|
210
|
-
{
|
|
211
|
-
start: argsArray[0],
|
|
212
|
-
end: argsArray[1],
|
|
213
|
-
args: argsArray[2],
|
|
214
|
-
fees: argsArray[3],
|
|
215
|
-
blobPublicInputs: argsArray[4],
|
|
216
|
-
aggregationObject: argsArray[5],
|
|
217
|
-
proof: proofHex,
|
|
218
|
-
},
|
|
219
|
-
] as const;
|
|
212
|
+
const txArgs = [this.getSubmitEpochProofArgs(args)] as const;
|
|
220
213
|
|
|
221
|
-
this.log.info(`
|
|
214
|
+
this.log.info(`Submitting epoch proof to L1 rollup contract`, {
|
|
215
|
+
proofSize: args.proof.withoutPublicInputs().length,
|
|
216
|
+
fromBlock: args.fromBlock,
|
|
217
|
+
toBlock: args.toBlock,
|
|
218
|
+
});
|
|
222
219
|
const data = encodeFunctionData({
|
|
223
220
|
abi: RollupAbi,
|
|
224
221
|
functionName: 'submitEpochRootProof',
|
|
225
222
|
args: txArgs,
|
|
226
223
|
});
|
|
227
224
|
try {
|
|
228
|
-
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({
|
|
229
|
-
to: this.rollupContract.address,
|
|
230
|
-
data,
|
|
231
|
-
});
|
|
232
|
-
|
|
225
|
+
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({ to: this.rollupContract.address, data });
|
|
233
226
|
return receipt;
|
|
234
227
|
} catch (err) {
|
|
235
228
|
this.log.error(`Rollup submit epoch proof failed`, err);
|
|
@@ -249,38 +242,52 @@ export class ProverNodePublisher {
|
|
|
249
242
|
}
|
|
250
243
|
}
|
|
251
244
|
|
|
252
|
-
private
|
|
245
|
+
private getEpochProofPublicInputsArgs(args: {
|
|
253
246
|
fromBlock: number;
|
|
254
247
|
toBlock: number;
|
|
255
248
|
publicInputs: RootRollupPublicInputs;
|
|
256
|
-
|
|
249
|
+
batchedBlobInputs: BatchedBlob;
|
|
250
|
+
attestations: ViemCommitteeAttestation[];
|
|
257
251
|
}) {
|
|
252
|
+
// Returns arguments for EpochProofLib.sol -> getEpochProofPublicInputs()
|
|
258
253
|
return [
|
|
259
|
-
BigInt(args.fromBlock)
|
|
260
|
-
BigInt(args.toBlock)
|
|
254
|
+
BigInt(args.fromBlock) /*_start*/,
|
|
255
|
+
BigInt(args.toBlock) /*_end*/,
|
|
261
256
|
{
|
|
262
|
-
previousArchive: args.publicInputs.
|
|
263
|
-
endArchive: args.publicInputs.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
endTimestamp: args.publicInputs.endTimestamp.toBigInt(),
|
|
267
|
-
outHash: args.publicInputs.outHash.toString(),
|
|
268
|
-
proverId: EthAddress.fromField(args.publicInputs.proverId).toString(),
|
|
269
|
-
},
|
|
257
|
+
previousArchive: args.publicInputs.previousArchiveRoot.toString(),
|
|
258
|
+
endArchive: args.publicInputs.endArchiveRoot.toString(),
|
|
259
|
+
proverId: EthAddress.fromField(args.publicInputs.constants.proverId).toString(),
|
|
260
|
+
} /*_args*/,
|
|
270
261
|
makeTuple(AZTEC_MAX_EPOCH_DURATION * 2, i =>
|
|
271
262
|
i % 2 === 0
|
|
272
263
|
? args.publicInputs.fees[i / 2].recipient.toField().toString()
|
|
273
264
|
: args.publicInputs.fees[(i - 1) / 2].value.toString(),
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
.filter((_, i) => i < args.toBlock - args.fromBlock + 1)
|
|
277
|
-
.map(b => b.toString())
|
|
278
|
-
.join(``)}`,
|
|
279
|
-
`0x${serializeToBuffer(args.proof.extractAggregationObject()).toString('hex')}`,
|
|
265
|
+
) /*_fees*/,
|
|
266
|
+
args.batchedBlobInputs.getEthBlobEvaluationInputs() /*_blobPublicInputs*/,
|
|
280
267
|
] as const;
|
|
281
268
|
}
|
|
282
269
|
|
|
283
|
-
|
|
284
|
-
|
|
270
|
+
private getSubmitEpochProofArgs(args: {
|
|
271
|
+
fromBlock: number;
|
|
272
|
+
toBlock: number;
|
|
273
|
+
publicInputs: RootRollupPublicInputs;
|
|
274
|
+
proof: Proof;
|
|
275
|
+
batchedBlobInputs: BatchedBlob;
|
|
276
|
+
attestations: ViemCommitteeAttestation[];
|
|
277
|
+
}) {
|
|
278
|
+
// Returns arguments for EpochProofLib.sol -> submitEpochRootProof()
|
|
279
|
+
const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
|
|
280
|
+
const argsArray = this.getEpochProofPublicInputsArgs(args);
|
|
281
|
+
return {
|
|
282
|
+
start: argsArray[0],
|
|
283
|
+
end: argsArray[1],
|
|
284
|
+
args: argsArray[2],
|
|
285
|
+
fees: argsArray[3],
|
|
286
|
+
attestations: new CommitteeAttestationsAndSigners(
|
|
287
|
+
args.attestations.map(a => CommitteeAttestation.fromViem(a)),
|
|
288
|
+
).getPackedAttestations(),
|
|
289
|
+
blobInputs: argsArray[4],
|
|
290
|
+
proof: proofHex,
|
|
291
|
+
};
|
|
285
292
|
}
|
|
286
293
|
}
|