diff options
Diffstat (limited to 'consensus/src/proof_of_contribution.rs')
| -rw-r--r-- | consensus/src/proof_of_contribution.rs | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/consensus/src/proof_of_contribution.rs b/consensus/src/proof_of_contribution.rs new file mode 100644 index 0000000..f13fe56 --- /dev/null +++ b/consensus/src/proof_of_contribution.rs @@ -0,0 +1,340 @@ +use crate::{Validator, RelayProof, BandwidthMetrics, UptimeMetrics, StorageMetrics}; +use serde::{Deserialize, Serialize}; + +/// Proof-of-Contribution: OnionCoin's unique consensus mechanism +/// Combines stake + relay work + bandwidth + uptime + storage +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProofOfContribution { + /// Validator public key + pub validator_pubkey: [u8; 32], + + /// Contribution score breakdown + pub score: ContributionScore, + + /// Timestamp + pub timestamp: i64, +} + +impl ProofOfContribution { + /// Calculate contribution score for a validator + pub fn calculate( + validator: &Validator, + relay_proof: Option<&RelayProof>, + bandwidth: &BandwidthMetrics, + uptime: &UptimeMetrics, + storage: &StorageMetrics, + ) -> Self { + let score = ContributionScore::calculate( + validator, + relay_proof, + bandwidth, + uptime, + storage, + ); + + Self { + validator_pubkey: validator.pubkey, + score, + timestamp: chrono::Utc::now().timestamp(), + } + } + + /// Get total weighted score + pub fn total_score(&self) -> f64 { + self.score.weighted_total() + } +} + +/// Breakdown of contribution score components +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ContributionScore { + /// Stake component score (0.0 - 1.0) + pub stake_score: f64, + + /// Relay work score (0.0 - 1.0) + pub relay_score: f64, + + /// Bandwidth score (0.0 - 1.0) + pub bandwidth_score: f64, + + /// Uptime score (0.0 - 1.0) + pub uptime_score: f64, + + /// Storage score (0.0 - 1.0) + pub storage_score: f64, + + /// Weights for each component + pub weights: ScoreWeights, +} + +/// Weights for different contribution components +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ScoreWeights { + pub stake: f64, + pub relay: f64, + pub bandwidth: f64, + pub uptime: f64, + pub storage: f64, +} + +impl Default for ScoreWeights { + fn default() -> Self { + Self { + stake: 0.40, // 40% - Still important but not dominant + relay: 0.30, // 30% - UNIQUE: Tor relay work + bandwidth: 0.15, // 15% - Network contribution + uptime: 0.10, // 10% - Reliability + storage: 0.05, // 5% - Data availability + } + } +} + +impl ScoreWeights { + /// Verify weights sum to 1.0 + pub fn is_valid(&self) -> bool { + let sum = self.stake + self.relay + self.bandwidth + self.uptime + self.storage; + (sum - 1.0).abs() < 0.001 + } +} + +impl ContributionScore { + /// Calculate contribution score from validator metrics + pub fn calculate( + validator: &Validator, + relay_proof: Option<&RelayProof>, + bandwidth: &BandwidthMetrics, + uptime: &UptimeMetrics, + storage: &StorageMetrics, + ) -> Self { + let weights = ScoreWeights::default(); + + // 1. Stake score (logarithmic to reduce whale advantage) + let stake_score = Self::calculate_stake_score(validator); + + // 2. Relay score (from Proof-of-Relay) + let relay_score = Self::calculate_relay_score(relay_proof); + + // 3. Bandwidth score + let bandwidth_score = bandwidth.score(); + + // 4. Uptime score + let uptime_score = uptime.score(); + + // 5. Storage score + let storage_score = storage.score(); + + Self { + stake_score, + relay_score, + bandwidth_score, + uptime_score, + storage_score, + weights, + } + } + + /// Calculate stake score (logarithmic) + /// This prevents whales from dominating + fn calculate_stake_score(validator: &Validator) -> f64 { + if validator.stake == 0 { + return 0.0; + } + + // Use log scale to reduce advantage of large stakes + // 10 ONC = 0.1, 100 ONC = 0.2, 1000 ONC = 0.3, etc. + let log_stake = (validator.stake as f64).log10(); + let max_log_stake = 5.0; // 100,000 ONC = max score + + // Apply tier multiplier + let tier_multiplier = validator.tier.stake_multiplier(); + + ((log_stake / max_log_stake) * tier_multiplier).clamp(0.0, 1.0) + } + + /// Calculate relay score from relay proof + fn calculate_relay_score(relay_proof: Option<&RelayProof>) -> f64 { + match relay_proof { + Some(proof) => { + // Base score from bytes relayed + let gb_relayed = proof.metrics.bytes_relayed as f64 / (1024.0 * 1024.0 * 1024.0); + let base_score = (gb_relayed / 100.0).clamp(0.0, 0.8); // Max 0.8 from volume + + // Quality multiplier + let quality = proof.metrics.quality_score(); + + // Circuit diversity bonus + let diversity_bonus = if proof.metrics.unique_circuits > 100 { + 0.2 + } else if proof.metrics.unique_circuits > 50 { + 0.1 + } else { + 0.0 + }; + + (base_score * quality + diversity_bonus).clamp(0.0, 1.0) + } + None => 0.0, + } + } + + /// Calculate weighted total score + pub fn weighted_total(&self) -> f64 { + self.stake_score * self.weights.stake + + self.relay_score * self.weights.relay + + self.bandwidth_score * self.weights.bandwidth + + self.uptime_score * self.weights.uptime + + self.storage_score * self.weights.storage + } + + /// Get human-readable breakdown + pub fn breakdown(&self) -> String { + format!( + "Stake: {:.2}×{:.0}% = {:.3}\n\ + Relay: {:.2}×{:.0}% = {:.3}\n\ + Bandwidth: {:.2}×{:.0}% = {:.3}\n\ + Uptime: {:.2}×{:.0}% = {:.3}\n\ + Storage: {:.2}×{:.0}% = {:.3}\n\ + ───────────────────────\n\ + TOTAL: {:.3}", + self.stake_score, + self.weights.stake * 100.0, + self.stake_score * self.weights.stake, + self.relay_score, + self.weights.relay * 100.0, + self.relay_score * self.weights.relay, + self.bandwidth_score, + self.weights.bandwidth * 100.0, + self.bandwidth_score * self.weights.bandwidth, + self.uptime_score, + self.weights.uptime * 100.0, + self.uptime_score * self.weights.uptime, + self.storage_score, + self.weights.storage * 100.0, + self.storage_score * self.weights.storage, + self.weighted_total() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::RelayMetrics; + + #[test] + fn test_score_weights_valid() { + let weights = ScoreWeights::default(); + assert!(weights.is_valid()); + } + + #[test] + fn test_stake_score_logarithmic() { + let validator_small = create_test_validator(100); // 100 ONC + let validator_large = create_test_validator(10000); // 10,000 ONC + + let score_small = ContributionScore::calculate_stake_score(&validator_small); + let score_large = ContributionScore::calculate_stake_score(&validator_large); + + // Large stake should have higher score but not 100x higher + assert!(score_large > score_small); + assert!(score_large < score_small * 2.0); // Logarithmic dampening + } + + #[test] + fn test_relay_score() { + let mut metrics = RelayMetrics::new(); + metrics.bytes_relayed = 50 * 1024 * 1024 * 1024; // 50 GB + metrics.unique_circuits = 150; + metrics.packets_relayed = 1_000_000; + + let proof = RelayProof::new([1u8; 32], 0, 3600, metrics); + let score = ContributionScore::calculate_relay_score(Some(&proof)); + + assert!(score > 0.0); + assert!(score <= 1.0); + } + + #[test] + fn test_contribution_score_calculation() { + let validator = create_test_validator(1000); + + let mut relay_metrics = RelayMetrics::new(); + relay_metrics.bytes_relayed = 10 * 1024 * 1024 * 1024; // 10 GB + relay_metrics.unique_circuits = 50; + relay_metrics.packets_relayed = 100_000; + + let relay_proof = RelayProof::new([1u8; 32], 0, 3600, relay_metrics); + + let mut bandwidth = BandwidthMetrics::new(); + bandwidth.update(1_000_000, 1_000_000, 100); + + let uptime = UptimeMetrics::new(0); + + let mut storage = StorageMetrics::new(); + storage.update(20 * 1024 * 1024 * 1024, 10 * 1024 * 1024 * 1024, 1000, 10000); + + let score = ContributionScore::calculate( + &validator, + Some(&relay_proof), + &bandwidth, + &uptime, + &storage, + ); + + let total = score.weighted_total(); + assert!(total > 0.0); + assert!(total <= 1.0); + + println!("{}", score.breakdown()); + } + + #[test] + fn test_micro_validator_can_compete() { + // Micro validator with great relay work + let micro = create_test_validator(10); // Only 10 ONC + let mut relay_metrics = RelayMetrics::new(); + relay_metrics.bytes_relayed = 100 * 1024 * 1024 * 1024; // 100 GB relay! + relay_metrics.unique_circuits = 200; + relay_metrics.packets_relayed = 1_000_000; + + let relay_proof = RelayProof::new([1u8; 32], 0, 3600, relay_metrics); + + let mut bandwidth = BandwidthMetrics::new(); + bandwidth.update(10_000_000, 10_000_000, 100); // Good bandwidth + + let mut uptime = UptimeMetrics::new(0); + for i in 1..=1000 { + uptime.record_heartbeat(i * 60); // Great uptime + } + + let storage = StorageMetrics::new(); + + let micro_score = ContributionScore::calculate( + µ, + Some(&relay_proof), + &bandwidth, + &uptime, + &storage, + ); + + // Power validator with just stake + let power = create_test_validator(10000); // 10,000 ONC + let power_score = ContributionScore::calculate( + &power, + None, // No relay work + &BandwidthMetrics::new(), + &UptimeMetrics::new(0), + &StorageMetrics::new(), + ); + + println!("Micro validator (10 ONC + relay work):\n{}", micro_score.breakdown()); + println!("\nPower validator (10,000 ONC, no work):\n{}", power_score.breakdown()); + + // Micro validator should be competitive! + assert!(micro_score.weighted_total() > power_score.weighted_total() * 0.5); + } + + fn create_test_validator(stake: u64) -> Validator { + Validator::new([1u8; 32], stake, "test.onion".to_string(), 0).unwrap() + } +} |
