summaryrefslogtreecommitdiffstats
path: root/timing/src/mixing_pool.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 /timing/src/mixing_pool.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 'timing/src/mixing_pool.rs')
-rw-r--r--timing/src/mixing_pool.rs307
1 files changed, 307 insertions, 0 deletions
diff --git a/timing/src/mixing_pool.rs b/timing/src/mixing_pool.rs
new file mode 100644
index 0000000..931f0be
--- /dev/null
+++ b/timing/src/mixing_pool.rs
@@ -0,0 +1,307 @@
+use std::collections::VecDeque;
+use std::time::{Duration, Instant};
+use rand::seq::SliceRandom;
+use serde::{Deserialize, Serialize};
+
+/// A pool that collects items and releases them in shuffled batches
+/// Used for temporal obfuscation of transaction broadcasts
+#[derive(Debug)]
+pub struct MixingPool<T> {
+ /// Items waiting to be released
+ pool: VecDeque<PoolItem<T>>,
+
+ /// Minimum time to collect items before releasing
+ min_batch_duration: Duration,
+
+ /// Maximum time to collect items before forcing release
+ max_batch_duration: Duration,
+
+ /// Maximum number of items in pool before forcing release
+ max_pool_size: usize,
+
+ /// Time when current batch started collecting
+ batch_start: Option<Instant>,
+}
+
+#[derive(Debug, Clone)]
+struct PoolItem<T> {
+ item: T,
+ #[allow(dead_code)]
+ added_at: Instant,
+}
+
+impl<T> MixingPool<T> {
+ /// Create a new mixing pool with custom parameters
+ pub fn new(
+ min_batch_duration: Duration,
+ max_batch_duration: Duration,
+ max_pool_size: usize,
+ ) -> Self {
+ Self {
+ pool: VecDeque::new(),
+ min_batch_duration,
+ max_batch_duration,
+ max_pool_size,
+ batch_start: None,
+ }
+ }
+
+ /// Create a mixing pool with default parameters (10-30 min batches)
+ pub fn default() -> Self {
+ Self::new(
+ Duration::from_secs(10 * 60), // 10 minutes min
+ Duration::from_secs(30 * 60), // 30 minutes max
+ 1000, // max 1000 items
+ )
+ }
+
+ /// Add an item to the mixing pool
+ pub fn add(&mut self, item: T) {
+ if self.batch_start.is_none() {
+ self.batch_start = Some(Instant::now());
+ }
+
+ self.pool.push_back(PoolItem {
+ item,
+ added_at: Instant::now(),
+ });
+ }
+
+ /// Check if pool should release a batch
+ pub fn should_release(&self) -> bool {
+ if self.pool.is_empty() {
+ return false;
+ }
+
+ if let Some(start) = self.batch_start {
+ let elapsed = start.elapsed();
+
+ // Force release if max duration reached
+ if elapsed >= self.max_batch_duration {
+ return true;
+ }
+
+ // Force release if pool is full
+ if self.pool.len() >= self.max_pool_size {
+ return true;
+ }
+
+ // Release if min duration passed and we have items
+ if elapsed >= self.min_batch_duration && !self.pool.is_empty() {
+ return true;
+ }
+ }
+
+ false
+ }
+
+ /// Release a shuffled batch of all pooled items
+ pub fn release_batch(&mut self) -> Vec<T> {
+ let mut items: Vec<T> = self.pool.drain(..).map(|pi| pi.item).collect();
+
+ // Shuffle to remove temporal ordering
+ let mut rng = rand::thread_rng();
+ items.shuffle(&mut rng);
+
+ // Reset batch timer
+ self.batch_start = None;
+
+ items
+ }
+
+ /// Get current pool size
+ pub fn len(&self) -> usize {
+ self.pool.len()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.pool.is_empty()
+ }
+
+ /// Get time until next potential release (min batch duration)
+ pub fn time_until_release(&self) -> Option<Duration> {
+ self.batch_start.map(|start| {
+ let elapsed = start.elapsed();
+ if elapsed >= self.min_batch_duration {
+ Duration::from_secs(0)
+ } else {
+ self.min_batch_duration - elapsed
+ }
+ })
+ }
+}
+
+/// Stats about mixing pool performance
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct MixingPoolStats {
+ pub total_items_processed: u64,
+ pub total_batches_released: u64,
+ pub avg_batch_size: f64,
+ pub avg_wait_time_secs: f64,
+}
+
+/// Mixing pool with statistics tracking
+#[derive(Debug)]
+pub struct MixingPoolWithStats<T> {
+ pool: MixingPool<T>,
+ stats: MixingPoolStats,
+ #[allow(dead_code)]
+ item_wait_times: Vec<Duration>,
+}
+
+impl<T> MixingPoolWithStats<T> {
+ pub fn new(pool: MixingPool<T>) -> Self {
+ Self {
+ pool,
+ stats: MixingPoolStats {
+ total_items_processed: 0,
+ total_batches_released: 0,
+ avg_batch_size: 0.0,
+ avg_wait_time_secs: 0.0,
+ },
+ item_wait_times: Vec::new(),
+ }
+ }
+
+ pub fn add(&mut self, item: T) {
+ self.pool.add(item);
+ }
+
+ pub fn should_release(&self) -> bool {
+ self.pool.should_release()
+ }
+
+ pub fn release_batch(&mut self) -> Vec<T> {
+ let batch = self.pool.release_batch();
+ let batch_size = batch.len();
+
+ // Update stats
+ self.stats.total_items_processed += batch_size as u64;
+ self.stats.total_batches_released += 1;
+
+ // Update average batch size
+ let total_batches = self.stats.total_batches_released as f64;
+ self.stats.avg_batch_size =
+ (self.stats.avg_batch_size * (total_batches - 1.0) + batch_size as f64) / total_batches;
+
+ batch
+ }
+
+ pub fn get_stats(&self) -> &MixingPoolStats {
+ &self.stats
+ }
+
+ pub fn len(&self) -> usize {
+ self.pool.len()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.pool.is_empty()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_mixing_pool_add() {
+ let mut pool = MixingPool::<u32>::default();
+ pool.add(1);
+ pool.add(2);
+ pool.add(3);
+
+ assert_eq!(pool.len(), 3);
+ }
+
+ #[test]
+ fn test_mixing_pool_release() {
+ let mut pool = MixingPool::<u32>::new(
+ Duration::from_millis(10),
+ Duration::from_secs(1),
+ 100,
+ );
+
+ pool.add(1);
+ pool.add(2);
+ pool.add(3);
+
+ // Should not release immediately
+ assert!(!pool.should_release());
+
+ // Wait for min duration
+ std::thread::sleep(Duration::from_millis(15));
+
+ assert!(pool.should_release());
+
+ let batch = pool.release_batch();
+ assert_eq!(batch.len(), 3);
+ assert!(pool.is_empty());
+ }
+
+ #[test]
+ fn test_mixing_pool_shuffle() {
+ let mut pool = MixingPool::<u32>::new(
+ Duration::from_millis(10),
+ Duration::from_secs(1),
+ 100,
+ );
+
+ // Add items in order
+ for i in 0..100 {
+ pool.add(i);
+ }
+
+ std::thread::sleep(Duration::from_millis(15));
+
+ let batch = pool.release_batch();
+
+ // Check all items are present
+ let mut sorted = batch.clone();
+ sorted.sort();
+ assert_eq!(sorted, (0..100).collect::<Vec<_>>());
+
+ // Check they were shuffled (very unlikely to be in original order)
+ assert_ne!(batch, sorted);
+ }
+
+ #[test]
+ fn test_mixing_pool_max_size() {
+ let mut pool = MixingPool::<u32>::new(
+ Duration::from_secs(100), // Long min duration
+ Duration::from_secs(200),
+ 10, // Small max size
+ );
+
+ // Add items up to max
+ for i in 0..10 {
+ pool.add(i);
+ }
+
+ // Should force release due to max size
+ assert!(pool.should_release());
+ }
+
+ #[test]
+ fn test_mixing_pool_with_stats() {
+ let pool = MixingPool::<u32>::new(
+ Duration::from_millis(10),
+ Duration::from_secs(1),
+ 100,
+ );
+ let mut pool_with_stats = MixingPoolWithStats::new(pool);
+
+ for i in 0..5 {
+ pool_with_stats.add(i);
+ }
+
+ std::thread::sleep(Duration::from_millis(15));
+
+ pool_with_stats.release_batch();
+
+ let stats = pool_with_stats.get_stats();
+ assert_eq!(stats.total_items_processed, 5);
+ assert_eq!(stats.total_batches_released, 1);
+ assert_eq!(stats.avg_batch_size, 5.0);
+ }
+}