@aztec/sequencer-client 0.47.1 → 0.49.2

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 (65) hide show
  1. package/dest/block_builder/index.d.ts +26 -0
  2. package/dest/block_builder/index.d.ts.map +1 -0
  3. package/dest/block_builder/index.js +40 -0
  4. package/dest/client/sequencer-client.d.ts +1 -2
  5. package/dest/client/sequencer-client.d.ts.map +1 -1
  6. package/dest/client/sequencer-client.js +5 -4
  7. package/dest/config.d.ts +6 -2
  8. package/dest/config.d.ts.map +1 -1
  9. package/dest/config.js +91 -31
  10. package/dest/global_variable_builder/global_builder.d.ts +14 -8
  11. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  12. package/dest/global_variable_builder/global_builder.js +10 -16
  13. package/dest/global_variable_builder/index.d.ts +2 -3
  14. package/dest/global_variable_builder/index.d.ts.map +1 -1
  15. package/dest/global_variable_builder/index.js +1 -1
  16. package/dest/global_variable_builder/viem-reader.d.ts +5 -4
  17. package/dest/global_variable_builder/viem-reader.d.ts.map +1 -1
  18. package/dest/global_variable_builder/viem-reader.js +11 -8
  19. package/dest/publisher/config.d.ts +7 -15
  20. package/dest/publisher/config.d.ts.map +1 -1
  21. package/dest/publisher/config.js +38 -11
  22. package/dest/publisher/index.d.ts +3 -2
  23. package/dest/publisher/index.d.ts.map +1 -1
  24. package/dest/publisher/index.js +4 -4
  25. package/dest/publisher/l1-publisher-metrics.d.ts +17 -0
  26. package/dest/publisher/l1-publisher-metrics.d.ts.map +1 -0
  27. package/dest/publisher/l1-publisher-metrics.js +75 -0
  28. package/dest/publisher/l1-publisher.d.ts +33 -5
  29. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  30. package/dest/publisher/l1-publisher.js +44 -36
  31. package/dest/publisher/viem-tx-sender.d.ts +9 -2
  32. package/dest/publisher/viem-tx-sender.d.ts.map +1 -1
  33. package/dest/publisher/viem-tx-sender.js +85 -16
  34. package/dest/receiver.d.ts +2 -8
  35. package/dest/receiver.d.ts.map +1 -1
  36. package/dest/sequencer/metrics.d.ts +17 -0
  37. package/dest/sequencer/metrics.d.ts.map +1 -0
  38. package/dest/sequencer/metrics.js +56 -0
  39. package/dest/sequencer/sequencer.d.ts +9 -7
  40. package/dest/sequencer/sequencer.d.ts.map +1 -1
  41. package/dest/sequencer/sequencer.js +66 -37
  42. package/dest/tx_validator/gas_validator.d.ts +1 -1
  43. package/dest/tx_validator/gas_validator.js +11 -11
  44. package/dest/tx_validator/tx_validator_factory.js +2 -2
  45. package/package.json +17 -15
  46. package/src/block_builder/index.ts +51 -0
  47. package/src/client/sequencer-client.ts +3 -4
  48. package/src/config.ts +106 -54
  49. package/src/global_variable_builder/global_builder.ts +35 -25
  50. package/src/global_variable_builder/index.ts +3 -3
  51. package/src/global_variable_builder/viem-reader.ts +14 -11
  52. package/src/publisher/config.ts +43 -31
  53. package/src/publisher/index.ts +5 -3
  54. package/src/publisher/l1-publisher-metrics.ts +108 -0
  55. package/src/publisher/l1-publisher.ts +78 -43
  56. package/src/publisher/viem-tx-sender.ts +89 -14
  57. package/src/receiver.ts +3 -8
  58. package/src/sequencer/metrics.ts +86 -0
  59. package/src/sequencer/sequencer.ts +89 -52
  60. package/src/tx_validator/gas_validator.ts +9 -9
  61. package/src/tx_validator/tx_validator_factory.ts +2 -2
  62. package/dest/global_variable_builder/config.d.ts +0 -19
  63. package/dest/global_variable_builder/config.d.ts.map +0 -1
  64. package/dest/global_variable_builder/config.js +0 -2
  65. package/src/global_variable_builder/config.ts +0 -20
@@ -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 of the current proposer or zero if anyone can submit. */
50
- getSubmitterAddressForBlock(): Promise<EthAddress>;
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);
168
+ }
169
+
170
+ public async senderAddress(): Promise<EthAddress> {
171
+ return await this.txSender.getSenderAddress();
138
172
  }
139
173
 
140
174
  public async isItMyTurnToSubmit(): Promise<boolean> {
141
- const submitter = await this.txSender.getSubmitterAddressForBlock();
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
- const txHash = await this.sendProcessTx(processTxArgs);
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(header: Header, archiveRoot: Fr, aggregationObject: Fr[], proof: Proof): Promise<boolean> {
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,13 +75,31 @@ 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
- async getSubmitterAddressForBlock(): Promise<EthAddress> {
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 submitter = await this.rollupContract.read.getCurrentProposer();
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
105
  this.log.warn(`Failed to get submitter: ${err}`);
@@ -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
- const args = [`0x${encodedData.header.toString('hex')}`, `0x${encodedData.archive.toString('hex')}`] as const;
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
- const gas = await this.rollupContract.estimateGas.process(args, {
164
- account: this.account,
165
- });
166
- const hash = await this.rollupContract.write.process(args, {
167
- gas,
168
- account: this.account,
169
- });
170
- return hash;
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
- import type { Fr, Proof } from '@aztec/circuits.js';
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
+ }