use super::MAX_BLOCKS_BEHIND;
use std::{cmp::Ordering, time::Instant};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SyncStatus {
Unsynced, Syncing, Synced, }
#[derive(Clone)]
pub(super) struct SyncState {
sync_height: u32,
greatest_peer_height: Option<u32>,
status: SyncStatus,
last_change: Instant,
}
impl Default for SyncState {
fn default() -> Self {
Self { sync_height: 0, greatest_peer_height: None, status: SyncStatus::Synced, last_change: Instant::now() }
}
}
impl SyncState {
pub fn new_with_height(height: u32) -> Self {
Self { sync_height: height, ..Default::default() }
}
pub fn is_block_synced(&self) -> bool {
self.status == SyncStatus::Synced
}
pub fn can_block_sync(&self) -> bool {
if let Some(num_behind) = self.num_blocks_behind() {
num_behind > 0
} else {
debug!("Cannot block sync: the node has not received block locators yet");
false
}
}
pub fn get_sync_height(&self) -> u32 {
self.sync_height
}
pub fn num_blocks_behind(&self) -> Option<u32> {
self.greatest_peer_height.map(|peer_height| peer_height.saturating_sub(self.sync_height))
}
pub fn get_greatest_peer_height(&self) -> Option<u32> {
self.greatest_peer_height
}
pub fn set_sync_height(&mut self, sync_height: u32) {
if sync_height <= self.sync_height {
return;
}
trace!("Sync height increased from {old_height} to {sync_height}", old_height = self.sync_height);
self.sync_height = sync_height;
self.update_is_block_synced();
}
pub fn set_greatest_peer_height(&mut self, peer_height: u32) {
if let Some(old_height) = self.greatest_peer_height {
match old_height.cmp(&peer_height) {
Ordering::Equal => return,
Ordering::Greater => warn!("Greatest peer height reduced from {old_height} to {peer_height}"),
Ordering::Less => trace!("Greatest peer height increased from {old_height} to {peer_height}"),
}
}
self.greatest_peer_height = Some(peer_height);
self.update_is_block_synced();
}
pub fn clear_greatest_peer_height(&mut self) {
if self.greatest_peer_height.is_none() {
return;
}
self.greatest_peer_height = None;
self.update_is_block_synced();
}
fn update_is_block_synced(&mut self) {
trace!(
"Updating is_block_synced: greatest_peer_height={greatest_peer:?}, current_height={current}, status={status:?}",
greatest_peer = self.greatest_peer_height,
current = self.sync_height,
status = self.status,
);
let num_blocks_behind = self.num_blocks_behind();
let old_status = self.status;
let new_status = match num_blocks_behind {
Some(num) if num <= MAX_BLOCKS_BEHIND => SyncStatus::Synced,
Some(_) => SyncStatus::Syncing,
None => SyncStatus::Unsynced,
};
if new_status == old_status {
return;
}
let now = Instant::now();
let elapsed = now.saturating_duration_since(self.last_change).as_secs();
self.status = new_status;
self.last_change = now;
match self.status {
SyncStatus::Synced => {
if old_status == SyncStatus::Syncing {
let elapsed =
if elapsed < 60 { format!("{elapsed} seconds") } else { format!("{} minutes", elapsed / 60) };
debug!("Block sync state changed to \"synced\". It took {elapsed} to catch up with the network.");
} else {
debug!("Block sync state changed to \"synced\".");
}
}
SyncStatus::Syncing => {
let behind_msg = num_blocks_behind.map(|n| n.to_string()).unwrap_or("unknown".to_string());
debug!("Block sync state changed to \"syncing\". We are {behind_msg} blocks behind.");
}
SyncStatus::Unsynced => {
debug!("Block sync state changed to \"unsynced\". Connect more peers to resume block sync.");
}
}
#[cfg(feature = "metrics")]
metrics::gauge(metrics::bft::IS_SYNCED, self.status == SyncStatus::Synced);
}
}