Skip to content

Commit ecd12e0

Browse files
committed
feat(metrics): add Prometheus metrics support for beacon client
* Implement core beacon metrics (head slot, epochs, active validators, reorgs, deposits) * Expose configurable metrics address and port in the beacon node CLI * Integrate with Grafana dashboard via `beaconMetrics.json` * Update ream_launcher.star + Kurtosis integration Closes #1526
1 parent 40fa88f commit ecd12e0

31 files changed

Lines changed: 2060 additions & 716 deletions

File tree

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/ream/src/cli/beacon_node.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use ream_p2p::bootnodes::Bootnodes;
88
use url::Url;
99

1010
use crate::cli::constants::{
11-
DEFAULT_DISABLE_DISCOVERY, DEFAULT_DISCOVERY_PORT, DEFAULT_HTTP_ADDRESS,
12-
DEFAULT_HTTP_ALLOW_ORIGIN, DEFAULT_HTTP_PORT, DEFAULT_NETWORK, DEFAULT_SOCKET_ADDRESS,
13-
DEFAULT_SOCKET_PORT,
11+
DEFAULT_BEACON_METRICS_ADDRESS, DEFAULT_BEACON_METRICS_PORT, DEFAULT_DISABLE_DISCOVERY,
12+
DEFAULT_DISCOVERY_PORT, DEFAULT_HTTP_ADDRESS, DEFAULT_HTTP_ALLOW_ORIGIN, DEFAULT_HTTP_PORT,
13+
DEFAULT_METRICS_ENABLED, DEFAULT_NETWORK, DEFAULT_SOCKET_ADDRESS, DEFAULT_SOCKET_PORT,
1414
};
1515

1616
#[derive(Debug, Parser)]
@@ -89,6 +89,15 @@ pub struct BeaconNodeConfig {
8989
help = "Number of epochs to retain blob sidecars. Defaults to network spec value (4096 epochs for mainnet, ~18 days)"
9090
)]
9191
pub blob_retention_epochs: Option<u64>,
92+
93+
#[arg(long = "metrics", help = "Enable metrics", default_value_t = DEFAULT_METRICS_ENABLED)]
94+
pub enable_metrics: bool,
95+
96+
#[arg(long, help = "Set metrics address", default_value_t = DEFAULT_BEACON_METRICS_ADDRESS)]
97+
pub metrics_address: IpAddr,
98+
99+
#[arg(long, help = "Set metrics port", default_value_t = DEFAULT_BEACON_METRICS_PORT)]
100+
pub metrics_port: u16,
92101
}
93102

94103
impl From<BeaconNodeConfig> for ManagerConfig {

bin/ream/src/cli/constants.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub const DEFAULT_HTTP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))
88
pub const DEFAULT_HTTP_ALLOW_ORIGIN: bool = false;
99
pub const DEFAULT_HTTP_PORT: u16 = 5052;
1010
pub const DEFAULT_KEY_MANAGER_HTTP_PORT: u16 = 8008;
11+
pub const DEFAULT_BEACON_METRICS_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
12+
pub const DEFAULT_BEACON_METRICS_PORT: u16 = 8008;
1113
pub const DEFAULT_METRICS_ENABLED: bool = false;
1214
pub const DEFAULT_METRICS_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
1315
pub const DEFAULT_METRICS_PORT: u16 = 8080;

