@aztec/prover-node 0.72.1 → 0.74.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.
- package/dest/bond/escrow-contract.d.ts +2 -5
- package/dest/bond/escrow-contract.d.ts.map +1 -1
- package/dest/bond/escrow-contract.js +1 -1
- package/dest/bond/factory.d.ts +3 -6
- package/dest/bond/factory.d.ts.map +1 -1
- package/dest/bond/factory.js +2 -4
- package/dest/bond/token-contract.d.ts +2 -5
- package/dest/bond/token-contract.d.ts.map +1 -1
- package/dest/bond/token-contract.js +1 -1
- package/dest/factory.d.ts +4 -2
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +13 -9
- package/dest/index.d.ts +3 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +4 -3
- package/dest/job/epoch-proving-job.d.ts +2 -2
- package/dest/job/epoch-proving-job.d.ts.map +1 -1
- package/dest/job/epoch-proving-job.js +14 -8
- package/dest/metrics.d.ts +13 -1
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +74 -2
- package/dest/monitors/claims-monitor.d.ts +2 -2
- package/dest/monitors/claims-monitor.d.ts.map +1 -1
- package/dest/monitors/claims-monitor.js +1 -1
- package/dest/prover-node-publisher.d.ts +67 -0
- package/dest/prover-node-publisher.d.ts.map +1 -0
- package/dest/prover-node-publisher.js +185 -0
- package/dest/prover-node.d.ts +3 -3
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +1 -1
- package/dest/test/index.d.ts +2 -2
- package/dest/test/index.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/bond/escrow-contract.ts +2 -15
- package/src/bond/factory.ts +4 -24
- package/src/bond/token-contract.ts +2 -15
- package/src/factory.ts +16 -9
- package/src/index.ts +3 -2
- package/src/job/epoch-proving-job.ts +15 -9
- package/src/metrics.ts +107 -1
- package/src/monitors/claims-monitor.ts +3 -2
- package/src/prover-node-publisher.ts +281 -0
- package/src/prover-node.ts +2 -2
- package/src/test/index.ts +2 -2
package/src/factory.ts
CHANGED
|
@@ -2,14 +2,14 @@ import { type Archiver, createArchiver } from '@aztec/archiver';
|
|
|
2
2
|
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
3
3
|
import { type ProverCoordination, type ProvingJobBroker } from '@aztec/circuit-types';
|
|
4
4
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
5
|
-
import { createEthereumChain } from '@aztec/ethereum';
|
|
5
|
+
import { L1TxUtils, RollupContract, createEthereumChain, createL1Clients } from '@aztec/ethereum';
|
|
6
6
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
7
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
7
8
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
8
9
|
import { type DataStoreConfig } from '@aztec/kv-store/config';
|
|
9
10
|
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
10
11
|
import { createProverClient } from '@aztec/prover-client';
|
|
11
12
|
import { createAndStartProvingBroker } from '@aztec/prover-client/broker';
|
|
12
|
-
import { L1Publisher } from '@aztec/sequencer-client';
|
|
13
13
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
14
14
|
import { createWorldStateSynchronizer } from '@aztec/world-state';
|
|
15
15
|
|
|
@@ -20,6 +20,7 @@ import { type ProverNodeConfig, type QuoteProviderConfig } from './config.js';
|
|
|
20
20
|
import { ClaimsMonitor } from './monitors/claims-monitor.js';
|
|
21
21
|
import { EpochMonitor } from './monitors/epoch-monitor.js';
|
|
22
22
|
import { createProverCoordination } from './prover-coordination/factory.js';
|
|
23
|
+
import { ProverNodePublisher } from './prover-node-publisher.js';
|
|
23
24
|
import { ProverNode, type ProverNodeOptions } from './prover-node.js';
|
|
24
25
|
import { HttpQuoteProvider } from './quote-provider/http.js';
|
|
25
26
|
import { SimpleQuoteProvider } from './quote-provider/simple.js';
|
|
@@ -33,13 +34,14 @@ export async function createProverNode(
|
|
|
33
34
|
log?: Logger;
|
|
34
35
|
aztecNodeTxProvider?: ProverCoordination;
|
|
35
36
|
archiver?: Archiver;
|
|
36
|
-
publisher?:
|
|
37
|
+
publisher?: ProverNodePublisher;
|
|
37
38
|
blobSinkClient?: BlobSinkClientInterface;
|
|
38
39
|
broker?: ProvingJobBroker;
|
|
40
|
+
l1TxUtils?: L1TxUtils;
|
|
39
41
|
} = {},
|
|
40
42
|
) {
|
|
41
43
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
42
|
-
const blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config
|
|
44
|
+
const blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config);
|
|
43
45
|
const log = deps.log ?? createLogger('prover-node');
|
|
44
46
|
const archiver = deps.archiver ?? (await createArchiver(config, blobSinkClient, { blockUntilSync: true }, telemetry));
|
|
45
47
|
log.verbose(`Created archiver and synced to block ${await archiver.getBlockNumber()}`);
|
|
@@ -51,8 +53,14 @@ export async function createProverNode(
|
|
|
51
53
|
const broker = deps.broker ?? (await createAndStartProvingBroker(config, telemetry));
|
|
52
54
|
const prover = await createProverClient(config, worldStateSynchronizer, broker, telemetry);
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
+
const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey } = config;
|
|
57
|
+
const chain = createEthereumChain(rpcUrl, chainId);
|
|
58
|
+
const { publicClient, walletClient } = createL1Clients(rpcUrl, publisherPrivateKey, chain.chainInfo);
|
|
59
|
+
|
|
60
|
+
const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
|
|
61
|
+
|
|
62
|
+
const l1TxUtils = deps.l1TxUtils ?? new L1TxUtils(publicClient, walletClient, log, config);
|
|
63
|
+
const publisher = deps.publisher ?? new ProverNodePublisher(config, { telemetry, rollupContract, l1TxUtils });
|
|
56
64
|
|
|
57
65
|
const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config);
|
|
58
66
|
|
|
@@ -81,9 +89,8 @@ export async function createProverNode(
|
|
|
81
89
|
const claimsMonitor = new ClaimsMonitor(publisher, proverNodeConfig, telemetry);
|
|
82
90
|
const epochMonitor = new EpochMonitor(archiver, proverNodeConfig, telemetry);
|
|
83
91
|
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const bondManager = await createBondManager(rollupContract, walletClient, config);
|
|
92
|
+
const escrowContractAddress = await rollupContract.getProofCommitmentEscrow();
|
|
93
|
+
const bondManager = await createBondManager(EthAddress.fromString(escrowContractAddress), walletClient, config);
|
|
87
94
|
|
|
88
95
|
return new ProverNode(
|
|
89
96
|
prover,
|
package/src/index.ts
CHANGED
|
@@ -13,13 +13,13 @@ import { asyncPool } from '@aztec/foundation/async-pool';
|
|
|
13
13
|
import { createLogger } from '@aztec/foundation/log';
|
|
14
14
|
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
15
15
|
import { Timer } from '@aztec/foundation/timer';
|
|
16
|
-
import { type L1Publisher } from '@aztec/sequencer-client';
|
|
17
16
|
import { type PublicProcessor, type PublicProcessorFactory } from '@aztec/simulator/server';
|
|
18
17
|
import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
19
18
|
|
|
20
19
|
import * as crypto from 'node:crypto';
|
|
21
20
|
|
|
22
21
|
import { type ProverNodeMetrics } from '../metrics.js';
|
|
22
|
+
import { type ProverNodePublisher } from '../prover-node-publisher.js';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Job that grabs a range of blocks from the unfinalised chain from L1, gets their txs given their hashes,
|
|
@@ -43,7 +43,7 @@ export class EpochProvingJob implements Traceable {
|
|
|
43
43
|
private txs: Tx[],
|
|
44
44
|
private prover: EpochProver,
|
|
45
45
|
private publicProcessorFactory: PublicProcessorFactory,
|
|
46
|
-
private publisher:
|
|
46
|
+
private publisher: ProverNodePublisher,
|
|
47
47
|
private l2BlockSource: L2BlockSource,
|
|
48
48
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
49
49
|
private metrics: ProverNodeMetrics,
|
|
@@ -91,19 +91,19 @@ export class EpochProvingJob implements Traceable {
|
|
|
91
91
|
|
|
92
92
|
try {
|
|
93
93
|
this.prover.startNewEpoch(epochNumber, fromBlock, epochSizeBlocks);
|
|
94
|
-
this.prover.startTubeCircuits(this.txs);
|
|
94
|
+
await this.prover.startTubeCircuits(this.txs);
|
|
95
95
|
|
|
96
96
|
await asyncPool(this.config.parallelBlockLimit, this.blocks, async block => {
|
|
97
97
|
this.checkState();
|
|
98
98
|
|
|
99
99
|
const globalVariables = block.header.globalVariables;
|
|
100
|
-
const txs = this.getTxs(block);
|
|
100
|
+
const txs = await this.getTxs(block);
|
|
101
101
|
const l1ToL2Messages = await this.getL1ToL2Messages(block);
|
|
102
102
|
const previousHeader = (await this.getBlockHeader(block.number - 1))!;
|
|
103
103
|
|
|
104
104
|
this.log.verbose(`Starting processing block ${block.number}`, {
|
|
105
105
|
number: block.number,
|
|
106
|
-
blockHash: block.hash().toString(),
|
|
106
|
+
blockHash: (await block.hash()).toString(),
|
|
107
107
|
lastArchive: block.header.lastArchive.root,
|
|
108
108
|
noteHashTreeRoot: block.header.state.partial.noteHashTree.root,
|
|
109
109
|
nullifierTreeRoot: block.header.state.partial.nullifierTree.root,
|
|
@@ -124,7 +124,7 @@ export class EpochProvingJob implements Traceable {
|
|
|
124
124
|
await db.close();
|
|
125
125
|
this.log.verbose(`Processed all ${txs.length} txs for block ${block.number}`, {
|
|
126
126
|
blockNumber: block.number,
|
|
127
|
-
blockHash: block.hash().toString(),
|
|
127
|
+
blockHash: (await block.hash()).toString(),
|
|
128
128
|
uuid: this.uuid,
|
|
129
129
|
});
|
|
130
130
|
|
|
@@ -144,7 +144,10 @@ export class EpochProvingJob implements Traceable {
|
|
|
144
144
|
throw new Error('Failed to submit epoch proof to L1');
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
this.log.info(`Submitted proof for epoch
|
|
147
|
+
this.log.info(`Submitted proof for epoch ${epochNumber} (blocks ${fromBlock} to ${toBlock})`, {
|
|
148
|
+
epochNumber,
|
|
149
|
+
uuid: this.uuid,
|
|
150
|
+
});
|
|
148
151
|
this.state = 'completed';
|
|
149
152
|
this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeBlocks, epochSizeTxs);
|
|
150
153
|
} catch (err: any) {
|
|
@@ -210,9 +213,12 @@ export class EpochProvingJob implements Traceable {
|
|
|
210
213
|
return this.l2BlockSource.getBlockHeader(blockNumber);
|
|
211
214
|
}
|
|
212
215
|
|
|
213
|
-
private getTxs(block: L2Block): Tx[] {
|
|
216
|
+
private async getTxs(block: L2Block): Promise<Tx[]> {
|
|
214
217
|
const txHashes = block.body.txEffects.map(tx => tx.txHash.toBigInt());
|
|
215
|
-
|
|
218
|
+
const txsAndHashes = await Promise.all(this.txs.map(async tx => ({ tx, hash: await tx.getTxHash() })));
|
|
219
|
+
return txsAndHashes
|
|
220
|
+
.filter(txAndHash => txHashes.includes(txAndHash.hash.toBigInt()))
|
|
221
|
+
.map(txAndHash => txAndHash.tx);
|
|
216
222
|
}
|
|
217
223
|
|
|
218
224
|
private getL1ToL2Messages(block: L2Block) {
|
package/src/metrics.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type L1PublishProofStats, type L1PublishStats } from '@aztec/circuit-types/stats';
|
|
2
|
+
import {
|
|
3
|
+
Attributes,
|
|
4
|
+
type Histogram,
|
|
5
|
+
Metrics,
|
|
6
|
+
type TelemetryClient,
|
|
7
|
+
type UpDownCounter,
|
|
8
|
+
ValueType,
|
|
9
|
+
} from '@aztec/telemetry-client';
|
|
10
|
+
|
|
11
|
+
import { formatEther } from 'viem';
|
|
2
12
|
|
|
3
13
|
export class ProverNodeMetrics {
|
|
4
14
|
proverEpochExecutionDuration: Histogram;
|
|
@@ -6,6 +16,15 @@ export class ProverNodeMetrics {
|
|
|
6
16
|
provingJobBlocks: Histogram;
|
|
7
17
|
provingJobTransactions: Histogram;
|
|
8
18
|
|
|
19
|
+
gasPrice: Histogram;
|
|
20
|
+
txCount: UpDownCounter;
|
|
21
|
+
txDuration: Histogram;
|
|
22
|
+
txGas: Histogram;
|
|
23
|
+
txCalldataSize: Histogram;
|
|
24
|
+
txCalldataGas: Histogram;
|
|
25
|
+
txBlobDataGasUsed: Histogram;
|
|
26
|
+
txBlobDataGasCost: Histogram;
|
|
27
|
+
|
|
9
28
|
constructor(public readonly client: TelemetryClient, name = 'ProverNode') {
|
|
10
29
|
const meter = client.getMeter(name);
|
|
11
30
|
this.proverEpochExecutionDuration = meter.createHistogram(Metrics.PROVER_NODE_EXECUTION_DURATION, {
|
|
@@ -26,6 +45,63 @@ export class ProverNodeMetrics {
|
|
|
26
45
|
description: 'Number of transactions in a proven epoch',
|
|
27
46
|
valueType: ValueType.INT,
|
|
28
47
|
});
|
|
48
|
+
|
|
49
|
+
this.gasPrice = meter.createHistogram(Metrics.L1_PUBLISHER_GAS_PRICE, {
|
|
50
|
+
description: 'The gas price used for transactions',
|
|
51
|
+
unit: 'gwei',
|
|
52
|
+
valueType: ValueType.DOUBLE,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
this.txCount = meter.createUpDownCounter(Metrics.L1_PUBLISHER_TX_COUNT, {
|
|
56
|
+
description: 'The number of transactions processed',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this.txDuration = meter.createHistogram(Metrics.L1_PUBLISHER_TX_DURATION, {
|
|
60
|
+
description: 'The duration of transaction processing',
|
|
61
|
+
unit: 'ms',
|
|
62
|
+
valueType: ValueType.INT,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.txGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_GAS, {
|
|
66
|
+
description: 'The gas consumed by transactions',
|
|
67
|
+
unit: 'gas',
|
|
68
|
+
valueType: ValueType.INT,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.txCalldataSize = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_SIZE, {
|
|
72
|
+
description: 'The size of the calldata in transactions',
|
|
73
|
+
unit: 'By',
|
|
74
|
+
valueType: ValueType.INT,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.txCalldataGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_GAS, {
|
|
78
|
+
description: 'The gas consumed by the calldata in transactions',
|
|
79
|
+
unit: 'gas',
|
|
80
|
+
valueType: ValueType.INT,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.txBlobDataGasUsed = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_USED, {
|
|
84
|
+
description: 'The amount of blob gas used in transactions',
|
|
85
|
+
unit: 'gas',
|
|
86
|
+
valueType: ValueType.INT,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
this.txBlobDataGasCost = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_COST, {
|
|
90
|
+
description: 'The gas cost of blobs in transactions',
|
|
91
|
+
unit: 'gwei',
|
|
92
|
+
valueType: ValueType.INT,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
recordFailedTx() {
|
|
97
|
+
this.txCount.add(1, {
|
|
98
|
+
[Attributes.L1_TX_TYPE]: 'submitProof',
|
|
99
|
+
[Attributes.OK]: false,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
recordSubmitProof(durationMs: number, stats: L1PublishProofStats) {
|
|
104
|
+
this.recordTx(durationMs, stats);
|
|
29
105
|
}
|
|
30
106
|
|
|
31
107
|
public recordProvingJob(executionTimeMs: number, totalTimeMs: number, numBlocks: number, numTxs: number) {
|
|
@@ -34,4 +110,34 @@ export class ProverNodeMetrics {
|
|
|
34
110
|
this.provingJobBlocks.record(Math.floor(numBlocks));
|
|
35
111
|
this.provingJobTransactions.record(Math.floor(numTxs));
|
|
36
112
|
}
|
|
113
|
+
|
|
114
|
+
private recordTx(durationMs: number, stats: L1PublishStats) {
|
|
115
|
+
const attributes = {
|
|
116
|
+
[Attributes.L1_TX_TYPE]: 'submitProof',
|
|
117
|
+
[Attributes.L1_SENDER]: stats.sender,
|
|
118
|
+
} as const;
|
|
119
|
+
|
|
120
|
+
this.txCount.add(1, {
|
|
121
|
+
...attributes,
|
|
122
|
+
[Attributes.OK]: true,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this.txDuration.record(Math.ceil(durationMs), attributes);
|
|
126
|
+
this.txGas.record(
|
|
127
|
+
// safe to downcast - total block limit is 30M gas which fits in a JS number
|
|
128
|
+
Number(stats.gasUsed),
|
|
129
|
+
attributes,
|
|
130
|
+
);
|
|
131
|
+
this.txCalldataGas.record(stats.calldataGas, attributes);
|
|
132
|
+
this.txCalldataSize.record(stats.calldataSize, attributes);
|
|
133
|
+
|
|
134
|
+
this.txBlobDataGasCost.record(Number(stats.blobDataGas), attributes);
|
|
135
|
+
this.txBlobDataGasUsed.record(Number(stats.blobGasUsed), attributes);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
this.gasPrice.record(parseInt(formatEther(stats.gasPrice, 'gwei'), 10));
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// ignore
|
|
141
|
+
}
|
|
142
|
+
}
|
|
37
143
|
}
|
|
@@ -2,7 +2,6 @@ import { type EpochProofClaim } from '@aztec/circuit-types';
|
|
|
2
2
|
import { type EthAddress } from '@aztec/circuits.js';
|
|
3
3
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
4
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
5
|
-
import { type L1Publisher } from '@aztec/sequencer-client';
|
|
6
5
|
import {
|
|
7
6
|
type TelemetryClient,
|
|
8
7
|
type Traceable,
|
|
@@ -11,6 +10,8 @@ import {
|
|
|
11
10
|
trackSpan,
|
|
12
11
|
} from '@aztec/telemetry-client';
|
|
13
12
|
|
|
13
|
+
import { type ProverNodePublisher } from '../prover-node-publisher.js';
|
|
14
|
+
|
|
14
15
|
export interface ClaimsMonitorHandler {
|
|
15
16
|
handleClaim(proofClaim: EpochProofClaim): Promise<void>;
|
|
16
17
|
}
|
|
@@ -25,7 +26,7 @@ export class ClaimsMonitor implements Traceable {
|
|
|
25
26
|
public readonly tracer: Tracer;
|
|
26
27
|
|
|
27
28
|
constructor(
|
|
28
|
-
private readonly l1Publisher:
|
|
29
|
+
private readonly l1Publisher: ProverNodePublisher,
|
|
29
30
|
private options: { pollingIntervalMs: number },
|
|
30
31
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
31
32
|
) {
|
|
@@ -0,0 +1,281 @@
|
|
|
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(
|
|
233
|
+
data,
|
|
234
|
+
{
|
|
235
|
+
args: [...txArgs],
|
|
236
|
+
functionName: 'submitEpochRootProof',
|
|
237
|
+
abi: RollupAbi,
|
|
238
|
+
address: this.rollupContract.address,
|
|
239
|
+
},
|
|
240
|
+
/*blobInputs*/ undefined,
|
|
241
|
+
/*stateOverride*/ [],
|
|
242
|
+
);
|
|
243
|
+
this.log.error(`Rollup submit epoch proof tx reverted. ${errorMsg}`);
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private getSubmitEpochProofArgs(args: {
|
|
249
|
+
fromBlock: number;
|
|
250
|
+
toBlock: number;
|
|
251
|
+
publicInputs: RootRollupPublicInputs;
|
|
252
|
+
proof: Proof;
|
|
253
|
+
}) {
|
|
254
|
+
return [
|
|
255
|
+
BigInt(args.toBlock - args.fromBlock + 1),
|
|
256
|
+
[
|
|
257
|
+
args.publicInputs.previousArchive.root.toString(),
|
|
258
|
+
args.publicInputs.endArchive.root.toString(),
|
|
259
|
+
args.publicInputs.previousBlockHash.toString(),
|
|
260
|
+
args.publicInputs.endBlockHash.toString(),
|
|
261
|
+
args.publicInputs.endTimestamp.toString(),
|
|
262
|
+
args.publicInputs.outHash.toString(),
|
|
263
|
+
args.publicInputs.proverId.toString(),
|
|
264
|
+
],
|
|
265
|
+
makeTuple(AZTEC_MAX_EPOCH_DURATION * 2, i =>
|
|
266
|
+
i % 2 === 0
|
|
267
|
+
? args.publicInputs.fees[i / 2].recipient.toField().toString()
|
|
268
|
+
: args.publicInputs.fees[(i - 1) / 2].value.toString(),
|
|
269
|
+
),
|
|
270
|
+
`0x${args.publicInputs.blobPublicInputs
|
|
271
|
+
.filter((_, i) => i < args.toBlock - args.fromBlock + 1)
|
|
272
|
+
.map(b => b.toString())
|
|
273
|
+
.join(``)}`,
|
|
274
|
+
`0x${serializeToBuffer(args.proof.extractAggregationObject()).toString('hex')}`,
|
|
275
|
+
] as const;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
protected async sleepOrInterrupted() {
|
|
279
|
+
await this.interruptibleSleep.sleep(this.sleepTimeMs);
|
|
280
|
+
}
|
|
281
|
+
}
|
package/src/prover-node.ts
CHANGED
|
@@ -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:
|
|
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!:
|
|
8
|
+
public override publisher!: ProverNodePublisher;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export type TestProverNode = TestProverNode_;
|