use serde::{Deserialize, Serialize}; use std::collections::VecDeque; /// Tracks bandwidth metrics for a validator #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BandwidthMetrics { /// Total bytes sent pub bytes_sent: u64, /// Total bytes received pub bytes_received: u64, /// Peak bandwidth (bytes/second) pub peak_bandwidth: u64, /// Average bandwidth (bytes/second) pub avg_bandwidth: u64, /// Measurement period (seconds) pub measurement_period: u64, } impl BandwidthMetrics { pub fn new() -> Self { Self { bytes_sent: 0, bytes_received: 0, peak_bandwidth: 0, avg_bandwidth: 0, measurement_period: 0, } } /// Total bandwidth (sent + received) pub fn total_bytes(&self) -> u64 { self.bytes_sent + self.bytes_received } /// Calculate bandwidth score (normalized 0.0 - 1.0) /// 1 Mbps = 125 KB/s = baseline score of 0.5 pub fn score(&self) -> f64 { if self.measurement_period == 0 { return 0.0; } let bytes_per_sec = self.total_bytes() / self.measurement_period; // Baseline: 125 KB/s (1 Mbps) = 0.5 score // 10 Mbps = 1.0 score const MAX_BYTES_PER_SEC: u64 = 1_250_000; // 10 Mbps let score = bytes_per_sec as f64 / MAX_BYTES_PER_SEC as f64; score.clamp(0.0, 1.0) } /// Update metrics with new measurement pub fn update(&mut self, bytes_sent: u64, bytes_received: u64, period_secs: u64) { self.bytes_sent += bytes_sent; self.bytes_received += bytes_received; self.measurement_period += period_secs; if period_secs > 0 { let current_bandwidth = (bytes_sent + bytes_received) / period_secs; self.peak_bandwidth = self.peak_bandwidth.max(current_bandwidth); // Update rolling average let total = self.total_bytes(); self.avg_bandwidth = total / self.measurement_period; } } } impl Default for BandwidthMetrics { fn default() -> Self { Self::new() } } /// Tracks uptime metrics for a validator #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UptimeMetrics { /// Total uptime in seconds pub total_uptime: u64, /// Total time window (seconds) pub total_time: u64, /// Number of disconnections pub disconnections: u32, /// Longest continuous uptime (seconds) pub longest_uptime: u64, /// Current uptime streak (seconds) pub current_streak: u64, /// Last seen timestamp pub last_seen: i64, } impl UptimeMetrics { pub fn new(current_time: i64) -> Self { Self { total_uptime: 0, total_time: 0, disconnections: 0, longest_uptime: 0, current_streak: 0, last_seen: current_time, } } /// Calculate uptime percentage pub fn uptime_percentage(&self) -> f64 { if self.total_time == 0 { return 0.0; } (self.total_uptime as f64 / self.total_time as f64) * 100.0 } /// Calculate uptime score (0.0 - 1.0) /// Factors: uptime %, disconnection rate, streak length pub fn score(&self) -> f64 { let uptime_score = self.uptime_percentage() / 100.0; // Penalty for frequent disconnections let disconnection_penalty = if self.total_time > 0 { let disconnects_per_day = (self.disconnections as f64) / (self.total_time as f64 / 86400.0); // More than 5 disconnects/day = penalty if disconnects_per_day > 5.0 { 0.5 } else if disconnects_per_day > 2.0 { 0.8 } else { 1.0 } } else { 1.0 }; // Bonus for long streaks let streak_bonus = if self.current_streak > 7 * 86400 { // > 7 days 1.2 } else if self.current_streak > 24 * 3600 { // > 24 hours 1.1 } else { 1.0 }; (uptime_score * disconnection_penalty * streak_bonus).clamp(0.0, 1.0) } /// Record heartbeat (node is online) pub fn record_heartbeat(&mut self, current_time: i64) { let elapsed = (current_time - self.last_seen).max(0) as u64; // If gap is small (< 5 min), consider continuous if elapsed < 300 { self.total_uptime += elapsed; self.current_streak += elapsed; if self.current_streak > self.longest_uptime { self.longest_uptime = self.current_streak; } } else { // Disconnection detected self.disconnections += 1; self.current_streak = 0; } self.total_time += elapsed; self.last_seen = current_time; } /// Record explicit disconnection pub fn record_disconnection(&mut self, current_time: i64) { self.disconnections += 1; self.current_streak = 0; self.last_seen = current_time; } } /// Tracks storage contribution metrics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StorageMetrics { /// Storage space provided (bytes) pub storage_provided: u64, /// Storage actually used (bytes) pub storage_used: u64, /// Number of blocks stored pub blocks_stored: u64, /// Number of transactions stored pub transactions_stored: u64, } impl StorageMetrics { pub fn new() -> Self { Self { storage_provided: 0, storage_used: 0, blocks_stored: 0, transactions_stored: 0, } } /// Calculate utilization percentage pub fn utilization(&self) -> f64 { if self.storage_provided == 0 { return 0.0; } (self.storage_used as f64 / self.storage_provided as f64) * 100.0 } /// Calculate storage score (0.0 - 1.0) /// 10 GB provided = baseline 0.5 /// 100 GB provided = 1.0 pub fn score(&self) -> f64 { const MAX_GB: u64 = 100; let gb_provided = self.storage_provided / (1024 * 1024 * 1024); let score = gb_provided as f64 / MAX_GB as f64; score.clamp(0.0, 1.0) } /// Update storage metrics pub fn update(&mut self, provided: u64, used: u64, blocks: u64, txs: u64) { self.storage_provided = provided; self.storage_used = used; self.blocks_stored = blocks; self.transactions_stored = txs; } } impl Default for StorageMetrics { fn default() -> Self { Self::new() } } /// Combined metrics tracker for a validator #[derive(Debug)] pub struct MetricsTracker { pub bandwidth: BandwidthMetrics, pub uptime: UptimeMetrics, pub storage: StorageMetrics, /// History of bandwidth measurements bandwidth_history: VecDeque, /// Maximum history entries to keep max_history: usize, } impl MetricsTracker { pub fn new(current_time: i64) -> Self { Self { bandwidth: BandwidthMetrics::new(), uptime: UptimeMetrics::new(current_time), storage: StorageMetrics::new(), bandwidth_history: VecDeque::new(), max_history: 24, // 24 hours of hourly measurements } } /// Record heartbeat pub fn heartbeat(&mut self, current_time: i64) { self.uptime.record_heartbeat(current_time); } /// Update bandwidth pub fn update_bandwidth(&mut self, bytes_sent: u64, bytes_received: u64, period_secs: u64) { self.bandwidth.update(bytes_sent, bytes_received, period_secs); // Save to history self.bandwidth_history.push_back(self.bandwidth.clone()); if self.bandwidth_history.len() > self.max_history { self.bandwidth_history.pop_front(); } } /// Update storage pub fn update_storage(&mut self, provided: u64, used: u64, blocks: u64, txs: u64) { self.storage.update(provided, used, blocks, txs); } /// Get average bandwidth score over history pub fn avg_bandwidth_score(&self) -> f64 { if self.bandwidth_history.is_empty() { return self.bandwidth.score(); } let total: f64 = self.bandwidth_history.iter().map(|b| b.score()).sum(); total / self.bandwidth_history.len() as f64 } /// Get combined metrics score pub fn combined_score(&self) -> f64 { let bandwidth_score = self.avg_bandwidth_score(); let uptime_score = self.uptime.score(); let storage_score = self.storage.score(); // Weighted average bandwidth_score * 0.4 + uptime_score * 0.4 + storage_score * 0.2 } } #[cfg(test)] mod tests { use super::*; #[test] fn test_bandwidth_metrics() { let mut metrics = BandwidthMetrics::new(); // 1 MB sent + received over 10 seconds = 100 KB/s metrics.update(500_000, 500_000, 10); assert_eq!(metrics.total_bytes(), 1_000_000); assert_eq!(metrics.avg_bandwidth, 100_000); } #[test] fn test_uptime_metrics() { let mut metrics = UptimeMetrics::new(0); // Record heartbeats every minute for 1 hour for i in 1..=60 { metrics.record_heartbeat(i * 60); } assert!(metrics.uptime_percentage() > 99.0); assert_eq!(metrics.disconnections, 0); } #[test] fn test_uptime_with_disconnections() { let mut metrics = UptimeMetrics::new(0); metrics.record_heartbeat(60); // 1 min metrics.record_heartbeat(120); // 2 min metrics.record_heartbeat(600); // 10 min (gap = disconnection) assert_eq!(metrics.disconnections, 1); } #[test] fn test_storage_metrics() { let mut metrics = StorageMetrics::new(); // 50 GB provided, 25 GB used metrics.update( 50 * 1024 * 1024 * 1024, 25 * 1024 * 1024 * 1024, 1000, 50000, ); assert_eq!(metrics.utilization(), 50.0); assert!(metrics.score() > 0.0); } #[test] fn test_metrics_tracker() { let mut tracker = MetricsTracker::new(0); tracker.heartbeat(60); tracker.update_bandwidth(1_000_000, 1_000_000, 60); tracker.update_storage(10 * 1024 * 1024 * 1024, 5 * 1024 * 1024 * 1024, 100, 1000); let score = tracker.combined_score(); assert!(score > 0.0); assert!(score <= 1.0); } }