@aztec/prover-node 0.0.0-test.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 (58) hide show
  1. package/README.md +1 -0
  2. package/dest/config.d.ts +28 -0
  3. package/dest/config.d.ts.map +1 -0
  4. package/dest/config.js +71 -0
  5. package/dest/factory.d.ts +25 -0
  6. package/dest/factory.d.ts.map +1 -0
  7. package/dest/factory.js +60 -0
  8. package/dest/http.d.ts +8 -0
  9. package/dest/http.d.ts.map +1 -0
  10. package/dest/http.js +9 -0
  11. package/dest/index.d.ts +6 -0
  12. package/dest/index.d.ts.map +1 -0
  13. package/dest/index.js +5 -0
  14. package/dest/job/epoch-proving-job.d.ts +54 -0
  15. package/dest/job/epoch-proving-job.d.ts.map +1 -0
  16. package/dest/job/epoch-proving-job.js +255 -0
  17. package/dest/metrics.d.ts +26 -0
  18. package/dest/metrics.d.ts.map +1 -0
  19. package/dest/metrics.js +129 -0
  20. package/dest/monitors/epoch-monitor.d.ts +40 -0
  21. package/dest/monitors/epoch-monitor.d.ts.map +1 -0
  22. package/dest/monitors/epoch-monitor.js +104 -0
  23. package/dest/monitors/index.d.ts +2 -0
  24. package/dest/monitors/index.d.ts.map +1 -0
  25. package/dest/monitors/index.js +1 -0
  26. package/dest/prover-coordination/config.d.ts +7 -0
  27. package/dest/prover-coordination/config.d.ts.map +1 -0
  28. package/dest/prover-coordination/config.js +11 -0
  29. package/dest/prover-coordination/factory.d.ts +22 -0
  30. package/dest/prover-coordination/factory.d.ts.map +1 -0
  31. package/dest/prover-coordination/factory.js +42 -0
  32. package/dest/prover-coordination/index.d.ts +3 -0
  33. package/dest/prover-coordination/index.d.ts.map +1 -0
  34. package/dest/prover-coordination/index.js +2 -0
  35. package/dest/prover-node-publisher.d.ts +61 -0
  36. package/dest/prover-node-publisher.d.ts.map +1 -0
  37. package/dest/prover-node-publisher.js +192 -0
  38. package/dest/prover-node.d.ts +102 -0
  39. package/dest/prover-node.d.ts.map +1 -0
  40. package/dest/prover-node.js +258 -0
  41. package/dest/test/index.d.ts +10 -0
  42. package/dest/test/index.d.ts.map +1 -0
  43. package/dest/test/index.js +5 -0
  44. package/package.json +98 -0
  45. package/src/config.ts +115 -0
  46. package/src/factory.ts +99 -0
  47. package/src/http.ts +13 -0
  48. package/src/index.ts +5 -0
  49. package/src/job/epoch-proving-job.ts +255 -0
  50. package/src/metrics.ts +164 -0
  51. package/src/monitors/epoch-monitor.ts +106 -0
  52. package/src/monitors/index.ts +1 -0
  53. package/src/prover-coordination/config.ts +17 -0
  54. package/src/prover-coordination/factory.ts +72 -0
  55. package/src/prover-coordination/index.ts +2 -0
  56. package/src/prover-node-publisher.ts +286 -0
  57. package/src/prover-node.ts +335 -0
  58. package/src/test/index.ts +11 -0
