summaryrefslogtreecommitdiffstats
path: root/consensus/src/genesis.rs
diff options
context:
space:
mode:
Diffstat (limited to 'consensus/src/genesis.rs')
-rw-r--r--consensus/src/genesis.rs369
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
+ }
+}