use serde::{Deserialize, Serialize}; use blake3::Hasher; use std::collections::VecDeque; /// Proof that a validator relayed traffic through Tor for OnionCoin network /// This is OnionCoin's UNIQUE feature: earn rewards by being a Tor relay! #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RelayProof { /// Validator's public key pub validator_pubkey: [u8; 32], /// Time period this proof covers (Unix timestamp range) pub period_start: i64, pub period_end: i64, /// Metrics for this period pub metrics: RelayMetrics, /// Cryptographic proof of relay work /// In production: ZK-SNARK proof of bandwidth relay /// For prototype: hash chain of relayed packet hashes pub proof_data: Vec, /// Signature from validator #[serde(with = "serde_bytes")] pub signature: [u8; 64], } impl RelayProof { /// Standard proof period (1 hour) pub const PROOF_PERIOD_SECONDS: i64 = 3600; /// Create a new relay proof pub fn new( validator_pubkey: [u8; 32], period_start: i64, period_end: i64, metrics: RelayMetrics, ) -> Self { // Generate proof data (simplified) let proof_data = Self::generate_proof_data(&metrics); Self { validator_pubkey, period_start, period_end, metrics, proof_data, signature: [0u8; 64], // Placeholder } } /// Verify relay proof pub fn verify(&self) -> bool { // Check time period is valid if self.period_end <= self.period_start { return false; } // Check period is not too long if self.period_end - self.period_start > Self::PROOF_PERIOD_SECONDS * 24 { return false; } // Verify metrics are reasonable if !self.metrics.is_valid() { return false; } // Verify proof data let expected_proof = Self::generate_proof_data(&self.metrics); if self.proof_data != expected_proof { return false; } true } /// Generate proof data from metrics fn generate_proof_data(metrics: &RelayMetrics) -> Vec { let mut hasher = Hasher::new(); hasher.update(&metrics.bytes_relayed.to_le_bytes()); hasher.update(&metrics.packets_relayed.to_le_bytes()); hasher.update(&metrics.unique_circuits.to_le_bytes()); hasher.finalize().as_bytes().to_vec() } /// Calculate reward for this relay proof pub fn calculate_reward(&self) -> u64 { // Base reward per GB relayed const REWARD_PER_GB: u64 = 1; // 1 ONC per GB let gb_relayed = self.metrics.bytes_relayed / (1024 * 1024 * 1024); // Bonus for high circuit diversity let circuit_bonus = if self.metrics.unique_circuits > 100 { 1.2 } else if self.metrics.unique_circuits > 50 { 1.1 } else { 1.0 }; ((gb_relayed * REWARD_PER_GB) as f64 * circuit_bonus) as u64 } } /// Metrics about relay activity #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RelayMetrics { /// Total bytes relayed in this period pub bytes_relayed: u64, /// Total packets relayed pub packets_relayed: u64, /// Number of unique circuits served pub unique_circuits: u32, /// Average latency (milliseconds) pub avg_latency_ms: u32, /// Number of failed relay attempts pub failed_relays: u32, /// Uptime during this period (seconds) pub uptime_seconds: u64, } impl RelayMetrics { pub fn new() -> Self { Self { bytes_relayed: 0, packets_relayed: 0, unique_circuits: 0, avg_latency_ms: 0, failed_relays: 0, uptime_seconds: 0, } } /// Check if metrics are valid (sanity checks) pub fn is_valid(&self) -> bool { // Reasonable bounds if self.bytes_relayed > 1_000_000_000_000 { // > 1 TB return false; } if self.packets_relayed > 1_000_000_000 { // > 1B packets return false; } if self.avg_latency_ms > 60_000 { // > 1 minute return false; } // Packets should roughly match bytes if self.packets_relayed > 0 { let avg_packet_size = self.bytes_relayed / self.packets_relayed; if avg_packet_size < 20 || avg_packet_size > 1_000_000 { // Unrealistic packet size return false; } } true } /// Calculate quality score (0.0 - 1.0) pub fn quality_score(&self) -> f64 { let mut score = 1.0; // Penalty for high latency if self.avg_latency_ms > 5000 { score *= 0.5; } else if self.avg_latency_ms > 2000 { score *= 0.8; } // Penalty for failed relays if self.packets_relayed > 0 { let failure_rate = self.failed_relays as f64 / self.packets_relayed as f64; score *= (1.0 - failure_rate).max(0.0); } score.clamp(0.0, 1.0) } } impl Default for RelayMetrics { fn default() -> Self { Self::new() } } /// Tracks relay activity for a validator over time #[derive(Debug)] pub struct RelayTracker { /// Validator public key validator_pubkey: [u8; 32], /// Current period metrics current_metrics: RelayMetrics, /// Period start time period_start: i64, /// Historical proofs (last 24 hours) history: VecDeque, } impl RelayTracker { /// Maximum history to keep (24 hours = 24 proofs) const MAX_HISTORY: usize = 24; pub fn new(validator_pubkey: [u8; 32], current_time: i64) -> Self { Self { validator_pubkey, current_metrics: RelayMetrics::new(), period_start: current_time, history: VecDeque::new(), } } /// Record relayed bytes pub fn record_relay(&mut self, bytes: u64, latency_ms: u32, success: bool) { self.current_metrics.bytes_relayed += bytes; self.current_metrics.packets_relayed += 1; // Update average latency (simple moving average) let total = self.current_metrics.packets_relayed; self.current_metrics.avg_latency_ms = ((self.current_metrics.avg_latency_ms as u64 * (total - 1) + latency_ms as u64) / total) as u32; if !success { self.current_metrics.failed_relays += 1; } } /// Record a unique circuit pub fn record_circuit(&mut self) { self.current_metrics.unique_circuits += 1; } /// Record uptime pub fn record_uptime(&mut self, seconds: u64) { self.current_metrics.uptime_seconds += seconds; } /// Finalize current period and generate proof pub fn finalize_period(&mut self, current_time: i64) -> RelayProof { let proof = RelayProof::new( self.validator_pubkey, self.period_start, current_time, self.current_metrics.clone(), ); // Add to history self.history.push_back(proof.clone()); // Trim history if self.history.len() > Self::MAX_HISTORY { self.history.pop_front(); } // Reset for next period self.current_metrics = RelayMetrics::new(); self.period_start = current_time; proof } /// Get total bytes relayed in last 24 hours pub fn get_24h_bytes(&self) -> u64 { self.history .iter() .map(|p| p.metrics.bytes_relayed) .sum::() + self.current_metrics.bytes_relayed } /// Get average quality score over last 24 hours pub fn get_avg_quality(&self) -> f64 { if self.history.is_empty() { return self.current_metrics.quality_score(); } let total: f64 = self .history .iter() .map(|p| p.metrics.quality_score()) .sum(); total / self.history.len() as f64 } /// Get current metrics pub fn current_metrics(&self) -> &RelayMetrics { &self.current_metrics } /// Get proof history pub fn history(&self) -> &VecDeque { &self.history } } #[cfg(test)] mod tests { use super::*; #[test] fn test_relay_metrics_validation() { let mut metrics = RelayMetrics::new(); metrics.bytes_relayed = 1_000_000; // 1 MB metrics.packets_relayed = 1000; metrics.avg_latency_ms = 100; assert!(metrics.is_valid()); // Invalid: too many bytes metrics.bytes_relayed = 2_000_000_000_000; assert!(!metrics.is_valid()); } #[test] fn test_relay_proof_creation() { let pubkey = [1u8; 32]; let mut metrics = RelayMetrics::new(); metrics.bytes_relayed = 1_000_000_000; // 1 GB metrics.packets_relayed = 10_000; metrics.unique_circuits = 50; let proof = RelayProof::new(pubkey, 0, 3600, metrics); assert!(proof.verify()); assert_eq!(proof.calculate_reward(), 1); // 1 ONC for 1 GB } #[test] fn test_relay_tracker() { let pubkey = [1u8; 32]; let mut tracker = RelayTracker::new(pubkey, 0); // Record some relay activity tracker.record_relay(1000, 100, true); tracker.record_relay(2000, 150, true); tracker.record_circuit(); assert_eq!(tracker.current_metrics().bytes_relayed, 3000); assert_eq!(tracker.current_metrics().packets_relayed, 2); assert_eq!(tracker.current_metrics().unique_circuits, 1); // Finalize period let proof = tracker.finalize_period(3600); assert!(proof.verify()); assert_eq!(tracker.history().len(), 1); } #[test] fn test_quality_score() { let mut metrics = RelayMetrics::new(); metrics.packets_relayed = 100; metrics.failed_relays = 10; // 10% failure rate metrics.avg_latency_ms = 1000; let score = metrics.quality_score(); assert!(score < 1.0); assert!(score > 0.0); } #[test] fn test_reward_calculation() { let pubkey = [1u8; 32]; // 5 GB relayed with good circuit diversity let mut metrics = RelayMetrics::new(); metrics.bytes_relayed = 5 * 1024 * 1024 * 1024; metrics.unique_circuits = 150; // High diversity = bonus let proof = RelayProof::new(pubkey, 0, 3600, metrics); let reward = proof.calculate_reward(); assert!(reward >= 5); // At least 5 ONC base assert!(reward > 5); // Should have bonus } }