Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Metrics

We collect various metrics and serve them via a Prometheus-compatible HTTP endpoint at http://<http_address>:<metrics_port>/metrics (default: http://127.0.0.1:5054/metrics).

A ready-to-use Grafana + Prometheus monitoring stack with pre-configured leanMetrics dashboards is available in lean-quickstart.

The exposed metrics follow the leanMetrics specification, with some metrics not yet implemented. We have a full list of implemented metrics below, with a checkbox indicating whether each metric is currently supported or not.

Node Info Metrics

NameTypeUsageSample collection eventLabelsSupported
lean_node_infoGaugeNode information (always 1)On node startname, version✅
lean_node_start_time_secondsGaugeStart timestampOn node start✅

PQ Signature Metrics

NameTypeUsageSample collection eventLabelsBucketsSupported
lean_pq_sig_attestation_signatures_totalCounterTotal number of individual attestation signaturesOn each attestation signing✅
lean_pq_sig_attestation_signatures_valid_totalCounterTotal number of valid individual attestation signaturesOn each attestation signature verification✅
lean_pq_sig_attestation_signatures_invalid_totalCounterTotal number of invalid individual attestation signaturesOn each attestation signature verification✅
lean_pq_sig_attestation_signing_time_secondsHistogramTime taken to sign an attestationOn each attestation signing0.005, 0.01, 0.025, 0.05, 0.1, 1✅
lean_pq_sig_attestation_verification_time_secondsHistogramTime taken to verify an attestation signatureOn each attestation signature verification0.005, 0.01, 0.025, 0.05, 0.1, 1✅
lean_pq_sig_aggregated_signatures_totalCounterTotal number of aggregated signaturesOn aggregated signature production✅
lean_pq_sig_aggregated_signatures_valid_totalCounterTotal number of valid aggregated signaturesOn aggregated signature verification✅
lean_pq_sig_aggregated_signatures_invalid_totalCounterTotal number of invalid aggregated signaturesOn aggregated signature verification✅
lean_pq_sig_attestations_in_aggregated_signatures_totalCounterTotal number of attestations included into aggregated signaturesOn aggregated signature production✅
lean_pq_sig_aggregated_signatures_building_time_secondsHistogramTime taken to build an aggregated attestation signatureOn aggregated signature production0.1, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 4✅
lean_pq_sig_aggregated_signatures_verification_time_secondsHistogramTime taken to verify an aggregated attestation signatureOn aggregated signature verification0.1, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 4✅

Block Production Metrics

NameTypeUsageSample collection eventLabelsBucketsSupported
lean_block_aggregated_payloadsHistogramNumber of aggregated_payloads in a blockOn block production1, 2, 4, 8, 16, 32, 64, 128✅
lean_block_building_payload_aggregation_time_secondsHistogramTime taken to build aggregated_payloads during block buildingOn block production0.1, 0.25, 0.5, 0.75, 1, 2, 3, 4✅
lean_block_building_time_secondsHistogramTime taken to build a blockOn block production0.1, 0.25, 0.5, 0.75, 1, 2, 4, 8✅
lean_block_building_success_totalCounterSuccessful block buildsOn block production✅
lean_block_building_failures_totalCounterFailed block builds (error building the block, signing the block root, or processing it locally)On block production failure✅
lean_block_proposal_attestation_build_phase_secondsHistogramPhase-level time in block proposal: attestation selection, compaction, state transition, then the seal (proposer signature, type-1 wrap, type-2 merge)On block productionphase=select_payloads,compact,stf_simulate,sign_proposer,wrap_proposer,merge_type20.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 4, 8✅
lean_block_proposal_attestation_builds_totalCounterAttestations selected during block-proposal selection (one per selection-loop round that picks an AttestationData)On each attestation selection✅
lean_block_proposal_child_payloads_consumed_totalCounterChild aggregated payloads selected during greedy proof picking (before compaction)On block production✅
lean_block_proposal_attestation_data_selectedHistogramDistinct AttestationData entries in the proposal block bodyOn block production0, 1, 2, 4, 8, 16, 32✅
lean_block_proposal_aggregates_selectedHistogramAggregated signature proofs in the proposal result after compactionOn block production0, 1, 2, 4, 8, 16, 32, 64, 128✅

