summaryrefslogtreecommitdiffstats
path: root/consensus/src/validator.rs
diff options
context:
space:
mode:
authorgabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
committergabrix73 <gabriel1@frozenstar.info>2026-06-01 18:41:36 +0200
commit9f5d864d533ce86459e654f5d78212933c0269ea (patch)
tree4b71e8c7ab4e78da93e5f3164803da4de68c7031 /consensus/src/validator.rs
downloadonioncoin-main.tar.gz
onioncoin-main.tar.xz
onioncoin-main.zip
Initial commit: OnionCoin prototype with Proof-of-Relay consensusHEADv0.1.0main
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.rs400
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%
+ }
+}