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/genesis.rs | |
| download | onioncoin-main.tar.gz onioncoin-main.tar.xz onioncoin-main.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/genesis.rs')
| -rw-r--r-- | consensus/src/genesis.rs | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/consensus/src/genesis.rs b/consensus/src/genesis.rs new file mode 100644 index 0000000..f30f29e --- /dev/null +++ b/consensus/src/genesis.rs @@ -0,0 +1,369 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Genesis mining: fair launch system for OnionCoin +/// Phase 1 (first 6 months): Earn ONC by running Tor relays +/// No pre-mine, no ICO, completely fair distribution +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenesisMining { + /// Genesis phase configuration + config: GenesisConfig, + + /// Registered relay nodes + relay_nodes: HashMap<[u8; 32], RelayNode>, + + /// Start time of genesis phase + start_time: i64, + + /// Total ONC distributed so far + total_distributed: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenesisConfig { + /// Duration of genesis phase (seconds) + /// Default: 6 months = 15,552,000 seconds + pub duration_seconds: i64, + + /// Total ONC to distribute in genesis phase + /// Default: 1,000,000 ONC (fair initial distribution) + pub total_genesis_supply: u64, + + /// Minimum relay activity to qualify (bytes) + /// Default: 1 GB + pub min_relay_bytes: u64, + + /// Bonus multiplier for early participants + /// First month: 2x, decreases linearly to 1x + pub early_bird_multiplier: bool, +} + +impl Default for GenesisConfig { + fn default() -> Self { + Self { + duration_seconds: 6 * 30 * 24 * 3600, // ~6 months + total_genesis_supply: 1_000_000, // 1M ONC + min_relay_bytes: 1024 * 1024 * 1024, // 1 GB + early_bird_multiplier: true, + } + } +} + +impl GenesisMining { + pub fn new(config: GenesisConfig, start_time: i64) -> Self { + Self { + config, + relay_nodes: HashMap::new(), + start_time, + total_distributed: 0, + } + } + + /// Register a relay node for genesis mining + pub fn register_relay(&mut self, pubkey: [u8; 32], onion_address: String, current_time: i64) { + if !self.is_active(current_time) { + return; + } + + self.relay_nodes.entry(pubkey).or_insert_with(|| RelayNode { + pubkey, + onion_address, + registration_time: current_time, + total_bytes_relayed: 0, + total_uptime_seconds: 0, + rewards_earned: 0, + }); + } + + /// Record relay activity + pub fn record_relay_activity( + &mut self, + pubkey: &[u8; 32], + bytes_relayed: u64, + uptime_seconds: u64, + ) { + if let Some(node) = self.relay_nodes.get_mut(pubkey) { + node.total_bytes_relayed += bytes_relayed; + node.total_uptime_seconds += uptime_seconds; + } + } + + /// Calculate rewards for a relay node + pub fn calculate_rewards(&self, pubkey: &[u8; 32], current_time: i64) -> u64 { + let node = match self.relay_nodes.get(pubkey) { + Some(n) => n, + None => return 0, + }; + + // Must meet minimum requirement + if node.total_bytes_relayed < self.config.min_relay_bytes { + return 0; + } + + // Base reward proportional to relay work + let total_relay_work: u64 = self + .relay_nodes + .values() + .map(|n| n.total_bytes_relayed) + .sum(); + + if total_relay_work == 0 { + return 0; + } + + let base_reward = (self.config.total_genesis_supply as f64 + * (node.total_bytes_relayed as f64 / total_relay_work as f64)) + as u64; + + // Early bird bonus + let multiplier = if self.config.early_bird_multiplier { + self.early_bird_multiplier(node.registration_time, current_time) + } else { + 1.0 + }; + + // Uptime bonus (up to 20% extra) + let uptime_bonus = self.uptime_bonus(node); + + ((base_reward as f64) * multiplier * (1.0 + uptime_bonus)) as u64 + } + + /// Early bird multiplier: 2x for first month, linear decay to 1x + fn early_bird_multiplier(&self, registration_time: i64, _current_time: i64) -> f64 { + let elapsed_since_genesis = registration_time - self.start_time; + let one_month = 30 * 24 * 3600; + + if elapsed_since_genesis < 0 { + return 2.0; // Registered before/at genesis + } + + if elapsed_since_genesis >= one_month { + return 1.0; // After first month + } + + // Linear decay: 2.0 -> 1.0 over first month + 2.0 - (elapsed_since_genesis as f64 / one_month as f64) + } + + /// Uptime bonus: up to 20% for 99%+ uptime + fn uptime_bonus(&self, node: &RelayNode) -> f64 { + let total_possible_uptime = chrono::Utc::now().timestamp() - node.registration_time; + + if total_possible_uptime <= 0 { + return 0.0; + } + + let uptime_ratio = node.total_uptime_seconds as f64 / total_possible_uptime as f64; + + if uptime_ratio >= 0.99 { + 0.20 // 20% bonus + } else if uptime_ratio >= 0.95 { + 0.10 // 10% bonus + } else if uptime_ratio >= 0.90 { + 0.05 // 5% bonus + } else { + 0.0 + } + } + + /// Distribute rewards to all qualifying nodes + pub fn distribute_rewards(&mut self, current_time: i64) -> HashMap<[u8; 32], u64> { + let mut rewards = HashMap::new(); + + for pubkey in self.relay_nodes.keys().copied().collect::<Vec<_>>() { + let reward = self.calculate_rewards(&pubkey, current_time); + + if reward > 0 { + if let Some(node) = self.relay_nodes.get_mut(&pubkey) { + node.rewards_earned = reward; + self.total_distributed += reward; + } + rewards.insert(pubkey, reward); + } + } + + rewards + } + + /// Check if genesis phase is still active + pub fn is_active(&self, current_time: i64) -> bool { + let elapsed = current_time - self.start_time; + elapsed >= 0 && elapsed < self.config.duration_seconds + } + + /// Get phase progress (0.0 - 1.0) + pub fn progress(&self, current_time: i64) -> f64 { + let elapsed = (current_time - self.start_time).max(0); + let progress = elapsed as f64 / self.config.duration_seconds as f64; + progress.clamp(0.0, 1.0) + } + + /// Get leaderboard of top relay nodes + pub fn leaderboard(&self, limit: usize) -> Vec<(&RelayNode, u64)> { + let mut nodes: Vec<_> = self + .relay_nodes + .values() + .map(|node| { + let reward = self.calculate_rewards(&node.pubkey, chrono::Utc::now().timestamp()); + (node, reward) + }) + .collect(); + + nodes.sort_by(|a, b| b.1.cmp(&a.1)); + nodes.into_iter().take(limit).collect() + } + + /// Get statistics + pub fn stats(&self) -> GenesisStats { + GenesisStats { + total_relay_nodes: self.relay_nodes.len(), + total_bytes_relayed: self.relay_nodes.values().map(|n| n.total_bytes_relayed).sum(), + total_distributed: self.total_distributed, + remaining_supply: self.config.total_genesis_supply.saturating_sub(self.total_distributed), + } + } +} + +/// A relay node participating in genesis mining +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RelayNode { + pub pubkey: [u8; 32], + pub onion_address: String, + pub registration_time: i64, + pub total_bytes_relayed: u64, + pub total_uptime_seconds: u64, + pub rewards_earned: u64, +} + +/// Statistics about genesis mining +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GenesisStats { + pub total_relay_nodes: usize, + pub total_bytes_relayed: u64, + pub total_distributed: u64, + pub remaining_supply: u64, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_genesis_mining_registration() { + let config = GenesisConfig::default(); + let mut genesis = GenesisMining::new(config, 0); + + genesis.register_relay([1u8; 32], "test1.onion".to_string(), 0); + genesis.register_relay([2u8; 32], "test2.onion".to_string(), 100); + + assert_eq!(genesis.relay_nodes.len(), 2); + } + + #[test] + fn test_relay_activity_recording() { + let config = GenesisConfig::default(); + let mut genesis = GenesisMining::new(config, 0); + + genesis.register_relay([1u8; 32], "test.onion".to_string(), 0); + genesis.record_relay_activity(&[1u8; 32], 1_000_000_000, 3600); + + let node = genesis.relay_nodes.get(&[1u8; 32]).unwrap(); + assert_eq!(node.total_bytes_relayed, 1_000_000_000); + assert_eq!(node.total_uptime_seconds, 3600); + } + + #[test] + fn test_reward_calculation() { + let config = GenesisConfig::default(); + let mut genesis = GenesisMining::new(config, 0); + + // Node 1: 50% of relay work + genesis.register_relay([1u8; 32], "test1.onion".to_string(), 0); + genesis.record_relay_activity(&[1u8; 32], 50 * 1024 * 1024 * 1024, 86400); + + // Node 2: 50% of relay work + genesis.register_relay([2u8; 32], "test2.onion".to_string(), 0); + genesis.record_relay_activity(&[2u8; 32], 50 * 1024 * 1024 * 1024, 86400); + + let reward1 = genesis.calculate_rewards(&[1u8; 32], 100); + let reward2 = genesis.calculate_rewards(&[2u8; 32], 100); + + // Should get roughly equal rewards (both ~50% of total) + assert!(reward1 > 400_000 && reward1 < 600_000); + assert!(reward2 > 400_000 && reward2 < 600_000); + } + + #[test] + fn test_early_bird_bonus() { + let config = GenesisConfig::default(); + let genesis = GenesisMining::new(config, 0); + + // Registered at genesis + let multiplier_early = genesis.early_bird_multiplier(0, 100); + assert_eq!(multiplier_early, 2.0); + + // Registered after 1 month + let one_month = 30 * 24 * 3600; + let multiplier_late = genesis.early_bird_multiplier(one_month, one_month + 100); + assert_eq!(multiplier_late, 1.0); + + // Registered mid-month + let multiplier_mid = genesis.early_bird_multiplier(one_month / 2, one_month / 2 + 100); + assert!(multiplier_mid > 1.0 && multiplier_mid < 2.0); + } + + #[test] + fn test_minimum_relay_requirement() { + let config = GenesisConfig::default(); + let mut genesis = GenesisMining::new(config, 0); + + genesis.register_relay([1u8; 32], "test.onion".to_string(), 0); + + // Below minimum + genesis.record_relay_activity(&[1u8; 32], 1_000_000, 3600); + let reward = genesis.calculate_rewards(&[1u8; 32], 100); + assert_eq!(reward, 0); + + // Above minimum + genesis.record_relay_activity(&[1u8; 32], 2 * 1024 * 1024 * 1024, 3600); + let reward = genesis.calculate_rewards(&[1u8; 32], 100); + assert!(reward > 0); + } + + #[test] + fn test_phase_progress() { + let config = GenesisConfig::default(); + let genesis = GenesisMining::new(config.clone(), 0); + + assert_eq!(genesis.progress(0), 0.0); + + let half_duration = config.duration_seconds / 2; + let mid_progress = genesis.progress(half_duration); + assert!((mid_progress - 0.5).abs() < 0.01); + + let end_progress = genesis.progress(config.duration_seconds); + assert_eq!(end_progress, 1.0); + } + + #[test] + fn test_leaderboard() { + let config = GenesisConfig::default(); + let mut genesis = GenesisMining::new(config, 0); + + // Register 3 nodes with different relay amounts + genesis.register_relay([1u8; 32], "test1.onion".to_string(), 0); + genesis.record_relay_activity(&[1u8; 32], 100 * 1024 * 1024 * 1024, 86400); + + genesis.register_relay([2u8; 32], "test2.onion".to_string(), 0); + genesis.record_relay_activity(&[2u8; 32], 50 * 1024 * 1024 * 1024, 86400); + + genesis.register_relay([3u8; 32], "test3.onion".to_string(), 0); + genesis.record_relay_activity(&[3u8; 32], 25 * 1024 * 1024 * 1024, 86400); + + let leaderboard = genesis.leaderboard(3); + + assert_eq!(leaderboard.len(), 3); + assert_eq!(leaderboard[0].0.pubkey, [1u8; 32]); // Top relay + assert!(leaderboard[0].1 > leaderboard[1].1); // Top has higher reward + } +} |
