@aztec/prover-node 0.71.0 → 0.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dest/bond/escrow-contract.d.ts +2 -5
  2. package/dest/bond/escrow-contract.d.ts.map +1 -1
  3. package/dest/bond/escrow-contract.js +1 -1
  4. package/dest/bond/factory.d.ts +3 -6
  5. package/dest/bond/factory.d.ts.map +1 -1
  6. package/dest/bond/factory.js +2 -4
  7. package/dest/bond/token-contract.d.ts +2 -5
  8. package/dest/bond/token-contract.d.ts.map +1 -1
  9. package/dest/bond/token-contract.js +1 -1
  10. package/dest/factory.d.ts +4 -2
  11. package/dest/factory.d.ts.map +1 -1
  12. package/dest/factory.js +13 -9
  13. package/dest/http.js +3 -3
  14. package/dest/index.d.ts +3 -2
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +4 -3
  17. package/dest/job/epoch-proving-job.d.ts +2 -2
  18. package/dest/job/epoch-proving-job.d.ts.map +1 -1
  19. package/dest/job/epoch-proving-job.js +21 -15
  20. package/dest/metrics.d.ts +13 -1
  21. package/dest/metrics.d.ts.map +1 -1
  22. package/dest/metrics.js +74 -2
  23. package/dest/monitors/claims-monitor.d.ts +2 -2
  24. package/dest/monitors/claims-monitor.d.ts.map +1 -1
  25. package/dest/monitors/claims-monitor.js +1 -1
  26. package/dest/prover-coordination/factory.d.ts.map +1 -1
  27. package/dest/prover-coordination/factory.js +3 -2
  28. package/dest/prover-node-publisher.d.ts +67 -0
  29. package/dest/prover-node-publisher.d.ts.map +1 -0
  30. package/dest/prover-node-publisher.js +183 -0
  31. package/dest/prover-node.d.ts +3 -3
  32. package/dest/prover-node.d.ts.map +1 -1
  33. package/dest/prover-node.js +1 -1
  34. package/dest/test/index.d.ts +2 -2
  35. package/dest/test/index.d.ts.map +1 -1
  36. package/package.json +19 -19
  37. package/src/bond/escrow-contract.ts +2 -15
  38. package/src/bond/factory.ts +4 -24
  39. package/src/bond/token-contract.ts +2 -15
  40. package/src/factory.ts +16 -9
  41. package/src/http.ts +2 -2
  42. package/src/index.ts +3 -2
  43. package/src/job/epoch-proving-job.ts +22 -16
  44. package/src/metrics.ts +107 -1
  45. package/src/monitors/claims-monitor.ts +3 -2
  46. package/src/prover-coordination/factory.ts +2 -2
  47. package/src/prover-node-publisher.ts +276 -0
  48. package/src/prover-node.ts +2 -2
  49. package/src/test/index.ts +2 -2
