@aztec/sequencer-client 0.47.0 → 0.48.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/client/sequencer-client.d.ts +2 -2
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +2 -2
- package/dest/config.d.ts +6 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +96 -29
- package/dest/global_variable_builder/global_builder.d.ts +14 -8
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +10 -16
- package/dest/global_variable_builder/index.d.ts +2 -3
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/global_variable_builder/index.js +1 -1
- package/dest/global_variable_builder/viem-reader.d.ts +5 -4
- package/dest/global_variable_builder/viem-reader.d.ts.map +1 -1
- package/dest/global_variable_builder/viem-reader.js +11 -8
- package/dest/index.d.ts +2 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +3 -1
- package/dest/publisher/config.d.ts +7 -15
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +38 -11
- package/dest/publisher/index.d.ts +3 -2
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +4 -4
- package/dest/publisher/l1-publisher-metrics.d.ts +17 -0
- package/dest/publisher/l1-publisher-metrics.d.ts.map +1 -0
- package/dest/publisher/l1-publisher-metrics.js +75 -0
- package/dest/publisher/l1-publisher.d.ts +34 -6
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +45 -37
- package/dest/publisher/viem-tx-sender.d.ts +9 -2
- package/dest/publisher/viem-tx-sender.d.ts.map +1 -1
- package/dest/publisher/viem-tx-sender.js +86 -17
- package/dest/receiver.d.ts +2 -8
- package/dest/receiver.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +17 -0
- package/dest/sequencer/metrics.d.ts.map +1 -0
- package/dest/sequencer/metrics.js +56 -0
- package/dest/sequencer/sequencer.d.ts +11 -5
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +104 -34
- package/dest/tx_validator/data_validator.d.ts +6 -0
- package/dest/tx_validator/data_validator.d.ts.map +1 -0
- package/dest/tx_validator/data_validator.js +47 -0
- package/dest/tx_validator/gas_validator.d.ts +1 -1
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +12 -12
- package/dest/tx_validator/phases_validator.d.ts +2 -3
- package/dest/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/tx_validator/phases_validator.js +4 -4
- package/dest/tx_validator/test_utils.d.ts.map +1 -1
- package/dest/tx_validator/test_utils.js +10 -4
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +4 -3
- package/package.json +15 -15
- package/src/client/sequencer-client.ts +3 -3
- package/src/config.ts +111 -50
- package/src/global_variable_builder/global_builder.ts +35 -25
- package/src/global_variable_builder/index.ts +3 -3
- package/src/global_variable_builder/viem-reader.ts +14 -11
- package/src/index.ts +2 -0
- package/src/publisher/config.ts +43 -31
- package/src/publisher/index.ts +5 -3
- package/src/publisher/l1-publisher-metrics.ts +108 -0
- package/src/publisher/l1-publisher.ts +79 -44
- package/src/publisher/viem-tx-sender.ts +90 -15
- package/src/receiver.ts +3 -8
- package/src/sequencer/metrics.ts +86 -0
- package/src/sequencer/sequencer.ts +144 -45
- package/src/tx_validator/data_validator.ts +61 -0
- package/src/tx_validator/gas_validator.ts +11 -9
- package/src/tx_validator/phases_validator.ts +13 -5
- package/src/tx_validator/test_utils.ts +10 -3
- package/src/tx_validator/tx_validator_factory.ts +4 -2
- package/dest/global_variable_builder/config.d.ts +0 -19
- package/dest/global_variable_builder/config.d.ts.map +0 -1
- package/dest/global_variable_builder/config.js +0 -2
- package/src/global_variable_builder/config.ts +0 -20
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { L1PublishBlockStats, L1PublishProofStats } 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/utils';
|
|
12
|
+
|
|
13
|
+
export type L1TxType = 'submitProof' | 'process';
|
|
14
|
+
|
|
15
|
+
export class L1PublisherMetrics {
|
|
16
|
+
private gasPrice: Histogram;
|
|
17
|
+
|
|
18
|
+
private txCount: UpDownCounter;
|
|
19
|
+
private txDuration: Histogram;
|
|
20
|
+
private txGas: Histogram;
|
|
21
|
+
private txCalldataSize: Histogram;
|
|
22
|
+
private txCalldataGas: Histogram;
|
|
23
|
+
|
|
24
|
+
constructor(client: TelemetryClient, name = 'L1Publisher') {
|
|
25
|
+
const meter = client.getMeter(name);
|
|
26
|
+
|
|
27
|
+
this.gasPrice = meter.createHistogram(Metrics.L1_PUBLISHER_GAS_PRICE, {
|
|
28
|
+
description: 'The gas price used for transactions',
|
|
29
|
+
unit: 'gwei',
|
|
30
|
+
valueType: ValueType.DOUBLE,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.txCount = meter.createUpDownCounter(Metrics.L1_PUBLISHER_TX_COUNT, {
|
|
34
|
+
description: 'The number of transactions processed',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.txDuration = meter.createHistogram(Metrics.L1_PUBLISHER_TX_DURATION, {
|
|
38
|
+
description: 'The duration of transaction processing',
|
|
39
|
+
unit: 'ms',
|
|
40
|
+
valueType: ValueType.INT,
|
|
41
|
+
advice: {
|
|
42
|
+
explicitBucketBoundaries: [10, 50, 100, 200, 500, 1000, 2000, 5000, 10000],
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
this.txGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_GAS, {
|
|
47
|
+
description: 'The gas consumed by transactions',
|
|
48
|
+
unit: 'gas',
|
|
49
|
+
valueType: ValueType.INT,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
this.txCalldataSize = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_SIZE, {
|
|
53
|
+
description: 'The size of the calldata in transactions',
|
|
54
|
+
unit: 'bytes',
|
|
55
|
+
valueType: ValueType.INT,
|
|
56
|
+
advice: {
|
|
57
|
+
explicitBucketBoundaries: [0, 100, 200, 500, 1000, 2000, 5000, 10000],
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.txCalldataGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_GAS, {
|
|
62
|
+
description: 'The gas consumed by the calldata in transactions',
|
|
63
|
+
unit: 'gas',
|
|
64
|
+
valueType: ValueType.INT,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
recordFailedTx(txType: L1TxType) {
|
|
69
|
+
this.txCount.add(1, {
|
|
70
|
+
[Attributes.L1_TX_TYPE]: txType,
|
|
71
|
+
[Attributes.OK]: false,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
recordSubmitProof(durationMs: number, stats: L1PublishProofStats) {
|
|
76
|
+
this.recordTx('submitProof', durationMs, stats);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
recordProcessBlockTx(durationMs: number, stats: L1PublishBlockStats) {
|
|
80
|
+
this.recordTx('process', durationMs, stats);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private recordTx(txType: L1TxType, durationMs: number, stats: Omit<L1PublishProofStats, 'eventName'>) {
|
|
84
|
+
const attributes = {
|
|
85
|
+
[Attributes.L1_TX_TYPE]: txType,
|
|
86
|
+
} as const;
|
|
87
|
+
|
|
88
|
+
this.txCount.add(1, {
|
|
89
|
+
...attributes,
|
|
90
|
+
[Attributes.OK]: true,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.txDuration.record(Math.ceil(durationMs), attributes);
|
|
94
|
+
this.txGas.record(
|
|
95
|
+
// safe to downcast - total block limit is 30M gas which fits in a JS number
|
|
96
|
+
Number(stats.gasUsed),
|
|
97
|
+
attributes,
|
|
98
|
+
);
|
|
99
|
+
this.txCalldataGas.record(stats.calldataGas, attributes);
|
|
100
|
+
this.txCalldataSize.record(stats.calldataSize, attributes);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
this.gasPrice.record(parseInt(formatEther(stats.gasPrice, 'gwei'), 10));
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// ignore
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -5,11 +5,14 @@ import { type Fr } from '@aztec/foundation/fields';
|
|
|
5
5
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
6
6
|
import { serializeToBuffer } from '@aztec/foundation/serialize';
|
|
7
7
|
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
8
|
+
import { Timer } from '@aztec/foundation/timer';
|
|
9
|
+
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
8
10
|
|
|
9
11
|
import pick from 'lodash.pick';
|
|
10
12
|
|
|
11
13
|
import { type L2BlockReceiver } from '../receiver.js';
|
|
12
14
|
import { type PublisherConfig } from './config.js';
|
|
15
|
+
import { L1PublisherMetrics } from './l1-publisher-metrics.js';
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Stats for a sent transaction.
|
|
@@ -39,15 +42,25 @@ export type MinimalTransactionReceipt = {
|
|
|
39
42
|
logs: any[];
|
|
40
43
|
};
|
|
41
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @notice An attestation for the sequencing model.
|
|
47
|
+
* @todo This is not where it belongs. But I think we should do a bigger rewrite of some of
|
|
48
|
+
* this spaghetti.
|
|
49
|
+
*/
|
|
50
|
+
export type Attestation = { isEmpty: boolean; v: number; r: `0x${string}`; s: `0x${string}` };
|
|
51
|
+
|
|
42
52
|
/**
|
|
43
53
|
* Pushes txs to the L1 chain and waits for their completion.
|
|
44
54
|
*/
|
|
45
55
|
export interface L1PublisherTxSender {
|
|
56
|
+
/** Attests to the given archive root. */
|
|
57
|
+
attest(archive: `0x${string}`): Promise<Attestation>;
|
|
58
|
+
|
|
46
59
|
/** Returns the EOA used for sending txs to L1. */
|
|
47
60
|
getSenderAddress(): Promise<EthAddress>;
|
|
48
61
|
|
|
49
|
-
/** Returns the address
|
|
50
|
-
|
|
62
|
+
/** Returns the address of the L2 proposer at the NEXT Ethereum block zero if anyone can submit. */
|
|
63
|
+
getProposerAtNextEthBlock(): Promise<EthAddress>;
|
|
51
64
|
|
|
52
65
|
/**
|
|
53
66
|
* Publishes tx effects to Availability Oracle.
|
|
@@ -63,6 +76,13 @@ export interface L1PublisherTxSender {
|
|
|
63
76
|
*/
|
|
64
77
|
sendProcessTx(encodedData: L1ProcessArgs): Promise<string | undefined>;
|
|
65
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Publishes tx effects to availability oracle and send L2 block to rollup contract
|
|
81
|
+
* @param encodedData - Data for processing the new L2 block.
|
|
82
|
+
* @returns The hash of the tx.
|
|
83
|
+
*/
|
|
84
|
+
sendPublishAndProcessTx(encodedData: L1ProcessArgs): Promise<string | undefined>;
|
|
85
|
+
|
|
66
86
|
/**
|
|
67
87
|
* Sends a tx to the L1 rollup contract with a proof. Returns once the tx has been mined.
|
|
68
88
|
* @param encodedData - Serialized data for processing the new L2 block.
|
|
@@ -105,6 +125,8 @@ export type L1ProcessArgs = {
|
|
|
105
125
|
archive: Buffer;
|
|
106
126
|
/** L2 block body. */
|
|
107
127
|
body: Buffer;
|
|
128
|
+
/** Attestations */
|
|
129
|
+
attestations?: Attestation[];
|
|
108
130
|
};
|
|
109
131
|
|
|
110
132
|
/** Arguments to the submitProof method of the rollup contract */
|
|
@@ -113,6 +135,8 @@ export type L1SubmitProofArgs = {
|
|
|
113
135
|
header: Buffer;
|
|
114
136
|
/** A root of the archive tree after the L2 block is applied. */
|
|
115
137
|
archive: Buffer;
|
|
138
|
+
/** Identifier of the prover. */
|
|
139
|
+
proverId: Buffer;
|
|
116
140
|
/** The proof for the block. */
|
|
117
141
|
proof: Buffer;
|
|
118
142
|
/** The aggregation object for the block's proof. */
|
|
@@ -131,14 +155,24 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
131
155
|
private interruptibleSleep = new InterruptibleSleep();
|
|
132
156
|
private sleepTimeMs: number;
|
|
133
157
|
private interrupted = false;
|
|
158
|
+
private metrics: L1PublisherMetrics;
|
|
134
159
|
private log = createDebugLogger('aztec:sequencer:publisher');
|
|
135
160
|
|
|
136
|
-
constructor(private txSender: L1PublisherTxSender, config?: PublisherConfig) {
|
|
161
|
+
constructor(private txSender: L1PublisherTxSender, client: TelemetryClient, config?: PublisherConfig) {
|
|
137
162
|
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
|
|
163
|
+
this.metrics = new L1PublisherMetrics(client, 'L1Publisher');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public async attest(archive: `0x${string}`): Promise<Attestation> {
|
|
167
|
+
return await this.txSender.attest(archive);
|
|
138
168
|
}
|
|
139
169
|
|
|
140
|
-
public async
|
|
141
|
-
|
|
170
|
+
public async senderAddress(): Promise<EthAddress> {
|
|
171
|
+
return await this.txSender.getSenderAddress();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public async isItMyTurnToSubmit(): Promise<boolean> {
|
|
175
|
+
const submitter = await this.txSender.getProposerAtNextEthBlock();
|
|
142
176
|
const sender = await this.txSender.getSenderAddress();
|
|
143
177
|
return submitter.isZero() || submitter.equals(sender);
|
|
144
178
|
}
|
|
@@ -148,7 +182,7 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
148
182
|
* @param block - L2 block to publish.
|
|
149
183
|
* @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise.
|
|
150
184
|
*/
|
|
151
|
-
public async processL2Block(block: L2Block): Promise<boolean> {
|
|
185
|
+
public async processL2Block(block: L2Block, attestations?: Attestation[]): Promise<boolean> {
|
|
152
186
|
const ctx = { blockNumber: block.number, blockHash: block.hash().toString() };
|
|
153
187
|
// TODO(#4148) Remove this block number check, it's here because we don't currently have proper genesis state on the contract
|
|
154
188
|
const lastArchive = block.header.lastArchive.root.toBuffer();
|
|
@@ -156,58 +190,35 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
156
190
|
this.log.info(`Detected different last archive prior to publishing a block, aborting publish...`, ctx);
|
|
157
191
|
return false;
|
|
158
192
|
}
|
|
159
|
-
|
|
160
193
|
const encodedBody = block.body.toBuffer();
|
|
161
194
|
|
|
162
|
-
// Publish block transaction effects
|
|
163
|
-
while (!this.interrupted) {
|
|
164
|
-
if (await this.txSender.checkIfTxsAreAvailable(block)) {
|
|
165
|
-
this.log.verbose(`Transaction effects of block ${block.number} already published.`, ctx);
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const txHash = await this.sendPublishTx(encodedBody);
|
|
170
|
-
if (!txHash) {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const receipt = await this.getTransactionReceipt(txHash);
|
|
175
|
-
if (!receipt) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (receipt.status) {
|
|
180
|
-
let txsEffectsHash;
|
|
181
|
-
if (receipt.logs.length === 1) {
|
|
182
|
-
// txsEffectsHash from IAvailabilityOracle.TxsPublished event
|
|
183
|
-
txsEffectsHash = receipt.logs[0].data;
|
|
184
|
-
} else {
|
|
185
|
-
this.log.warn(`Expected 1 log, got ${receipt.logs.length}`, ctx);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
this.log.info(`Block txs effects published`, { ...ctx, txsEffectsHash });
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
this.log.error(`AvailabilityOracle.publish tx status failed: ${receipt.transactionHash}`, ctx);
|
|
193
|
-
await this.sleepOrInterrupted();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
195
|
const processTxArgs = {
|
|
197
196
|
header: block.header.toBuffer(),
|
|
198
197
|
archive: block.archive.root.toBuffer(),
|
|
199
198
|
body: encodedBody,
|
|
199
|
+
attestations,
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
-
// Process block
|
|
202
|
+
// Process block and publish the body if needed (if not already published)
|
|
203
203
|
while (!this.interrupted) {
|
|
204
|
-
|
|
204
|
+
let txHash;
|
|
205
|
+
const timer = new Timer();
|
|
206
|
+
|
|
207
|
+
if (await this.txSender.checkIfTxsAreAvailable(block)) {
|
|
208
|
+
this.log.verbose(`Transaction effects of block ${block.number} already published.`, ctx);
|
|
209
|
+
txHash = await this.sendProcessTx(processTxArgs);
|
|
210
|
+
} else {
|
|
211
|
+
txHash = await this.sendPublishAndProcessTx(processTxArgs);
|
|
212
|
+
}
|
|
213
|
+
|
|
205
214
|
if (!txHash) {
|
|
215
|
+
this.log.info(`Failed to publish block ${block.number} to L1`, ctx);
|
|
206
216
|
break;
|
|
207
217
|
}
|
|
208
218
|
|
|
209
219
|
const receipt = await this.getTransactionReceipt(txHash);
|
|
210
220
|
if (!receipt) {
|
|
221
|
+
this.log.info(`Failed to get receipt for tx ${txHash}`, ctx);
|
|
211
222
|
break;
|
|
212
223
|
}
|
|
213
224
|
|
|
@@ -221,9 +232,12 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
221
232
|
eventName: 'rollup-published-to-l1',
|
|
222
233
|
};
|
|
223
234
|
this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
235
|
+
this.metrics.recordProcessBlockTx(timer.ms(), stats);
|
|
224
236
|
return true;
|
|
225
237
|
}
|
|
226
238
|
|
|
239
|
+
this.metrics.recordFailedTx('process');
|
|
240
|
+
|
|
227
241
|
// Check if someone else incremented the block number
|
|
228
242
|
if (!(await this.checkLastArchiveHash(lastArchive))) {
|
|
229
243
|
this.log.warn('Publish failed. Detected different last archive hash.', ctx);
|
|
@@ -238,18 +252,26 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
238
252
|
return false;
|
|
239
253
|
}
|
|
240
254
|
|
|
241
|
-
public async submitProof(
|
|
255
|
+
public async submitProof(
|
|
256
|
+
header: Header,
|
|
257
|
+
archiveRoot: Fr,
|
|
258
|
+
proverId: Fr,
|
|
259
|
+
aggregationObject: Fr[],
|
|
260
|
+
proof: Proof,
|
|
261
|
+
): Promise<boolean> {
|
|
242
262
|
const ctx = { blockNumber: header.globalVariables.blockNumber };
|
|
243
263
|
|
|
244
264
|
const txArgs: L1SubmitProofArgs = {
|
|
245
265
|
header: header.toBuffer(),
|
|
246
266
|
archive: archiveRoot.toBuffer(),
|
|
267
|
+
proverId: proverId.toBuffer(),
|
|
247
268
|
aggregationObject: serializeToBuffer(aggregationObject),
|
|
248
269
|
proof: proof.withoutPublicInputs(),
|
|
249
270
|
};
|
|
250
271
|
|
|
251
272
|
// Process block
|
|
252
273
|
while (!this.interrupted) {
|
|
274
|
+
const timer = new Timer();
|
|
253
275
|
const txHash = await this.sendSubmitProofTx(txArgs);
|
|
254
276
|
if (!txHash) {
|
|
255
277
|
break;
|
|
@@ -269,9 +291,11 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
269
291
|
eventName: 'proof-published-to-l1',
|
|
270
292
|
};
|
|
271
293
|
this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
294
|
+
this.metrics.recordSubmitProof(timer.ms(), stats);
|
|
272
295
|
return true;
|
|
273
296
|
}
|
|
274
297
|
|
|
298
|
+
this.metrics.recordFailedTx('submitProof');
|
|
275
299
|
this.log.error(`Rollup.submitProof tx status failed: ${receipt.transactionHash}`, ctx);
|
|
276
300
|
await this.sleepOrInterrupted();
|
|
277
301
|
}
|
|
@@ -345,6 +369,17 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
345
369
|
}
|
|
346
370
|
}
|
|
347
371
|
|
|
372
|
+
private async sendPublishAndProcessTx(encodedData: L1ProcessArgs): Promise<string | undefined> {
|
|
373
|
+
while (!this.interrupted) {
|
|
374
|
+
try {
|
|
375
|
+
return await this.txSender.sendPublishAndProcessTx(encodedData);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
this.log.error(`Rollup publish failed`, err);
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
348
383
|
private async getTransactionReceipt(txHash: string): Promise<MinimalTransactionReceipt | undefined> {
|
|
349
384
|
while (!this.interrupted) {
|
|
350
385
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type L2Block } from '@aztec/circuit-types';
|
|
2
|
-
import { EthAddress } from '@aztec/circuits.js';
|
|
2
|
+
import { ETHEREUM_SLOT_DURATION, EthAddress } from '@aztec/circuits.js';
|
|
3
3
|
import { createEthereumChain } from '@aztec/ethereum';
|
|
4
4
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { AvailabilityOracleAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
@@ -16,12 +16,14 @@ import {
|
|
|
16
16
|
getContract,
|
|
17
17
|
hexToBytes,
|
|
18
18
|
http,
|
|
19
|
+
parseSignature,
|
|
19
20
|
} from 'viem';
|
|
20
21
|
import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts';
|
|
21
22
|
import * as chains from 'viem/chains';
|
|
22
23
|
|
|
23
24
|
import { type TxSenderConfig } from './config.js';
|
|
24
25
|
import {
|
|
26
|
+
type Attestation,
|
|
25
27
|
type L1PublisherTxSender,
|
|
26
28
|
type L1SubmitProofArgs,
|
|
27
29
|
type MinimalTransactionReceipt,
|
|
@@ -47,7 +49,7 @@ export class ViemTxSender implements L1PublisherTxSender {
|
|
|
47
49
|
private account: PrivateKeyAccount;
|
|
48
50
|
|
|
49
51
|
constructor(config: TxSenderConfig) {
|
|
50
|
-
const { rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config;
|
|
52
|
+
const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config;
|
|
51
53
|
const chain = createEthereumChain(rpcUrl, chainId);
|
|
52
54
|
this.account = privateKeyToAccount(publisherPrivateKey);
|
|
53
55
|
const walletClient = createWalletClient({
|
|
@@ -73,16 +75,34 @@ export class ViemTxSender implements L1PublisherTxSender {
|
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
async attest(archive: `0x{string}`): Promise<Attestation> {
|
|
79
|
+
// @note Something seems slightly off in viem, think it should be Hex instead of Hash
|
|
80
|
+
// but as they both are just `0x${string}` it should be fine anyways.
|
|
81
|
+
const signature = await this.account.signMessage({ message: { raw: archive } });
|
|
82
|
+
const { r, s, v } = parseSignature(signature as `0x${string}`);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
isEmpty: false,
|
|
86
|
+
v: v ? Number(v) : 0,
|
|
87
|
+
r: r,
|
|
88
|
+
s: s,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
76
92
|
getSenderAddress(): Promise<EthAddress> {
|
|
77
93
|
return Promise.resolve(EthAddress.fromString(this.account.address));
|
|
78
94
|
}
|
|
79
95
|
|
|
80
|
-
|
|
96
|
+
// Computes who will be the L2 proposer at the next Ethereum block
|
|
97
|
+
// Using next Ethereum block so we do NOT need to wait for it being mined before seeing the effect
|
|
98
|
+
// @note Assumes that all ethereum slots have blocks
|
|
99
|
+
async getProposerAtNextEthBlock(): Promise<EthAddress> {
|
|
81
100
|
try {
|
|
82
|
-
const
|
|
101
|
+
const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
|
|
102
|
+
const submitter = await this.rollupContract.read.getProposerAt([ts]);
|
|
83
103
|
return EthAddress.fromString(submitter);
|
|
84
104
|
} catch (err) {
|
|
85
|
-
this.log.warn(`Failed to get submitter
|
|
105
|
+
this.log.warn(`Failed to get submitter: ${err}`);
|
|
86
106
|
return EthAddress.ZERO;
|
|
87
107
|
}
|
|
88
108
|
}
|
|
@@ -158,16 +178,70 @@ export class ViemTxSender implements L1PublisherTxSender {
|
|
|
158
178
|
* @returns The hash of the mined tx.
|
|
159
179
|
*/
|
|
160
180
|
async sendProcessTx(encodedData: ProcessTxArgs): Promise<string | undefined> {
|
|
161
|
-
|
|
181
|
+
if (encodedData.attestations) {
|
|
182
|
+
const args = [
|
|
183
|
+
`0x${encodedData.header.toString('hex')}`,
|
|
184
|
+
`0x${encodedData.archive.toString('hex')}`,
|
|
185
|
+
encodedData.attestations,
|
|
186
|
+
] as const;
|
|
162
187
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
188
|
+
const gas = await this.rollupContract.estimateGas.process(args, {
|
|
189
|
+
account: this.account,
|
|
190
|
+
});
|
|
191
|
+
return await this.rollupContract.write.process(args, {
|
|
192
|
+
gas,
|
|
193
|
+
account: this.account,
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
const args = [`0x${encodedData.header.toString('hex')}`, `0x${encodedData.archive.toString('hex')}`] as const;
|
|
197
|
+
|
|
198
|
+
const gas = await this.rollupContract.estimateGas.process(args, {
|
|
199
|
+
account: this.account,
|
|
200
|
+
});
|
|
201
|
+
return await this.rollupContract.write.process(args, {
|
|
202
|
+
gas,
|
|
203
|
+
account: this.account,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @notice Publishes the body AND process the block in one transaction
|
|
210
|
+
* @param encodedData - Serialized data for processing the new L2 block.
|
|
211
|
+
* @returns The hash of the transaction
|
|
212
|
+
*/
|
|
213
|
+
async sendPublishAndProcessTx(encodedData: ProcessTxArgs): Promise<string | undefined> {
|
|
214
|
+
// @note This is quite a sin, but I'm committing war crimes in this code already.
|
|
215
|
+
if (encodedData.attestations) {
|
|
216
|
+
const args = [
|
|
217
|
+
`0x${encodedData.header.toString('hex')}`,
|
|
218
|
+
`0x${encodedData.archive.toString('hex')}`,
|
|
219
|
+
encodedData.attestations,
|
|
220
|
+
`0x${encodedData.body.toString('hex')}`,
|
|
221
|
+
] as const;
|
|
222
|
+
|
|
223
|
+
const gas = await this.rollupContract.estimateGas.publishAndProcess(args, {
|
|
224
|
+
account: this.account,
|
|
225
|
+
});
|
|
226
|
+
return await this.rollupContract.write.publishAndProcess(args, {
|
|
227
|
+
gas,
|
|
228
|
+
account: this.account,
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
const args = [
|
|
232
|
+
`0x${encodedData.header.toString('hex')}`,
|
|
233
|
+
`0x${encodedData.archive.toString('hex')}`,
|
|
234
|
+
`0x${encodedData.body.toString('hex')}`,
|
|
235
|
+
] as const;
|
|
236
|
+
|
|
237
|
+
const gas = await this.rollupContract.estimateGas.publishAndProcess(args, {
|
|
238
|
+
account: this.account,
|
|
239
|
+
});
|
|
240
|
+
return await this.rollupContract.write.publishAndProcess(args, {
|
|
241
|
+
gas,
|
|
242
|
+
account: this.account,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
171
245
|
}
|
|
172
246
|
|
|
173
247
|
/**
|
|
@@ -176,10 +250,11 @@ export class ViemTxSender implements L1PublisherTxSender {
|
|
|
176
250
|
* @returns The hash of the mined tx.
|
|
177
251
|
*/
|
|
178
252
|
async sendSubmitProofTx(submitProofArgs: L1SubmitProofArgs): Promise<string | undefined> {
|
|
179
|
-
const { header, archive, aggregationObject, proof } = submitProofArgs;
|
|
253
|
+
const { header, archive, proverId, aggregationObject, proof } = submitProofArgs;
|
|
180
254
|
const args = [
|
|
181
255
|
`0x${header.toString('hex')}`,
|
|
182
256
|
`0x${archive.toString('hex')}`,
|
|
257
|
+
`0x${proverId.toString('hex')}`,
|
|
183
258
|
`0x${aggregationObject.toString('hex')}`,
|
|
184
259
|
`0x${proof.toString('hex')}`,
|
|
185
260
|
] as const;
|
package/src/receiver.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { type L2Block } from '@aztec/circuit-types';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { type Attestation } from './publisher/l1-publisher.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Given the necessary rollup data, verifies it, and updates the underlying state accordingly to advance the state of the system.
|
|
6
7
|
* See https://hackmd.io/ouVCnacHQRq2o1oRc5ksNA#RollupReceiver.
|
|
7
8
|
*/
|
|
8
9
|
export interface L2BlockReceiver {
|
|
9
|
-
|
|
10
|
-
* Receive and L2 block and process it, returns true if successful.
|
|
11
|
-
* @param l2BlockData - L2 block to process.
|
|
12
|
-
* @param aggregationObject - The aggregation object for the block's proof.
|
|
13
|
-
* @param proof - The proof for the block.
|
|
14
|
-
*/
|
|
15
|
-
processL2Block(l2BlockData: L2Block, aggregationObject: Fr[], proof: Proof): Promise<boolean>;
|
|
10
|
+
processL2Block(block: L2Block, attestations?: Attestation[]): Promise<boolean>;
|
|
16
11
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Attributes,
|
|
3
|
+
type Gauge,
|
|
4
|
+
type Histogram,
|
|
5
|
+
Metrics,
|
|
6
|
+
type TelemetryClient,
|
|
7
|
+
type Tracer,
|
|
8
|
+
type UpDownCounter,
|
|
9
|
+
ValueType,
|
|
10
|
+
millisecondBuckets,
|
|
11
|
+
} from '@aztec/telemetry-client';
|
|
12
|
+
|
|
13
|
+
type SequencerStateCallback = () => number;
|
|
14
|
+
|
|
15
|
+
export class SequencerMetrics {
|
|
16
|
+
public readonly tracer: Tracer;
|
|
17
|
+
|
|
18
|
+
private blockCounter: UpDownCounter;
|
|
19
|
+
private blockBuildDuration: Histogram;
|
|
20
|
+
private currentBlockNumber: Gauge;
|
|
21
|
+
private currentBlockSize: Gauge;
|
|
22
|
+
|
|
23
|
+
constructor(client: TelemetryClient, getState: SequencerStateCallback, name = 'Sequencer') {
|
|
24
|
+
const meter = client.getMeter(name);
|
|
25
|
+
this.tracer = client.getTracer(name);
|
|
26
|
+
|
|
27
|
+
this.blockCounter = meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_COUNT);
|
|
28
|
+
this.blockBuildDuration = meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_DURATION, {
|
|
29
|
+
unit: 'ms',
|
|
30
|
+
description: 'Duration to build a block',
|
|
31
|
+
valueType: ValueType.INT,
|
|
32
|
+
advice: {
|
|
33
|
+
explicitBucketBoundaries: millisecondBuckets(2),
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const currentState = meter.createObservableGauge(Metrics.SEQUENCER_CURRENT_STATE, {
|
|
38
|
+
description: 'Current state of the sequencer',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
currentState.addCallback(observer => {
|
|
42
|
+
observer.observe(getState());
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.currentBlockNumber = meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_NUMBER, {
|
|
46
|
+
description: 'Current block number',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
this.currentBlockSize = meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_SIZE, {
|
|
50
|
+
description: 'Current block number',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.setCurrentBlock(0, 0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
recordCancelledBlock() {
|
|
57
|
+
this.blockCounter.add(1, {
|
|
58
|
+
[Attributes.STATUS]: 'cancelled',
|
|
59
|
+
});
|
|
60
|
+
this.setCurrentBlock(0, 0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
recordPublishedBlock(buildDurationMs: number) {
|
|
64
|
+
this.blockCounter.add(1, {
|
|
65
|
+
[Attributes.STATUS]: 'published',
|
|
66
|
+
});
|
|
67
|
+
this.blockBuildDuration.record(Math.ceil(buildDurationMs));
|
|
68
|
+
this.setCurrentBlock(0, 0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
recordFailedBlock() {
|
|
72
|
+
this.blockCounter.add(1, {
|
|
73
|
+
[Attributes.STATUS]: 'failed',
|
|
74
|
+
});
|
|
75
|
+
this.setCurrentBlock(0, 0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
recordNewBlock(blockNumber: number, txCount: number) {
|
|
79
|
+
this.setCurrentBlock(blockNumber, txCount);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private setCurrentBlock(blockNumber: number, txCount: number) {
|
|
83
|
+
this.currentBlockNumber.record(blockNumber);
|
|
84
|
+
this.currentBlockSize.record(txCount);
|
|
85
|
+
}
|
|
86
|
+
}
|