lean_block_building_time_seconds intentionally deviates from the leanMetrics bucket set, which tops out at 1s. Real builds on our devnets routinely run past that, so every sample landed in +Inf and histogram_quantile reported a flat 1s ceiling. The range now covers the same span as the lean_block_proposal_attestation_build_phase_seconds phases it contains.

Fork-Choice Metrics

NameTypeUsageSample collection eventLabelsBucketsSupported
lean_head_slotGaugeLatest slot of the lean chainOn get fork choice head✅
lean_current_slotGaugeCurrent slot of the lean chainOn scrape✅(*)
lean_safe_target_slotGaugeSafe target slotOn safe target update✅
lean_fork_choice_block_processing_time_secondsHistogramTime taken to process blockOn fork choice process block0.005, 0.01, 0.025, 0.05, 0.1, 1, 1.25, 1.5, 2, 4✅
lean_attestations_valid_totalCounterTotal number of valid attestationsOn validate attestation✅
lean_attestations_invalid_totalCounterTotal number of invalid attestationsOn validate attestation✅
lean_attestation_validation_time_secondsHistogramTime taken to validate attestationOn validate attestation0.005, 0.01, 0.025, 0.05, 0.1, 1✅
lean_fork_choice_reorgs_totalCounterTotal number of fork choice reorgsOn fork choice reorg✅
lean_fork_choice_reorg_depthHistogramDepth of fork choice reorgs (in blocks)On fork choice reorg1, 2, 3, 5, 7, 10, 20, 30, 50, 100✅
lean_tick_interval_duration_secondsHistogramElapsed time between clock ticks in secondsAt the start of each tick interval0.4, 0.6, 0.75, 0.8, 0.805, 0.81, 0.815, 0.82, 0.825, 0.85, 0.9, 1.0, 1.2, 1.6✅
lean_gossip_signaturesGaugeNumber of gossip signatures in fork-choice storeOn gossip signatures update✅
lean_latest_new_aggregated_payloadsGaugeNumber of new aggregated payload itemsOn latest_new_aggregated_payloads update✅
lean_latest_known_aggregated_payloadsGaugeNumber of known aggregated payload itemsOn latest_known_aggregated_payloads update✅
lean_committee_signatures_aggregation_time_secondsHistogramTime taken to aggregate committee signaturesOn committee signatures aggregation0.05, 0.1, 0.25, 0.5, 0.75, 1, 2, 3, 4✅
lean_node_sync_statusGaugeNode sync statusOn node sync status changestatus=idle,syncing,synced✅

State Transition Metrics

NameTypeUsageSample collection eventLabelsBucketsSupported
lean_latest_justified_slotGaugeLatest justified slotOn state transition✅
lean_latest_finalized_slotGaugeLatest finalized slotOn state transition✅
lean_justified_slotGaugeCurrent justified slotOn state transition❌
lean_finalized_slotGaugeCurrent finalized slotOn state transition❌
lean_finalizations_totalCounterTotal number of finalization attemptsOn finalization attemptresult=success,error✅
lean_state_transition_time_secondsHistogramTime to process state transitionOn state transition0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 2.5, 3, 4✅
lean_state_transition_slots_processed_totalCounterTotal number of processed slotsOn state transition process slots✅
lean_state_transition_slots_processing_time_secondsHistogramTime taken to process slotsOn state transition process slots0.005, 0.01, 0.025, 0.05, 0.1, 1✅
lean_state_transition_block_processing_time_secondsHistogramTime taken to process blockOn state transition process block0.005, 0.01, 0.025, 0.05, 0.1, 1✅
lean_state_transition_attestations_processed_totalCounterTotal number of processed attestationsOn state transition process attestations✅
lean_state_transition_attestations_processing_time_secondsHistogramTime taken to process attestationsOn state transition process attestations0.005, 0.01, 0.025, 0.05, 0.1, 1✅

