summaryrefslogtreecommitdiffstats
path: root/inheritance/src/unlock.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 /inheritance/src/unlock.rs
downloadonioncoin-9f5d864d533ce86459e654f5d78212933c0269ea.tar.gz
onioncoin-9f5d864d533ce86459e654f5d78212933c0269ea.tar.xz
onioncoin-9f5d864d533ce86459e654f5d78212933c0269ea.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 'inheritance/src/unlock.rs')
-rw-r--r--inheritance/src/unlock.rs469
1 files changed, 469 insertions, 0 deletions
diff --git a/inheritance/src/unlock.rs b/inheritance/src/unlock.rs
new file mode 100644
index 0000000..61d3e5c
--- /dev/null
+++ b/inheritance/src/unlock.rs
@@ -0,0 +1,469 @@
+use serde::{Deserialize, Serialize};
+use crate::contract::{InheritanceContract, ContractStatus};
+
+/// Progressive unlock tier
+/// This is OnionCoin's INNOVATIVE feature: gradual unlock gives owner time to recover!
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct UnlockTier {
+ /// Tier number (1, 2, 3...)
+ pub tier: u32,
+
+ /// Days after grace period expiry
+ pub days_after_expiry: i64,
+
+ /// Percentage of total to unlock at this tier
+ pub unlock_percentage: u8,
+
+ /// Has this tier been unlocked?
+ pub unlocked: bool,
+
+ /// Timestamp when unlocked
+ pub unlock_time: Option<i64>,
+}
+
+impl UnlockTier {
+ pub fn new(tier: u32, days_after_expiry: i64, unlock_percentage: u8) -> Self {
+ Self {
+ tier,
+ days_after_expiry,
+ unlock_percentage,
+ unlocked: false,
+ unlock_time: None,
+ }
+ }
+
+ /// Check if this tier should be unlocked
+ pub fn should_unlock(&self, time_since_expiry: i64) -> bool {
+ !self.unlocked && time_since_expiry >= self.days_after_expiry * 24 * 3600
+ }
+
+ /// Mark tier as unlocked
+ pub fn mark_unlocked(&mut self, current_time: i64) {
+ self.unlocked = true;
+ self.unlock_time = Some(current_time);
+ }
+}
+
+/// Complete unlock schedule with multiple tiers
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct UnlockSchedule {
+ /// All unlock tiers
+ pub tiers: Vec<UnlockTier>,
+
+ /// Grace period expiry time
+ pub grace_expiry_time: i64,
+
+ /// Total unlocked so far (percentage)
+ pub total_unlocked_percentage: u8,
+}
+
+impl UnlockSchedule {
+ /// Create standard 4-tier progressive unlock
+ /// Tier 1 (30 days): 10%
+ /// Tier 2 (60 days): 25% (35% total)
+ /// Tier 3 (90 days): 35% (70% total)
+ /// Tier 4 (120 days): 30% (100% total)
+ pub fn standard(grace_expiry_time: i64) -> Self {
+ let tiers = vec![
+ UnlockTier::new(1, 30, 10), // 10% after 30 days
+ UnlockTier::new(2, 60, 25), // 25% after 60 days (35% total)
+ UnlockTier::new(3, 90, 35), // 35% after 90 days (70% total)
+ UnlockTier::new(4, 120, 30), // 30% after 120 days (100% total)
+ ];
+
+ Self {
+ tiers,
+ grace_expiry_time,
+ total_unlocked_percentage: 0,
+ }
+ }
+
+ /// Create aggressive 3-tier unlock (faster)
+ /// Tier 1 (14 days): 25%
+ /// Tier 2 (30 days): 35% (60% total)
+ /// Tier 3 (60 days): 40% (100% total)
+ pub fn aggressive(grace_expiry_time: i64) -> Self {
+ let tiers = vec![
+ UnlockTier::new(1, 14, 25), // 25% after 14 days
+ UnlockTier::new(2, 30, 35), // 35% after 30 days
+ UnlockTier::new(3, 60, 40), // 40% after 60 days
+ ];
+
+ Self {
+ tiers,
+ grace_expiry_time,
+ total_unlocked_percentage: 0,
+ }
+ }
+
+ /// Create conservative 5-tier unlock (slower, more chances to recover)
+ /// Tier 1 (60 days): 5%
+ /// Tier 2 (90 days): 10% (15% total)
+ /// Tier 3 (120 days): 20% (35% total)
+ /// Tier 4 (180 days): 30% (65% total)
+ /// Tier 5 (365 days): 35% (100% total)
+ pub fn conservative(grace_expiry_time: i64) -> Self {
+ let tiers = vec![
+ UnlockTier::new(1, 60, 5), // 5% after 60 days
+ UnlockTier::new(2, 90, 10), // 10% after 90 days
+ UnlockTier::new(3, 120, 20), // 20% after 120 days
+ UnlockTier::new(4, 180, 30), // 30% after 180 days
+ UnlockTier::new(5, 365, 35), // 35% after 365 days
+ ];
+
+ Self {
+ tiers,
+ grace_expiry_time,
+ total_unlocked_percentage: 0,
+ }
+ }
+
+ /// Custom unlock schedule
+ pub fn custom(grace_expiry_time: i64, tiers: Vec<UnlockTier>) -> Result<Self, UnlockError> {
+ // Validate total percentage = 100%
+ let total: u32 = tiers.iter().map(|t| t.unlock_percentage as u32).sum();
+ if total != 100 {
+ return Err(UnlockError::InvalidTotalPercentage(total));
+ }
+
+ // Validate tiers are in order
+ for i in 1..tiers.len() {
+ if tiers[i].days_after_expiry <= tiers[i - 1].days_after_expiry {
+ return Err(UnlockError::InvalidTierOrder);
+ }
+ }
+
+ Ok(Self {
+ tiers,
+ grace_expiry_time,
+ total_unlocked_percentage: 0,
+ })
+ }
+
+ /// Process unlocks for current time
+ pub fn process_unlocks(&mut self, current_time: i64) -> Vec<UnlockEvent> {
+ let time_since_expiry = current_time - self.grace_expiry_time;
+ let mut events = Vec::new();
+
+ for tier in &mut self.tiers {
+ if tier.should_unlock(time_since_expiry) {
+ tier.mark_unlocked(current_time);
+ self.total_unlocked_percentage += tier.unlock_percentage;
+
+ events.push(UnlockEvent {
+ tier: tier.tier,
+ percentage: tier.unlock_percentage,
+ timestamp: current_time,
+ });
+ }
+ }
+
+ events
+ }
+
+ /// Get next unlock tier
+ pub fn next_unlock(&self) -> Option<&UnlockTier> {
+ self.tiers.iter().find(|t| !t.unlocked)
+ }
+
+ /// Get time until next unlock
+ pub fn time_until_next_unlock(&self, current_time: i64) -> Option<i64> {
+ self.next_unlock().map(|tier| {
+ let target_time = self.grace_expiry_time + tier.days_after_expiry * 24 * 3600;
+ (target_time - current_time).max(0)
+ })
+ }
+
+ /// Check if fully unlocked
+ pub fn is_fully_unlocked(&self) -> bool {
+ self.total_unlocked_percentage >= 100
+ }
+
+ /// Get summary of unlock progress
+ pub fn progress_summary(&self, current_time: i64) -> String {
+ let mut summary = format!("Unlocked: {}%\n", self.total_unlocked_percentage);
+
+ for tier in &self.tiers {
+ let status = if tier.unlocked {
+ format!("✓ Unlocked at {}",
+ chrono::DateTime::from_timestamp(tier.unlock_time.unwrap(), 0)
+ .map(|dt| dt.format("%Y-%m-%d").to_string())
+ .unwrap_or_else(|| "unknown".to_string()))
+ } else {
+ let time_until = self.grace_expiry_time + tier.days_after_expiry * 24 * 3600 - current_time;
+ if time_until > 0 {
+ format!("⏳ In {} days", time_until / (24 * 3600))
+ } else {
+ "⏳ Ready to unlock".to_string()
+ }
+ };
+
+ summary.push_str(&format!(
+ "Tier {}: {}% - {}\n",
+ tier.tier, tier.unlock_percentage, status
+ ));
+ }
+
+ summary
+ }
+}
+
+/// Event when a tier is unlocked
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct UnlockEvent {
+ pub tier: u32,
+ pub percentage: u8,
+ pub timestamp: i64,
+}
+
+/// Manager for progressive unlock
+pub struct ProgressiveUnlock;
+
+impl ProgressiveUnlock {
+ /// Initialize progressive unlock for a contract
+ pub fn initialize(
+ contract: &mut InheritanceContract,
+ schedule_type: UnlockScheduleType,
+ ) -> Result<(), UnlockError> {
+ if !contract.config.progressive_unlock {
+ return Err(UnlockError::ProgressiveUnlockDisabled);
+ }
+
+ if contract.status != ContractStatus::Unlocking {
+ return Err(UnlockError::ContractNotUnlocking);
+ }
+
+ let grace_expiry = contract.last_heartbeat + contract.config.total_timeout();
+
+ let schedule = match schedule_type {
+ UnlockScheduleType::Standard => UnlockSchedule::standard(grace_expiry),
+ UnlockScheduleType::Aggressive => UnlockSchedule::aggressive(grace_expiry),
+ UnlockScheduleType::Conservative => UnlockSchedule::conservative(grace_expiry),
+ UnlockScheduleType::Custom(tiers) => {
+ UnlockSchedule::custom(grace_expiry, tiers)?
+ }
+ };
+
+ contract.unlock_schedule = Some(schedule);
+ Ok(())
+ }
+
+ /// Process unlocks and distribute to beneficiaries
+ pub fn process(
+ contract: &mut InheritanceContract,
+ current_time: i64,
+ ) -> Result<Vec<Distribution>, UnlockError> {
+ let schedule = contract
+ .unlock_schedule
+ .as_mut()
+ .ok_or(UnlockError::NoSchedule)?;
+
+ let events = schedule.process_unlocks(current_time);
+
+ if events.is_empty() {
+ return Ok(Vec::new());
+ }
+
+ let mut distributions = Vec::new();
+
+ for event in events {
+ // Calculate amount to unlock for this tier
+ let tier_amount = (contract.locked_amount as f64 * (event.percentage as f64 / 100.0)) as u64;
+
+ // Distribute to beneficiaries
+ for beneficiary in &mut contract.beneficiaries {
+ let beneficiary_amount = beneficiary.calculate_amount(tier_amount);
+ beneficiary.unlocked_amount += beneficiary_amount;
+
+ distributions.push(Distribution {
+ beneficiary: beneficiary.pubkey,
+ amount: beneficiary_amount,
+ tier: event.tier,
+ timestamp: event.timestamp,
+ });
+ }
+ }
+
+ // Mark as completed if fully unlocked
+ if schedule.is_fully_unlocked() {
+ contract.status = ContractStatus::Completed;
+ }
+
+ Ok(distributions)
+ }
+
+ /// Get unlock status
+ pub fn status(contract: &InheritanceContract, current_time: i64) -> String {
+ if let Some(schedule) = &contract.unlock_schedule {
+ schedule.progress_summary(current_time)
+ } else {
+ "No unlock schedule".to_string()
+ }
+ }
+}
+
+/// Type of unlock schedule
+#[derive(Debug, Clone)]
+pub enum UnlockScheduleType {
+ Standard,
+ Aggressive,
+ Conservative,
+ Custom(Vec<UnlockTier>),
+}
+
+/// Distribution to a beneficiary
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Distribution {
+ pub beneficiary: [u8; 32],
+ pub amount: u64,
+ pub tier: u32,
+ pub timestamp: i64,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum UnlockError {
+ #[error("Invalid total percentage: {0}% (must be 100%)")]
+ InvalidTotalPercentage(u32),
+
+ #[error("Unlock tiers must be in chronological order")]
+ InvalidTierOrder,
+
+ #[error("Progressive unlock is disabled for this contract")]
+ ProgressiveUnlockDisabled,
+
+ #[error("Contract is not in unlocking state")]
+ ContractNotUnlocking,
+
+ #[error("No unlock schedule configured")]
+ NoSchedule,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::contract::InheritanceConfig;
+
+ #[test]
+ fn test_unlock_tier() {
+ let mut tier = UnlockTier::new(1, 30, 10);
+
+ assert!(!tier.unlocked);
+ assert!(tier.should_unlock(31 * 24 * 3600));
+ assert!(!tier.should_unlock(29 * 24 * 3600));
+
+ tier.mark_unlocked(1000);
+ assert!(tier.unlocked);
+ assert_eq!(tier.unlock_time, Some(1000));
+ }
+
+ #[test]
+ fn test_standard_schedule() {
+ let schedule = UnlockSchedule::standard(0);
+
+ assert_eq!(schedule.tiers.len(), 4);
+ assert_eq!(schedule.total_unlocked_percentage, 0);
+
+ let total: u32 = schedule.tiers.iter().map(|t| t.unlock_percentage as u32).sum();
+ assert_eq!(total, 100);
+ }
+
+ #[test]
+ fn test_schedule_processing() {
+ let mut schedule = UnlockSchedule::standard(0);
+
+ // 31 days after expiry - should unlock tier 1 (10%)
+ let events = schedule.process_unlocks(31 * 24 * 3600);
+
+ assert_eq!(events.len(), 1);
+ assert_eq!(events[0].tier, 1);
+ assert_eq!(events[0].percentage, 10);
+ assert_eq!(schedule.total_unlocked_percentage, 10);
+ }
+
+ #[test]
+ fn test_multiple_tier_unlock() {
+ let mut schedule = UnlockSchedule::standard(0);
+
+ // 91 days after expiry - should unlock tiers 1, 2, and 3
+ let events = schedule.process_unlocks(91 * 24 * 3600);
+
+ assert_eq!(events.len(), 3);
+ assert_eq!(schedule.total_unlocked_percentage, 70); // 10 + 25 + 35
+ }
+
+ #[test]
+ fn test_full_unlock() {
+ let mut schedule = UnlockSchedule::standard(0);
+
+ schedule.process_unlocks(121 * 24 * 3600);
+
+ assert!(schedule.is_fully_unlocked());
+ assert_eq!(schedule.total_unlocked_percentage, 100);
+ }
+
+ #[test]
+ fn test_custom_schedule() {
+ let tiers = vec![
+ UnlockTier::new(1, 10, 50),
+ UnlockTier::new(2, 20, 50),
+ ];
+
+ let schedule = UnlockSchedule::custom(0, tiers);
+ assert!(schedule.is_ok());
+ }
+
+ #[test]
+ fn test_invalid_custom_schedule() {
+ // Invalid total percentage
+ let tiers = vec![
+ UnlockTier::new(1, 10, 50),
+ UnlockTier::new(2, 20, 40), // Total = 90%
+ ];
+
+ let schedule = UnlockSchedule::custom(0, tiers);
+ assert!(matches!(
+ schedule.unwrap_err(),
+ UnlockError::InvalidTotalPercentage(90)
+ ));
+ }
+
+ #[test]
+ fn test_progressive_unlock_with_contract() {
+ use crate::contract::Beneficiary;
+
+ let beneficiaries = vec![
+ Beneficiary::new([1u8; 32], 60).unwrap(),
+ Beneficiary::new([2u8; 32], 40).unwrap(),
+ ];
+
+ let mut contract = InheritanceContract::new(
+ [0u8; 32],
+ beneficiaries,
+ InheritanceConfig::default(),
+ 10000,
+ 0,
+ )
+ .unwrap();
+
+ // Simulate unlocking state
+ contract.status = ContractStatus::Unlocking;
+
+ // Initialize progressive unlock
+ ProgressiveUnlock::initialize(&mut contract, UnlockScheduleType::Standard).unwrap();
+
+ assert!(contract.unlock_schedule.is_some());
+
+ // Process unlocks after 31 days
+ let grace_expiry = contract.config.total_timeout();
+ let current_time = grace_expiry + 31 * 24 * 3600;
+
+ let distributions = ProgressiveUnlock::process(&mut contract, current_time).unwrap();
+
+ // Tier 1 unlocks 10% = 1000 ONC
+ // Beneficiary 1: 60% of 1000 = 600
+ // Beneficiary 2: 40% of 1000 = 400
+ assert_eq!(distributions.len(), 2);
+ assert_eq!(distributions[0].amount, 600);
+ assert_eq!(distributions[1].amount, 400);
+ }
+}