diff options
| author | gabrix73 <gabriel1@frozenstar.info> | 2026-06-01 18:41:36 +0200 |
|---|---|---|
| committer | gabrix73 <gabriel1@frozenstar.info> | 2026-06-01 18:41:36 +0200 |
| commit | 9f5d864d533ce86459e654f5d78212933c0269ea (patch) | |
| tree | 4b71e8c7ab4e78da93e5f3164803da4de68c7031 /consensus/src/metrics.rs | |
| download | onioncoin-9e960fa6d453b29aef970ba3cd81e9cbbfd94bcc.tar.gz onioncoin-9e960fa6d453b29aef970ba3cd81e9cbbfd94bcc.tar.xz onioncoin-9e960fa6d453b29aef970ba3cd81e9cbbfd94bcc.zip | |
OnionCoin is a privacy cryptocurrency that rewards Tor relay operators
through a unique Proof-of-Contribution consensus mechanism.
Core Features:
- Proof-of-Relay: 30% of block rewards go to Tor operators
- Native .onion node identity (no IP exposure)
- Temporal obfuscation protocols
- Dandelion++ over Tor propagation
- Native inheritance system with dead man's switch
Technical Stack:
- Rust workspace with 8 crates
- Ed25519/X25519 cryptography
- arti (Rust Tor client) integration planned
- 10 minute block time, 5-10 TPS design
Status: Prototype
- Consensus logic complete with passing tests (30/33)
- Network layer conceptual design complete
- Tor integration pending
- Testnet launch planned Q3 2026
License: MIT
Author: Gabriele Salati (virebent)
Contact: g48rix@gmail.com
Website: https://www.gabrielesalati.eu
Repository: https://git.virebent.art/virebent/onioncoin
Diffstat (limited to 'consensus/src/metrics.rs')
| -rw-r--r-- | consensus/src/metrics.rs | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/consensus/src/metrics.rs b/consensus/src/metrics.rs new file mode 100644 index 0000000..68da6ad --- /dev/null +++ b/consensus/src/metrics.rs @@ -0,0 +1,388 @@ +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<BandwidthMetrics>, + + /// 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); + } +} |
