forest/chain_sync/
sync_status.rs1use crate::blocks::TipsetKey;
4use crate::lotus_json::lotus_json_with_self;
5use crate::networks::calculate_expected_epoch;
6use crate::shim::clock::ChainEpoch;
7use crate::state_manager::StateManager;
8use arc_swap::ArcSwap;
9use chrono::{DateTime, Utc};
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12use std::sync::Arc;
13use tracing::log;
14
15const SYNCED_EPOCH_THRESHOLD: u64 = 2;
17
18#[derive(
20 Serialize,
21 Deserialize,
22 Debug,
23 Clone,
24 Copy,
25 Default,
26 PartialEq,
27 Eq,
28 JsonSchema,
29 strum::Display,
30 strum::EnumString,
31)]
32pub enum NodeSyncStatus {
33 #[default]
35 #[strum(to_string = "Intializing")]
36 Initializing,
37 #[strum(to_string = "Syncing")]
39 Syncing,
40 #[strum(to_string = "Synced")]
42 Synced,
43 #[strum(to_string = "Error")]
45 Error,
46 #[strum(to_string = "Offline")]
48 Offline,
49}
50
51#[derive(
53 Serialize,
54 Deserialize,
55 Debug,
56 Clone,
57 PartialEq,
58 Eq,
59 JsonSchema,
60 strum::Display,
61 strum::EnumString,
62)]
63pub enum ForkSyncStage {
64 #[strum(to_string = "Fetching Headers")]
66 FetchingHeaders,
67 #[strum(to_string = "Validating Tipsets")]
69 ValidatingTipsets,
70 #[strum(to_string = "Complete")]
72 Complete,
73 #[strum(to_string = "Stalled")]
75 Stalled,
76 #[strum(to_string = "Error")]
78 Error,
79}
80
81#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
83#[serde(rename_all = "camelCase")]
84pub struct ForkSyncInfo {
85 #[schemars(with = "crate::lotus_json::LotusJson<TipsetKey>")]
87 #[serde(with = "crate::lotus_json")]
88 pub(crate) target_tipset_key: TipsetKey,
89 pub(crate) target_epoch: ChainEpoch,
91 pub(crate) target_sync_epoch_start: ChainEpoch,
94 pub(crate) stage: ForkSyncStage,
96 pub(crate) validated_chain_head_epoch: ChainEpoch,
99 pub(crate) start_time: Option<DateTime<Utc>>,
101 pub(crate) last_updated: Option<DateTime<Utc>>,
103}
104
105pub type SyncStatus = Arc<ArcSwap<SyncStatusReport>>;
106
107#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
109#[serde(rename_all = "camelCase")]
110pub struct SyncStatusReport {
111 pub(crate) status: NodeSyncStatus,
113 pub(crate) current_head_epoch: ChainEpoch,
115 #[schemars(with = "crate::lotus_json::LotusJson<TipsetKey>")]
117 #[serde(with = "crate::lotus_json")]
118 pub(crate) current_head_key: Option<TipsetKey>,
119 pub(crate) network_head_epoch: ChainEpoch,
121 pub(crate) epochs_behind: i64,
124 pub(crate) active_forks: Vec<ForkSyncInfo>,
126 pub(crate) node_start_time: DateTime<Utc>,
128 pub(crate) last_updated: DateTime<Utc>,
130}
131
132lotus_json_with_self!(SyncStatusReport);
133
134impl SyncStatusReport {
135 pub(crate) fn init() -> Self {
136 Self {
137 node_start_time: Utc::now(),
138 ..Default::default()
139 }
140 }
141
142 pub(crate) fn update(
145 &self,
146 state_manager: &StateManager,
147 active_forks: Vec<ForkSyncInfo>,
148 stateless_mode: bool,
149 ) -> Self {
150 let heaviest = state_manager.chain_store().heaviest_tipset();
151 let current_head_epoch = heaviest.epoch();
152 let current_head_key = Some(heaviest.key().clone());
153
154 let last_updated = Utc::now();
155 let last_updated_ts = last_updated.timestamp() as u64;
156 let seconds_per_epoch = state_manager.chain_config().block_delay_secs;
157 let network_head_epoch = calculate_expected_epoch(
158 last_updated_ts,
159 state_manager.chain_store().genesis_block_header().timestamp,
160 seconds_per_epoch,
161 );
162
163 let epochs_behind = network_head_epoch.saturating_sub(current_head_epoch);
164 log::trace!(
165 "Sync status report: current head epoch: {}, network head epoch: {}, epochs behind: {}",
166 current_head_epoch,
167 network_head_epoch,
168 epochs_behind
169 );
170
171 let time_diff = last_updated_ts.saturating_sub(heaviest.min_timestamp());
172 let status = match stateless_mode {
173 true => NodeSyncStatus::Offline,
174 false => {
175 if time_diff < u64::from(seconds_per_epoch) * SYNCED_EPOCH_THRESHOLD {
176 NodeSyncStatus::Synced
177 } else {
178 NodeSyncStatus::Syncing
179 }
180 }
181 };
182
183 Self {
184 node_start_time: self.node_start_time,
185 current_head_epoch,
186 current_head_key,
187 network_head_epoch,
188 epochs_behind,
189 status,
190 active_forks,
191 last_updated,
192 }
193 }
194
195 pub(crate) fn is_synced(&self) -> bool {
196 self.status == NodeSyncStatus::Synced
197 }
198
199 pub(crate) fn get_min_starting_block(&self) -> Option<ChainEpoch> {
200 self.active_forks
201 .iter()
202 .map(|fork_info| fork_info.target_sync_epoch_start)
203 .min()
204 }
205
206 #[cfg(test)]
207 pub fn with_status(mut self, status: NodeSyncStatus) -> Self {
208 self.status = status;
209 self
210 }
211
212 #[cfg(test)]
213 pub fn with_current_head_epoch(mut self, current_head_epoch: ChainEpoch) -> Self {
214 self.current_head_epoch = current_head_epoch;
215 self
216 }
217}