diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 9c4ebc5be25d..f3af0f403304 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -23,6 +23,7 @@ import type { P2PClientType } from '@aztec/stdlib/p2p'; import type { Tx, TxHash } from '@aztec/stdlib/tx'; import { Attributes, + L1Metrics, type TelemetryClient, type Traceable, type Tracer, @@ -57,6 +58,7 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable private jobs: Map = new Map(); private options: ProverNodeOptions; private metrics: ProverNodeMetrics; + private l1Metrics: L1Metrics; private txFetcher: RunningPromise; private lastBlockNumber: number | undefined; @@ -75,6 +77,10 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable options: Partial = {}, protected readonly telemetryClient: TelemetryClient = getTelemetryClient(), ) { + this.l1Metrics = new L1Metrics(telemetryClient.getMeter('ProverNodeL1Metrics'), publisher.l1TxUtils.publicClient, [ + publisher.getSenderAddress(), + ]); + this.options = { pollingIntervalMs: 1_000, maxPendingJobs: 100, @@ -135,6 +141,7 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable start() { this.txFetcher.start(); this.epochsMonitor.start(this); + this.l1Metrics.start(); this.log.info('Started ProverNode', this.options); } @@ -151,6 +158,7 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable await Promise.all(Array.from(this.jobs.values()).map(job => job.stop())); await this.worldState.stop(); await tryStop(this.coordination); + this.l1Metrics.stop(); await this.telemetryClient.stop(); this.log.info('Stopped ProverNode'); } diff --git a/yarn-project/sequencer-client/src/sequencer/metrics.ts b/yarn-project/sequencer-client/src/sequencer/metrics.ts index 108ac266b487..17d7e0aa07a1 100644 --- a/yarn-project/sequencer-client/src/sequencer/metrics.ts +++ b/yarn-project/sequencer-client/src/sequencer/metrics.ts @@ -78,7 +78,17 @@ export class SequencerMetrics { valueType: ValueType.INT, }); + // Init gauges and counters this.setCurrentBlock(0, 0); + this.blockCounter.add(0, { + [Attributes.STATUS]: 'cancelled', + }); + this.blockCounter.add(0, { + [Attributes.STATUS]: 'failed', + }); + this.blockCounter.add(0, { + [Attributes.STATUS]: 'built', + }); } startCollectingAttestationsTimer(): () => void { diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index b13385a04620..5a8c100d7028 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -33,7 +33,14 @@ import { Tx, type TxHash, } from '@aztec/stdlib/tx'; -import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client'; +import { + Attributes, + L1Metrics, + type TelemetryClient, + type Tracer, + getTelemetryClient, + trackSpan, +} from '@aztec/telemetry-client'; import type { ValidatorClient } from '@aztec/validator-client'; import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js'; @@ -73,6 +80,7 @@ export class Sequencer { private maxBlockSizeInBytes: number = 1024 * 1024; private maxBlockGas: Gas = new Gas(100e9, 100e9); private metrics: SequencerMetrics; + private l1Metrics: L1Metrics; private isFlushing: boolean = false; /** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */ @@ -99,6 +107,9 @@ export class Sequencer { protected log = createLogger('sequencer'), ) { this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer'); + this.l1Metrics = new L1Metrics(telemetry.getMeter('SequencerL1Metrics'), publisher.l1TxUtils.publicClient, [ + publisher.getSenderAddress(), + ]); // Register the block builder with the validator client for re-execution this.validatorClient?.registerBlockBuilder(this.buildBlock.bind(this)); @@ -187,6 +198,7 @@ export class Sequencer { this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.pollingIntervalMs); this.setState(SequencerState.IDLE, 0n, true /** force */); this.runningPromise.start(); + this.l1Metrics.start(); this.log.info(`Sequencer started with address ${this.publisher.getSenderAddress().toString()}`); } @@ -200,6 +212,7 @@ export class Sequencer { this.slasherClient.stop(); this.publisher.interrupt(); this.setState(SequencerState.STOPPED, 0n, true /** force */); + this.l1Metrics.stop(); this.log.info('Stopped sequencer'); } diff --git a/yarn-project/telemetry-client/package.json b/yarn-project/telemetry-client/package.json index 89863d86c8c1..77320fc928d6 100644 --- a/yarn-project/telemetry-client/package.json +++ b/yarn-project/telemetry-client/package.json @@ -42,7 +42,8 @@ "@opentelemetry/sdk-metrics": "^1.28.0", "@opentelemetry/sdk-trace-node": "^1.28.0", "@opentelemetry/semantic-conventions": "^1.28.0", - "prom-client": "^15.1.3" + "prom-client": "^15.1.3", + "viem": "2.23.7" }, "devDependencies": { "@jest/globals": "^29.5.0", diff --git a/yarn-project/telemetry-client/src/index.ts b/yarn-project/telemetry-client/src/index.ts index 06b14789b5a6..db9f09d8edfd 100644 --- a/yarn-project/telemetry-client/src/index.ts +++ b/yarn-project/telemetry-client/src/index.ts @@ -4,6 +4,7 @@ export * as Attributes from './attributes.js'; export * from './with_tracer.js'; export * from './prom_otel_adapter.js'; export * from './lmdb_metrics.js'; +export * from './l1_metrics.js'; export * from './wrappers/index.js'; export * from './start.js'; export * from './otel_propagation.js'; diff --git a/yarn-project/telemetry-client/src/l1_metrics.ts b/yarn-project/telemetry-client/src/l1_metrics.ts new file mode 100644 index 000000000000..2d42294db566 --- /dev/null +++ b/yarn-project/telemetry-client/src/l1_metrics.ts @@ -0,0 +1,76 @@ +import type { EthAddress } from '@aztec/foundation/eth-address'; + +import { type Hex, type PublicClient, formatEther } from 'viem'; + +import { L1_SENDER } from './attributes.js'; +import { L1_BALANCE_ETH, L1_BLOB_BASE_FEE_WEI, L1_BLOCK_HEIGHT, L1_GAS_PRICE_WEI } from './metrics.js'; +import { type BatchObservableResult, type Meter, type ObservableGauge, ValueType } from './telemetry.js'; + +export class L1Metrics { + private l1BlockHeight: ObservableGauge; + private l1BalanceEth: ObservableGauge; + private gasPriceWei: ObservableGauge; + private blobBaseFeeWei: ObservableGauge; + private addresses: Hex[]; + + constructor(private meter: Meter, private client: PublicClient, addresses: EthAddress[]) { + this.l1BlockHeight = meter.createObservableGauge(L1_BLOCK_HEIGHT, { + description: 'The latest L1 block seen', + valueType: ValueType.INT, + }); + this.l1BalanceEth = meter.createObservableGauge(L1_BALANCE_ETH, { + description: 'Eth balance of an address', + unit: 'eth', + valueType: ValueType.DOUBLE, + }); + this.gasPriceWei = meter.createObservableGauge(L1_GAS_PRICE_WEI, { + description: 'L1 gas price', + unit: 'wei', + valueType: ValueType.DOUBLE, + }); + this.blobBaseFeeWei = meter.createObservableGauge(L1_BLOB_BASE_FEE_WEI, { + description: 'L1 blob fee', + unit: 'wei', + valueType: ValueType.DOUBLE, + }); + + this.addresses = addresses.map(addr => addr.toString()); + } + + start() { + this.meter.addBatchObservableCallback(this.observe, [this.l1BalanceEth, this.l1BlockHeight]); + } + + stop() { + this.meter.removeBatchObservableCallback(this.observe, [this.l1BalanceEth, this.l1BlockHeight]); + } + + private observe = async (observer: BatchObservableResult) => { + const blockNumber = await this.client.getBlockNumber(); + const ethBalances = await Promise.all( + this.addresses.map(address => + this.client.getBalance({ + address, + blockNumber, + }), + ), + ); + + const gasPrice = await this.client.getGasPrice(); + const blobFee = await this.client.getBlobBaseFee(); + + observer.observe(this.l1BlockHeight, Number(blockNumber)); + observer.observe(this.gasPriceWei, Number(gasPrice)); + observer.observe(this.blobBaseFeeWei, Number(blobFee)); + + for (let i = 0; i < ethBalances.length; i++) { + const wei = ethBalances[i]; + const address = this.addresses[i]; + const eth = parseFloat(formatEther(wei, 'wei')); + + observer.observe(this.l1BalanceEth, eth, { + [L1_SENDER]: address, + }); + } + }; +} diff --git a/yarn-project/telemetry-client/src/metrics.ts b/yarn-project/telemetry-client/src/metrics.ts index ad4087815c53..6b854a0f0afd 100644 --- a/yarn-project/telemetry-client/src/metrics.ts +++ b/yarn-project/telemetry-client/src/metrics.ts @@ -71,6 +71,11 @@ export const L1_PUBLISHER_BLOB_TX_SUCCESS = 'aztec.l1_publisher.blob_tx_success' export const L1_PUBLISHER_BLOB_TX_FAILURE = 'aztec.l1_publisher.blob_tx_failure'; export const L1_PUBLISHER_BALANCE = 'aztec.l1_publisher.balance'; +export const L1_BLOCK_HEIGHT = 'aztec.l1.block_height'; +export const L1_BALANCE_ETH = 'aztec.l1.balance'; +export const L1_GAS_PRICE_WEI = 'aztec.l1.gas_price'; +export const L1_BLOB_BASE_FEE_WEI = 'aztec.l1.blob_base_fee'; + export const PEER_MANAGER_GOODBYES_SENT = 'aztec.peer_manager.goodbyes_sent'; export const PEER_MANAGER_GOODBYES_RECEIVED = 'aztec.peer_manager.goodbyes_received'; export const PEER_MANAGER_PEER_COUNT = 'aztec.peer_manager.peer_count'; diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 4deebb6e8cfa..9117581e2b9d 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -1396,6 +1396,7 @@ __metadata: prom-client: "npm:^15.1.3" ts-node: "npm:^10.9.1" typescript: "npm:^5.0.4" + viem: "npm:2.23.7" languageName: unknown linkType: soft