@aztec/prover-node 0.56.0 → 0.58.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/bond-manager.d.ts +22 -0
- package/dest/bond/bond-manager.d.ts.map +1 -0
- package/dest/bond/bond-manager.js +42 -0
- package/dest/bond/config.d.ts +8 -0
- package/dest/bond/config.d.ts.map +1 -0
- package/dest/bond/config.js +17 -0
- package/dest/bond/escrow-contract.d.ts +22 -0
- package/dest/bond/escrow-contract.d.ts.map +1 -0
- package/dest/bond/escrow-contract.js +32 -0
- package/dest/bond/factory.d.ts +9 -0
- package/dest/bond/factory.d.ts.map +1 -0
- package/dest/bond/factory.js +19 -0
- package/dest/bond/index.d.ts +3 -0
- package/dest/bond/index.d.ts.map +1 -0
- package/dest/bond/index.js +3 -0
- package/dest/bond/token-contract.d.ts +29 -0
- package/dest/bond/token-contract.d.ts.map +1 -0
- package/dest/bond/token-contract.js +58 -0
- package/dest/config.d.ts +10 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +30 -9
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +39 -9
- package/dest/job/epoch-proving-job.d.ts +39 -0
- package/dest/job/epoch-proving-job.d.ts.map +1 -0
- package/dest/job/epoch-proving-job.js +127 -0
- package/dest/monitors/claims-monitor.d.ts +22 -0
- package/dest/monitors/claims-monitor.d.ts.map +1 -0
- package/dest/monitors/claims-monitor.js +37 -0
- package/dest/monitors/epoch-monitor.d.ts +20 -0
- package/dest/monitors/epoch-monitor.d.ts.map +1 -0
- package/dest/monitors/epoch-monitor.js +34 -0
- package/dest/monitors/index.d.ts +3 -0
- package/dest/monitors/index.d.ts.map +1 -0
- package/dest/monitors/index.js +3 -0
- package/dest/prover-coordination/config.d.ts +7 -0
- package/dest/prover-coordination/config.d.ts.map +1 -0
- package/dest/prover-coordination/config.js +12 -0
- package/dest/prover-coordination/factory.d.ts +4 -0
- package/dest/prover-coordination/factory.d.ts.map +1 -0
- package/dest/prover-coordination/factory.js +10 -0
- package/dest/prover-coordination/index.d.ts +3 -0
- package/dest/prover-coordination/index.d.ts.map +1 -0
- package/dest/prover-coordination/index.js +3 -0
- package/dest/prover-node.d.ts +57 -33
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +128 -76
- package/dest/quote-provider/http.d.ts +15 -0
- package/dest/quote-provider/http.d.ts.map +1 -0
- package/dest/quote-provider/http.js +32 -0
- package/dest/quote-provider/index.d.ts +6 -0
- package/dest/quote-provider/index.d.ts.map +1 -0
- package/dest/quote-provider/index.js +2 -0
- package/dest/quote-provider/simple.d.ts +9 -0
- package/dest/quote-provider/simple.d.ts.map +1 -0
- package/dest/quote-provider/simple.js +11 -0
- package/dest/quote-provider/utils.d.ts +4 -0
- package/dest/quote-provider/utils.d.ts.map +1 -0
- package/dest/quote-provider/utils.js +8 -0
- package/dest/quote-signer.d.ts +13 -0
- package/dest/quote-signer.d.ts.map +1 -0
- package/dest/quote-signer.js +18 -0
- package/package.json +19 -13
- package/src/bond/bond-manager.ts +48 -0
- package/src/bond/config.ts +25 -0
- package/src/bond/escrow-contract.ts +63 -0
- package/src/bond/factory.ts +48 -0
- package/src/bond/index.ts +2 -0
- package/src/bond/token-contract.ts +85 -0
- package/src/config.ts +47 -12
- package/src/factory.ts +51 -10
- package/src/job/{block-proving-job.ts → epoch-proving-job.ts} +59 -57
- package/src/monitors/claims-monitor.ts +52 -0
- package/src/monitors/epoch-monitor.ts +48 -0
- package/src/monitors/index.ts +2 -0
- package/src/prover-coordination/config.ts +17 -0
- package/src/prover-coordination/factory.ts +11 -0
- package/src/{tx-provider → prover-coordination}/index.ts +1 -2
- package/src/prover-node.ts +169 -99
- package/src/quote-provider/http.ts +47 -0
- package/src/quote-provider/index.ts +8 -0
- package/src/quote-provider/simple.ts +15 -0
- package/src/quote-provider/utils.ts +10 -0
- package/src/quote-signer.ts +24 -0
- package/dest/job/block-proving-job.d.ts +0 -33
- package/dest/job/block-proving-job.d.ts.map +0 -1
- package/dest/job/block-proving-job.js +0 -120
- package/dest/tx-provider/aztec-node-tx-provider.d.ts +0 -8
- package/dest/tx-provider/aztec-node-tx-provider.d.ts.map +0 -1
- package/dest/tx-provider/aztec-node-tx-provider.js +0 -10
- package/dest/tx-provider/config.d.ts +0 -7
- package/dest/tx-provider/config.d.ts.map +0 -1
- package/dest/tx-provider/config.js +0 -12
- package/dest/tx-provider/factory.d.ts +0 -4
- package/dest/tx-provider/factory.d.ts.map +0 -1
- package/dest/tx-provider/factory.js +0 -12
- package/dest/tx-provider/index.d.ts +0 -4
- package/dest/tx-provider/index.d.ts.map +0 -1
- package/dest/tx-provider/index.js +0 -4
- package/src/tx-provider/aztec-node-tx-provider.ts +0 -10
- package/src/tx-provider/config.ts +0 -17
- package/src/tx-provider/factory.ts +0 -13
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type L2BlockSource } from '@aztec/circuit-types';
|
|
2
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
3
|
+
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
4
|
+
|
|
5
|
+
export interface EpochMonitorHandler {
|
|
6
|
+
handleInitialEpochSync(epochNumber: bigint): Promise<void>;
|
|
7
|
+
handleEpochCompleted(epochNumber: bigint): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class EpochMonitor {
|
|
11
|
+
private runningPromise: RunningPromise;
|
|
12
|
+
private log = createDebugLogger('aztec:prover-node:epoch-monitor');
|
|
13
|
+
|
|
14
|
+
private handler: EpochMonitorHandler | undefined;
|
|
15
|
+
|
|
16
|
+
private latestEpochNumber: bigint | undefined;
|
|
17
|
+
|
|
18
|
+
constructor(private readonly l2BlockSource: L2BlockSource, private options: { pollingIntervalMs: number }) {
|
|
19
|
+
this.runningPromise = new RunningPromise(this.work.bind(this), this.options.pollingIntervalMs);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public start(handler: EpochMonitorHandler) {
|
|
23
|
+
this.handler = handler;
|
|
24
|
+
this.runningPromise.start();
|
|
25
|
+
this.log.info('Started EpochMonitor', this.options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async stop() {
|
|
29
|
+
await this.runningPromise.stop();
|
|
30
|
+
this.log.info('Stopped EpochMonitor');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public async work() {
|
|
34
|
+
if (!this.latestEpochNumber) {
|
|
35
|
+
const epochNumber = await this.l2BlockSource.getL2EpochNumber();
|
|
36
|
+
if (epochNumber > 0n) {
|
|
37
|
+
await this.handler?.handleInitialEpochSync(epochNumber - 1n);
|
|
38
|
+
}
|
|
39
|
+
this.latestEpochNumber = epochNumber;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (await this.l2BlockSource.isEpochComplete(this.latestEpochNumber)) {
|
|
44
|
+
await this.handler?.handleEpochCompleted(this.latestEpochNumber);
|
|
45
|
+
this.latestEpochNumber += 1n;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundation/config';
|
|
2
|
+
|
|
3
|
+
export type ProverCoordinationConfig = {
|
|
4
|
+
proverCoordinationNodeUrl: string | undefined;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const proverCoordinationConfigMappings: ConfigMappingsType<ProverCoordinationConfig> = {
|
|
8
|
+
proverCoordinationNodeUrl: {
|
|
9
|
+
env: 'PROVER_COORDINATION_NODE_URL',
|
|
10
|
+
description: 'The URL of the tx provider node',
|
|
11
|
+
parseEnv: (val: string) => val,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function getTxProviderConfigFromEnv(): ProverCoordinationConfig {
|
|
16
|
+
return getConfigFromMappings<ProverCoordinationConfig>(proverCoordinationConfigMappings);
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ProverCoordination, createAztecNodeClient } from '@aztec/circuit-types';
|
|
2
|
+
|
|
3
|
+
import { type ProverCoordinationConfig } from './config.js';
|
|
4
|
+
|
|
5
|
+
export function createProverCoordination(config: ProverCoordinationConfig): ProverCoordination {
|
|
6
|
+
if (config.proverCoordinationNodeUrl) {
|
|
7
|
+
return createAztecNodeClient(config.proverCoordinationNodeUrl);
|
|
8
|
+
} else {
|
|
9
|
+
throw new Error(`Aztec Node URL for Tx Provider is not set.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
package/src/prover-node.ts
CHANGED
|
@@ -1,64 +1,160 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type EpochProofClaim,
|
|
3
|
+
type EpochProofQuote,
|
|
4
|
+
EpochProofQuotePayload,
|
|
5
|
+
type EpochProverManager,
|
|
2
6
|
type L1ToL2MessageSource,
|
|
7
|
+
type L2Block,
|
|
3
8
|
type L2BlockSource,
|
|
4
|
-
type
|
|
5
|
-
type
|
|
6
|
-
type TxProvider,
|
|
9
|
+
type MerkleTreeWriteOperations,
|
|
10
|
+
type ProverCoordination,
|
|
7
11
|
type WorldStateSynchronizer,
|
|
8
12
|
} from '@aztec/circuit-types';
|
|
13
|
+
import { type ContractDataSource } from '@aztec/circuits.js';
|
|
14
|
+
import { compact } from '@aztec/foundation/collection';
|
|
9
15
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
10
|
-
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
11
16
|
import { type L1Publisher } from '@aztec/sequencer-client';
|
|
12
17
|
import { PublicProcessorFactory, type SimulationProvider } from '@aztec/simulator';
|
|
13
18
|
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
14
|
-
import { type ContractDataSource } from '@aztec/types/contracts';
|
|
15
19
|
|
|
16
|
-
import {
|
|
20
|
+
import { type BondManager } from './bond/bond-manager.js';
|
|
21
|
+
import { EpochProvingJob, type EpochProvingJobState } from './job/epoch-proving-job.js';
|
|
17
22
|
import { ProverNodeMetrics } from './metrics.js';
|
|
23
|
+
import { type ClaimsMonitor, type ClaimsMonitorHandler } from './monitors/claims-monitor.js';
|
|
24
|
+
import { type EpochMonitor, type EpochMonitorHandler } from './monitors/epoch-monitor.js';
|
|
25
|
+
import { type QuoteProvider } from './quote-provider/index.js';
|
|
26
|
+
import { type QuoteSigner } from './quote-signer.js';
|
|
27
|
+
|
|
28
|
+
export type ProverNodeOptions = {
|
|
29
|
+
pollingIntervalMs: number;
|
|
30
|
+
maxPendingJobs: number;
|
|
31
|
+
};
|
|
18
32
|
|
|
19
33
|
/**
|
|
20
34
|
* An Aztec Prover Node is a standalone process that monitors the unfinalised chain on L1 for unproven blocks,
|
|
21
|
-
*
|
|
22
|
-
*
|
|
35
|
+
* submits bids for proving them, and monitors if they are accepted. If so, the prover node fetches the txs
|
|
36
|
+
* from a tx source in the p2p network or an external node, re-executes their public functions, creates a rollup
|
|
37
|
+
* proof for the epoch, and submits it to L1.
|
|
23
38
|
*/
|
|
24
|
-
export class ProverNode {
|
|
39
|
+
export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler {
|
|
25
40
|
private log = createDebugLogger('aztec:prover-node');
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
private jobs: Map<string,
|
|
29
|
-
private options:
|
|
41
|
+
|
|
42
|
+
private latestEpochWeAreProving: bigint | undefined;
|
|
43
|
+
private jobs: Map<string, EpochProvingJob> = new Map();
|
|
44
|
+
private options: ProverNodeOptions;
|
|
30
45
|
private metrics: ProverNodeMetrics;
|
|
31
46
|
|
|
32
47
|
constructor(
|
|
33
|
-
private prover:
|
|
34
|
-
private publisher: L1Publisher,
|
|
35
|
-
private l2BlockSource: L2BlockSource,
|
|
36
|
-
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
37
|
-
private contractDataSource: ContractDataSource,
|
|
38
|
-
private worldState: WorldStateSynchronizer,
|
|
39
|
-
private
|
|
40
|
-
private simulator: SimulationProvider,
|
|
41
|
-
private
|
|
42
|
-
|
|
48
|
+
private readonly prover: EpochProverManager,
|
|
49
|
+
private readonly publisher: L1Publisher,
|
|
50
|
+
private readonly l2BlockSource: L2BlockSource,
|
|
51
|
+
private readonly l1ToL2MessageSource: L1ToL2MessageSource,
|
|
52
|
+
private readonly contractDataSource: ContractDataSource,
|
|
53
|
+
private readonly worldState: WorldStateSynchronizer,
|
|
54
|
+
private readonly coordination: ProverCoordination,
|
|
55
|
+
private readonly simulator: SimulationProvider,
|
|
56
|
+
private readonly quoteProvider: QuoteProvider,
|
|
57
|
+
private readonly quoteSigner: QuoteSigner,
|
|
58
|
+
private readonly claimsMonitor: ClaimsMonitor,
|
|
59
|
+
private readonly epochsMonitor: EpochMonitor,
|
|
60
|
+
private readonly bondManager: BondManager,
|
|
61
|
+
private readonly telemetryClient: TelemetryClient,
|
|
62
|
+
options: Partial<ProverNodeOptions> = {},
|
|
43
63
|
) {
|
|
44
64
|
this.options = {
|
|
45
65
|
pollingIntervalMs: 1_000,
|
|
46
|
-
disableAutomaticProving: false,
|
|
47
66
|
maxPendingJobs: 100,
|
|
48
|
-
...options,
|
|
67
|
+
...compact(options),
|
|
49
68
|
};
|
|
50
69
|
|
|
51
70
|
this.metrics = new ProverNodeMetrics(telemetryClient, 'ProverNode');
|
|
52
71
|
}
|
|
53
72
|
|
|
73
|
+
async handleClaim(proofClaim: EpochProofClaim): Promise<void> {
|
|
74
|
+
if (proofClaim.epochToProve === this.latestEpochWeAreProving) {
|
|
75
|
+
this.log.verbose(`Already proving claim for epoch ${proofClaim.epochToProve}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await this.startProof(proofClaim.epochToProve);
|
|
81
|
+
this.latestEpochWeAreProving = proofClaim.epochToProve;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
this.log.error(`Error handling claim for epoch ${proofClaim.epochToProve}`, err);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Staked amounts are lowered after a claim, so this is a good time for doing a top-up if needed
|
|
88
|
+
await this.bondManager.ensureBond();
|
|
89
|
+
} catch (err) {
|
|
90
|
+
this.log.error(`Error ensuring prover bond after handling claim for epoch ${proofClaim.epochToProve}`, err);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Handles the epoch number to prove when the prover node starts by checking if there
|
|
96
|
+
* is an existing claim for it. If not, it creates and sends a quote for it.
|
|
97
|
+
* @param epochNumber - The epoch immediately before the current one when the prover node starts.
|
|
98
|
+
*/
|
|
99
|
+
async handleInitialEpochSync(epochNumber: bigint): Promise<void> {
|
|
100
|
+
try {
|
|
101
|
+
const claim = await this.publisher.getProofClaim();
|
|
102
|
+
if (!claim || claim.epochToProve < epochNumber) {
|
|
103
|
+
await this.handleEpochCompleted(epochNumber);
|
|
104
|
+
} else if (claim && claim.bondProvider.equals(this.publisher.getSenderAddress())) {
|
|
105
|
+
const lastEpochProven = await this.l2BlockSource.getProvenL2EpochNumber();
|
|
106
|
+
if (lastEpochProven === undefined || lastEpochProven < claim.epochToProve) {
|
|
107
|
+
await this.handleClaim(claim);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
this.log.error(`Error handling initial epoch sync`, err);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Handles an epoch being completed by sending a quote for proving it.
|
|
117
|
+
* @param epochNumber - The epoch number that was just completed.
|
|
118
|
+
*/
|
|
119
|
+
async handleEpochCompleted(epochNumber: bigint): Promise<void> {
|
|
120
|
+
try {
|
|
121
|
+
// Construct a quote for the epoch
|
|
122
|
+
const blocks = await this.l2BlockSource.getBlocksForEpoch(epochNumber);
|
|
123
|
+
const partialQuote = await this.quoteProvider.getQuote(Number(epochNumber), blocks);
|
|
124
|
+
if (!partialQuote) {
|
|
125
|
+
this.log.verbose(`No quote produced for epoch ${epochNumber}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Ensure we have deposited enough funds for sending this quote
|
|
130
|
+
await this.bondManager.ensureBond(partialQuote.bondAmount);
|
|
131
|
+
|
|
132
|
+
// Assemble and sign full quote
|
|
133
|
+
const quote = EpochProofQuotePayload.from({
|
|
134
|
+
...partialQuote,
|
|
135
|
+
epochToProve: BigInt(epochNumber),
|
|
136
|
+
prover: this.publisher.getSenderAddress(),
|
|
137
|
+
validUntilSlot: partialQuote.validUntilSlot ?? BigInt(Number.MAX_SAFE_INTEGER), // Should we constrain this?
|
|
138
|
+
});
|
|
139
|
+
const signed = await this.quoteSigner.sign(quote);
|
|
140
|
+
|
|
141
|
+
// Send it to the coordinator
|
|
142
|
+
await this.sendEpochProofQuote(signed);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
this.log.error(`Error handling epoch completed`, err);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
54
148
|
/**
|
|
55
|
-
* Starts the prover node so it periodically checks for unproven
|
|
56
|
-
*
|
|
149
|
+
* Starts the prover node so it periodically checks for unproven epochs in the unfinalised chain from L1 and sends
|
|
150
|
+
* quotes for them, as well as monitors the claims for the epochs it has sent quotes for and starts proving jobs.
|
|
151
|
+
* This method returns once the prover node has deposited an initial bond into the escrow contract.
|
|
57
152
|
*/
|
|
58
|
-
start() {
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
this.
|
|
153
|
+
async start() {
|
|
154
|
+
await this.bondManager.ensureBond();
|
|
155
|
+
this.epochsMonitor.start(this);
|
|
156
|
+
this.claimsMonitor.start(this);
|
|
157
|
+
this.log.info('Started ProverNode', this.options);
|
|
62
158
|
}
|
|
63
159
|
|
|
64
160
|
/**
|
|
@@ -66,79 +162,38 @@ export class ProverNode {
|
|
|
66
162
|
*/
|
|
67
163
|
async stop() {
|
|
68
164
|
this.log.info('Stopping ProverNode');
|
|
69
|
-
await this.
|
|
165
|
+
await this.epochsMonitor.stop();
|
|
166
|
+
await this.claimsMonitor.stop();
|
|
70
167
|
await this.prover.stop();
|
|
71
168
|
await this.l2BlockSource.stop();
|
|
72
169
|
this.publisher.interrupt();
|
|
73
|
-
this.jobs.
|
|
170
|
+
await Promise.all(Array.from(this.jobs.values()).map(job => job.stop()));
|
|
74
171
|
await this.worldState.stop();
|
|
75
172
|
this.log.info('Stopped ProverNode');
|
|
76
173
|
}
|
|
77
174
|
|
|
78
175
|
/**
|
|
79
|
-
*
|
|
80
|
-
* Checks whether there are new blocks to prove, proves them, and submits them.
|
|
176
|
+
* Sends an epoch proof quote to the coordinator.
|
|
81
177
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!this.checkMaximumPendingJobs()) {
|
|
89
|
-
this.log.debug(`Maximum pending proving jobs reached. Skipping work.`, {
|
|
90
|
-
maxPendingJobs: this.options.maxPendingJobs,
|
|
91
|
-
pendingJobs: this.jobs.size,
|
|
92
|
-
});
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const [latestBlockNumber, latestProvenBlockNumber] = await Promise.all([
|
|
97
|
-
this.l2BlockSource.getBlockNumber(),
|
|
98
|
-
this.l2BlockSource.getProvenBlockNumber(),
|
|
99
|
-
]);
|
|
100
|
-
|
|
101
|
-
// Consider both the latest block we are proving and the last block proven on the chain
|
|
102
|
-
const latestBlockBeingProven = this.latestBlockWeAreProving ?? 0;
|
|
103
|
-
const latestProven = Math.max(latestBlockBeingProven, latestProvenBlockNumber);
|
|
104
|
-
if (latestProven >= latestBlockNumber) {
|
|
105
|
-
this.log.debug(`No new blocks to prove`, {
|
|
106
|
-
latestBlockNumber,
|
|
107
|
-
latestProvenBlockNumber,
|
|
108
|
-
latestBlockBeingProven,
|
|
109
|
-
});
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const fromBlock = latestProven + 1;
|
|
114
|
-
const toBlock = fromBlock; // We only prove one block at a time for now
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
await this.startProof(fromBlock, toBlock);
|
|
118
|
-
} finally {
|
|
119
|
-
// If we fail to create a proving job for the given block, skip it instead of getting stuck on it.
|
|
120
|
-
this.log.verbose(`Setting ${toBlock} as latest block we are proving`);
|
|
121
|
-
this.latestBlockWeAreProving = toBlock;
|
|
122
|
-
}
|
|
123
|
-
} catch (err) {
|
|
124
|
-
this.log.error(`Error in prover node work`, err);
|
|
125
|
-
}
|
|
178
|
+
public sendEpochProofQuote(quote: EpochProofQuote): Promise<void> {
|
|
179
|
+
this.log.info(`Sending quote for epoch`, quote.toViemArgs().quote);
|
|
180
|
+
return this.coordination.addEpochProofQuote(quote);
|
|
126
181
|
}
|
|
127
182
|
|
|
128
183
|
/**
|
|
129
184
|
* Creates a proof for a block range. Returns once the proof has been submitted to L1.
|
|
130
185
|
*/
|
|
131
|
-
public async prove(
|
|
132
|
-
const job = await this.createProvingJob(
|
|
133
|
-
return job.run(
|
|
186
|
+
public async prove(epochNumber: number | bigint) {
|
|
187
|
+
const job = await this.createProvingJob(BigInt(epochNumber));
|
|
188
|
+
return job.run();
|
|
134
189
|
}
|
|
135
190
|
|
|
136
191
|
/**
|
|
137
192
|
* Starts a proving process and returns immediately.
|
|
138
193
|
*/
|
|
139
|
-
public async startProof(
|
|
140
|
-
const job = await this.createProvingJob(
|
|
141
|
-
void job.run(
|
|
194
|
+
public async startProof(epochNumber: number | bigint) {
|
|
195
|
+
const job = await this.createProvingJob(BigInt(epochNumber));
|
|
196
|
+
void job.run().catch(err => this.log.error(`Error proving epoch ${epochNumber}`, err));
|
|
142
197
|
}
|
|
143
198
|
|
|
144
199
|
/**
|
|
@@ -151,7 +206,7 @@ export class ProverNode {
|
|
|
151
206
|
/**
|
|
152
207
|
* Returns an array of jobs being processed.
|
|
153
208
|
*/
|
|
154
|
-
public getJobs(): { uuid: string; status:
|
|
209
|
+
public getJobs(): { uuid: string; status: EpochProvingJobState }[] {
|
|
155
210
|
return Array.from(this.jobs.entries()).map(([uuid, job]) => ({ uuid, status: job.getState() }));
|
|
156
211
|
}
|
|
157
212
|
|
|
@@ -160,52 +215,67 @@ export class ProverNode {
|
|
|
160
215
|
return maxPendingJobs === 0 || this.jobs.size < maxPendingJobs;
|
|
161
216
|
}
|
|
162
217
|
|
|
163
|
-
private async createProvingJob(
|
|
218
|
+
private async createProvingJob(epochNumber: bigint) {
|
|
164
219
|
if (!this.checkMaximumPendingJobs()) {
|
|
165
220
|
throw new Error(`Maximum pending proving jobs ${this.options.maxPendingJobs} reached. Cannot create new job.`);
|
|
166
221
|
}
|
|
167
222
|
|
|
168
|
-
|
|
169
|
-
|
|
223
|
+
// Gather blocks for this epoch
|
|
224
|
+
const blocks = await this.l2BlockSource.getBlocksForEpoch(epochNumber);
|
|
225
|
+
if (blocks.length === 0) {
|
|
226
|
+
throw new Error(`No blocks found for epoch ${epochNumber}`);
|
|
170
227
|
}
|
|
228
|
+
const fromBlock = blocks[0].number;
|
|
229
|
+
const toBlock = blocks.at(-1)!.number;
|
|
171
230
|
|
|
172
231
|
// Fast forward world state to right before the target block and get a fork
|
|
173
|
-
this.log.verbose(`Creating proving job for block ${fromBlock}`);
|
|
174
|
-
|
|
232
|
+
this.log.verbose(`Creating proving job for epoch ${epochNumber} for block range ${fromBlock} to ${toBlock}`);
|
|
233
|
+
await this.worldState.syncImmediate(fromBlock - 1);
|
|
234
|
+
const db = await this.worldState.fork(fromBlock - 1);
|
|
175
235
|
|
|
176
236
|
// Create a processor using the forked world state
|
|
177
237
|
const publicProcessorFactory = new PublicProcessorFactory(
|
|
178
|
-
db,
|
|
179
238
|
this.contractDataSource,
|
|
180
239
|
this.simulator,
|
|
181
240
|
this.telemetryClient,
|
|
182
241
|
);
|
|
183
242
|
|
|
184
243
|
const cleanUp = async () => {
|
|
185
|
-
await db.
|
|
244
|
+
await db.close();
|
|
186
245
|
this.jobs.delete(job.getId());
|
|
187
246
|
};
|
|
188
247
|
|
|
189
|
-
const job = this.
|
|
248
|
+
const job = this.doCreateEpochProvingJob(epochNumber, blocks, db, publicProcessorFactory, cleanUp);
|
|
190
249
|
this.jobs.set(job.getId(), job);
|
|
191
250
|
return job;
|
|
192
251
|
}
|
|
193
252
|
|
|
194
253
|
/** Extracted for testing purposes. */
|
|
195
|
-
protected
|
|
196
|
-
|
|
254
|
+
protected doCreateEpochProvingJob(
|
|
255
|
+
epochNumber: bigint,
|
|
256
|
+
blocks: L2Block[],
|
|
257
|
+
db: MerkleTreeWriteOperations,
|
|
197
258
|
publicProcessorFactory: PublicProcessorFactory,
|
|
198
259
|
cleanUp: () => Promise<void>,
|
|
199
260
|
) {
|
|
200
|
-
return new
|
|
201
|
-
|
|
261
|
+
return new EpochProvingJob(
|
|
262
|
+
db,
|
|
263
|
+
epochNumber,
|
|
264
|
+
blocks,
|
|
265
|
+
this.prover.createEpochProver(db),
|
|
202
266
|
publicProcessorFactory,
|
|
203
267
|
this.publisher,
|
|
204
268
|
this.l2BlockSource,
|
|
205
269
|
this.l1ToL2MessageSource,
|
|
206
|
-
this.
|
|
270
|
+
this.coordination,
|
|
207
271
|
this.metrics,
|
|
208
272
|
cleanUp,
|
|
209
273
|
);
|
|
210
274
|
}
|
|
275
|
+
|
|
276
|
+
/** Extracted for testing purposes. */
|
|
277
|
+
protected async triggerMonitors() {
|
|
278
|
+
await this.epochsMonitor.work();
|
|
279
|
+
await this.claimsMonitor.work();
|
|
280
|
+
}
|
|
211
281
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type L2Block } from '@aztec/circuit-types';
|
|
2
|
+
|
|
3
|
+
import { type QuoteProvider, type QuoteProviderResult } from './index.js';
|
|
4
|
+
import { getTotalFees, getTxCount } from './utils.js';
|
|
5
|
+
|
|
6
|
+
export class HttpQuoteProvider implements QuoteProvider {
|
|
7
|
+
constructor(private readonly url: string) {}
|
|
8
|
+
|
|
9
|
+
public async getQuote(epochNumber: number, epoch: L2Block[]): Promise<QuoteProviderResult | undefined> {
|
|
10
|
+
const payload: HttpQuoteRequestPayload = {
|
|
11
|
+
epochNumber,
|
|
12
|
+
fromBlock: epoch[0].number,
|
|
13
|
+
toBlock: epoch.at(-1)!.number,
|
|
14
|
+
totalFees: getTotalFees(epoch).toString(),
|
|
15
|
+
txCount: getTxCount(epoch),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const response = await fetch(this.url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
body: JSON.stringify(payload),
|
|
21
|
+
headers: { 'content-type': 'application/json' },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`Failed to fetch quote: ${response.statusText}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
if (!data.basisPointFee || !data.bondAmount) {
|
|
30
|
+
throw new Error(`Missing required fields in response: ${JSON.stringify(data)}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const basisPointFee = Number(data.basisPointFee);
|
|
34
|
+
const bondAmount = BigInt(data.bondAmount);
|
|
35
|
+
const validUntilSlot = data.validUntilSlot ? BigInt(data.validUntilSlot) : undefined;
|
|
36
|
+
|
|
37
|
+
return { basisPointFee, bondAmount, validUntilSlot };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type HttpQuoteRequestPayload = {
|
|
42
|
+
epochNumber: number;
|
|
43
|
+
fromBlock: number;
|
|
44
|
+
toBlock: number;
|
|
45
|
+
totalFees: string;
|
|
46
|
+
txCount: number;
|
|
47
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type EpochProofQuotePayload, type L2Block } from '@aztec/circuit-types';
|
|
2
|
+
|
|
3
|
+
export type QuoteProviderResult = Pick<EpochProofQuotePayload, 'basisPointFee' | 'bondAmount'> &
|
|
4
|
+
Partial<Pick<EpochProofQuotePayload, 'validUntilSlot'>>;
|
|
5
|
+
|
|
6
|
+
export interface QuoteProvider {
|
|
7
|
+
getQuote(epochNumber: number, epoch: L2Block[]): Promise<QuoteProviderResult | undefined>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type EpochProofQuotePayload, type L2Block } from '@aztec/circuit-types';
|
|
2
|
+
|
|
3
|
+
import { type QuoteProvider } from './index.js';
|
|
4
|
+
|
|
5
|
+
export class SimpleQuoteProvider implements QuoteProvider {
|
|
6
|
+
constructor(public readonly basisPointFee: number, public readonly bondAmount: bigint) {}
|
|
7
|
+
|
|
8
|
+
getQuote(
|
|
9
|
+
_epochNumber: number,
|
|
10
|
+
_epoch: L2Block[],
|
|
11
|
+
): Promise<Pick<EpochProofQuotePayload, 'basisPointFee' | 'bondAmount'>> {
|
|
12
|
+
const { basisPointFee, bondAmount } = this;
|
|
13
|
+
return Promise.resolve({ basisPointFee, bondAmount });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type L2Block } from '@aztec/circuit-types';
|
|
2
|
+
import { Fr } from '@aztec/circuits.js';
|
|
3
|
+
|
|
4
|
+
export function getTotalFees(epoch: L2Block[]) {
|
|
5
|
+
return epoch.reduce((total, block) => total.add(block.header.totalFees), Fr.ZERO).toBigInt();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function getTxCount(epoch: L2Block[]) {
|
|
9
|
+
return epoch.reduce((total, block) => total + block.body.txEffects.length, 0);
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EpochProofQuote, type EpochProofQuotePayload } from '@aztec/circuit-types';
|
|
2
|
+
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
3
|
+
import { Secp256k1Signer } from '@aztec/foundation/crypto';
|
|
4
|
+
import { type RollupAbi } from '@aztec/l1-artifacts';
|
|
5
|
+
|
|
6
|
+
import { type GetContractReturnType, type PublicClient } from 'viem';
|
|
7
|
+
|
|
8
|
+
export class QuoteSigner {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly signer: Secp256k1Signer,
|
|
11
|
+
private readonly quoteToDigest: (payload: EpochProofQuotePayload) => Promise<Buffer32>,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
static new(privateKey: Buffer32, rollupContract: GetContractReturnType<typeof RollupAbi, PublicClient>): QuoteSigner {
|
|
15
|
+
const quoteToDigest = (payload: EpochProofQuotePayload) =>
|
|
16
|
+
rollupContract.read.quoteToDigest([payload.toViemArgs()]).then(Buffer32.fromString);
|
|
17
|
+
return new QuoteSigner(new Secp256k1Signer(privateKey), quoteToDigest);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async sign(payload: EpochProofQuotePayload) {
|
|
21
|
+
const digest = await this.quoteToDigest(payload);
|
|
22
|
+
return EpochProofQuote.new(digest, payload, this.signer);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { type BlockProver, type L1ToL2MessageSource, type L2BlockSource, type TxProvider } from '@aztec/circuit-types';
|
|
2
|
-
import { type L1Publisher } from '@aztec/sequencer-client';
|
|
3
|
-
import { type PublicProcessorFactory } from '@aztec/simulator';
|
|
4
|
-
import { type ProverNodeMetrics } from '../metrics.js';
|
|
5
|
-
/**
|
|
6
|
-
* Job that grabs a range of blocks from the unfinalised chain from L1, gets their txs given their hashes,
|
|
7
|
-
* re-executes their public calls, generates a rollup proof, and submits it to L1. This job will update the
|
|
8
|
-
* world state as part of public call execution via the public processor.
|
|
9
|
-
*/
|
|
10
|
-
export declare class BlockProvingJob {
|
|
11
|
-
private prover;
|
|
12
|
-
private publicProcessorFactory;
|
|
13
|
-
private publisher;
|
|
14
|
-
private l2BlockSource;
|
|
15
|
-
private l1ToL2MessageSource;
|
|
16
|
-
private txProvider;
|
|
17
|
-
private metrics;
|
|
18
|
-
private cleanUp;
|
|
19
|
-
private state;
|
|
20
|
-
private log;
|
|
21
|
-
private uuid;
|
|
22
|
-
constructor(prover: BlockProver, publicProcessorFactory: PublicProcessorFactory, publisher: L1Publisher, l2BlockSource: L2BlockSource, l1ToL2MessageSource: L1ToL2MessageSource, txProvider: TxProvider, metrics: ProverNodeMetrics, cleanUp?: (job: BlockProvingJob) => Promise<void>);
|
|
23
|
-
getId(): string;
|
|
24
|
-
getState(): BlockProvingJobState;
|
|
25
|
-
run(fromBlock: number, toBlock: number): Promise<void>;
|
|
26
|
-
stop(): void;
|
|
27
|
-
private getBlock;
|
|
28
|
-
private getTxs;
|
|
29
|
-
private getL1ToL2Messages;
|
|
30
|
-
private processTxs;
|
|
31
|
-
}
|
|
32
|
-
export type BlockProvingJobState = 'initialized' | 'processing' | 'awaiting-prover' | 'publishing-proof' | 'completed' | 'failed';
|
|
33
|
-
//# sourceMappingURL=block-proving-job.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"block-proving-job.d.ts","sourceRoot":"","sources":["../../src/job/block-proving-job.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,mBAAmB,EAExB,KAAK,aAAa,EAKlB,KAAK,UAAU,EAChB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAwB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAIrF,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;;GAIG;AACH,qBAAa,eAAe;IAMxB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;IAZjB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,GAAG,CAAgD;IAC3D,OAAO,CAAC,IAAI,CAAS;gBAGX,MAAM,EAAE,WAAW,EACnB,sBAAsB,EAAE,sBAAsB,EAC9C,SAAS,EAAE,WAAW,EACtB,aAAa,EAAE,aAAa,EAC5B,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAA2B;IAK7E,KAAK,IAAI,MAAM;IAIf,QAAQ,IAAI,oBAAoB;IAI1B,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IA8E5C,IAAI;YAIG,QAAQ;YAQR,MAAM;IAWpB,OAAO,CAAC,iBAAiB;YAIX,UAAU;CAoBzB;AAED,MAAM,MAAM,oBAAoB,GAC5B,aAAa,GACb,YAAY,GACZ,iBAAiB,GACjB,kBAAkB,GAClB,WAAW,GACX,QAAQ,CAAC"}
|