forest/rpc/methods/
node.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
6use crate::{
7    lotus_json::lotus_json_with_self,
8    rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError},
9};
10use enumflags2::BitFlags;
11use fvm_ipld_blockstore::Blockstore;
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14
15pub enum NodeStatus {}
16impl RpcMethod<0> for NodeStatus {
17    const NAME: &'static str = "Filecoin.NodeStatus";
18    const PARAM_NAMES: [&'static str; 0] = [];
19    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
20    const PERMISSION: Permission = Permission::Read;
21
22    type Params = ();
23    type Ok = NodeStatusResult;
24
25    async fn handle(ctx: Ctx<impl Blockstore>, (): Self::Params) -> Result<Self::Ok, ServerError> {
26        let mut node_status = NodeStatusResult::default();
27
28        let head = ctx.chain_store().heaviest_tipset();
29        let cur_duration: Duration = SystemTime::now().duration_since(UNIX_EPOCH)?;
30
31        let ts = head.min_timestamp();
32        let cur_duration_secs = cur_duration.as_secs();
33        let behind = if ts <= cur_duration_secs + 1 {
34            cur_duration_secs.saturating_sub(ts)
35        } else {
36            return Err(anyhow::anyhow!(
37                "System time should not be behind tipset timestamp, please sync the system clock."
38            )
39            .into());
40        };
41
42        let chain_finality = ctx.chain_config().policy.chain_finality;
43
44        node_status.sync_status.epoch = head.epoch() as u64;
45        node_status.sync_status.behind = behind;
46
47        if head.epoch() > chain_finality {
48            let mut block_count = 0;
49            let mut ts = head;
50
51            for _ in 0..100 {
52                block_count += ts.block_headers().len();
53                let tsk = ts.parents();
54                ts = ctx.chain_index().load_required_tipset(tsk)?;
55            }
56
57            node_status.chain_status.blocks_per_tipset_last_100 = block_count as f64 / 100.;
58
59            for _ in 100..chain_finality {
60                block_count += ts.block_headers().len();
61                let tsk = ts.parents();
62                ts = ctx.chain_index().load_required_tipset(tsk)?;
63            }
64
65            node_status.chain_status.blocks_per_tipset_last_finality =
66                block_count as f64 / chain_finality as f64;
67        }
68
69        Ok(node_status)
70    }
71}
72
73#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Clone, JsonSchema)]
74pub struct NodeSyncStatus {
75    pub epoch: u64,
76    pub behind: u64,
77}
78lotus_json_with_self!(NodeSyncStatus);
79
80#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Clone, JsonSchema)]
81pub struct NodePeerStatus {
82    pub peers_to_publish_msgs: u32,
83    pub peers_to_publish_blocks: u32,
84}
85lotus_json_with_self!(NodePeerStatus);
86
87#[derive(Debug, PartialEq, Serialize, Deserialize, Default, Clone, JsonSchema)]
88pub struct NodeChainStatus {
89    pub blocks_per_tipset_last_100: f64,
90    pub blocks_per_tipset_last_finality: f64,
91}
92lotus_json_with_self!(NodeChainStatus);
93
94#[derive(Debug, Deserialize, Default, Serialize, Clone, JsonSchema, PartialEq)]
95pub struct NodeStatusResult {
96    pub sync_status: NodeSyncStatus,
97    pub peer_status: NodePeerStatus,
98    pub chain_status: NodeChainStatus,
99}
100lotus_json_with_self!(NodeStatusResult);