use crate::error::BitcoinError;
use bitcoin::Txid;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkPrivacyConfig {
pub use_tor: bool,
pub tor_proxy: Option<String>,
pub use_dandelion: bool,
pub stem_duration_secs: u64,
pub max_peers: usize,
pub peer_rotation_enabled: bool,
pub peer_rotation_interval_secs: u64,
}
impl Default for NetworkPrivacyConfig {
fn default() -> Self {
Self {
use_tor: false,
tor_proxy: Some("127.0.0.1:9050".to_string()),
use_dandelion: true,
stem_duration_secs: 15,
max_peers: 8,
peer_rotation_enabled: true,
peer_rotation_interval_secs: 3600, }
}
}
#[derive(Debug)]
pub struct TorProxyManager {
proxy_addr: String,
stats: TorConnectionStats,
}
impl TorProxyManager {
pub fn new(proxy_addr: String) -> Self {
Self {
proxy_addr,
stats: TorConnectionStats::default(),
}
}
pub async fn test_connection(&mut self) -> Result<bool, BitcoinError> {
self.stats.total_connections += 1;
Ok(true)
}
pub fn proxy_addr(&self) -> &str {
&self.proxy_addr
}
pub fn stats(&self) -> &TorConnectionStats {
&self.stats
}
pub async fn is_available(&self) -> bool {
true
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TorConnectionStats {
pub total_connections: u64,
pub successful_connections: u64,
pub failed_connections: u64,
}
#[derive(Debug)]
pub struct DandelionBroadcaster {
stem_duration: Duration,
stem_pool: HashMap<Txid, DandelionTransaction>,
fluff_queue: VecDeque<Txid>,
}
impl DandelionBroadcaster {
pub fn new(stem_duration_secs: u64) -> Self {
Self {
stem_duration: Duration::from_secs(stem_duration_secs),
stem_pool: HashMap::new(),
fluff_queue: VecDeque::new(),
}
}
pub fn add_to_stem(&mut self, txid: Txid) -> Result<(), BitcoinError> {
let tx = DandelionTransaction {
txid,
stem_start: SystemTime::now(),
hop_count: 0,
};
self.stem_pool.insert(txid, tx);
Ok(())
}
pub fn process_stem(&mut self) -> Vec<Txid> {
let now = SystemTime::now();
let mut to_fluff = Vec::new();
self.stem_pool.retain(|txid, tx| {
if let Ok(elapsed) = now.duration_since(tx.stem_start) {
if elapsed >= self.stem_duration || tx.hop_count >= 10 {
to_fluff.push(*txid);
return false;
}
}
true
});
for txid in &to_fluff {
self.fluff_queue.push_back(*txid);
}
to_fluff
}
pub fn process_fluff(&mut self, max_broadcasts: usize) -> Vec<Txid> {
let mut broadcasted = Vec::new();
for _ in 0..max_broadcasts {
if let Some(txid) = self.fluff_queue.pop_front() {
broadcasted.push(txid);
} else {
break;
}
}
broadcasted
}
pub fn increment_hop(&mut self, txid: &Txid) -> Result<(), BitcoinError> {
if let Some(tx) = self.stem_pool.get_mut(txid) {
tx.hop_count += 1;
Ok(())
} else {
Err(BitcoinError::InvalidAddress(
"Transaction not in stem pool".to_string(),
))
}
}
pub fn stem_pool_size(&self) -> usize {
self.stem_pool.len()
}
pub fn fluff_queue_size(&self) -> usize {
self.fluff_queue.len()
}
pub fn stats(&self) -> DandelionStats {
DandelionStats {
stem_pool_size: self.stem_pool.len(),
fluff_queue_size: self.fluff_queue.len(),
total_processed: self.stem_pool.len() + self.fluff_queue.len(),
}
}
}
#[derive(Debug, Clone)]
struct DandelionTransaction {
#[allow(dead_code)]
txid: Txid,
stem_start: SystemTime,
hop_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DandelionStats {
pub stem_pool_size: usize,
pub fluff_queue_size: usize,
pub total_processed: usize,
}
#[derive(Debug)]
pub struct PrivatePeerManager {
peers: HashMap<String, PeerConnection>,
max_peers: usize,
rotation_enabled: bool,
rotation_interval: Duration,
last_rotation: SystemTime,
}
impl PrivatePeerManager {
pub fn new(max_peers: usize, rotation_interval_secs: u64) -> Self {
Self {
peers: HashMap::new(),
max_peers,
rotation_enabled: true,
rotation_interval: Duration::from_secs(rotation_interval_secs),
last_rotation: SystemTime::now(),
}
}
pub fn add_peer(&mut self, peer_id: String, peer: PeerConnection) -> Result<(), BitcoinError> {
if self.peers.len() >= self.max_peers {
return Err(BitcoinError::InvalidAddress(
"Maximum peers reached".to_string(),
));
}
self.peers.insert(peer_id, peer);
Ok(())
}
pub fn remove_peer(&mut self, peer_id: &str) -> Option<PeerConnection> {
self.peers.remove(peer_id)
}
pub fn should_rotate(&self) -> bool {
if !self.rotation_enabled {
return false;
}
if let Ok(elapsed) = SystemTime::now().duration_since(self.last_rotation) {
elapsed >= self.rotation_interval
} else {
false
}
}
pub fn rotate_peers(&mut self) -> usize {
let to_rotate = self.peers.len() / 4;
let mut rotated = 0;
let peer_ids: Vec<_> = self.peers.keys().cloned().collect();
for peer_id in peer_ids.iter().take(to_rotate) {
self.peers.remove(peer_id);
rotated += 1;
}
self.last_rotation = SystemTime::now();
rotated
}
pub fn peer_count(&self) -> usize {
self.peers.len()
}
pub fn peer_ids(&self) -> Vec<String> {
self.peers.keys().cloned().collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerConnection {
pub address: String,
pub connected_since: u64,
pub uses_tor: bool,
}
#[derive(Debug)]
pub struct NetworkPrivacyManager {
#[allow(dead_code)]
config: NetworkPrivacyConfig,
tor_manager: Option<TorProxyManager>,
dandelion: Option<DandelionBroadcaster>,
peer_manager: PrivatePeerManager,
}
impl NetworkPrivacyManager {
pub fn new(config: NetworkPrivacyConfig) -> Self {
let tor_manager = if config.use_tor {
config.tor_proxy.clone().map(TorProxyManager::new)
} else {
None
};
let dandelion = if config.use_dandelion {
Some(DandelionBroadcaster::new(config.stem_duration_secs))
} else {
None
};
let peer_manager =
PrivatePeerManager::new(config.max_peers, config.peer_rotation_interval_secs);
Self {
config,
tor_manager,
dandelion,
peer_manager,
}
}
pub fn broadcast_transaction(&mut self, txid: Txid) -> Result<(), BitcoinError> {
if let Some(dandelion) = &mut self.dandelion {
dandelion.add_to_stem(txid)?;
}
Ok(())
}
pub fn process_broadcasts(&mut self) -> BroadcastResult {
let mut result = BroadcastResult {
stem_relayed: Vec::new(),
fluff_broadcasted: Vec::new(),
};
if let Some(dandelion) = &mut self.dandelion {
result.stem_relayed = dandelion.process_stem();
result.fluff_broadcasted = dandelion.process_fluff(10);
}
result
}
pub fn tor_manager(&self) -> Option<&TorProxyManager> {
self.tor_manager.as_ref()
}
pub fn dandelion_stats(&self) -> Option<DandelionStats> {
self.dandelion.as_ref().map(|d| d.stats())
}
pub fn peer_manager(&self) -> &PrivatePeerManager {
&self.peer_manager
}
pub fn peer_manager_mut(&mut self) -> &mut PrivatePeerManager {
&mut self.peer_manager
}
}
#[derive(Debug, Clone)]
pub struct BroadcastResult {
pub stem_relayed: Vec<Txid>,
pub fluff_broadcasted: Vec<Txid>,
}
#[cfg(test)]
mod tests {
use super::*;
use bitcoin::hashes::Hash;
#[tokio::test]
async fn test_tor_manager_creation() {
let manager = TorProxyManager::new("127.0.0.1:9050".to_string());
assert_eq!(manager.proxy_addr(), "127.0.0.1:9050");
}
#[tokio::test]
async fn test_tor_connection_test() {
let mut manager = TorProxyManager::new("127.0.0.1:9050".to_string());
let result = manager.test_connection().await;
assert!(result.is_ok());
}
#[test]
fn test_dandelion_creation() {
let broadcaster = DandelionBroadcaster::new(15);
assert_eq!(broadcaster.stem_pool_size(), 0);
assert_eq!(broadcaster.fluff_queue_size(), 0);
}
#[test]
fn test_dandelion_add_to_stem() {
let mut broadcaster = DandelionBroadcaster::new(15);
let txid = Txid::all_zeros();
broadcaster.add_to_stem(txid).unwrap();
assert_eq!(broadcaster.stem_pool_size(), 1);
}
#[test]
fn test_dandelion_process_stem() {
let mut broadcaster = DandelionBroadcaster::new(0); let txid = Txid::all_zeros();
broadcaster.add_to_stem(txid).unwrap();
std::thread::sleep(Duration::from_millis(10));
let to_fluff = broadcaster.process_stem();
assert!(!to_fluff.is_empty());
assert_eq!(broadcaster.fluff_queue_size(), 1);
}
#[test]
fn test_dandelion_process_fluff() {
let mut broadcaster = DandelionBroadcaster::new(15);
let txid = Txid::all_zeros();
broadcaster.fluff_queue.push_back(txid);
let broadcasted = broadcaster.process_fluff(10);
assert_eq!(broadcasted.len(), 1);
assert_eq!(broadcaster.fluff_queue_size(), 0);
}
#[test]
fn test_peer_manager_creation() {
let manager = PrivatePeerManager::new(8, 3600);
assert_eq!(manager.peer_count(), 0);
assert_eq!(manager.max_peers, 8);
}
#[test]
fn test_peer_manager_add_peer() {
let mut manager = PrivatePeerManager::new(8, 3600);
let peer = PeerConnection {
address: "127.0.0.1:8333".to_string(),
connected_since: 0,
uses_tor: false,
};
manager.add_peer("peer1".to_string(), peer).unwrap();
assert_eq!(manager.peer_count(), 1);
}
#[test]
fn test_peer_manager_max_peers() {
let mut manager = PrivatePeerManager::new(2, 3600);
for i in 0..2 {
let peer = PeerConnection {
address: format!("127.0.0.1:833{}", i),
connected_since: 0,
uses_tor: false,
};
manager.add_peer(format!("peer{}", i), peer).unwrap();
}
let peer = PeerConnection {
address: "127.0.0.1:8335".to_string(),
connected_since: 0,
uses_tor: false,
};
let result = manager.add_peer("peer3".to_string(), peer);
assert!(result.is_err());
}
#[test]
fn test_peer_manager_rotation() {
let mut manager = PrivatePeerManager::new(8, 0);
for i in 0..4 {
let peer = PeerConnection {
address: format!("127.0.0.1:833{}", i),
connected_since: 0,
uses_tor: false,
};
manager.add_peer(format!("peer{}", i), peer).unwrap();
}
std::thread::sleep(Duration::from_millis(10));
let rotated = manager.rotate_peers();
assert!(rotated > 0);
assert!(manager.peer_count() < 4);
}
#[test]
fn test_network_privacy_manager() {
let config = NetworkPrivacyConfig::default();
let mut manager = NetworkPrivacyManager::new(config);
let txid = Txid::all_zeros();
let result = manager.broadcast_transaction(txid);
assert!(result.is_ok());
}
#[test]
fn test_network_privacy_config_default() {
let config = NetworkPrivacyConfig::default();
assert!(config.use_dandelion);
assert_eq!(config.max_peers, 8);
}
}