Validator Metrics

NameTypeUsageSample collection eventLabelsBucketsSupported
lean_validators_countGaugeNumber of validators managed by a nodeOn scrape✅(*)
lean_is_aggregatorGaugeValidator’s is_aggregator status. True=1, False=0On node start✅
lean_attestations_production_time_secondsHistogramTime taken to produce attestationOn attestation production0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.75, 1✅

Network Metrics

NameTypeUsageSample collection eventLabelsSupported
lean_attestation_committee_countGaugeNumber of attestation committeesOn node start✅
lean_attestation_committee_subnetGaugeNode’s attestation committee subnetOn node start✅
lean_aggregation_window_widthHistogramWidth in subnets of the subnet window derived for one aggregation candidateOn each aggregation candidate✅
lean_aggregation_skipped_redundant_totalCounterCandidates this aggregator sat out because the redundancy-skipping rotation gave their level to another duty subnetOn each skipped aggregation candidate✅
lean_aggregation_window_fallback_totalCounterMerges the subnet window would have dropped, recovered by retrying selection at the full committee setOn each aggregation candidate the full-width retry recovers✅
lean_connected_peersGaugeNumber of connected peersOn scrapeclient=ethlambda,grandine,lantern,lighthouse,qlean,ream,zeam✅(*)
lean_gossip_mesh_peersGaugeNumber of peers in the gossipsub meshOn scrapeclient=<name>_<N>,unknown (ex. zeam_0)✅(*)
lean_peer_connection_events_totalCounterTotal number of peer connection eventsOn peer connectiondirection=inbound,outbound
result=success,timeout,error
✅
lean_peer_disconnection_events_totalCounterTotal number of peer disconnection eventsOn peer disconnectiondirection=inbound,outbound
reason=timeout,remote_close,local_close,error
✅

All three are emitted only by aggregators, once per candidate AttestationData per interval-2 session. lean_aggregation_window_width has buckets 1, 2, 4, 8, 16, 32, 64 and climbs from 1 as the aggregator’s anchor proof climbs the reduction tree; it is capped at lean_attestation_committee_count, so samples pinned there mean the window no longer restricts selection. Compare against that gauge rather than reading the buckets alone: at a committee count that is not a power of two, two different widths can share a bucket. A width stuck at 1 while the network is aggregating means the pool holds nothing on this node’s duty subnet, so it is only aggregating its own raw signatures; check the duty subnet against the aggregator placement. lean_aggregation_skipped_redundant_total only increments with --skip-redundant-aggregation, once per candidate handed to another duty subnet; read it against lean_aggregation_window_width_count for the share of candidates sat out. lean_aggregation_window_fallback_total counts recoveries, not attempts: it increments only when a windowed selection produced no viable job and the full-committee-width retry did. Candidates that are non-viable whatever the window, a lone raw signature or a group with a single proof, never reach the retry and never increment it, so a sparse (strided) aggregator placement across subnets is the only expected cause and the counter should stay at or near zero on a well-tiled deployment. It stays flat entirely under --skip-redundant-aggregation, which disables the fallback.

Custom Metrics (non-leanMetrics)

The metrics below are not part of the leanMetrics specification. They are ethlambda-specific observability around on-wire message sizes and post-quantum aggregated proof sizes.

PQ Signature Sizes

NameTypeUsageSample collection eventLabelsBuckets
lean_aggregated_proof_size_bytesHistogramBytes size of an aggregated signature proof’s proof_data fieldOn aggregated signature production1024, 4096, 16384, 65536, 131072, 262144, 524288, 1048576

Network Sizes