@@ -0,0 +1,276 @@
1
+ import { type L1PublishProofStats } from '@aztec/circuit-types/stats';
2
+ import { AGGREGATION_OBJECT_LENGTH, AZTEC_MAX_EPOCH_DURATION, type Proof } from '@aztec/circuits.js';
3
+ import { type FeeRecipient, type RootRollupPublicInputs } from '@aztec/circuits.js/rollup';
4
+ import { type L1TxUtils, type RollupContract } from '@aztec/ethereum';
5
+ import { makeTuple } from '@aztec/foundation/array';
6
+ import { areArraysEqual, times } from '@aztec/foundation/collection';
7
+ import { EthAddress } from '@aztec/foundation/eth-address';
8
+ import { Fr } from '@aztec/foundation/fields';
9
+ import { createLogger } from '@aztec/foundation/log';
10
+ import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
11
+ import { InterruptibleSleep } from '@aztec/foundation/sleep';
12
+ import { Timer } from '@aztec/foundation/timer';
13
+ import { RollupAbi } from '@aztec/l1-artifacts';
14
+ import { type PublisherConfig, type TxSenderConfig } from '@aztec/sequencer-client';
15
+ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
16
+
17
+ import { type Hex, type TransactionReceipt, encodeFunctionData } from 'viem';
18
+
19
+ import { ProverNodeMetrics } from './metrics.js';
20
+
21
+ /**
22
+ * Stats for a sent transaction.
23
+ */
24
+ /** Arguments to the submitEpochProof method of the rollup contract */
25
+ export type L1SubmitEpochProofArgs = {
26
+ epochSize: number;
27
+ previousArchive: Fr;
28
+ endArchive: Fr;
29
+ previousBlockHash: Fr;
30
+ endBlockHash: Fr;
31
+ endTimestamp: Fr;
32
+ outHash: Fr;
33
+ proverId: Fr;
34
+ fees: Tuple<FeeRecipient, typeof AZTEC_MAX_EPOCH_DURATION>;
35
+ proof: Proof;
36
+ };
37
+
38
+ export class ProverNodePublisher {
39
+ private interruptibleSleep = new InterruptibleSleep();
40
+ private sleepTimeMs: number;
41
+ private interrupted = false;
42
+ private metrics: ProverNodeMetrics;
43
+
44
+ protected log = createLogger('prover-node:l1-tx-publisher');
45
+
46
+ protected rollupContract: RollupContract;
47
+
48
+ public readonly l1TxUtils: L1TxUtils;
49
+
50
+ constructor(
51
+ config: TxSenderConfig & PublisherConfig,
52
+ deps: {
53
+ rollupContract: RollupContract;
54
+ l1TxUtils: L1TxUtils;
55
+ telemetry?: TelemetryClient;
56
+ },
57
+ ) {
58
+ this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
59
+
60
+ const telemetry = deps.telemetry ?? getTelemetryClient();
61
+
62
+ this.metrics = new ProverNodeMetrics(telemetry, 'ProverNode');
63
+
64
+ this.rollupContract = deps.rollupContract;
65
+ this.l1TxUtils = deps.l1TxUtils;
66
+ }
67
+
68
+ /**
69
+ * Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
70
+ * Be warned, the call may return false even if the tx subsequently gets successfully mined.
71
+ * In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail.
72
+ * A call to `restart` is required before you can continue publishing.
73
+ */
74
+ public interrupt() {
75
+ this.interrupted = true;
76
+ this.interruptibleSleep.interrupt();
77
+ }
78
+
79
+ /** Restarts the publisher after calling `interrupt`. */
80
+ public restart() {
81
+ this.interrupted = false;
82
+ }
83
+
84
+ public getSenderAddress() {
85
+ return EthAddress.fromString(this.l1TxUtils.getSenderAddress());
86
+ }
87
+
88
+ public getProofClaim() {
89
+ return this.rollupContract.getProofClaim();
90
+ }
91
+
92
+ public async submitEpochProof(args: {
93
+ epochNumber: number;
94
+ fromBlock: number;
95
+ toBlock: number;
96
+ publicInputs: RootRollupPublicInputs;
97
+ proof: Proof;
98
+ }): Promise<boolean> {
99
+ const { epochNumber, fromBlock, toBlock } = args;
100
+ const ctx = { epochNumber, fromBlock, toBlock };
101
+ if (!this.interrupted) {
102
+ const timer = new Timer();
103
+
104
+ // Validate epoch proof range and hashes are correct before submitting
105
+ await this.validateEpochProofSubmission(args);
106
+
107
+ const txReceipt = await this.sendSubmitEpochProofTx(args);
108
+ if (!txReceipt) {
109
+ return false;
110
+ }
111
+
112
+ // Tx was mined successfully
113
+ if (txReceipt.status) {
114
+ const tx = await this.l1TxUtils.getTransactionStats(txReceipt.transactionHash);
115
+ const stats: L1PublishProofStats = {
116
+ gasPrice: txReceipt.effectiveGasPrice,
117
+ gasUsed: txReceipt.gasUsed,
118
+ transactionHash: txReceipt.transactionHash,
119
+ calldataGas: tx!.calldataGas,
120
+ calldataSize: tx!.calldataSize,
121
+ sender: tx!.sender,
122
+ blobDataGas: 0n,
123
+ blobGasUsed: 0n,
124
+ eventName: 'proof-published-to-l1',
125
+ };
126
+ this.log.info(`Published epoch proof to L1 rollup contract`, { ...stats, ...ctx });
127
+ this.metrics.recordSubmitProof(timer.ms(), stats);
128
+ return true;
129
+ }
130
+
131
+ this.metrics.recordFailedTx();
132
+ this.log.error(`Rollup.submitEpochProof tx status failed: ${txReceipt.transactionHash}`, ctx);
133
+ await this.sleepOrInterrupted();
134
+ }
135
+
136
+ this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
137
+ return false;
138
+ }
139
+
140
+ private async validateEpochProofSubmission(args: {
141
+ fromBlock: number;
142
+ toBlock: number;
143
+ publicInputs: RootRollupPublicInputs;
144
+ proof: Proof;
145
+ }) {
146
+ const { fromBlock, toBlock, publicInputs, proof } = args;
147
+
148
+ // Check that the block numbers match the expected epoch to be proven
149
+ const { pendingBlockNumber: pending, provenBlockNumber: proven } = await this.rollupContract.getTips();
150
+ if (proven !== BigInt(fromBlock) - 1n) {
151
+ throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as proven block is ${proven}`);
152
+ }
153
+ if (toBlock > pending) {
154
+ throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as pending block is ${pending}`);
155
+ }
156
+
157
+ // Check the block hash and archive for the immediate block before the epoch
158
+ const blockLog = await this.rollupContract.getBlock(proven);
159
+ if (publicInputs.previousArchive.root.toString() !== blockLog.archive) {
160
+ throw new Error(
161
+ `Previous archive root mismatch: ${publicInputs.previousArchive.root.toString()} !== ${blockLog.archive}`,
162
+ );
163
+ }
164
+ // TODO: Remove zero check once we inject the proper zero blockhash
165
+ if (blockLog.blockHash !== Fr.ZERO.toString() && publicInputs.previousBlockHash.toString() !== blockLog.blockHash) {
166
+ throw new Error(
167
+ `Previous block hash mismatch: ${publicInputs.previousBlockHash.toString()} !== ${blockLog.blockHash}`,
168
+ );
169
+ }
170
+
171
+ // Check the block hash and archive for the last block in the epoch
172
+ const endBlockLog = await this.rollupContract.getBlock(BigInt(toBlock));
173
+ if (publicInputs.endArchive.root.toString() !== endBlockLog.archive) {
174
+ throw new Error(
175
+ `End archive root mismatch: ${publicInputs.endArchive.root.toString()} !== ${endBlockLog.archive}`,
176
+ );
177
+ }
178
+ if (publicInputs.endBlockHash.toString() !== endBlockLog.blockHash) {
179
+ throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockLog.blockHash}`);
180
+ }
181
+
182
+ // Compare the public inputs computed by the contract with the ones injected
183
+ const rollupPublicInputs = await this.rollupContract.getEpochProofPublicInputs(this.getSubmitEpochProofArgs(args));
184
+ const aggregationObject = proof.isEmpty()
185
+ ? times(AGGREGATION_OBJECT_LENGTH, Fr.zero)
186
+ : proof.extractAggregationObject();
187
+ const argsPublicInputs = [...publicInputs.toFields(), ...aggregationObject];
188
+
189
+ if (!areArraysEqual(rollupPublicInputs.map(Fr.fromHexString), argsPublicInputs, (a, b) => a.equals(b))) {
190
+ const fmt = (inputs: Fr[] | readonly string[]) => inputs.map(x => x.toString()).join(', ');
191
+ throw new Error(
192
+ `Root rollup public inputs mismatch:\nRollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
193
+ );
194
+ }
195
+ }
196
+
197
+ private async sendSubmitEpochProofTx(args: {
198
+ fromBlock: number;
199
+ toBlock: number;
200
+ publicInputs: RootRollupPublicInputs;
201
+ proof: Proof;
202
+ }): Promise<TransactionReceipt | undefined> {
203
+ const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
204
+ const argsArray = this.getSubmitEpochProofArgs(args);
205
+
206
+ const txArgs = [
207
+ {
208
+ epochSize: argsArray[0],
209
+ args: argsArray[1],
210
+ fees: argsArray[2],
211
+ blobPublicInputs: argsArray[3],
212
+ aggregationObject: argsArray[4],
213
+ proof: proofHex,
214
+ },
215
+ ] as const;
216
+
217
+ this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
218
+ const data = encodeFunctionData({
219
+ abi: RollupAbi,
220
+ functionName: 'submitEpochRootProof',
221
+ args: txArgs,
222
+ });
223
+ try {
224
+ const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({
225
+ to: this.rollupContract.address,
226
+ data,
227
+ });
228
+
229
+ return receipt;
230
+ } catch (err) {
231
+ this.log.error(`Rollup submit epoch proof failed`, err);
232
+ const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(data, {
233
+ args: [...txArgs],
234
+ functionName: 'submitEpochRootProof',
235
+ abi: RollupAbi,
236
+ address: this.rollupContract.address,
237
+ });
238
+ this.log.error(`Rollup submit epoch proof tx reverted. ${errorMsg}`);
239
+ return undefined;
240
+ }
241
+ }
242
+
243
+ private getSubmitEpochProofArgs(args: {
244
+ fromBlock: number;
245
+ toBlock: number;
246
+ publicInputs: RootRollupPublicInputs;
247
+ proof: Proof;
248
+ }) {
249
+ return [
250
+ BigInt(args.toBlock - args.fromBlock + 1),
251
+ [
252
+ args.publicInputs.previousArchive.root.toString(),
253
+ args.publicInputs.endArchive.root.toString(),
254
+ args.publicInputs.previousBlockHash.toString(),
255
+ args.publicInputs.endBlockHash.toString(),
256
+ args.publicInputs.endTimestamp.toString(),
257
+ args.publicInputs.outHash.toString(),
258
+ args.publicInputs.proverId.toString(),
259
+ ],
260
+ makeTuple(AZTEC_MAX_EPOCH_DURATION * 2, i =>
261
+ i % 2 === 0
262
+ ? args.publicInputs.fees[i / 2].recipient.toField().toString()
263
+ : args.publicInputs.fees[(i - 1) / 2].value.toString(),
264
+ ),
265
+ `0x${args.publicInputs.blobPublicInputs
266
+ .filter((_, i) => i < args.toBlock - args.fromBlock + 1)
267
+ .map(b => b.toString())
268
+ .join(``)}`,
269
+ `0x${serializeToBuffer(args.proof.extractAggregationObject()).toString('hex')}`,
270
+ ] as const;
271
+ }
272
+
273
+ protected async sleepOrInterrupted() {
274
+ await this.interruptibleSleep.sleep(this.sleepTimeMs);
275
+ }
276
+ }
@@ -26,7 +26,6 @@ import { retryUntil } from '@aztec/foundation/retry';
26
26
  import { DateProvider } from '@aztec/foundation/timer';
