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/validator.rs | |
| download | onioncoin-0.1.0.tar.gz onioncoin-0.1.0.tar.xz onioncoin-0.1.0.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/validator.rs')
| -rw-r--r-- | consensus/src/validator.rs | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/consensus/src/validator.rs b/consensus/src/validator.rs new file mode 100644 index 0000000..b5c3a70 --- /dev/null +++ b/consensus/src/validator.rs @@ -0,0 +1,400 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Validator tier based on stake amount +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ValidatorTier { + /// Micro validator: 10 ONC minimum stake + /// Perfect for Raspberry Pi Zero, old hardware + Micro, + + /// Light validator: 100 ONC minimum stake + /// Raspberry Pi 4, basic VPS + Light, + + /// Standard validator: 1000 ONC minimum stake + /// Desktop PC, decent VPS + Standard, + + /// Power validator: 10000 ONC minimum stake + /// Dedicated server, 24/7 operation + Power, +} + +impl ValidatorTier { + /// Get minimum stake required for this tier + pub fn min_stake(&self) -> u64 { + match self { + Self::Micro => 10, + Self::Light => 100, + Self::Standard => 1000, + Self::Power => 10000, + } + } + + /// Get expected monthly return for this tier (approximate) + pub fn expected_monthly_return(&self) -> f64 { + match self { + Self::Micro => 0.1, + Self::Light => 1.0, + Self::Standard => 10.0, + Self::Power => 100.0, + } + } + + /// Determine tier from stake amount + pub fn from_stake(stake: u64) -> Self { + if stake >= 10000 { + Self::Power + } else if stake >= 1000 { + Self::Standard + } else if stake >= 100 { + Self::Light + } else { + Self::Micro + } + } + + /// Get stake weight multiplier for this tier + pub fn stake_multiplier(&self) -> f64 { + match self { + Self::Micro => 1.0, + Self::Light => 1.1, // 10% bonus + Self::Standard => 1.2, // 20% bonus + Self::Power => 1.3, // 30% bonus + } + } +} + +/// A validator in the OnionCoin network +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Validator { + /// Validator's public key (also .onion identity) + pub pubkey: [u8; 32], + + /// Staked amount + pub stake: u64, + + /// Validator tier + pub tier: ValidatorTier, + + /// When the stake was created (Unix timestamp) + pub stake_time: i64, + + /// Lock period end time (Unix timestamp) + pub unlock_time: i64, + + /// Onion address of the validator node + pub onion_address: String, + + /// Is currently active? + pub active: bool, + + /// Total blocks validated + pub blocks_validated: u64, + + /// Total rewards earned + pub total_rewards: u64, +} + +impl Validator { + /// Minimum lock period for stake (30 days) + pub const LOCK_PERIOD_DAYS: i64 = 30; + + /// Create a new validator + pub fn new( + pubkey: [u8; 32], + stake: u64, + onion_address: String, + current_time: i64, + ) -> Result<Self, ValidatorError> { + // Check minimum stake + if stake < ValidatorTier::Micro.min_stake() { + return Err(ValidatorError::InsufficientStake { + provided: stake, + minimum: ValidatorTier::Micro.min_stake(), + }); + } + + let tier = ValidatorTier::from_stake(stake); + let unlock_time = current_time + (Self::LOCK_PERIOD_DAYS * 24 * 3600); + + Ok(Self { + pubkey, + stake, + tier, + stake_time: current_time, + unlock_time, + onion_address, + active: true, + blocks_validated: 0, + total_rewards: 0, + }) + } + + /// Check if stake is locked + pub fn is_locked(&self, current_time: i64) -> bool { + current_time < self.unlock_time + } + + /// Add stake to existing validator + pub fn add_stake(&mut self, amount: u64, current_time: i64) { + self.stake += amount; + self.tier = ValidatorTier::from_stake(self.stake); + + // Extend lock period + self.unlock_time = current_time + (Self::LOCK_PERIOD_DAYS * 24 * 3600); + } + + /// Remove stake (only if unlocked) + pub fn remove_stake( + &mut self, + amount: u64, + current_time: i64, + ) -> Result<(), ValidatorError> { + if self.is_locked(current_time) { + return Err(ValidatorError::StakeLocked { + unlock_time: self.unlock_time, + }); + } + + if amount > self.stake { + return Err(ValidatorError::InsufficientStake { + provided: self.stake, + minimum: amount, + }); + } + + self.stake -= amount; + self.tier = ValidatorTier::from_stake(self.stake); + + // Deactivate if below minimum + if self.stake < ValidatorTier::Micro.min_stake() { + self.active = false; + } + + Ok(()) + } + + /// Record a validated block + pub fn record_block(&mut self, reward: u64) { + self.blocks_validated += 1; + self.total_rewards += reward; + } + + /// Calculate ROI percentage + pub fn roi_percentage(&self) -> f64 { + if self.stake == 0 { + 0.0 + } else { + (self.total_rewards as f64 / self.stake as f64) * 100.0 + } + } +} + +/// Registry of all validators in the network +#[derive(Debug, Default)] +pub struct ValidatorRegistry { + validators: HashMap<[u8; 32], Validator>, +} + +impl ValidatorRegistry { + pub fn new() -> Self { + Self { + validators: HashMap::new(), + } + } + + /// Register a new validator + pub fn register(&mut self, validator: Validator) -> Result<(), ValidatorError> { + if self.validators.contains_key(&validator.pubkey) { + return Err(ValidatorError::AlreadyRegistered); + } + + self.validators.insert(validator.pubkey, validator); + Ok(()) + } + + /// Get a validator by pubkey + pub fn get(&self, pubkey: &[u8; 32]) -> Option<&Validator> { + self.validators.get(pubkey) + } + + /// Get a mutable validator by pubkey + pub fn get_mut(&mut self, pubkey: &[u8; 32]) -> Option<&mut Validator> { + self.validators.get_mut(pubkey) + } + + /// Get all active validators + pub fn get_active(&self) -> Vec<&Validator> { + self.validators + .values() + .filter(|v| v.active) + .collect() + } + + /// Get validators by tier + pub fn get_by_tier(&self, tier: ValidatorTier) -> Vec<&Validator> { + self.validators + .values() + .filter(|v| v.tier == tier && v.active) + .collect() + } + + /// Total staked amount across all validators + pub fn total_staked(&self) -> u64 { + self.validators + .values() + .filter(|v| v.active) + .map(|v| v.stake) + .sum() + } + + /// Count of active validators + pub fn active_count(&self) -> usize { + self.validators + .values() + .filter(|v| v.active) + .count() + } + + /// Count by tier + pub fn count_by_tier(&self) -> HashMap<ValidatorTier, usize> { + let mut counts = HashMap::new(); + + for validator in self.validators.values() { + if validator.active { + *counts.entry(validator.tier).or_insert(0) += 1; + } + } + + counts + } + + /// Deactivate validator + pub fn deactivate(&mut self, pubkey: &[u8; 32]) -> Result<(), ValidatorError> { + let validator = self.validators.get_mut(pubkey) + .ok_or(ValidatorError::NotFound)?; + + validator.active = false; + Ok(()) + } + + /// Remove validator (only if stake is 0) + pub fn remove(&mut self, pubkey: &[u8; 32]) -> Result<(), ValidatorError> { + let validator = self.validators.get(pubkey) + .ok_or(ValidatorError::NotFound)?; + + if validator.stake > 0 { + return Err(ValidatorError::CannotRemoveWithStake); + } + + self.validators.remove(pubkey); + Ok(()) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ValidatorError { + #[error("Insufficient stake: provided {provided}, minimum {minimum}")] + InsufficientStake { provided: u64, minimum: u64 }, + + #[error("Stake is locked until timestamp {unlock_time}")] + StakeLocked { unlock_time: i64 }, + + #[error("Validator already registered")] + AlreadyRegistered, + + #[error("Validator not found")] + NotFound, + + #[error("Cannot remove validator with non-zero stake")] + CannotRemoveWithStake, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_validator_tiers() { + assert_eq!(ValidatorTier::from_stake(10), ValidatorTier::Micro); + assert_eq!(ValidatorTier::from_stake(100), ValidatorTier::Light); + assert_eq!(ValidatorTier::from_stake(1000), ValidatorTier::Standard); + assert_eq!(ValidatorTier::from_stake(10000), ValidatorTier::Power); + } + + #[test] + fn test_validator_creation() { + let pubkey = [1u8; 32]; + let validator = Validator::new( + pubkey, + 1000, + "test.onion:9333".to_string(), + 0, + ).unwrap(); + + assert_eq!(validator.stake, 1000); + assert_eq!(validator.tier, ValidatorTier::Standard); + assert!(validator.active); + } + + #[test] + fn test_stake_locking() { + let pubkey = [1u8; 32]; + let mut validator = Validator::new( + pubkey, + 1000, + "test.onion:9333".to_string(), + 0, + ).unwrap(); + + // Should be locked + assert!(validator.is_locked(1000)); + + // Should be unlocked after lock period + let unlock_time = Validator::LOCK_PERIOD_DAYS * 24 * 3600; + assert!(!validator.is_locked(unlock_time + 1)); + + // Cannot remove stake while locked + assert!(validator.remove_stake(100, 1000).is_err()); + + // Can remove stake after unlock + assert!(validator.remove_stake(100, unlock_time + 1).is_ok()); + assert_eq!(validator.stake, 900); + } + + #[test] + fn test_validator_registry() { + let mut registry = ValidatorRegistry::new(); + + let validator = Validator::new( + [1u8; 32], + 1000, + "test.onion:9333".to_string(), + 0, + ).unwrap(); + + registry.register(validator).unwrap(); + + assert_eq!(registry.active_count(), 1); + assert_eq!(registry.total_staked(), 1000); + } + + #[test] + fn test_validator_roi() { + let mut validator = Validator::new( + [1u8; 32], + 1000, + "test.onion:9333".to_string(), + 0, + ).unwrap(); + + validator.record_block(5); + validator.record_block(5); + + assert_eq!(validator.blocks_validated, 2); + assert_eq!(validator.total_rewards, 10); + assert_eq!(validator.roi_percentage(), 1.0); // 10/1000 = 1% + } +} |
