summaryrefslogtreecommitdiffstats
path: root/consensus/src/metrics.rs
diff options
context:
space:
mode:
authorgabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
committergabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
commit9f5d864d533ce86459e654f5d78212933c0269ea (patch)
tree4b71e8c7ab4e78da93e5f3164803da4de68c7031 /consensus/src/metrics.rs
downloadonioncoin-main.tar.gz
onioncoin-main.tar.xz
onioncoin-main.zip
Initial commit: OnionCoin prototype with Proof-of-Relay consensusHEADv0.1.0main
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.rs388
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);
+ }
+}