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)]
83pub struct ForkSyncInfo {
84 #[schemars(with = "crate::lotus_json::LotusJson<TipsetKey>")]
86 #[serde(with = "crate::lotus_json")]
87 pub(crate) target_tipset_key: TipsetKey,
88 pub(crate) target_epoch: ChainEpoch,
90 pub(crate) target_sync_epoch_start: ChainEpoch,
93 pub(crate) stage: ForkSyncStage,
95 pub(crate) validated_chain_head_epoch: ChainEpoch,
98 pub(crate) start_time: Option<DateTime<Utc>>,
100 pub(crate) last_updated: Option<DateTime<Utc>>,
102}
103
104pub type SyncStatus = Arc<ArcSwap<SyncStatusReport>>;
105
106#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
108pub struct SyncStatusReport {
109 pub(crate) status: NodeSyncStatus,
111 pub(crate) current_head_epoch: ChainEpoch,
113 #[schemars(with = "crate::lotus_json::LotusJson<TipsetKey>")]
115 #[serde(with = "crate::lotus_json")]
116 pub(crate) current_head_key: Option<TipsetKey>,
117 pub(crate) network_head_epoch: ChainEpoch,
119 pub(crate) epochs_behind: i64,
122 pub(crate) active_forks: Vec<ForkSyncInfo>,
124 pub(crate) node_start_time: DateTime<Utc>,
126 pub(crate) last_updated: DateTime<Utc>,
128}
129
130lotus_json_with_self!(SyncStatusReport);
131
132impl SyncStatusReport {
133 pub(crate) fn init() -> Self {
134 Self {
135 node_start_time: Utc::now(),
136 ..Default::default()
137 }
138 }
139
140 pub(crate) fn update(
143 &self,
144 state_manager: &StateManager,
145 active_forks: Vec<ForkSyncInfo>,
146 stateless_mode: bool,
147 ) -> Self {
148 let heaviest = state_manager.chain_store().heaviest_tipset();
149 let current_head_epoch = heaviest.epoch();
150 let current_head_key = Some(heaviest.key().clone());
151
152 let last_updated = Utc::now();
153 let last_updated_ts = last_updated.timestamp() as u64;
154 let seconds_per_epoch = state_manager.chain_config().block_delay_secs;
155 let network_head_epoch = calculate_expected_epoch(
156 last_updated_ts,
157 state_manager.chain_store().genesis_block_header().timestamp,
158 seconds_per_epoch,
159 );
160
161 let epochs_behind = network_head_epoch.saturating_sub(current_head_epoch);
162 log::trace!(
163 "Sync status report: current head epoch: {}, network head epoch: {}, epochs behind: {}",
164 current_head_epoch,
165 network_head_epoch,
166 epochs_behind
167 );
168
169 let time_diff = last_updated_ts.saturating_sub(heaviest.min_timestamp());
170 let status = match stateless_mode {
171 true => NodeSyncStatus::Offline,
172 false => {
173 if time_diff < u64::from(seconds_per_epoch) * SYNCED_EPOCH_THRESHOLD {
174 NodeSyncStatus::Synced
175 } else {
176 NodeSyncStatus::Syncing
177 }
178 }
179 };
180
181 Self {
182 node_start_time: self.node_start_time,
183 current_head_epoch,
184 current_head_key,
185 network_head_epoch,
186 epochs_behind,
187 status,
188 active_forks,
189 last_updated,
190 }
191 }
192
193 pub(crate) fn is_synced(&self) -> bool {
194 self.status == NodeSyncStatus::Synced
195 }
196
197 pub(crate) fn get_min_starting_block(&self) -> Option<ChainEpoch> {
198 self.active_forks
199 .iter()
200 .map(|fork_info| fork_info.target_sync_epoch_start)
201 .min()
202 }
203
204 #[cfg(test)]
205 pub fn with_status(mut self, status: NodeSyncStatus) -> Self {
206 self.status = status;
207 self
208 }
209
210 #[cfg(test)]
211 pub fn with_current_head_epoch(mut self, current_head_epoch: ChainEpoch) -> Self {
212 self.current_head_epoch = current_head_epoch;
213 self
214 }
215}