bin/ream/src/main.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ use ream_fork_choice_lean::{
5454
};
5555
use ream_keystore::keystore::EncryptedKeystore;
5656
use ream_metrics::{
57-
ATTESTATION_COMMITTEE_SUBNET, NODE_INFO, NODE_START_TIME_SECONDS, set_int_gauge_vec,
57+
ATTESTATION_COMMITTEE_SUBNET, NODE_INFO, NODE_START_TIME_SECONDS, init_node_metrics,
58+
set_int_gauge_vec,
5859
};
5960
use ream_network_manager::service::NetworkManagerService;
6061
use ream_network_spec::networks::{
@@ -495,6 +496,17 @@ async fn run_beacon_node_inner(
495496
) {
496497
info!("starting up beacon node...");
497498

499+
if config.enable_metrics {
500+
let address = SocketAddr::new(config.metrics_address, config.metrics_port);
501+
prometheus_exporter::start(address).expect("Failed to start prometheus exporter");
502+
info!(
503+
"Metrics started on {}:{}",
504+
config.metrics_address, config.metrics_port
505+
);
506+
507+
init_node_metrics();
508+
}
509+
498510
if initialize_globals {
499511
set_beacon_network_spec(config.network.clone());
500512
}

book/cli/ream/beacon_node.md

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/common/chain/beacon/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ream-consensus-misc.workspace = true
2323
ream-events-beacon.workspace = true
2424
ream-execution-engine.workspace = true
2525
ream-fork-choice-beacon.workspace = true
26+
ream-metrics.workspace = true
2627
ream-network-spec.workspace = true
2728
ream-operation-pool.workspace = true
2829
ream-req-resp.workspace = true

crates/common/chain/beacon/src/beacon_chain.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
use std::sync::Arc;
22

3-
use anyhow::bail;
3+
use anyhow::{anyhow, bail};
44
use ream_consensus_beacon::{
55
attestation::Attestation, attester_slashing::AttesterSlashing,
66
electra::beacon_block::SignedBeaconBlock,
77
};
8-
use ream_consensus_misc::constants::beacon::{FULU_FORK_EPOCH, genesis_validators_root};
8+
use ream_consensus_misc::{
9+
constants::beacon::{FULU_FORK_EPOCH, genesis_validators_root},
10+
misc::compute_epoch_at_slot,
11+
};
912
use ream_events_beacon::{BeaconEvent, BeaconEventSender, event::chain::BlockEvent};
1013
use ream_execution_engine::ExecutionEngine;
1114
use ream_fork_choice_beacon::{
1215
handlers::{on_attestation, on_attester_slashing, on_block, on_tick},
1316
store::Store,
1417
};
18+
use ream_metrics::{BEACON_HEAD_EPOCH, BEACON_HEAD_SLOT, BEACON_REORGS_TOTAL};
1519
use ream_network_spec::networks::beacon_network_spec;
1620
use ream_operation_pool::OperationPool;
1721
use ream_req_resp::beacon::messages::status::Status;
@@ -48,6 +52,7 @@ impl BeaconChain {
4852

4953
pub async fn process_block(&self, signed_block: SignedBeaconBlock) -> anyhow::Result<()> {
5054
let mut store = self.store.lock().await;
55+
let previous_head = store.get_head().ok();
5156

5257
on_block(
5358
&mut store,
@@ -57,7 +62,34 @@ impl BeaconChain {
5762
)
5863
.await?;
5964

60-
// Build and Emit Block event
65+
let new_head = store.get_head()?;
66+
let new_head_block = store
67+
.db
68+
.block_provider()
69+
.get(new_head)?
70+
.ok_or_else(|| anyhow!("head block not found in store"))?;
71+
let new_head_slot = new_head_block.message.slot;
72+
73+
BEACON_HEAD_SLOT.set(new_head_slot as i64);
74+
BEACON_HEAD_EPOCH.set(compute_epoch_at_slot(new_head_slot) as i64);
75+
76+
// Detect canonical chain reorgs for beacon_reorgs_total.
77+
// A head change alone is insufficient because normal chain extensions
78+
// also change the head. We only count a reorg when the previous head
79+
// is no longer an ancestor of the new head.
80+
if let Some(previous_head) = previous_head
81+
&& previous_head != new_head
82+
&& let Some(previous_head_block) = store.db.block_provider().get(previous_head)?
83+
{
84+
let previous_head_slot = previous_head_block.message.slot;
85+
let still_ancestor =
86+
store.get_ancestor(new_head, previous_head_slot).ok() == Some(previous_head);
87+
88+
if !still_ancestor {
89+
BEACON_REORGS_TOTAL.inc();
90+
}
91+
}
92+
6193
let finalized_checkpoint = store.db.finalized_checkpoint_provider().get().ok();
6294
let block_event =
6395
BlockEvent::from_block(&signed_block, finalized_checkpoint, |block_root, epoch| {

crates/common/consensus/beacon/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ ream-consensus-misc.workspace = true
4343
ream-execution-engine.workspace = true
4444
ream-execution-rpc-types.workspace = true
4545
ream-merkle.workspace = true
46+
ream-metrics.workspace = true
4647
ream-network-spec.workspace = true
4748

4849
[dev-dependencies]

crates/common/consensus/beacon/src/data_column_sidecar.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ use ream_consensus_misc::{
55
polynomial_commitments::{kzg_commitment::KZGCommitment, kzg_proof::KZGProof},
66
};
77
use ream_merkle::is_valid_merkle_branch;
8+
use ream_metrics::{
9+
BEACON_DATA_COLUMN_SIDECAR_COMPUTATION_SECONDS,
10+
BEACON_DATA_COLUMN_SIDECAR_INCLUSION_PROOF_VERIFICATION_SECONDS,
11+
};
812
use ream_network_spec::networks::beacon_network_spec;
913
use serde::{Deserialize, Serialize};
1014
use ssz_derive::{Decode, Encode};
@@ -64,6 +68,7 @@ impl DataColumnSidecar {
6468

6569
/// Verifies that the kzg_commitments list is included in the block body
6670
pub fn verify_inclusion_proof(&self) -> bool {
71+
let _timer = BEACON_DATA_COLUMN_SIDECAR_INCLUSION_PROOF_VERIFICATION_SECONDS.start_timer();
6772
is_valid_merkle_branch(
6873
self.kzg_commitments.tree_hash_root(),
6974
&self.kzg_commitments_inclusion_proof,
@@ -111,6 +116,8 @@ pub fn get_data_column_sidecars(
111116
kzg_commitments_inclusion_proof: FixedVector<B256, typenum::U4>,
112117
cells_and_kzg_proofs: Vec<(Vec<Cell>, Vec<KZGProof>)>,
113118
) -> Result<Vec<DataColumnSidecar>, DataColumnSidecarError> {
119+
let _timer = BEACON_DATA_COLUMN_SIDECAR_COMPUTATION_SECONDS.start_timer();
120+
114121
if cells_and_kzg_proofs.len() != kzg_commitments.len() {
115122
return Err(DataColumnSidecarError::CommitmentCountMismatch {
116123
actual: cells_and_kzg_proofs.len(),

crates/common/consensus/beacon/src/matrix_entry.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ use ream_consensus_misc::{
33
constants::beacon::CELLS_PER_EXT_BLOB, polynomial_commitments::kzg_proof::KZGProof,
44
};
55
use ream_execution_rpc_types::get_blobs::Blob;
6+
use ream_metrics::{
7+
BEACON_DATA_AVAILABILITY_RECONSTRUCTED_COLUMNS_TOTAL,
8+
BEACON_DATA_AVAILABILITY_RECONSTRUCTION_TIME_SECONDS,
9+
};
610
use rust_eth_kzg::{Cell as KZGCell, DASContext, KZGProof as Proof};
711
use ssz_types::FixedVector;
812

@@ -40,7 +44,9 @@ pub fn recover_matrix(
4044
blob_count: u64,
4145
das_context: &DASContext,
4246
) -> Result<Vec<MatrixEntry>> {
47+
let _timer = BEACON_DATA_AVAILABILITY_RECONSTRUCTION_TIME_SECONDS.start_timer();
4348
let mut matrix = Vec::new();
49+
let mut reconstructed_count: u64 = 0;
4450

4551
for blob_index in 0..blob_count {
4652
let (cell_indices, cells): (Vec<u64>, Vec<Cell>) = partial_matrix
@@ -49,9 +55,14 @@ pub fn recover_matrix(
4955
.map(|entry| (entry.column_index, entry.cell.clone()))
5056
.unzip();
5157

58+
let known_count = cell_indices.len() as u64;
59+
5260
let (recovered_cells, recovered_proofs) =
5361
recover_cells_and_kzg_proofs(cell_indices, cells, das_context)?;
5462

63+
let newly_reconstructed = recovered_cells.len() as u64 - known_count;
64+
reconstructed_count += newly_reconstructed;
65+
5566
for (cell_index, (cell, kzg_proof)) in recovered_cells
5667
.into_iter()
5768
.zip(recovered_proofs)
@@ -66,6 +77,8 @@ pub fn recover_matrix(
6677
}
6778
}
6879

80+
BEACON_DATA_AVAILABILITY_RECONSTRUCTED_COLUMNS_TOTAL.inc_by(reconstructed_count);
81+
6982
Ok(matrix)
7083
}
7184

0 commit comments

Comments
 (0)