27
27
  import { type Maybe } from '@aztec/foundation/types';
28
28
  import { type P2P } from '@aztec/p2p';
29
- import { type L1Publisher } from '@aztec/sequencer-client';
30
29
  import { PublicProcessorFactory } from '@aztec/simulator/server';
31
30
  import {
32
31
  Attributes,
@@ -42,6 +41,7 @@ import { EpochProvingJob, type EpochProvingJobState } from './job/epoch-proving-
42
41
  import { ProverNodeMetrics } from './metrics.js';
43
42
  import { type ClaimsMonitor, type ClaimsMonitorHandler } from './monitors/claims-monitor.js';
44
43
  import { type EpochMonitor, type EpochMonitorHandler } from './monitors/epoch-monitor.js';
44
+ import { type ProverNodePublisher } from './prover-node-publisher.js';
45
45
  import { type QuoteProvider } from './quote-provider/index.js';
46
46
  import { type QuoteSigner } from './quote-signer.js';
47
47
 
@@ -74,7 +74,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr
74
74
 
75
75
  constructor(
76
76
  protected readonly prover: EpochProverManager,
77
- protected readonly publisher: L1Publisher,
77
+ protected readonly publisher: ProverNodePublisher,
78
78
  protected readonly l2BlockSource: L2BlockSource & Maybe<Service>,
79
79
  protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
80
80
  protected readonly contractDataSource: ContractDataSource,
package/src/test/index.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { type EpochProverManager } from '@aztec/circuit-types';
2
- import { type L1Publisher } from '@aztec/sequencer-client';
3
2
 
3
+ import { type ProverNodePublisher } from '../prover-node-publisher.js';
4
4
  import { ProverNode } from '../prover-node.js';
5
5
 
6
6
  class TestProverNode_ extends ProverNode {
7
7
  public override prover!: EpochProverManager;
8
- public override publisher!: L1Publisher;
8
+ public override publisher!: ProverNodePublisher;
9
9
  }
10
10
 
11
11
  export type TestProverNode = TestProverNode_;