@@ -0,0 +1,129 @@
1
+ import { createLogger } from '@aztec/foundation/log';
2
+ import { Attributes, Metrics, ValueType } from '@aztec/telemetry-client';
3
+ import { formatEther } from 'viem';
4
+ export class ProverNodeMetrics {
5
+ client;
6
+ logger;
7
+ proverEpochExecutionDuration;
8
+ provingJobDuration;
9
+ provingJobBlocks;
10
+ provingJobTransactions;
11
+ gasPrice;
12
+ txCount;
13
+ txDuration;
14
+ txGas;
15
+ txCalldataSize;
16
+ txCalldataGas;
17
+ txBlobDataGasUsed;
18
+ txBlobDataGasCost;
19
+ senderBalance;
20
+ constructor(client, name = 'ProverNode', logger = createLogger('prover-node:publisher:metrics')){
21
+ this.client = client;
22
+ this.logger = logger;
23
+ const meter = client.getMeter(name);
24
+ this.proverEpochExecutionDuration = meter.createHistogram(Metrics.PROVER_NODE_EXECUTION_DURATION, {
25
+ description: 'Duration of execution of an epoch by the prover',
26
+ unit: 'ms',
27
+ valueType: ValueType.INT
28
+ });
29
+ this.provingJobDuration = meter.createHistogram(Metrics.PROVER_NODE_JOB_DURATION, {
30
+ description: 'Duration of proving job',
31
+ unit: 's',
32
+ valueType: ValueType.DOUBLE
33
+ });
34
+ this.provingJobBlocks = meter.createGauge(Metrics.PROVER_NODE_JOB_BLOCKS, {
35
+ description: 'Number of blocks in a proven epoch',
36
+ valueType: ValueType.INT
37
+ });
38
+ this.provingJobTransactions = meter.createGauge(Metrics.PROVER_NODE_JOB_TRANSACTIONS, {
39
+ description: 'Number of transactions in a proven epoch',
40
+ valueType: ValueType.INT
41
+ });
42
+ this.gasPrice = meter.createHistogram(Metrics.L1_PUBLISHER_GAS_PRICE, {
43
+ description: 'The gas price used for transactions',
44
+ unit: 'gwei',
45
+ valueType: ValueType.DOUBLE
46
+ });
47
+ this.txCount = meter.createUpDownCounter(Metrics.L1_PUBLISHER_TX_COUNT, {
48
+ description: 'The number of transactions processed'
49
+ });
50
+ this.txDuration = meter.createHistogram(Metrics.L1_PUBLISHER_TX_DURATION, {
51
+ description: 'The duration of transaction processing',
52
+ unit: 'ms',
53
+ valueType: ValueType.INT
54
+ });
55
+ this.txGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_GAS, {
56
+ description: 'The gas consumed by transactions',
57
+ unit: 'gas',
58
+ valueType: ValueType.INT
59
+ });
60
+ this.txCalldataSize = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_SIZE, {
61
+ description: 'The size of the calldata in transactions',
62
+ unit: 'By',
63
+ valueType: ValueType.INT
64
+ });
65
+ this.txCalldataGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_GAS, {
66
+ description: 'The gas consumed by the calldata in transactions',
67
+ unit: 'gas',
68
+ valueType: ValueType.INT
69
+ });
70
+ this.txBlobDataGasUsed = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_USED, {
71
+ description: 'The amount of blob gas used in transactions',
72
+ unit: 'gas',
73
+ valueType: ValueType.INT
74
+ });
75
+ this.txBlobDataGasCost = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_COST, {
76
+ description: 'The gas cost of blobs in transactions',
77
+ unit: 'gwei',
78
+ valueType: ValueType.INT
79
+ });
80
+ this.senderBalance = meter.createGauge(Metrics.L1_PUBLISHER_BALANCE, {
81
+ unit: 'eth',
82
+ description: 'The balance of the sender address',
83
+ valueType: ValueType.DOUBLE
84
+ });
85
+ }
86
+ recordFailedTx() {
87
+ this.txCount.add(1, {
88
+ [Attributes.L1_TX_TYPE]: 'submitProof',
89
+ [Attributes.OK]: false
90
+ });
91
+ }
92
+ recordSubmitProof(durationMs, stats) {
93
+ this.recordTx(durationMs, stats);
94
+ }
95
+ recordProvingJob(executionTimeMs, totalTimeMs, numBlocks, numTxs) {
96
+ this.proverEpochExecutionDuration.record(Math.ceil(executionTimeMs));
97
+ this.provingJobDuration.record(totalTimeMs / 1000);
98
+ this.provingJobBlocks.record(Math.floor(numBlocks));
99
+ this.provingJobTransactions.record(Math.floor(numTxs));
100
+ }
101
+ recordSenderBalance(wei, senderAddress) {
102
+ const eth = parseFloat(formatEther(wei, 'wei'));
103
+ this.senderBalance.record(eth, {
104
+ [Attributes.SENDER_ADDRESS]: senderAddress
105
+ });
106
+ }
107
+ recordTx(durationMs, stats) {
108
+ const attributes = {
109
+ [Attributes.L1_TX_TYPE]: 'submitProof',
110
+ [Attributes.L1_SENDER]: stats.sender
111
+ };
112
+ this.txCount.add(1, {
113
+ ...attributes,
114
+ [Attributes.OK]: true
115
+ });
116
+ this.txDuration.record(Math.ceil(durationMs), attributes);
117
+ this.txGas.record(// safe to downcast - total block limit is 30M gas which fits in a JS number
118
+ Number(stats.gasUsed), attributes);
119
+ this.txCalldataGas.record(stats.calldataGas, attributes);
120
+ this.txCalldataSize.record(stats.calldataSize, attributes);
121
+ this.txBlobDataGasCost.record(Number(stats.blobDataGas), attributes);
122
+ this.txBlobDataGasUsed.record(Number(stats.blobGasUsed), attributes);
123
+ try {
124
+ this.gasPrice.record(parseInt(formatEther(stats.gasPrice, 'gwei'), 10));
125
+ } catch (e) {
126
+ // ignore
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,40 @@
1
+ import type { L2BlockSource } from '@aztec/stdlib/block';
2
+ import { type L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
3
+ import { type TelemetryClient, type Traceable, type Tracer } from '@aztec/telemetry-client';
4
+ export interface EpochMonitorHandler {
5
+ handleEpochReadyToProve(epochNumber: bigint): Promise<void>;
6
+ }
7
+ /**
8
+ * Fires an event when a new epoch ready to prove is detected.
9
+ *
10
+ * We define an epoch as ready to prove when:
11
+ * - The epoch is complete
12
+ * - Its blocks have not been reorg'd out due to a missing L2 proof
13
+ * - Its first block is the immediate successor of the last proven block
14
+ *
15
+ * This class periodically hits the L2BlockSource.
16
+ * On start it will trigger the event for the last epoch ready to prove.
17
+ */
18
+ export declare class EpochMonitor implements Traceable {
19
+ private readonly l2BlockSource;
20
+ private readonly l1Constants;
21
+ private options;
22
+ private runningPromise;
23
+ private log;
24
+ readonly tracer: Tracer;
25
+ private handler;
26
+ private latestEpochNumber;
27
+ constructor(l2BlockSource: L2BlockSource, l1Constants: Pick<L1RollupConstants, 'epochDuration'>, options: {
28
+ pollingIntervalMs: number;
29
+ }, telemetry?: TelemetryClient);
30
+ static create(l2BlockSource: L2BlockSource, options: {
31
+ pollingIntervalMs: number;
32
+ }, telemetry?: TelemetryClient): Promise<EpochMonitor>;
33
+ start(handler: EpochMonitorHandler): void;
34
+ /** Exposed for testing */
35
+ setHandler(handler: EpochMonitorHandler): void;
36
+ stop(): Promise<void>;
37
+ work(): Promise<void>;
38
+ private getEpochNumberToProve;
39
+ }
40
+ //# sourceMappingURL=epoch-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"epoch-monitor.d.ts","sourceRoot":"","sources":["../../src/monitors/epoch-monitor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,KAAK,iBAAiB,EAAkB,MAAM,6BAA6B,CAAC;AACrF,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,MAAM,EAGZ,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,mBAAmB;IAClC,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED;;;;;;;;;;GAUG;AACH,qBAAa,YAAa,YAAW,SAAS;IAS1C,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,OAAO;IAVjB,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,GAAG,CAA6C;IACxD,SAAgB,MAAM,EAAE,MAAM,CAAC;IAE/B,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,iBAAiB,CAAqB;gBAG3B,aAAa,EAAE,aAAa,EAC5B,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,EAC9D,OAAO,EAAE;QAAE,iBAAiB,EAAE,MAAM,CAAA;KAAE,EAC9C,SAAS,GAAE,eAAsC;WAM/B,MAAM,CACxB,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE;QAAE,iBAAiB,EAAE,MAAM,CAAA;KAAE,EACtC,SAAS,GAAE,eAAsC,GAChD,OAAO,CAAC,YAAY,CAAC;IAKjB,KAAK,CAAC,OAAO,EAAE,mBAAmB;IAMzC,0BAA0B;IACnB,UAAU,CAAC,OAAO,EAAE,mBAAmB;IAIjC,IAAI;IAMJ,IAAI;YAsBH,qBAAqB;CAYpC"}
@@ -0,0 +1,104 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ import { createLogger } from '@aztec/foundation/log';
8
+ import { RunningPromise } from '@aztec/foundation/running-promise';
9
+ import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
10
+ import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
11
+ /**
12
+ * Fires an event when a new epoch ready to prove is detected.
13
+ *
14
+ * We define an epoch as ready to prove when:
15
+ * - The epoch is complete
16
+ * - Its blocks have not been reorg'd out due to a missing L2 proof
17
+ * - Its first block is the immediate successor of the last proven block
18
+ *
19
+ * This class periodically hits the L2BlockSource.
20
+ * On start it will trigger the event for the last epoch ready to prove.
21
+ */ export class EpochMonitor {
22
+ l2BlockSource;
23
+ l1Constants;
24
+ options;
25
+ runningPromise;
26
+ log;
27
+ tracer;
28
+ handler;
29
+ latestEpochNumber;
30
+ constructor(l2BlockSource, l1Constants, options, telemetry = getTelemetryClient()){
31
+ this.l2BlockSource = l2BlockSource;
32
+ this.l1Constants = l1Constants;
33
+ this.options = options;
34
+ this.log = createLogger('prover-node:epoch-monitor');
35
+ this.tracer = telemetry.getTracer('EpochMonitor');
36
+ this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.options.pollingIntervalMs);
37
+ }
38
+ static async create(l2BlockSource, options, telemetry = getTelemetryClient()) {
39
+ const l1Constants = await l2BlockSource.getL1Constants();
40
+ return new EpochMonitor(l2BlockSource, l1Constants, options, telemetry);
41
+ }
42
+ start(handler) {
43
+ this.handler = handler;
44
+ this.runningPromise.start();
45
+ this.log.info('Started EpochMonitor', this.options);
46
+ }
47
+ /** Exposed for testing */ setHandler(handler) {
48
+ this.handler = handler;
49
+ }
50
+ async stop() {
51
+ await this.runningPromise.stop();
52
+ this.log.info('Stopped EpochMonitor');
53
+ }
54
+ async work() {
55
+ const { epochToProve, blockNumber, slotNumber } = await this.getEpochNumberToProve();
56
+ if (epochToProve === undefined) {
57
+ this.log.trace(`Next block to prove ${blockNumber} not yet mined`, {
58
+ blockNumber
59
+ });
60
+ return;
61
+ }
62
+ if (this.latestEpochNumber !== undefined && epochToProve <= this.latestEpochNumber) {
63
+ this.log.trace(`Epoch ${epochToProve} already processed`, {
64
+ epochToProve,
65
+ blockNumber,
66
+ slotNumber
67
+ });
68
+ return;
69
+ }
70
+ const isCompleted = await this.l2BlockSource.isEpochComplete(epochToProve);
71
+ if (!isCompleted) {
72
+ this.log.trace(`Epoch ${epochToProve} is not complete`, {
73
+ epochToProve,
74
+ blockNumber,
75
+ slotNumber
76
+ });
77
+ return;
78
+ }
79
+ this.log.debug(`Epoch ${epochToProve} is ready to be proven`);
80
+ await this.handler?.handleEpochReadyToProve(epochToProve);
81
+ this.latestEpochNumber = epochToProve;
82
+ }
83
+ async getEpochNumberToProve() {
84
+ const lastBlockProven = await this.l2BlockSource.getProvenBlockNumber();
85
+ const firstBlockToProve = lastBlockProven + 1;
86
+ const firstBlockHeaderToProve = await this.l2BlockSource.getBlockHeader(firstBlockToProve);
87
+ if (!firstBlockHeaderToProve) {
88
+ return {
89
+ epochToProve: undefined,
90
+ blockNumber: firstBlockToProve
91
+ };
92
+ }
93
+ const firstSlotOfEpochToProve = firstBlockHeaderToProve.getSlot();
94
+ const epochToProve = getEpochAtSlot(firstSlotOfEpochToProve, this.l1Constants);
95
+ return {
96
+ epochToProve,
97
+ blockNumber: firstBlockToProve,
98
+ slotNumber: firstSlotOfEpochToProve
99
+ };
100
+ }
101
+ }
102
+ _ts_decorate([
103
+ trackSpan('EpochMonitor.work')
104
+ ], EpochMonitor.prototype, "work", null);
@@ -0,0 +1,2 @@
1
+ export * from './epoch-monitor.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/monitors/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './epoch-monitor.js';
@@ -0,0 +1,7 @@
1
+ import { type ConfigMappingsType } from '@aztec/foundation/config';
2
+ export type ProverCoordinationConfig = {
3
+ proverCoordinationNodeUrl: string | undefined;
4
+ };
5
+ export declare const proverCoordinationConfigMappings: ConfigMappingsType<ProverCoordinationConfig>;
6
+ export declare function getTxProviderConfigFromEnv(): ProverCoordinationConfig;
7
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/prover-coordination/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAyB,MAAM,0BAA0B,CAAC;AAE1F,MAAM,MAAM,wBAAwB,GAAG;IACrC,yBAAyB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,CAAC;AAEF,eAAO,MAAM,gCAAgC,EAAE,kBAAkB,CAAC,wBAAwB,CAMzF,CAAC;AAEF,wBAAgB,0BAA0B,IAAI,wBAAwB,CAErE"}
@@ -0,0 +1,11 @@
1
+ import { getConfigFromMappings } from '@aztec/foundation/config';
2
+ export const proverCoordinationConfigMappings = {
3
+ proverCoordinationNodeUrl: {
4
+ env: 'PROVER_COORDINATION_NODE_URL',
5
+ description: 'The URL of the tx provider node',
6
+ parseEnv: (val)=>val
7
+ }
8
+ };
9
+ export function getTxProviderConfigFromEnv() {
10
+ return getConfigFromMappings(proverCoordinationConfigMappings);
11
+ }
@@ -0,0 +1,22 @@
1
+ import type { ArchiveSource, Archiver } from '@aztec/archiver';
2
+ import type { EpochCache } from '@aztec/epoch-cache';
3
+ import type { DataStoreConfig } from '@aztec/kv-store/config';
4
+ import type { ProverCoordination, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
5
+ import { type TelemetryClient } from '@aztec/telemetry-client';
6
+ import type { ProverNodeConfig } from '../config.js';
7
+ type ProverCoordinationDeps = {
8
+ aztecNodeTxProvider?: ProverCoordination;
9
+ worldStateSynchronizer?: WorldStateSynchronizer;
10
+ archiver?: Archiver | ArchiveSource;
11
+ telemetry?: TelemetryClient;
12
+ epochCache?: EpochCache;
13
+ };
14
+ /**
15
+ * Creates a prover coordination service.
16
+ * If p2p is enabled, prover coordination is done via p2p.
17
+ * If an Aztec node URL is provided, prover coordination is done via the Aztec node over http.
18
+ * If an aztec node is provided, it is returned directly.
19
+ */
20
+ export declare function createProverCoordination(config: ProverNodeConfig & DataStoreConfig, deps: ProverCoordinationDeps): Promise<ProverCoordination>;
21
+ export {};
22
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/prover-coordination/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAK9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAGlG,OAAO,EAAE,KAAK,eAAe,EAAmB,MAAM,yBAAyB,CAAC;AAEhF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,KAAK,sBAAsB,GAAG;IAC5B,mBAAmB,CAAC,EAAE,kBAAkB,CAAC;IACzC,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,QAAQ,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACpC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,gBAAgB,GAAG,eAAe,EAC1C,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAqC7B"}
@@ -0,0 +1,42 @@
1
+ import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
2
+ import { createLogger } from '@aztec/foundation/log';
3
+ import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
4
+ import { createP2PClient } from '@aztec/p2p';
5
+ import { protocolContractTreeRoot } from '@aztec/protocol-contracts';
6
+ import { createAztecNodeClient } from '@aztec/stdlib/interfaces/client';
7
+ import { P2PClientType } from '@aztec/stdlib/p2p';
8
+ import { getComponentsVersionsFromConfig } from '@aztec/stdlib/versioning';
9
+ import { makeTracedFetch } from '@aztec/telemetry-client';
10
+ /**
11
+ * Creates a prover coordination service.
12
+ * If p2p is enabled, prover coordination is done via p2p.
13
+ * If an Aztec node URL is provided, prover coordination is done via the Aztec node over http.
14
+ * If an aztec node is provided, it is returned directly.
15
+ */ export async function createProverCoordination(config, deps) {
16
+ const log = createLogger('prover-node:prover-coordination');
17
+ if (deps.aztecNodeTxProvider) {
18
+ log.info('Using prover coordination via aztec node');
19
+ return deps.aztecNodeTxProvider;
20
+ }
21
+ if (config.p2pEnabled) {
22
+ log.info('Using prover coordination via p2p');
23
+ if (!deps.archiver || !deps.worldStateSynchronizer || !deps.telemetry || !deps.epochCache) {
24
+ throw new Error('Missing dependencies for p2p prover coordination');
25
+ }
26
+ const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier();
27
+ const p2pClient = await createP2PClient(P2PClientType.Prover, config, deps.archiver, proofVerifier, deps.worldStateSynchronizer, deps.epochCache, deps.telemetry);
28
+ await p2pClient.start();
29
+ return p2pClient;
30
+ }
31
+ if (config.proverCoordinationNodeUrl) {
32
+ log.info('Using prover coordination via node url');
33
+ const versions = getComponentsVersionsFromConfig(config, protocolContractTreeRoot, getVKTreeRoot());
34
+ return createAztecNodeClient(config.proverCoordinationNodeUrl, versions, makeTracedFetch([
35
+ 1,
36
+ 2,
37
+ 3
38
+ ], false));
39
+ } else {
40
+ throw new Error(`Aztec Node URL for Tx Provider is not set.`);
41
+ }
42
+ }
@@ -0,0 +1,3 @@
1
+ export * from './config.js';
2
+ export * from './factory.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prover-coordination/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './config.js';
2
+ export * from './factory.js';
@@ -0,0 +1,61 @@
1
+ import { AZTEC_MAX_EPOCH_DURATION } from '@aztec/constants';
2
+ import type { L1TxUtils, RollupContract } from '@aztec/ethereum';
3
+ import { EthAddress } from '@aztec/foundation/eth-address';
4
+ import { Fr } from '@aztec/foundation/fields';
5
+ import { type Tuple } from '@aztec/foundation/serialize';
6
+ import type { PublisherConfig, TxSenderConfig } from '@aztec/sequencer-client';
7
+ import type { Proof } from '@aztec/stdlib/proofs';
8
+ import type { FeeRecipient, RootRollupPublicInputs } from '@aztec/stdlib/rollup';
9
+ import { type TelemetryClient } from '@aztec/telemetry-client';
10
+ /**
11
+ * Stats for a sent transaction.
12
+ */
13
+ /** Arguments to the submitEpochProof method of the rollup contract */
14
+ export type L1SubmitEpochProofArgs = {
15
+ epochSize: number;
16
+ previousArchive: Fr;
17
+ endArchive: Fr;
18
+ previousBlockHash: Fr;
19
+ endBlockHash: Fr;
20
+ endTimestamp: Fr;
21
+ outHash: Fr;
22
+ proverId: Fr;
23
+ fees: Tuple<FeeRecipient, typeof AZTEC_MAX_EPOCH_DURATION>;
24
+ proof: Proof;
25
+ };
26
+ export declare class ProverNodePublisher {
27
+ private interruptibleSleep;
28
+ private sleepTimeMs;
29
+ private interrupted;
30
+ private metrics;
31
+ protected log: import("@aztec/foundation/log").Logger;
32
+ protected rollupContract: RollupContract;
33
+ readonly l1TxUtils: L1TxUtils;
34
+ constructor(config: TxSenderConfig & PublisherConfig, deps: {
35
+ rollupContract: RollupContract;
36
+ l1TxUtils: L1TxUtils;
37
+ telemetry?: TelemetryClient;
38
+ });
39
+ /**
40
+ * Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
41
+ * Be warned, the call may return false even if the tx subsequently gets successfully mined.
42
+ * In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail.
43
+ * A call to `restart` is required before you can continue publishing.
44
+ */
45
+ interrupt(): void;
46
+ /** Restarts the publisher after calling `interrupt`. */
47
+ restart(): void;
48
+ getSenderAddress(): EthAddress;
49
+ submitEpochProof(args: {
50
+ epochNumber: number;
51
+ fromBlock: number;
52
+ toBlock: number;
53
+ publicInputs: RootRollupPublicInputs;
54
+ proof: Proof;
55
+ }): Promise<boolean>;
56
+ private validateEpochProofSubmission;
57
+ private sendSubmitEpochProofTx;
58
+ private getSubmitEpochProofArgs;
59
+ protected sleepOrInterrupted(): Promise<void>;
60
+ }
61
+ //# sourceMappingURL=prover-node-publisher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prover-node-publisher.d.ts","sourceRoot":"","sources":["../src/prover-node-publisher.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAE9C,OAAO,EAAE,KAAK,KAAK,EAAqB,MAAM,6BAA6B,CAAC;AAI5E,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAMnF;;GAEG;AACH,sEAAsE;AACtE,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,EAAE,CAAC;IACpB,UAAU,EAAE,EAAE,CAAC;IACf,iBAAiB,EAAE,EAAE,CAAC;IACtB,YAAY,EAAE,EAAE,CAAC;IACjB,YAAY,EAAE,EAAE,CAAC;IACjB,OAAO,EAAE,EAAE,CAAC;IACZ,QAAQ,EAAE,EAAE,CAAC;IACb,IAAI,EAAE,KAAK,CAAC,YAAY,EAAE,OAAO,wBAAwB,CAAC,CAAC;IAC3D,KAAK,EAAE,KAAK,CAAC;CACd,CAAC;AAEF,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAoB;IAEnC,SAAS,CAAC,GAAG,yCAA+C;IAE5D,SAAS,CAAC,cAAc,EAAE,cAAc,CAAC;IAEzC,SAAgB,SAAS,EAAE,SAAS,CAAC;gBAGnC,MAAM,EAAE,cAAc,GAAG,eAAe,EACxC,IAAI,EAAE;QACJ,cAAc,EAAE,cAAc,CAAC;QAC/B,SAAS,EAAE,SAAS,CAAC;QACrB,SAAS,CAAC,EAAE,eAAe,CAAC;KAC7B;IAYH;;;;;OAKG;IACI,SAAS;IAKhB,wDAAwD;IACjD,OAAO;IAIP,gBAAgB;IAIV,gBAAgB,CAAC,IAAI,EAAE;QAClC,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,sBAAsB,CAAC;QACrC,KAAK,EAAE,KAAK,CAAC;KACd,GAAG,OAAO,CAAC,OAAO,CAAC;YAgDN,4BAA4B;YAyD5B,sBAAsB;IAoDpC,OAAO,CAAC,uBAAuB;cA+Bf,kBAAkB;CAGnC"}
@@ -0,0 +1,192 @@
1
+ import { AGGREGATION_OBJECT_LENGTH, AZTEC_MAX_EPOCH_DURATION } from '@aztec/constants';
2
+ import { makeTuple } from '@aztec/foundation/array';
3
+ import { areArraysEqual, times } from '@aztec/foundation/collection';
4
+ import { EthAddress } from '@aztec/foundation/eth-address';
5
+ import { Fr } from '@aztec/foundation/fields';
6
+ import { createLogger } from '@aztec/foundation/log';
7
+ import { serializeToBuffer } from '@aztec/foundation/serialize';
8
+ import { InterruptibleSleep } from '@aztec/foundation/sleep';
9
+ import { Timer } from '@aztec/foundation/timer';
10
+ import { RollupAbi } from '@aztec/l1-artifacts';
11
+ import { getTelemetryClient } from '@aztec/telemetry-client';
12
+ import { encodeFunctionData } from 'viem';
13
+ import { ProverNodeMetrics } from './metrics.js';
14
+ export class ProverNodePublisher {
15
+ interruptibleSleep = new InterruptibleSleep();
16
+ sleepTimeMs;
17
+ interrupted = false;
18
+ metrics;
19
+ log = createLogger('prover-node:l1-tx-publisher');
20
+ rollupContract;
21
+ l1TxUtils;
22
+ constructor(config, deps){
23
+ this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
24
+ const telemetry = deps.telemetry ?? getTelemetryClient();
25
+ this.metrics = new ProverNodeMetrics(telemetry, 'ProverNode');
26
+ this.rollupContract = deps.rollupContract;
27
+ this.l1TxUtils = deps.l1TxUtils;
28
+ }
29
+ /**
30
+ * Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
31
+ * Be warned, the call may return false even if the tx subsequently gets successfully mined.
32
+ * In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail.
33
+ * A call to `restart` is required before you can continue publishing.
34
+ */ interrupt() {
35
+ this.interrupted = true;
36
+ this.interruptibleSleep.interrupt();
37
+ }
38
+ /** Restarts the publisher after calling `interrupt`. */ restart() {
39
+ this.interrupted = false;
40
+ }
41
+ getSenderAddress() {
42
+ return EthAddress.fromString(this.l1TxUtils.getSenderAddress());
43
+ }
44
+ async submitEpochProof(args) {
45
+ const { epochNumber, fromBlock, toBlock } = args;
46
+ const ctx = {
47
+ epochNumber,
48
+ fromBlock,
49
+ toBlock
50
+ };
51
+ if (!this.interrupted) {
52
+ const timer = new Timer();
53
+ // Validate epoch proof range and hashes are correct before submitting
54
+ await this.validateEpochProofSubmission(args);
55
+ const txReceipt = await this.sendSubmitEpochProofTx(args);
56
+ if (!txReceipt) {
57
+ return false;
58
+ }
59
+ try {
60
+ this.metrics.recordSenderBalance(await this.l1TxUtils.getSenderBalance(), this.l1TxUtils.getSenderAddress());
61
+ } catch (err) {
62
+ this.log.warn(`Failed to record the ETH balance of the prover node: ${err}`);
63
+ }
64
+ // Tx was mined successfully
65
+ if (txReceipt.status) {
66
+ const tx = await this.l1TxUtils.getTransactionStats(txReceipt.transactionHash);
67
+ const stats = {
68
+ gasPrice: txReceipt.effectiveGasPrice,
69
+ gasUsed: txReceipt.gasUsed,
70
+ transactionHash: txReceipt.transactionHash,
71
+ calldataGas: tx.calldataGas,
72
+ calldataSize: tx.calldataSize,
73
+ sender: tx.sender,
74
+ blobDataGas: 0n,
75
+ blobGasUsed: 0n,
76
+ eventName: 'proof-published-to-l1'
77
+ };
78
+ this.log.info(`Published epoch proof to L1 rollup contract`, {
79
+ ...stats,
80
+ ...ctx
81
+ });
82
+ this.metrics.recordSubmitProof(timer.ms(), stats);
83
+ return true;
84
+ }
85
+ this.metrics.recordFailedTx();
86
+ this.log.error(`Rollup.submitEpochProof tx status failed: ${txReceipt.transactionHash}`, ctx);
87
+ await this.sleepOrInterrupted();
88
+ }
89
+ this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
90
+ return false;
91
+ }
92
+ async validateEpochProofSubmission(args) {
93
+ const { fromBlock, toBlock, publicInputs, proof } = args;
94
+ // Check that the block numbers match the expected epoch to be proven
95
+ const { pendingBlockNumber: pending, provenBlockNumber: proven } = await this.rollupContract.getTips();
96
+ if (proven !== BigInt(fromBlock) - 1n) {
97
+ throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as proven block is ${proven}`);
98
+ }
99
+ if (toBlock > pending) {
100
+ throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as pending block is ${pending}`);
101
+ }
102
+ // Check the block hash and archive for the immediate block before the epoch
103
+ const blockLog = await this.rollupContract.getBlock(proven);
104
+ if (publicInputs.previousArchive.root.toString() !== blockLog.archive) {
105
+ throw new Error(`Previous archive root mismatch: ${publicInputs.previousArchive.root.toString()} !== ${blockLog.archive}`);
106
+ }
107
+ // TODO: Remove zero check once we inject the proper zero blockhash
108
+ if (blockLog.blockHash !== Fr.ZERO.toString() && publicInputs.previousBlockHash.toString() !== blockLog.blockHash) {
109
+ throw new Error(`Previous block hash mismatch: ${publicInputs.previousBlockHash.toString()} !== ${blockLog.blockHash}`);
110
+ }
111
+ // Check the block hash and archive for the last block in the epoch
112
+ const endBlockLog = await this.rollupContract.getBlock(BigInt(toBlock));
113
+ if (publicInputs.endArchive.root.toString() !== endBlockLog.archive) {
114
+ throw new Error(`End archive root mismatch: ${publicInputs.endArchive.root.toString()} !== ${endBlockLog.archive}`);
115
+ }
116
+ if (publicInputs.endBlockHash.toString() !== endBlockLog.blockHash) {
117
+ throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockLog.blockHash}`);
118
+ }
119
+ // Compare the public inputs computed by the contract with the ones injected
120
+ const rollupPublicInputs = await this.rollupContract.getEpochProofPublicInputs(this.getSubmitEpochProofArgs(args));
121
+ const aggregationObject = proof.isEmpty() ? times(AGGREGATION_OBJECT_LENGTH, Fr.zero) : proof.extractAggregationObject();
122
+ const argsPublicInputs = [
123
+ ...publicInputs.toFields(),
124
+ ...aggregationObject
125
+ ];
126
+ if (!areArraysEqual(rollupPublicInputs.map(Fr.fromHexString), argsPublicInputs, (a, b)=>a.equals(b))) {
127
+ const fmt = (inputs)=>inputs.map((x)=>x.toString()).join(', ');
128
+ throw new Error(`Root rollup public inputs mismatch:\nRollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`);
129
+ }
130
+ }
131
+ async sendSubmitEpochProofTx(args) {
132
+ const proofHex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
133
+ const argsArray = this.getSubmitEpochProofArgs(args);
134
+ const txArgs = [
135
+ {
136
+ start: argsArray[0],
137
+ end: argsArray[1],
138
+ args: argsArray[2],
139
+ fees: argsArray[3],
140
+ blobPublicInputs: argsArray[4],
141
+ aggregationObject: argsArray[5],
142
+ proof: proofHex
143
+ }
144
+ ];
145
+ this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
146
+ const data = encodeFunctionData({
147
+ abi: RollupAbi,
148
+ functionName: 'submitEpochRootProof',
149
+ args: txArgs
150
+ });
151
+ try {
152
+ const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({
153
+ to: this.rollupContract.address,
154
+ data
155
+ });
156
+ return receipt;
157
+ } catch (err) {
158
+ this.log.error(`Rollup submit epoch proof failed`, err);
159
+ const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(data, {
160
+ args: [
161
+ ...txArgs
162
+ ],
163
+ functionName: 'submitEpochRootProof',
164
+ abi: RollupAbi,
165
+ address: this.rollupContract.address
166
+ }, /*blobInputs*/ undefined, /*stateOverride*/ []);
167
+ this.log.error(`Rollup submit epoch proof tx reverted. ${errorMsg}`);
168
+ return undefined;
169
+ }
170
+ }
171
+ getSubmitEpochProofArgs(args) {
172
+ return [
173
+ BigInt(args.fromBlock),
174
+ BigInt(args.toBlock),
175
+ {
176
+ previousArchive: args.publicInputs.previousArchive.root.toString(),
177
+ endArchive: args.publicInputs.endArchive.root.toString(),
178
+ previousBlockHash: args.publicInputs.previousBlockHash.toString(),
179
+ endBlockHash: args.publicInputs.endBlockHash.toString(),
180
+ endTimestamp: args.publicInputs.endTimestamp.toBigInt(),
181
+ outHash: args.publicInputs.outHash.toString(),
182
+ proverId: EthAddress.fromField(args.publicInputs.proverId).toString()
183
+ },
184
+ makeTuple(AZTEC_MAX_EPOCH_DURATION * 2, (i)=>i % 2 === 0 ? args.publicInputs.fees[i / 2].recipient.toField().toString() : args.publicInputs.fees[(i - 1) / 2].value.toString()),
185
+ `0x${args.publicInputs.blobPublicInputs.filter((_, i)=>i < args.toBlock - args.fromBlock + 1).map((b)=>b.toString()).join(``)}`,
186
+ `0x${serializeToBuffer(args.proof.extractAggregationObject()).toString('hex')}`
187
+ ];
188
+ }
189
+ async sleepOrInterrupted() {
190
+ await this.interruptibleSleep.sleep(this.sleepTimeMs);
191
+ }
192
+ }