NameTypeUsageSample collection eventLabelsBuckets
lean_gossip_block_size_bytesHistogramBytes size of a gossip block message (raw SSZ or snappy on-wire)On gossip block send/receivecompression=raw,snappy10000, 50000, 100000, 250000, 500000, 1000000, 2000000, 5000000
lean_gossip_attestation_size_bytesHistogramBytes size of a gossip attestation message (raw SSZ or snappy on-wire)On gossip attestation send/receivecompression=raw,snappy512, 1024, 2048, 4096, 8192, 16384
lean_gossip_aggregation_size_bytesHistogramBytes size of a gossip aggregated attestation message (raw SSZ or snappy on-wire)On gossip aggregation send/receivecompression=raw,snappy1024, 4096, 16384, 65536, 131072, 262144, 524288, 1048576
lean_reqresp_request_size_bytesHistogramBytes size of a req/resp request (raw SSZ or snappy on-wire)On req/resp request send/receiveprotocol=status,blocks_by_root
compression=raw,snappy
64, 128, 256, 512, 1024, 4096, 16384, 65536
lean_reqresp_response_chunk_size_bytesHistogramBytes size of a single req/resp response chunk (raw SSZ or snappy on-wire)On req/resp response chunk send/receiveprotocol=status,blocks_by_root
compression=raw,snappy
128, 1024, 10000, 100000, 500000, 1000000, 5000000, 10000000

Peer Discovery

Only emitted when discv5 discovery is enabled (--discovery.enable); see Peer discovery. Counts dials discovery initiated, as opposed to the static bootnode dials every node makes. Connection outcomes are not repeated here: a discovery dial that succeeds or fails shows up in lean_peer_connection_events_total like any other.

NameTypeUsageSample collection eventLabels
lean_discovered_peers_dialed_totalCounterPeers dialed as a result of discv5 discoveryOn dialing a discovered peer

Transport Mix

Which transport actually carried each established connection, read off the connection’s own multiaddr rather than off the address we dialed: libp2p races a peer’s QUIC and TCP addresses within one dial, so the answer is not knowable before the connection exists. tcp counts are what say the fallback in Peer discovery is doing work rather than merely being advertised.

Counts connections rather than peers, so it can exceed lean_peer_connection_events_total{result="success"}, which fires only on a peer’s first connection. unknown covers a multiaddr naming neither transport, which nothing ethlambda binds produces.

NameTypeUsageSample collection eventLabels
lean_peer_connections_by_transport_totalCounterEstablished peer connections by the transport that carried themOn connection establisheddirection=inbound,outbound
transport=quic,tcp,unknown

Gossip Arrival Timing

These histograms record the absolute distance between a gossip message’s arrival and the start of the interval it was due in, so an arrival that is early by some amount and one that is late by the same amount land in the same bucket; the counters’ position label is what tells them apart. inside means the message arrived within the interval it was due in, not merely somewhere in the right slot: an attestation for slot 10 that lands during slot 10’s interval 2 is after, not inside, since it missed the AttestationProduction interval it was actually due in.

The bucket boundaries are the interval and slot edges of the default 4-second cadence. Prometheus fixes buckets when a histogram is registered, so a network that sets MILLISECONDS_PER_SLOT reads these histograms against the default grid rather than its own; the position label still follows the configured interval width.

Blocks anchor to interval 0 of their own slot and attestations to interval 1 of their data slot; both are unbounded above, so a message that never arrives close to real time can be arbitrarily late. Aggregates anchor instead to the most recent aggregation-interval boundary rather than their own data slot, since a stale-group catch-up aggregate can carry a data.slot several slots in the past; anchoring to the latest boundary bounds the delay to one slot and rules out before entirely.

Only gossip-received blocks are sampled here: blocks fetched via req/resp during sync are excluded, since sync backfill delivers blocks long after they were due and would swamp these histograms with catch-up noise rather than gossip-health signal.

The aggregate metrics do include an aggregator’s own freshly produced aggregates, which never come back over gossip; without them an aggregator would report an empty aggregate profile. The two populations are not quite the same measurement: delivery of a locally produced aggregate is held until the interval-2 boundary, so it lands near zero unless proving overran the interval, whereas a received one adds propagation on top of whenever the producer managed to publish it.

