From 4f1da5248fc7266877d2d984f06f9976c7bfd951 Mon Sep 17 00:00:00 2001 From: dicethedev Date: Mon, 15 Jun 2026 08:49:01 +0100 Subject: [PATCH] refactor(blockchain): extract sync status tracker --- crates/blockchain/src/lib.rs | 105 +-------------------------- crates/blockchain/src/sync_status.rs | 101 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 103 deletions(-) create mode 100644 crates/blockchain/src/sync_status.rs diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index 16c7c7f1..ee4a098e 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -18,6 +18,7 @@ use crate::aggregation::{ AggregationSession, PRIOR_WORKER_JOIN_TIMEOUT, run_aggregation_worker, }; use crate::key_manager::ValidatorKeyPair; +use crate::sync_status::SyncStatusTracker; use spawned_concurrency::actor; use spawned_concurrency::error::ActorError; use spawned_concurrency::protocol; @@ -35,6 +36,7 @@ pub mod key_manager; pub mod metrics; pub mod reaggregate; pub mod store; +mod sync_status; pub struct BlockChain { handle: ActorRef, @@ -55,49 +57,6 @@ pub use ethlambda_types::block::MAX_ATTESTATIONS_DATA; /// /// See: leanSpec PR #682. pub const GOSSIP_DISPARITY_INTERVALS: u64 = 1; -/// Local head lag beyond which the node is considered to be syncing. -/// -/// See: leanSpec PR #708. -const SYNC_LAG_THRESHOLD: u64 = 4; -/// Freshest-known block lag beyond which the network is considered stalled. -/// -/// During a network-wide stall the node remains synced so validators can help -/// the chain recover. -const NETWORK_STALL_THRESHOLD: u64 = 8; -/// Recovery band that prevents the sync status from flapping near the threshold. -const SYNC_HYSTERESIS_BAND: u64 = 2; - -#[derive(Default)] -struct SyncStatusTracker { - syncing: bool, -} - -impl SyncStatusTracker { - fn update( - &mut self, - current_slot: u64, - head_slot: u64, - max_seen_slot: u64, - ) -> metrics::SyncStatus { - let head_lag = current_slot.saturating_sub(head_slot); - let network_lag = current_slot.saturating_sub(max_seen_slot); - - if network_lag > NETWORK_STALL_THRESHOLD { - self.syncing = false; - } else if self.syncing { - self.syncing = head_lag > SYNC_LAG_THRESHOLD.saturating_sub(SYNC_HYSTERESIS_BAND); - } else { - self.syncing = head_lag > SYNC_LAG_THRESHOLD; - } - - if self.syncing { - metrics::SyncStatus::Syncing - } else { - metrics::SyncStatus::Synced - } - } -} - /// Milliseconds until the next interval boundary, measured relative to genesis. fn ms_until_next_interval(now_ms: u64, genesis_time_ms: u64) -> u64 { // Before genesis: wait until genesis itself. @@ -1053,63 +1012,3 @@ impl Handler for BlockChainServer { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sync_status_allows_lag_through_threshold() { - let mut tracker = SyncStatusTracker::default(); - - for lag in 0..=SYNC_LAG_THRESHOLD { - assert_eq!( - tracker.update(10 + lag, 10, 10 + lag), - metrics::SyncStatus::Synced - ); - } - } - - #[test] - fn sync_status_detects_local_lag_when_fresh_blocks_are_known() { - let mut tracker = SyncStatusTracker::default(); - let current_slot = 10 + SYNC_LAG_THRESHOLD + 1; - - assert_eq!( - tracker.update(current_slot, 10, current_slot), - metrics::SyncStatus::Syncing - ); - } - - #[test] - fn sync_status_treats_stale_known_blocks_as_network_stall() { - let mut tracker = SyncStatusTracker::default(); - - assert_eq!(tracker.update(100, 0, 0), metrics::SyncStatus::Synced); - } - - #[test] - fn sync_status_hysteresis_prevents_flapping() { - let mut tracker = SyncStatusTracker::default(); - - assert_eq!(tracker.update(15, 10, 15), metrics::SyncStatus::Syncing); - assert_eq!(tracker.update(15, 11, 15), metrics::SyncStatus::Syncing); - assert_eq!(tracker.update(15, 10, 15), metrics::SyncStatus::Syncing); - assert_eq!(tracker.update(15, 13, 15), metrics::SyncStatus::Synced); - } - - #[test] - fn network_stall_reopens_sync_status() { - let mut tracker = SyncStatusTracker::default(); - - assert_eq!(tracker.update(20, 0, 20), metrics::SyncStatus::Syncing); - assert_eq!(tracker.update(30, 0, 20), metrics::SyncStatus::Synced); - } - - #[test] - fn future_head_saturates_lag_at_zero() { - let mut tracker = SyncStatusTracker::default(); - - assert_eq!(tracker.update(15, 20, 20), metrics::SyncStatus::Synced); - } -} diff --git a/crates/blockchain/src/sync_status.rs b/crates/blockchain/src/sync_status.rs new file mode 100644 index 00000000..36ce987d --- /dev/null +++ b/crates/blockchain/src/sync_status.rs @@ -0,0 +1,101 @@ +use crate::metrics::SyncStatus; + +/// Local head lag beyond which the node is considered to be syncing. +/// +/// See: leanSpec PR #708. +const SYNC_LAG_THRESHOLD: u64 = 4; +/// Freshest-known block lag beyond which the network is considered stalled. +/// +/// During a network-wide stall the node remains synced so validators can help +/// the chain recover. +const NETWORK_STALL_THRESHOLD: u64 = 8; +/// Recovery band that prevents the sync status from flapping near the threshold. +const SYNC_HYSTERESIS_BAND: u64 = 2; + +#[derive(Default)] +pub(crate) struct SyncStatusTracker { + syncing: bool, +} + +impl SyncStatusTracker { + pub(crate) fn update( + &mut self, + current_slot: u64, + head_slot: u64, + max_seen_slot: u64, + ) -> SyncStatus { + let head_lag = current_slot.saturating_sub(head_slot); + let network_lag = current_slot.saturating_sub(max_seen_slot); + + if network_lag > NETWORK_STALL_THRESHOLD { + self.syncing = false; + } else if self.syncing { + self.syncing = head_lag > SYNC_LAG_THRESHOLD.saturating_sub(SYNC_HYSTERESIS_BAND); + } else { + self.syncing = head_lag > SYNC_LAG_THRESHOLD; + } + + if self.syncing { + SyncStatus::Syncing + } else { + SyncStatus::Synced + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sync_status_allows_lag_through_threshold() { + let mut tracker = SyncStatusTracker::default(); + + for lag in 0..=SYNC_LAG_THRESHOLD { + assert_eq!(tracker.update(10 + lag, 10, 10 + lag), SyncStatus::Synced); + } + } + + #[test] + fn sync_status_detects_local_lag_when_fresh_blocks_are_known() { + let mut tracker = SyncStatusTracker::default(); + let current_slot = 10 + SYNC_LAG_THRESHOLD + 1; + + assert_eq!( + tracker.update(current_slot, 10, current_slot), + SyncStatus::Syncing + ); + } + + #[test] + fn sync_status_treats_stale_known_blocks_as_network_stall() { + let mut tracker = SyncStatusTracker::default(); + + assert_eq!(tracker.update(100, 0, 0), SyncStatus::Synced); + } + + #[test] + fn sync_status_hysteresis_prevents_flapping() { + let mut tracker = SyncStatusTracker::default(); + + assert_eq!(tracker.update(15, 10, 15), SyncStatus::Syncing); + assert_eq!(tracker.update(15, 11, 15), SyncStatus::Syncing); + assert_eq!(tracker.update(15, 10, 15), SyncStatus::Syncing); + assert_eq!(tracker.update(15, 13, 15), SyncStatus::Synced); + } + + #[test] + fn network_stall_reopens_sync_status() { + let mut tracker = SyncStatusTracker::default(); + + assert_eq!(tracker.update(20, 0, 20), SyncStatus::Syncing); + assert_eq!(tracker.update(30, 0, 20), SyncStatus::Synced); + } + + #[test] + fn future_head_saturates_lag_at_zero() { + let mut tracker = SyncStatusTracker::default(); + + assert_eq!(tracker.update(15, 20, 20), SyncStatus::Synced); + } +}