use crate::blocks::TipsetKey;
use crate::lotus_json::lotus_json_with_self;
use crate::networks::calculate_expected_epoch;
use crate::shim::clock::ChainEpoch;
use crate::state_manager::StateManager;
use chrono::{DateTime, Utc};
use fvm_ipld_blockstore::Blockstore;
use parking_lot::RwLock;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tracing::log;
const SYNCED_EPOCH_THRESHOLD: u64 = 10;
#[derive(
Serialize,
Deserialize,
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
JsonSchema,
strum::Display,
strum::EnumString,
)]
pub enum NodeSyncStatus {
#[default]
#[strum(to_string = "Intializing")]
Initializing,
#[strum(to_string = "Syncing")]
Syncing,
#[strum(to_string = "Synced")]
Synced,
#[strum(to_string = "Error")]
Error,
#[strum(to_string = "Offline")]
Offline,
}
#[derive(
Serialize,
Deserialize,
Debug,
Clone,
PartialEq,
Eq,
JsonSchema,
strum::Display,
strum::EnumString,
)]
pub enum ForkSyncStage {
#[strum(to_string = "Fetching Headers")]
FetchingHeaders,
#[strum(to_string = "Validating Tipsets")]
ValidatingTipsets,
#[strum(to_string = "Complete")]
Complete,
#[strum(to_string = "Stalled")]
Stalled,
#[strum(to_string = "Error")]
Error,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
pub struct ForkSyncInfo {
#[schemars(with = "crate::lotus_json::LotusJson<TipsetKey>")]
#[serde(with = "crate::lotus_json")]
pub(crate) target_tipset_key: TipsetKey,
pub(crate) target_epoch: ChainEpoch,
pub(crate) target_sync_epoch_start: ChainEpoch,
pub(crate) stage: ForkSyncStage,
pub(crate) validated_chain_head_epoch: ChainEpoch,
pub(crate) start_time: Option<DateTime<Utc>>,
pub(crate) last_updated: Option<DateTime<Utc>>,
}
pub type SyncStatus = Arc<RwLock<SyncStatusReport>>;
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
pub struct SyncStatusReport {
pub(crate) status: NodeSyncStatus,
pub(crate) current_head_epoch: ChainEpoch,
#[schemars(with = "crate::lotus_json::LotusJson<TipsetKey>")]
#[serde(with = "crate::lotus_json")]
pub(crate) current_head_key: Option<TipsetKey>,
pub(crate) network_head_epoch: ChainEpoch,
pub(crate) epochs_behind: i64,
pub(crate) active_forks: Vec<ForkSyncInfo>,
pub(crate) node_start_time: DateTime<Utc>,
pub(crate) last_updated: DateTime<Utc>,
}
lotus_json_with_self!(SyncStatusReport);
impl SyncStatusReport {
pub(crate) fn init() -> Self {
Self {
node_start_time: Utc::now(),
..Default::default()
}
}
pub(crate) fn update<DB: Blockstore + Sync + Send + 'static>(
&self,
state_manager: &StateManager<DB>,
active_forks: Vec<ForkSyncInfo>,
stateless_mode: bool,
) -> Self {
let heaviest = state_manager.chain_store().heaviest_tipset();
let current_head_epoch = heaviest.epoch();
let current_head_key = Some(heaviest.key().clone());
let last_updated = Utc::now();
let last_updated_ts = last_updated.timestamp() as u64;
let seconds_per_epoch = state_manager.chain_config().block_delay_secs;
let network_head_epoch = calculate_expected_epoch(
last_updated_ts,
state_manager.chain_store().genesis_block_header().timestamp,
seconds_per_epoch,
);
let epochs_behind = network_head_epoch.saturating_sub(current_head_epoch);
log::trace!(
"Sync status report: current head epoch: {}, network head epoch: {}, epochs behind: {}",
current_head_epoch,
network_head_epoch,
epochs_behind
);
let time_diff = last_updated_ts.saturating_sub(heaviest.min_timestamp());
let status = match stateless_mode {
true => NodeSyncStatus::Offline,
false => {
if time_diff < u64::from(seconds_per_epoch) * SYNCED_EPOCH_THRESHOLD {
NodeSyncStatus::Synced
} else {
NodeSyncStatus::Syncing
}
}
};
Self {
node_start_time: self.node_start_time,
current_head_epoch,
current_head_key,
network_head_epoch,
epochs_behind,
status,
active_forks,
last_updated,
}
}
pub(crate) fn is_synced(&self) -> bool {
self.status == NodeSyncStatus::Synced
}
pub(crate) fn get_min_starting_block(&self) -> Option<ChainEpoch> {
self.active_forks
.iter()
.map(|fork_info| fork_info.target_sync_epoch_start)
.min()
}
}