diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc6d11bc8a6..4e8a894670c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,10 @@ ### Added - [#6057](https://github.com/ChainSafe/forest/issues/6057) Added `--no-progress-timeout` to `forest-cli f3 ready` subcommand to exit when F3 is stuck for the given timeout. -- [#6000](https://github.com/ChainSafe/forest/pull/6000) Add support for the `Filecoin.StateDecodeParams` API methods to enable decoding actors method params. + +- [#6000](https://github.com/ChainSafe/forest/pull/6000) Added support for the `Filecoin.StateDecodeParams` API methods to enable decoding actors method params. + +- [#6079](https://github.com/ChainSafe/forest/pull/6079) Added prometheus metrics `network_version`, `network_version_revision` and `actor_version`. - [#6068](https://github.com/ChainSafe/forest/issues/6068) Added `--index-backfill-epochs` to `forest-tool api serve`. diff --git a/docs/docs/users/reference/metrics.md b/docs/docs/users/reference/metrics.md index 292e1f7cb114..9ad3cb27afb1 100644 --- a/docs/docs/users/reference/metrics.md +++ b/docs/docs/users/reference/metrics.md @@ -17,6 +17,9 @@ title: Metrics | `full_peers` | Gauge | Count | Number of healthy peers recognized by the node | | `bad_peers` | Gauge | Count | Number of bad peers recognized by the node | | `expected_network_height` | Gauge | Count | The expected network height based on the current time and the genesis block time | +| `network_version` | Gauge | Count | Network version of the current chain head | +| `network_version_revision` | Gauge | Count | Network version revision of the current chain head | +| `actor_version` | Gauge | Count | Actor version of the current chain head | | `forest_db_size` | Gauge | Bytes | Size of Forest database in bytes | | `bitswap_message_count` | Counter | Count | Number of `bitswap` messages. Indexed by `type` | | `bitswap_container_capacities` | Gauge | Count | Capacity for each `bitswap` container. Indexed by `type` | @@ -288,6 +291,33 @@ expected_network_height 2519530 ``` +
+ Example `network_version` output +``` +# HELP network_version Network version of the current chain head +# TYPE network_version gauge +network_version 27 +``` +
+ +
+ Example `network_version_revision` output +``` +# HELP network_version_revision Network version revision of the current chain head +# TYPE network_version_revision gauge +network_version_revision 0 +``` +
+ +
+ Example `actor_version` output +``` +# HELP actor_version Actor version of the current chain head +# TYPE actor_version gauge +actor_version 17 +``` +
+
Example `build_info` output ``` diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index e28b7a8df907..14c718c1b7b4 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -6,6 +6,7 @@ use super::{ index::{ChainIndex, ResolveNullTipset}, tipset_tracker::TipsetTracker, }; +use crate::db::{EthMappingsStore, EthMappingsStoreExt, IndicesStore, IndicesStoreExt}; use crate::interpreter::{BlockMessages, VMTrace}; use crate::libp2p_bitswap::{BitswapStoreRead, BitswapStoreReadWrite}; use crate::message::{ChainMessage, Message as MessageTrait, SignedMessage}; @@ -22,10 +23,6 @@ use crate::{ blocks::{CachingBlockHeader, Tipset, TipsetKey, TxMeta}, db::HeaviestTipsetKeyProvider, }; -use crate::{ - chain_sync::metrics, - db::{EthMappingsStore, EthMappingsStoreExt, IndicesStore, IndicesStoreExt}, -}; use crate::{fil_cns, utils::cache::SizeTrackingLruCache}; use ahash::{HashMap, HashMapExt, HashSet}; use anyhow::Context as _; @@ -146,7 +143,6 @@ where /// Sets heaviest tipset pub fn set_heaviest_tipset(&self, ts: Arc) -> Result<(), Error> { - metrics::HEAD_EPOCH.set(ts.epoch()); self.heaviest_tipset_key_provider .set_heaviest_tipset_key(ts.key())?; if self.publisher.send(HeadChange::Apply(ts)).is_err() { diff --git a/src/chain_sync/metrics.rs b/src/chain_sync/metrics.rs index 95efe617cd74..e620a92d2185 100644 --- a/src/chain_sync/metrics.rs +++ b/src/chain_sync/metrics.rs @@ -3,7 +3,7 @@ use prometheus_client::{ encoding::{EncodeLabelKey, EncodeLabelSet, EncodeLabelValue, LabelSetEncoder}, - metrics::{counter::Counter, family::Family, gauge::Gauge, histogram::Histogram}, + metrics::{counter::Counter, family::Family, histogram::Histogram}, }; use std::sync::LazyLock; @@ -44,15 +44,6 @@ pub static INVALID_TIPSET_TOTAL: LazyLock = LazyLock::new(|| { ); metric }); -pub static HEAD_EPOCH: LazyLock = LazyLock::new(|| { - let metric = Gauge::default(); - crate::metrics::default_registry().register( - "head_epoch", - "Latest epoch synchronized to the node", - metric.clone(), - ); - metric -}); #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct Libp2pMessageKindLabel(&'static str); diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index a9f740bffd68..a0d611ce47b5 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -15,8 +15,10 @@ use crate::cli_shared::{ chain_path, cli::{CliOpts, Config}, }; -use crate::daemon::context::{AppContext, DbType}; -use crate::daemon::db_util::import_chain_as_forest_car; +use crate::daemon::{ + context::{AppContext, DbType}, + db_util::import_chain_as_forest_car, +}; use crate::db::gc::SnapshotGarbageCollector; use crate::db::ttl::EthMappingCollector; use crate::libp2p::{Libp2pService, PeerManager}; @@ -26,6 +28,7 @@ use crate::rpc::RPCState; use crate::rpc::eth::filter::EthEventHandler; use crate::rpc::start_rpc; use crate::shim::clock::ChainEpoch; +use crate::shim::state_tree::StateTree; use crate::shim::version::NetworkVersion; use crate::utils; use crate::utils::{proofs_api::ensure_proof_params_downloaded, version::FOREST_VERSION_STRING}; @@ -202,10 +205,47 @@ async fn maybe_start_metrics_service( ); let db_directory = crate::db::db_engine::db_root(&chain_path(config))?; let db = ctx.db.writer().clone(); - services.spawn(async { - crate::metrics::init_prometheus(prometheus_listener, db_directory, db) + + let get_chain_head_height = Arc::new({ + // Use `Weak` to not dead lock GC. + let chain_store = Arc::downgrade(ctx.state_manager.chain_store()); + move || { + chain_store + .upgrade() + .map(|cs| cs.heaviest_tipset().epoch()) + .unwrap_or_default() + } + }); + let get_chain_head_actor_version = Arc::new({ + // Use `Weak` to not dead lock GC. + let chain_store = Arc::downgrade(ctx.state_manager.chain_store()); + move || { + if let Some(cs) = chain_store.upgrade() + && let Ok(state) = + StateTree::new_from_root(cs.db.clone(), cs.heaviest_tipset().parent_state()) + && let Ok(bundle_meta) = state.get_actor_bundle_metadata() + && let Ok(actor_version) = bundle_meta.actor_major_version() + { + return actor_version; + } + 0 + } + }); + services.spawn({ + let chain_config = ctx.chain_config().clone(); + let get_chain_head_height = get_chain_head_height.clone(); + async { + crate::metrics::init_prometheus( + prometheus_listener, + db_directory, + db, + chain_config, + get_chain_head_height, + get_chain_head_actor_version, + ) .await .context("Failed to initiate prometheus server") + } }); crate::metrics::register_collector(Box::new( @@ -215,6 +255,7 @@ async fn maybe_start_metrics_service( .chain_store() .genesis_block_header() .timestamp, + get_chain_head_height, ), )); } diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs index f2862265e7ad..f9be6aae249f 100644 --- a/src/metrics/mod.rs +++ b/src/metrics/mod.rs @@ -3,7 +3,7 @@ pub mod db; -use crate::db::DBStatistics; +use crate::{db::DBStatistics, networks::ChainConfig, shim::clock::ChainEpoch}; use axum::{Router, http::StatusCode, response::IntoResponse, routing::get}; use parking_lot::{RwLock, RwLockWriteGuard}; use prometheus_client::{ @@ -86,6 +86,9 @@ pub async fn init_prometheus( prometheus_listener: TcpListener, db_directory: PathBuf, db: Arc, + chain_config: Arc, + get_chain_head_height: Arc ChainEpoch + Send + Sync + 'static>, + get_chain_head_actor_version: Arc u64 + Send + Sync + 'static>, ) -> anyhow::Result<()> where DB: DBStatistics + Send + Sync + 'static, @@ -101,6 +104,13 @@ where crate::utils::version::ForestVersionCollector::new(), )); register_collector(Box::new(crate::metrics::db::DBCollector::new(db_directory))); + register_collector(Box::new( + crate::networks::metrics::NetworkVersionCollector::new( + chain_config, + get_chain_head_height, + get_chain_head_actor_version, + ), + )); // Create an configure HTTP server let app = Router::new() diff --git a/src/networks/metrics.rs b/src/networks/metrics.rs index 058d899ac83b..3eb62693e382 100644 --- a/src/networks/metrics.rs +++ b/src/networks/metrics.rs @@ -1,47 +1,162 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use prometheus_client::{collector::Collector, encoding::EncodeMetric, metrics::gauge::Gauge}; +use std::sync::Arc; + +use educe::Educe; +use prometheus_client::{ + collector::Collector, + encoding::{DescriptorEncoder, EncodeMetric}, + metrics::gauge::Gauge, +}; use super::calculate_expected_epoch; +use crate::{networks::ChainConfig, shim::clock::ChainEpoch}; -#[derive(Debug)] -pub struct NetworkHeightCollector { +#[derive(Educe)] +#[educe(Debug)] +pub struct NetworkHeightCollector +where + F: Fn() -> ChainEpoch, +{ block_delay_secs: u32, genesis_timestamp: u64, - network_height: Gauge, + #[educe(Debug(ignore))] + get_chain_head_height: Arc, } -impl NetworkHeightCollector { - pub fn new(block_delay_secs: u32, genesis_timestamp: u64) -> Self { +impl NetworkHeightCollector +where + F: Fn() -> ChainEpoch, +{ + pub fn new( + block_delay_secs: u32, + genesis_timestamp: u64, + get_chain_head_height: Arc, + ) -> Self { Self { block_delay_secs, genesis_timestamp, - network_height: Gauge::default(), + get_chain_head_height, } } } -impl Collector for NetworkHeightCollector { +impl Collector for NetworkHeightCollector +where + F: Fn() -> ChainEpoch + Send + Sync + 'static, +{ fn encode( &self, mut encoder: prometheus_client::encoding::DescriptorEncoder, ) -> Result<(), std::fmt::Error> { - let metric_encoder = encoder.encode_descriptor( - "expected_network_height", - "The expected network height based on the current time and the genesis block time", - None, - self.network_height.metric_type(), - )?; - - let expected_epoch = calculate_expected_epoch( - chrono::Utc::now().timestamp() as u64, - self.genesis_timestamp, - self.block_delay_secs, - ); - self.network_height.set(expected_epoch); - self.network_height.encode(metric_encoder)?; + { + let network_height: Gauge = Default::default(); + let epoch = (self.get_chain_head_height)(); + network_height.set(epoch); + let metric_encoder = encoder.encode_descriptor( + "head_epoch", + "Latest epoch synchronized to the node", + None, + network_height.metric_type(), + )?; + network_height.encode(metric_encoder)?; + } + { + let expected_network_height: Gauge = Default::default(); + let expected_epoch = calculate_expected_epoch( + chrono::Utc::now().timestamp() as u64, + self.genesis_timestamp, + self.block_delay_secs, + ); + expected_network_height.set(expected_epoch); + let metric_encoder = encoder.encode_descriptor( + "expected_network_height", + "The expected network height based on the current time and the genesis block time", + None, + expected_network_height.metric_type(), + )?; + expected_network_height.encode(metric_encoder)?; + } + Ok(()) + } +} + +#[derive(Educe)] +#[educe(Debug)] +pub struct NetworkVersionCollector +where + F1: Fn() -> ChainEpoch, + F2: Fn() -> u64, +{ + chain_config: Arc, + #[educe(Debug(ignore))] + get_chain_head_height: Arc, + #[educe(Debug(ignore))] + get_chain_head_actor_version: Arc, +} +impl NetworkVersionCollector +where + F1: Fn() -> ChainEpoch, + F2: Fn() -> u64, +{ + pub fn new( + chain_config: Arc, + get_chain_head_height: Arc, + get_chain_head_actor_version: Arc, + ) -> Self { + Self { + chain_config, + get_chain_head_height, + get_chain_head_actor_version, + } + } +} + +impl Collector for NetworkVersionCollector +where + F1: Fn() -> ChainEpoch + Send + Sync + 'static, + F2: Fn() -> u64 + Send + Sync + 'static, +{ + fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> { + let epoch = (self.get_chain_head_height)(); + { + let network_version = self.chain_config.network_version(epoch); + let nv_gauge: Gauge = Default::default(); + nv_gauge.set(u32::from(network_version) as _); + let metric_encoder = encoder.encode_descriptor( + "network_version", + "Network version of the current chain head", + None, + nv_gauge.metric_type(), + )?; + nv_gauge.encode(metric_encoder)?; + } + { + let network_version_revision = self.chain_config.network_version_revision(epoch); + let nv_gauge: Gauge = Default::default(); + nv_gauge.set(network_version_revision); + let metric_encoder = encoder.encode_descriptor( + "network_version_revision", + "Network version revision of the current chain head", + None, + nv_gauge.metric_type(), + )?; + nv_gauge.encode(metric_encoder)?; + } + { + let actor_version = (self.get_chain_head_actor_version)(); + let av_gauge: Gauge = Default::default(); + av_gauge.set(actor_version as _); + let metric_encoder = encoder.encode_descriptor( + "actor_version", + "Actor version of the current chain head", + None, + av_gauge.metric_type(), + )?; + av_gauge.encode(metric_encoder)?; + } Ok(()) } } diff --git a/src/networks/mod.rs b/src/networks/mod.rs index d067c8157372..fc3723107de1 100644 --- a/src/networks/mod.rs +++ b/src/networks/mod.rs @@ -10,7 +10,9 @@ use fil_actors_shared::v13::runtime::Policy; use itertools::Itertools; use libp2p::Multiaddr; use serde::{Deserialize, Serialize}; +use strum::IntoEnumIterator; use strum_macros::Display; +use strum_macros::EnumIter; use tracing::warn; use crate::beacon::{BeaconPoint, BeaconSchedule, DrandBeacon, DrandConfig}; @@ -27,8 +29,8 @@ pub use network_name::{GenesisNetworkName, StateNetworkName}; mod actors_bundle; pub use actors_bundle::{ - ACTOR_BUNDLES, ACTOR_BUNDLES_METADATA, ActorBundleInfo, generate_actor_bundle, - get_actor_bundles_metadata, + ACTOR_BUNDLES, ACTOR_BUNDLES_METADATA, ActorBundleInfo, ActorBundleMetadata, + generate_actor_bundle, get_actor_bundles_metadata, }; mod drand; @@ -134,7 +136,7 @@ impl NetworkChain { } /// Defines the meaningful heights of the protocol. -#[derive(Debug, Display, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Debug, Display, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, EnumIter)] #[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))] pub enum Height { Breeze, @@ -411,19 +413,36 @@ impl ChainConfig { } } - /// Returns the network version at the given epoch. - /// If the epoch is before the first upgrade, the genesis network version is returned. - pub fn network_version(&self, epoch: ChainEpoch) -> NetworkVersion { + fn network_height(&self, epoch: ChainEpoch) -> Option { self.height_infos .iter() .sorted_by_key(|(_, info)| info.epoch) .rev() .find(|(_, info)| epoch > info.epoch) - .map(|(height, _)| NetworkVersion::from(*height)) + .map(|(height, _)| *height) + } + + /// Returns the network version at the given epoch. + /// If the epoch is before the first upgrade, the genesis network version is returned. + pub fn network_version(&self, epoch: ChainEpoch) -> NetworkVersion { + self.network_height(epoch) + .map(NetworkVersion::from) .unwrap_or(self.genesis_network_version()) .max(self.genesis_network) } + /// Returns the network version revision at the given epoch for distinguishing network upgrades + /// that do not bump the network version. + pub fn network_version_revision(&self, epoch: ChainEpoch) -> i64 { + if let Some(height) = self.network_height(epoch) { + let nv = NetworkVersion::from(height); + if let Some(rev0_height) = Height::iter().find(|h| NetworkVersion::from(*h) == nv) { + return (height as i64) - (rev0_height as i64); + } + } + 0 + } + pub fn get_beacon_schedule(&self, genesis_ts: u64) -> BeaconSchedule { let ds_iter = match self.network { NetworkChain::Mainnet => mainnet::DRAND_SCHEDULE.iter(), @@ -721,4 +740,17 @@ mod tests { ChainConfig::devnet(); ChainConfig::butterflynet(); } + + #[test] + fn network_version() { + let cfg = ChainConfig::calibnet(); + assert_eq!(cfg.network_version(1_013_134 - 1), NetworkVersion::V20); + assert_eq!(cfg.network_version(1_013_134), NetworkVersion::V20); + assert_eq!(cfg.network_version(1_013_134 + 1), NetworkVersion::V21); + assert_eq!(cfg.network_version_revision(1_013_134 + 1), 0); + assert_eq!(cfg.network_version(1_070_494), NetworkVersion::V21); + assert_eq!(cfg.network_version_revision(1_070_494), 0); + assert_eq!(cfg.network_version(1_070_494 + 1), NetworkVersion::V21); + assert_eq!(cfg.network_version_revision(1_070_494 + 1), 1); + } } diff --git a/src/shim/state_tree.rs b/src/shim/state_tree.rs index a86b3fcb96c8..da5910cde9f7 100644 --- a/src/shim/state_tree.rs +++ b/src/shim/state_tree.rs @@ -5,7 +5,10 @@ use std::{ sync::Arc, }; -use crate::shim::actors::account; +use crate::{ + networks::{ACTOR_BUNDLES_METADATA, ActorBundleMetadata}, + shim::actors::account, +}; use anyhow::{Context as _, anyhow, bail}; use cid::Cid; use fvm_ipld_blockstore::Blockstore; @@ -194,6 +197,15 @@ where .with_context(|| format!("Actor not found: addr={addr}")) } + /// Get the actor bundle metadata + pub fn get_actor_bundle_metadata(&self) -> anyhow::Result<&ActorBundleMetadata> { + let system_actor_code = self.get_required_actor(&Address::SYSTEM_ACTOR)?.code; + ACTOR_BUNDLES_METADATA + .values() + .find(|v| v.manifest.get_system() == system_actor_code) + .with_context(|| format!("actor bundle not found for system actor {system_actor_code}")) + } + /// Get actor state from an address. Will be resolved to ID address. pub fn get_actor(&self, addr: &Address) -> anyhow::Result> { match self { diff --git a/src/shim/version.rs b/src/shim/version.rs index 8b327774b0d1..de0222ae531f 100644 --- a/src/shim/version.rs +++ b/src/shim/version.rs @@ -88,6 +88,12 @@ impl From for NetworkVersion { } } +impl From for u32 { + fn from(value: NetworkVersion) -> Self { + value.0.into() + } +} + impl From for NetworkVersion { fn from(value: NetworkVersion_v2) -> Self { NetworkVersion((value as u32).into())