In practice the distribution is bimodal and dominated by production rather than propagation: a mode in the lowest bucket for aggregates that made their interval, plus a tail for those whose proving overran it. A late aggregate is late for every node at once, so that tail shows up on receivers too and is not evidence of a slow network. Read a rising tail as aggregation cost, and cross-check lean_pq_sig_aggregated_signatures_building_time_seconds and lean_committee_signatures_aggregation_time_seconds to confirm.

NameTypeUsageSample collection eventLabelsBuckets
lean_gossip_block_arrival_delay_secondsHistogramAbsolute delay between a gossip block’s arrival and the start of the interval it was due inOn gossip block receipt, before import0.05, 0.1, 0.2, 0.4, 0.8, 1.2, 1.6, 2.4, 4, 8, 16
lean_gossip_attestation_arrival_delay_secondsHistogramAbsolute delay between a gossip attestation’s arrival and the start of the interval it was due inOn gossip attestation receipt0.05, 0.1, 0.2, 0.4, 0.8, 1.2, 1.6, 2.4, 4, 8, 16
lean_gossip_aggregation_arrival_delay_secondsHistogramAbsolute delay between an aggregate becoming available (gossip receipt, or local production) and the most recent aggregation-interval boundary at or before itOn gossip aggregated-attestation receipt, or on local aggregate production0.05, 0.1, 0.2, 0.4, 0.8, 1.2, 1.6, 2.4, 4, 8, 16
lean_gossip_block_arrival_totalCounterGossip blocks by arrival position relative to the interval they were due inOn gossip block receipt, before importposition=before,inside,after
lean_gossip_attestation_arrival_totalCounterGossip attestations by arrival position relative to the interval they were due inOn gossip attestation receiptposition=before,inside,after
lean_gossip_aggregation_arrival_totalCounterAggregates by arrival position relative to the most recent aggregation-interval boundaryOn gossip aggregated-attestation receipt, or on local aggregate productionposition=inside,after

Storage

NameTypeUsageSample collection eventLabels
lean_table_bytesGaugeEstimated byte size of a storage table (key + value bytes)After each processed block (one update per table); retains its previous value on empty slotstable=<table_name>

Attestation Aggregate Coverage

Observability into how many validators/subnets are covered by the attestations the node has aggregated, broken down by pipeline section (the section label). The slot is the X-axis. These are sampled roughly once per slot, but emission is gated by the section’s source data, so a gauge can retain its previous value:

  • timely, late, block, combined and the diff_validators directions are emitted on block import, and only when the canonical head block carries that round’s votes (otherwise the round is skipped and prior values are kept).
  • agg_start_new is emitted at interval 2, right before fork-choice aggregation runs.
  • proposal_combined is emitted only when this node proposes a block.
NameTypeUsageSample collection eventLabels
lean_attestation_aggregate_coverage_validatorsGaugeValidator coverage in attestation aggregate reportsPer round, per section (see note above)section=timely,late,block,combined,agg_start_new,proposal_combined
subnet=combined,subnet_0,subnet_1,…,subnet_N-1
lean_attestation_aggregate_coverage_subnetsGaugeNumber of covered subnets in attestation aggregate reportsPer round, per section (see note above)section=timely,late,block,combined,agg_start_new,proposal_combined
lean_attestation_aggregate_coverage_diff_validatorsGaugeValidators in the symmetric difference between block-included aggregates and locally-aggregated timely aggregates for the same slotOn block import, when the head carries the round’s votes (see note above)direction=block_only,timely_only

✅(*) Partial support: These metrics are implemented but not collected “on scrape” as the spec requires. They are updated on specific events (e.g., on tick, on block processing) rather than being computed fresh on each Prometheus scrape.

Troubleshooting

Docker Desktop on MacOS

lean-quickstart uses the host network mode for Docker containers, which is a problem on MacOS. To work around this, enable the “Enable host networking” option in Docker Desktop settings under Resources > Network.