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 /inheritance/src/heartbeat.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 'inheritance/src/heartbeat.rs')
| -rw-r--r-- | inheritance/src/heartbeat.rs | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/inheritance/src/heartbeat.rs b/inheritance/src/heartbeat.rs new file mode 100644 index 0000000..ef19776 --- /dev/null +++ b/inheritance/src/heartbeat.rs @@ -0,0 +1,447 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use crate::contract::InheritanceContract; + +/// Manages heartbeat system for inheritance contracts +/// Owner must periodically "check in" to prove they're alive +pub struct HeartbeatManager { + /// Contract heartbeat tracking + contracts: HashMap<[u8; 32], HeartbeatRecord>, + + /// Notification settings + notification_settings: NotificationSettings, +} + +/// Record of heartbeat activity for a contract +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HeartbeatRecord { + /// Contract ID + pub contract_id: [u8; 32], + + /// Last heartbeat timestamp + pub last_heartbeat: i64, + + /// Total heartbeats sent + pub heartbeat_count: u64, + + /// Average interval between heartbeats + pub avg_interval_days: f64, + + /// Warnings sent to owner + pub warnings_sent: u32, + + /// Notifications sent to beneficiaries + pub beneficiary_notifications_sent: u32, +} + +/// Notification settings for heartbeat warnings +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NotificationSettings { + /// Send warning when X% of interval remaining + pub warning_threshold_percentage: u8, + + /// Send critical warning when in grace period + pub grace_period_warning: bool, + + /// Notify beneficiaries when grace period starts + pub notify_beneficiaries_grace: bool, + + /// Notify beneficiaries on each unlock tier + pub notify_beneficiaries_unlock: bool, +} + +impl Default for NotificationSettings { + fn default() -> Self { + Self { + warning_threshold_percentage: 80, // Warn at 80% of interval + grace_period_warning: true, + notify_beneficiaries_grace: true, + notify_beneficiaries_unlock: true, + } + } +} + +impl HeartbeatManager { + pub fn new() -> Self { + Self { + contracts: HashMap::new(), + notification_settings: NotificationSettings::default(), + } + } + + /// Register a contract for heartbeat monitoring + pub fn register_contract(&mut self, contract_id: [u8; 32], current_time: i64) { + let record = HeartbeatRecord { + contract_id, + last_heartbeat: current_time, + heartbeat_count: 0, + avg_interval_days: 0.0, + warnings_sent: 0, + beneficiary_notifications_sent: 0, + }; + + self.contracts.insert(contract_id, record); + } + + /// Process a heartbeat + pub fn process_heartbeat( + &mut self, + contract: &mut InheritanceContract, + current_time: i64, + ) -> Result<HeartbeatStatus, HeartbeatError> { + // Record heartbeat in contract + contract.heartbeat(current_time) + .map_err(|_| HeartbeatError::ContractError)?; + + // Update heartbeat record + if let Some(record) = self.contracts.get_mut(&contract.contract_id) { + let interval = current_time - record.last_heartbeat; + let interval_days = interval as f64 / (24.0 * 3600.0); + + // Update average interval + let total = record.heartbeat_count as f64; + record.avg_interval_days = + (record.avg_interval_days * total + interval_days) / (total + 1.0); + + record.last_heartbeat = current_time; + record.heartbeat_count += 1; + record.warnings_sent = 0; // Reset warnings + + Ok(HeartbeatStatus::Success { + next_required: current_time + contract.config.heartbeat_interval, + avg_interval_days: record.avg_interval_days, + }) + } else { + Err(HeartbeatError::ContractNotRegistered) + } + } + + /// Check all contracts and generate notifications + pub fn check_contracts( + &mut self, + contracts: &[&InheritanceContract], + current_time: i64, + ) -> Vec<Notification> { + let mut notifications = Vec::new(); + + for contract in contracts { + if let Some(record) = self.contracts.get_mut(&contract.contract_id) { + let time_since_heartbeat = current_time - contract.last_heartbeat; + let interval = contract.config.heartbeat_interval; + + // Calculate percentage of interval elapsed + let percentage_elapsed = (time_since_heartbeat as f64 / interval as f64 * 100.0) as u8; + + // Check for warnings + if percentage_elapsed >= self.notification_settings.warning_threshold_percentage + && record.warnings_sent == 0 + { + notifications.push(Notification { + contract_id: contract.contract_id, + notification_type: NotificationType::WarningOwner, + message: format!( + "{}% of heartbeat interval elapsed. Please send heartbeat soon!", + percentage_elapsed + ), + urgency: Urgency::Medium, + timestamp: current_time, + }); + record.warnings_sent += 1; + } + + // Check if grace period started + if contract.is_timeout(current_time) + && !contract.is_grace_expired(current_time) + && self.notification_settings.grace_period_warning + && record.warnings_sent < 2 + { + let grace_remaining = contract.config.grace_period + - (time_since_heartbeat - interval); + + notifications.push(Notification { + contract_id: contract.contract_id, + notification_type: NotificationType::CriticalOwner, + message: format!( + "CRITICAL: Grace period active! {} days remaining before unlock!", + grace_remaining / (24 * 3600) + ), + urgency: Urgency::Critical, + timestamp: current_time, + }); + + // Notify beneficiaries + if self.notification_settings.notify_beneficiaries_grace + && record.beneficiary_notifications_sent == 0 + { + notifications.push(Notification { + contract_id: contract.contract_id, + notification_type: NotificationType::InfoBeneficiaries, + message: "Inheritance contract has entered grace period. Unlock will begin if owner does not respond.".to_string(), + urgency: Urgency::Low, + timestamp: current_time, + }); + record.beneficiary_notifications_sent += 1; + } + + record.warnings_sent += 1; + } + + // Check if unlocking started + if contract.is_grace_expired(current_time) + && record.beneficiary_notifications_sent < 2 + && self.notification_settings.notify_beneficiaries_unlock + { + notifications.push(Notification { + contract_id: contract.contract_id, + notification_type: NotificationType::UnlockStarted, + message: "Inheritance unlock has begun. Funds will be distributed according to the schedule.".to_string(), + urgency: Urgency::High, + timestamp: current_time, + }); + record.beneficiary_notifications_sent += 1; + } + } + } + + notifications + } + + /// Get heartbeat statistics for a contract + pub fn get_stats(&self, contract_id: &[u8; 32]) -> Option<&HeartbeatRecord> { + self.contracts.get(contract_id) + } + + /// Suggest optimal heartbeat frequency for user + pub fn suggest_frequency(&self, contract_id: &[u8; 32]) -> Option<String> { + self.contracts.get(contract_id).map(|record| { + if record.heartbeat_count < 3 { + "Not enough data yet. Continue sending regular heartbeats.".to_string() + } else if record.avg_interval_days < 30.0 { + format!( + "You're checking in every {:.0} days. Consider spacing out to 30-60 days.", + record.avg_interval_days + ) + } else if record.avg_interval_days > 60.0 { + format!( + "You're checking in every {:.0} days. Consider more frequent heartbeats (30-45 days) for safety.", + record.avg_interval_days + ) + } else { + format!( + "Good frequency! Checking in every {:.0} days.", + record.avg_interval_days + ) + } + }) + } + + /// Create simple heartbeat transaction + /// Just send tiny amount to yourself to prove you're alive + pub fn create_heartbeat_tx(owner: [u8; 32]) -> HeartbeatTransaction { + HeartbeatTransaction { + from: owner, + to: owner, // Send to yourself + amount: 1, // Minimum amount (0.00000001 ONC) + timestamp: chrono::Utc::now().timestamp(), + heartbeat_marker: true, + } + } +} + +impl Default for HeartbeatManager { + fn default() -> Self { + Self::new() + } +} + +/// Result of heartbeat processing +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum HeartbeatStatus { + Success { + next_required: i64, + avg_interval_days: f64, + }, + Warning { + message: String, + }, + Error { + message: String, + }, +} + +/// Notification to send to owner or beneficiaries +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Notification { + pub contract_id: [u8; 32], + pub notification_type: NotificationType, + pub message: String, + pub urgency: Urgency, + pub timestamp: i64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum NotificationType { + WarningOwner, + CriticalOwner, + InfoBeneficiaries, + UnlockStarted, + TierUnlocked, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Urgency { + Low, + Medium, + High, + Critical, +} + +/// Simple heartbeat transaction +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HeartbeatTransaction { + pub from: [u8; 32], + pub to: [u8; 32], + pub amount: u64, + pub timestamp: i64, + pub heartbeat_marker: bool, +} + +#[derive(Debug, thiserror::Error)] +pub enum HeartbeatError { + #[error("Contract not registered with heartbeat manager")] + ContractNotRegistered, + + #[error("Contract error occurred")] + ContractError, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::contract::{InheritanceContract, Beneficiary, InheritanceConfig}; + + fn create_test_contract() -> InheritanceContract { + let beneficiaries = vec![Beneficiary::new([1u8; 32], 100).unwrap()]; + + InheritanceContract::new( + [0u8; 32], + beneficiaries, + InheritanceConfig::default(), + 10000, + 0, + ) + .unwrap() + } + + #[test] + fn test_heartbeat_manager_registration() { + let mut manager = HeartbeatManager::new(); + let contract_id = [1u8; 32]; + + manager.register_contract(contract_id, 0); + + assert!(manager.contracts.contains_key(&contract_id)); + } + + #[test] + fn test_heartbeat_processing() { + let mut manager = HeartbeatManager::new(); + let mut contract = create_test_contract(); + + manager.register_contract(contract.contract_id, 0); + + let result = manager.process_heartbeat(&mut contract, 1000); + + assert!(result.is_ok()); + assert_eq!(contract.last_heartbeat, 1000); + + let record = manager.get_stats(&contract.contract_id).unwrap(); + assert_eq!(record.heartbeat_count, 1); + } + + #[test] + fn test_average_interval_calculation() { + let mut manager = HeartbeatManager::new(); + let mut contract = create_test_contract(); + + manager.register_contract(contract.contract_id, 0); + + // First heartbeat at day 30 + manager.process_heartbeat(&mut contract, 30 * 24 * 3600).unwrap(); + + // Second heartbeat at day 60 + manager.process_heartbeat(&mut contract, 60 * 24 * 3600).unwrap(); + + let record = manager.get_stats(&contract.contract_id).unwrap(); + assert!((record.avg_interval_days - 30.0).abs() < 1.0); + } + + #[test] + fn test_notification_generation() { + let mut manager = HeartbeatManager::new(); + let mut contract = create_test_contract(); + + // Set short interval for testing + contract.config.heartbeat_interval = 100; + contract.config.grace_period = 50; + + manager.register_contract(contract.contract_id, 0); + + // Check at 85% of interval (should generate warning) + let notifications = manager.check_contracts(&[&contract], 85); + + assert!(!notifications.is_empty()); + assert_eq!(notifications[0].notification_type, NotificationType::WarningOwner); + assert_eq!(notifications[0].urgency, Urgency::Medium); + } + + #[test] + fn test_grace_period_notifications() { + let mut manager = HeartbeatManager::new(); + let mut contract = create_test_contract(); + + contract.config.heartbeat_interval = 100; + contract.config.grace_period = 50; + + manager.register_contract(contract.contract_id, 0); + + // Enter grace period + let notifications = manager.check_contracts(&[&contract], 101); + + // Should have critical warning for owner and info for beneficiaries + assert!(notifications.len() >= 2); + assert!(notifications.iter().any(|n| n.notification_type == NotificationType::CriticalOwner)); + assert!(notifications.iter().any(|n| n.notification_type == NotificationType::InfoBeneficiaries)); + } + + #[test] + fn test_heartbeat_transaction() { + let owner = [42u8; 32]; + let tx = HeartbeatManager::create_heartbeat_tx(owner); + + assert_eq!(tx.from, owner); + assert_eq!(tx.to, owner); // Sent to self + assert_eq!(tx.amount, 1); // Minimal amount + assert!(tx.heartbeat_marker); + } + + #[test] + fn test_frequency_suggestion() { + let mut manager = HeartbeatManager::new(); + let contract_id = [1u8; 32]; + + manager.register_contract(contract_id, 0); + + // Not enough data initially + let suggestion = manager.suggest_frequency(&contract_id).unwrap(); + assert!(suggestion.contains("Not enough data")); + + // Simulate multiple heartbeats + let mut record = manager.contracts.get_mut(&contract_id).unwrap(); + record.heartbeat_count = 5; + record.avg_interval_days = 45.0; + + let suggestion = manager.suggest_frequency(&contract_id).unwrap(); + assert!(suggestion.contains("Good frequency")); + } +} |
