forest/rpc/methods/
node.rs1use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
6use crate::{
7 lotus_json::lotus_json_with_self,
8 networks::calculate_expected_epoch,
9 rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError},
10 shim::clock::EPOCH_DURATION_SECONDS,
11};
12use anyhow::ensure;
13use enumflags2::BitFlags;
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16
17fn epochs_behind_head(head_timestamp: u64, now_secs: u64) -> anyhow::Result<u64> {
22 ensure!(
23 head_timestamp <= now_secs + EPOCH_DURATION_SECONDS as u64,
24 "Head tipset timestamp is more than one epoch ahead of system time, please sync the system clock."
25 );
26 Ok(calculate_expected_epoch(now_secs, head_timestamp, EPOCH_DURATION_SECONDS as u32) as u64)
27}
28
29pub enum NodeStatus {}
30impl RpcMethod<1> for NodeStatus {
31 const NAME: &'static str = "Filecoin.NodeStatus";
32 const PARAM_NAMES: [&'static str; 1] = ["inclChainStatus"];
33 const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
34 const PERMISSION: Permission = Permission::Read;
35 const DESCRIPTION: &'static str =
36 "Returns the node's status, including sync and chain health information.";
37
38 type Params = (bool,);
39 type Ok = NodeStatusResult;
40
41 async fn handle(
42 ctx: Ctx,
43 (incl_chain_status,): Self::Params,
44 _: &http::Extensions,
45 ) -> Result<Self::Ok, ServerError> {
46 let mut node_status = NodeStatusResult::default();
47
48 let head = ctx.chain_store().heaviest_tipset();
49 let cur_duration: Duration = SystemTime::now().duration_since(UNIX_EPOCH)?;
50
51 let chain_finality = ctx.chain_config().policy.chain_finality;
52
53 node_status.sync_status.epoch = head.epoch() as u64;
54 node_status.sync_status.behind =
55 epochs_behind_head(head.min_timestamp(), cur_duration.as_secs())?;
56
57 if incl_chain_status && head.epoch() > chain_finality {
58 let mut block_count = 0;
59 let mut ts = head;
60
61 for _ in 0..100 {
62 block_count += ts.block_headers().len();
63 let tsk = ts.parents();
64 ts = ctx.chain_index().load_required_tipset(tsk)?;
65 }
66
67 node_status.chain_status.blocks_per_tipset_last_100 = block_count as f64 / 100.;
68
69 for _ in 100..chain_finality {
70 block_count += ts.block_headers().len();
71 let tsk = ts.parents();
72 ts = ctx.chain_index().load_required_tipset(tsk)?;
73 }
74
75 node_status.chain_status.blocks_per_tipset_last_finality =
76 block_count as f64 / chain_finality as f64;
77 }
78
79 Ok(node_status)
80 }
81}
82
83#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Clone, JsonSchema)]
84#[serde(rename_all = "PascalCase")]
85pub struct NodeSyncStatus {
86 pub epoch: u64,
87 pub behind: u64,
88}
89lotus_json_with_self!(NodeSyncStatus);
90
91#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, Clone, JsonSchema)]
92#[serde(rename_all = "PascalCase")]
93pub struct NodePeerStatus {
94 pub peers_to_publish_msgs: u32,
95 pub peers_to_publish_blocks: u32,
96}
97lotus_json_with_self!(NodePeerStatus);
98
99#[derive(Debug, PartialEq, Serialize, Deserialize, Default, Clone, JsonSchema)]
100#[serde(rename_all = "PascalCase")]
101pub struct NodeChainStatus {
102 pub blocks_per_tipset_last_100: f64,
103 pub blocks_per_tipset_last_finality: f64,
104}
105lotus_json_with_self!(NodeChainStatus);
106
107#[derive(Debug, Deserialize, Default, Serialize, Clone, JsonSchema, PartialEq)]
108#[serde(rename_all = "PascalCase")]
109pub struct NodeStatusResult {
110 pub sync_status: NodeSyncStatus,
111 pub peer_status: NodePeerStatus,
112 pub chain_status: NodeChainStatus,
113}
114lotus_json_with_self!(NodeStatusResult);
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn epochs_behind_head_is_reported_in_epochs() {
122 let epoch = EPOCH_DURATION_SECONDS as u64;
123 assert_eq!(epochs_behind_head(1_000, 1_000).unwrap(), 0);
124 assert_eq!(epochs_behind_head(1_000, 1_000 + epoch).unwrap(), 1);
125 assert_eq!(epochs_behind_head(1_000, 1_000 + 10 * epoch).unwrap(), 10);
126 assert_eq!(epochs_behind_head(1_000, 1_000 + epoch - 1).unwrap(), 0);
127 assert_eq!(epochs_behind_head(1_000, 1_000 + 2 * epoch - 1).unwrap(), 1);
128 }
129
130 #[test]
131 fn epochs_behind_head_tolerates_skew_up_to_one_epoch() {
132 let epoch = EPOCH_DURATION_SECONDS as u64;
133 assert_eq!(epochs_behind_head(1_001, 1_000).unwrap(), 0);
134 assert_eq!(epochs_behind_head(1_000 + epoch, 1_000).unwrap(), 0);
135 }
136
137 #[test]
138 fn epochs_behind_head_errors_on_skew_above_one_epoch() {
139 let epoch = EPOCH_DURATION_SECONDS as u64;
140 assert!(epochs_behind_head(1_000 + epoch + 1, 1_000).is_err());
141 }
142}