use rand::Rng; use std::time::Duration; use serde::{Deserialize, Serialize}; /// Strategy for introducing random delays in transaction propagation #[derive(Debug, Clone, Serialize, Deserialize)] pub enum DelayStrategy { /// Fixed delay Fixed(Duration), /// Random delay within a range Random { min: Duration, max: Duration }, /// Exponential backoff with jitter Exponential { base: Duration, max: Duration, attempt: u32, }, /// Delay based on observed Tor latency TorAdaptive { observed_latency: Duration, multiplier: f64, }, } impl DelayStrategy { /// Wallet broadcast delay: 5-60 minutes pub fn wallet_broadcast() -> Self { Self::Random { min: Duration::from_secs(5 * 60), max: Duration::from_secs(60 * 60), } } /// Node rebroadcast delay: 10s - 2 minutes pub fn node_rebroadcast() -> Self { Self::Random { min: Duration::from_secs(10), max: Duration::from_secs(120), } } /// Mixing pool batch delay: 10-30 minutes pub fn mixing_pool() -> Self { Self::Random { min: Duration::from_secs(10 * 60), max: Duration::from_secs(30 * 60), } } /// Validation delay before processing: 5s - 60s pub fn validation() -> Self { Self::Random { min: Duration::from_secs(5), max: Duration::from_secs(60), } } /// Dandelion STEM hop delay: 30s - 5 minutes pub fn dandelion_stem() -> Self { Self::Random { min: Duration::from_secs(30), max: Duration::from_secs(5 * 60), } } /// Create adaptive delay based on observed Tor latency pub fn from_tor_latency(observed: Duration) -> Self { Self::TorAdaptive { observed_latency: observed, multiplier: 2.0, // Amplify variability } } /// Calculate the actual delay to use pub fn calculate(&self) -> Duration { let mut rng = rand::thread_rng(); match self { Self::Fixed(d) => *d, Self::Random { min, max } => { let min_ms = min.as_millis() as u64; let max_ms = max.as_millis() as u64; let delay_ms = rng.gen_range(min_ms..=max_ms); Duration::from_millis(delay_ms) } Self::Exponential { base, max, attempt } => { let base_ms = base.as_millis() as u64; let max_ms = max.as_millis() as u64; // Exponential: base * 2^attempt with jitter let exp_delay = base_ms.saturating_mul(2u64.saturating_pow(*attempt)); let capped = exp_delay.min(max_ms); // Add ±25% jitter let jitter_range = (capped as f64 * 0.25) as u64; let jitter = rng.gen_range(0..=jitter_range); let with_jitter = if rng.gen_bool(0.5) { capped.saturating_add(jitter) } else { capped.saturating_sub(jitter) }; Duration::from_millis(with_jitter) } Self::TorAdaptive { observed_latency, multiplier } => { let base_ms = observed_latency.as_millis() as u64; let amplified = (base_ms as f64 * multiplier) as u64; // Random delay up to amplified latency let delay_ms = rng.gen_range(0..=amplified); Duration::from_millis(delay_ms) } } } /// Async sleep with this delay strategy pub async fn sleep(&self) { let delay = self.calculate(); tokio::time::sleep(delay).await; } } /// Helper for managing multiple delays in sequence #[derive(Debug)] pub struct DelaySequence { strategies: Vec, current: usize, } impl DelaySequence { pub fn new(strategies: Vec) -> Self { Self { strategies, current: 0, } } /// Create a Dandelion++ STEM sequence (1-4 random hops) pub fn dandelion_stem() -> Self { let mut rng = rand::thread_rng(); let hops = rng.gen_range(1..=4); let strategies = (0..hops) .map(|_| DelayStrategy::dandelion_stem()) .collect(); Self::new(strategies) } /// Get next delay, returns None when sequence is exhausted pub fn next(&mut self) -> Option<&DelayStrategy> { if self.current < self.strategies.len() { let strategy = &self.strategies[self.current]; self.current += 1; Some(strategy) } else { None } } /// Reset sequence to beginning pub fn reset(&mut self) { self.current = 0; } /// Total number of delays in sequence pub fn len(&self) -> usize { self.strategies.len() } pub fn is_empty(&self) -> bool { self.strategies.is_empty() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_fixed_delay() { let strategy = DelayStrategy::Fixed(Duration::from_secs(10)); let delay = strategy.calculate(); assert_eq!(delay, Duration::from_secs(10)); } #[test] fn test_random_delay() { let strategy = DelayStrategy::Random { min: Duration::from_secs(5), max: Duration::from_secs(10), }; for _ in 0..100 { let delay = strategy.calculate(); assert!(delay >= Duration::from_secs(5)); assert!(delay <= Duration::from_secs(10)); } } #[test] fn test_exponential_delay() { let base = Duration::from_secs(1); let max = Duration::from_secs(100); for attempt in 0..5 { let strategy = DelayStrategy::Exponential { base, max, attempt, }; let delay = strategy.calculate(); assert!(delay <= max); } } #[test] fn test_tor_adaptive_delay() { let observed = Duration::from_secs(2); let strategy = DelayStrategy::from_tor_latency(observed); for _ in 0..100 { let delay = strategy.calculate(); // Should be between 0 and 2x observed latency assert!(delay <= Duration::from_secs(4)); } } #[test] fn test_delay_sequence() { let mut sequence = DelaySequence::dandelion_stem(); let len = sequence.len(); assert!(len >= 1 && len <= 4); let mut count = 0; while sequence.next().is_some() { count += 1; } assert_eq!(count, len); } #[tokio::test] async fn test_async_sleep() { let strategy = DelayStrategy::Fixed(Duration::from_millis(10)); let start = std::time::Instant::now(); strategy.sleep().await; let elapsed = start.elapsed(); assert!(elapsed >= Duration::from_millis(10)); } }