diff --git a/crates/core/src/topology/connection_evaluator/mod.rs b/crates/core/src/topology/connection_evaluator/mod.rs index 18826c218..c7b46d004 100644 --- a/crates/core/src/topology/connection_evaluator/mod.rs +++ b/crates/core/src/topology/connection_evaluator/mod.rs @@ -7,10 +7,10 @@ use std::time::{Duration, Instant}; /// any other scores within a predefined time window. A score is considered better if it's higher /// than all other scores in the time window, or if no scores were recorded within the window's /// duration. -/// +/// /// In the Freenet context, this will be used to titrate the rate of new connection requests accepted /// by a node. The node will only accept a new connection if the score of the connection is better -/// than all other scores within the time window. +/// than all other scores within the time window. pub(crate) struct ConnectionEvaluator { scores: VecDeque<(Instant, f64)>, window_duration: Duration, diff --git a/crates/core/src/topology/connection_evaluator/tests.rs b/crates/core/src/topology/connection_evaluator/tests.rs index 1e968a049..52bbf4974 100644 --- a/crates/core/src/topology/connection_evaluator/tests.rs +++ b/crates/core/src/topology/connection_evaluator/tests.rs @@ -4,12 +4,11 @@ use super::*; fn test_record_first_score() { let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(10)); let current_time = Instant::now(); - assert_eq!(evaluator.record_and_eval_with_current_time(5.0, current_time), true); + assert!(evaluator.record_and_eval_with_current_time(5.0, current_time)); // Assert evaluator.scores contains the new score assert_eq!(evaluator.scores.len(), 1); assert_eq!(evaluator.scores[0].1, 5.0); assert_eq!(evaluator.scores[0].0, current_time); - } #[test] @@ -18,7 +17,7 @@ fn test_not_best_in_time_window() { let start_time = Instant::now(); evaluator.record_and_eval_with_current_time(5.0, start_time); - assert_eq!(evaluator.record_and_eval_with_current_time(4.0, start_time + Duration::from_secs(5)), false); + assert!(!evaluator.record_and_eval_with_current_time(4.0, start_time + Duration::from_secs(5)),); } #[test] @@ -27,7 +26,7 @@ fn test_best_in_time_window() { let start_time = Instant::now(); evaluator.record_and_eval_with_current_time(5.0, start_time); - assert_eq!(evaluator.record_and_eval_with_current_time(4.0, start_time + Duration::from_secs(11)), true); + assert!(evaluator.record_and_eval_with_current_time(4.0, start_time + Duration::from_secs(11)),); } #[test] @@ -45,8 +44,8 @@ fn test_remove_outdated_scores() { fn test_empty_window_duration() { let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(0)); let current_time = Instant::now(); - assert_eq!(evaluator.record_and_eval_with_current_time(5.0, current_time), true); - assert_eq!(evaluator.record_and_eval_with_current_time(4.0, current_time), false); + assert!(evaluator.record_and_eval_with_current_time(5.0, current_time)); + assert!(!evaluator.record_and_eval_with_current_time(4.0, current_time)); } #[test] @@ -56,16 +55,20 @@ fn test_multiple_scores_same_timestamp() { evaluator.record_only_with_current_time(5.0, current_time); evaluator.record_only_with_current_time(6.0, current_time); assert_eq!(evaluator.scores.len(), 2); - assert_eq!(evaluator.record_and_eval_with_current_time(4.0, current_time + Duration::from_secs(5)), false); + assert!( + !evaluator.record_and_eval_with_current_time(4.0, current_time + Duration::from_secs(5)), + ); } #[test] fn test_negative_scores() { let mut evaluator = ConnectionEvaluator::new(Duration::from_secs(10)); let start_time = Instant::now(); - assert_eq!(evaluator.record_and_eval_with_current_time(-5.0, start_time), true); - assert_eq!(evaluator.record_and_eval_with_current_time(-4.0, start_time + Duration::from_secs(5)), true); - assert_eq!(evaluator.record_and_eval_with_current_time(-6.0, start_time + Duration::from_secs(5)), false); + assert!(evaluator.record_and_eval_with_current_time(-5.0, start_time),); + assert!(evaluator.record_and_eval_with_current_time(-4.0, start_time + Duration::from_secs(5)),); + assert!( + !evaluator.record_and_eval_with_current_time(-6.0, start_time + Duration::from_secs(5)), + ); } #[test] @@ -75,5 +78,7 @@ fn test_large_number_of_scores() { for i in 0..1000 { evaluator.record_only_with_current_time(i as f64, start_time + Duration::from_secs(i)); } - assert_eq!(evaluator.record_and_eval_with_current_time(1000.0, start_time + Duration::from_secs(1001)), true); + assert!( + evaluator.record_and_eval_with_current_time(1000.0, start_time + Duration::from_secs(1001)), + ); } diff --git a/crates/core/src/topology/mod.rs b/crates/core/src/topology/mod.rs index ef3c4f289..42d1e7be8 100644 --- a/crates/core/src/topology/mod.rs +++ b/crates/core/src/topology/mod.rs @@ -12,7 +12,7 @@ mod small_world_rand; mod connection_evaluator; const SLOW_CONNECTION_EVALUATOR_WINDOW_DURATION: Duration = Duration::from_secs(5 * 60); -const FAST_CONNECTION_EVALUATOR_WINDOW_DURATION: Duration = Duration::from_secs(1 * 60); +const FAST_CONNECTION_EVALUATOR_WINDOW_DURATION: Duration = Duration::from_secs(60); const REQUEST_DENSITY_TRACKER_WINDOW_SIZE: usize = 10_000; const REGENERATE_DENSITY_MAP_INTERVAL: Duration = Duration::from_secs(60); const RANDOM_CLOSEST_DISTANCE: f64 = 1.0 / 1000.0; diff --git a/crates/core/src/topology/request_density_tracker/cached_density_map.rs b/crates/core/src/topology/request_density_tracker/cached_density_map.rs index 2869c7884..db18ff85c 100644 --- a/crates/core/src/topology/request_density_tracker/cached_density_map.rs +++ b/crates/core/src/topology/request_density_tracker/cached_density_map.rs @@ -1,6 +1,10 @@ -use std::{time::{Duration, Instant}, collections::BTreeMap, rc::Rc}; use crate::ring::Location; use crate::topology::request_density_tracker::{self, DensityMapError}; +use std::{ + collections::BTreeMap, + rc::Rc, + time::{Duration, Instant}, +}; /// Struct to handle caching of DensityMap pub(in crate::topology) struct CachedDensityMap { @@ -16,7 +20,11 @@ impl CachedDensityMap { } } - pub(in crate::topology) fn get_or_create(&mut self, tracker: &request_density_tracker::RequestDensityTracker, current_neighbors: &BTreeMap) -> Result, DensityMapError> { + pub(in crate::topology) fn get_or_create( + &mut self, + tracker: &request_density_tracker::RequestDensityTracker, + current_neighbors: &BTreeMap, + ) -> Result, DensityMapError> { let now = Instant::now(); if let Some((density_map, last_update)) = &self.density_map { if now.duration_since(*last_update) < self.regenerate_interval { diff --git a/crates/core/src/topology/request_density_tracker/mod.rs b/crates/core/src/topology/request_density_tracker/mod.rs index 99994fdeb..8449162ae 100644 --- a/crates/core/src/topology/request_density_tracker/mod.rs +++ b/crates/core/src/topology/request_density_tracker/mod.rs @@ -3,9 +3,9 @@ pub mod cached_density_map; #[cfg(test)] mod tests; +use crate::ring::Location; use std::collections::{BTreeMap, LinkedList}; use thiserror::Error; -use crate::ring::Location; /// Tracks requests sent by a node to its neighbors and creates a density map, which /// is useful for determining which new neighbors to connect to based on their @@ -45,35 +45,52 @@ impl RequestDensityTracker { } } - pub(crate) fn create_density_map(&self, neighbors: &BTreeMap) -> Result { + pub(crate) fn create_density_map( + &self, + neighbors: &BTreeMap, + ) -> Result { if neighbors.is_empty() { return Err(DensityMapError::EmptyNeighbors); } - + let smoothing_radius = 2; let mut density_map = DensityMap { neighbor_request_counts: BTreeMap::new(), }; - + for (sample_location, sample_count) in self.ordered_map.iter() { - let previous_neighbor = neighbors.range(..*sample_location).rev().next() - .or_else(|| neighbors.iter().rev().next()); - let next_neighbor = neighbors.range(*sample_location..).next() + let previous_neighbor = neighbors + .range(..*sample_location) + .next_back() + .or_else(|| neighbors.iter().next_back()); + let next_neighbor = neighbors + .range(*sample_location..) + .next() .or_else(|| neighbors.iter().next()); - - match (previous_neighbor, next_neighbor) { - (Some((previous_neighbor_location, _)), Some((next_neighbor_location, _))) => { - if sample_location.distance(*previous_neighbor_location) < sample_location.distance(*next_neighbor_location) { - *density_map.neighbor_request_counts.entry(*previous_neighbor_location).or_insert(0) += sample_count; - } else { - *density_map.neighbor_request_counts.entry(*next_neighbor_location).or_insert(0) += sample_count; - } - }, - // The None cases have been removed as they should not occur given the new logic - _ => unreachable!("This shouldn't be possible given that we verify neighbors is not empty"), + + match (previous_neighbor, next_neighbor) { + (Some((previous_neighbor_location, _)), Some((next_neighbor_location, _))) => { + if sample_location.distance(*previous_neighbor_location) + < sample_location.distance(*next_neighbor_location) + { + *density_map + .neighbor_request_counts + .entry(*previous_neighbor_location) + .or_insert(0) += sample_count; + } else { + *density_map + .neighbor_request_counts + .entry(*next_neighbor_location) + .or_insert(0) += sample_count; + } } + // The None cases have been removed as they should not occur given the new logic + _ => unreachable!( + "This shouldn't be possible given that we verify neighbors is not empty" + ), + } } - + Ok(density_map) } } @@ -89,24 +106,37 @@ impl DensityMap { } // Determine the locations below and above the given location - let previous_neighbor = self.neighbor_request_counts.range(..location).rev().next() - .or_else(|| self.neighbor_request_counts.iter().rev().next()); - - let next_neighbor = self.neighbor_request_counts.range(location..).next() + let previous_neighbor = self + .neighbor_request_counts + .range(..location) + .next_back() + .or_else(|| self.neighbor_request_counts.iter().next_back()); + + let next_neighbor = self + .neighbor_request_counts + .range(location..) + .next() .or_else(|| self.neighbor_request_counts.iter().next()); // Determine the value proportionate to the distance to the previous and next neighbor let count_estimate = match (previous_neighbor, next_neighbor) { - (Some((previous_neighbor_location, previous_neighbor_count)), Some((next_neighbor_location, next_neighbor_count))) => { - let previous_neighbor_dist = location.distance(*previous_neighbor_location).as_f64(); + ( + Some((previous_neighbor_location, previous_neighbor_count)), + Some((next_neighbor_location, next_neighbor_count)), + ) => { + let previous_neighbor_dist = + location.distance(*previous_neighbor_location).as_f64(); let next_neighbor_dist = location.distance(*next_neighbor_location).as_f64(); let total_dist = previous_neighbor_dist + next_neighbor_dist; let previous_neighbor_prop = previous_neighbor_dist / total_dist; let next_neighbor_prop = next_neighbor_dist / total_dist; - next_neighbor_prop * *previous_neighbor_count as f64 + previous_neighbor_prop * *next_neighbor_count as f64 - }, + next_neighbor_prop * *previous_neighbor_count as f64 + + previous_neighbor_prop * *next_neighbor_count as f64 + } // The None cases have been removed as they should not occur given the new logic - _ => unreachable!("This shouldn't be possible given that we verify neighbors is not empty"), + _ => unreachable!( + "This shouldn't be possible given that we verify neighbors is not empty" + ), }; Ok(count_estimate) @@ -123,25 +153,35 @@ impl DensityMap { let mut max_density = 0; for ( - (previous_neighbor_location, previous_neighbor_count), (next_neighbor_location, next_neighbor_count)) - in - self.neighbor_request_counts.iter().zip(self.neighbor_request_counts.iter().skip(1)) { + (previous_neighbor_location, previous_neighbor_count), + (next_neighbor_location, next_neighbor_count), + ) in self + .neighbor_request_counts + .iter() + .zip(self.neighbor_request_counts.iter().skip(1)) + { let combined_count = previous_neighbor_count + next_neighbor_count; if combined_count > max_density { max_density = combined_count; - max_density_location = Location::new((previous_neighbor_location.as_f64() + next_neighbor_location.as_f64()) / 2.0); + max_density_location = Location::new( + (previous_neighbor_location.as_f64() + next_neighbor_location.as_f64()) / 2.0, + ); } } // We need to also check the first and last neighbors as locations are circular let first_neighbor = self.neighbor_request_counts.iter().next(); - let last_neighbor = self.neighbor_request_counts.iter().rev().next(); - if let (Some((first_neighbor_location, first_neighbor_count)), Some((last_neighbor_location, last_neighbor_count))) = (first_neighbor, last_neighbor) { + let last_neighbor = self.neighbor_request_counts.iter().next_back(); + if let ( + Some((first_neighbor_location, first_neighbor_count)), + Some((last_neighbor_location, last_neighbor_count)), + ) = (first_neighbor, last_neighbor) + { let combined_count = first_neighbor_count + last_neighbor_count; if combined_count > max_density { // max_density = combined_count; Not needed as this is the last check let distance = first_neighbor_location.distance(*last_neighbor_location); - let mut mp = first_neighbor_location.as_f64() - (distance.as_f64()/2.0); + let mut mp = first_neighbor_location.as_f64() - (distance.as_f64() / 2.0); if mp < 0.0 { mp += 1.0; } @@ -160,10 +200,7 @@ pub enum DensityError { CantFindBounds, #[error("Window radius too big. Window radius should be <= 50% of the number of samples ({samples}) and window size ({window_size}).")] - WindowTooBig { - samples: usize, - window_size: usize, - }, + WindowTooBig { samples: usize, window_size: usize }, } #[derive(Error, Debug)] @@ -171,5 +208,3 @@ pub enum DensityMapError { #[error("The neighbors BTreeMap is empty.")] EmptyNeighbors, } - - diff --git a/crates/core/src/topology/request_density_tracker/tests.rs b/crates/core/src/topology/request_density_tracker/tests.rs index 3e6d0f93d..8f9a3fb39 100644 --- a/crates/core/src/topology/request_density_tracker/tests.rs +++ b/crates/core/src/topology/request_density_tracker/tests.rs @@ -16,8 +16,14 @@ fn test_create_density_map() { let result = sw.create_density_map(&neighbors); assert!(result.is_ok()); let result = result.unwrap(); - assert_eq!(result.neighbor_request_counts.get(&Location::new(0.2)), Some(&3)); - assert_eq!(result.neighbor_request_counts.get(&Location::new(0.6)), Some(&2)); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.2)), + Some(&3) + ); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.6)), + Some(&2) + ); } #[test] @@ -36,8 +42,14 @@ fn test_wrap_around() { let result = sw.create_density_map(&neighbors); assert!(result.is_ok()); let result = result.unwrap(); - assert_eq!(result.neighbor_request_counts.get(&Location::new(0.9)), Some(&3)); - assert_eq!(result.neighbor_request_counts.get(&Location::new(0.6)), Some(&2)); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.9)), + Some(&3) + ); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.6)), + Some(&2) + ); } #[test] @@ -63,7 +75,11 @@ fn test_interpolate() { let location = Location::new(i as f64 / 100.0); let density = result.get_density_at(location).unwrap(); // Print and round density to 2 decimals - println!("{}\t{}", location.as_f64(), (density * 100.0).round() / 100.0); + println!( + "{}\t{}", + location.as_f64(), + (density * 100.0).round() / 100.0 + ); } assert_eq!(result.get_density_at(Location::new(0.2)).unwrap(), 3.0); @@ -88,9 +104,14 @@ fn test_drop() { let result = sw.create_density_map(&neighbors); assert!(result.is_ok()); let result = result.unwrap(); - assert_eq!(result.neighbor_request_counts.get(&Location::new(0.2)), Some(&2)); - assert_eq!(result.neighbor_request_counts.get(&Location::new(0.6)), Some(&2)); - + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.2)), + Some(&2) + ); + assert_eq!( + result.neighbor_request_counts.get(&Location::new(0.6)), + Some(&2) + ); } #[test] @@ -107,9 +128,15 @@ fn test_get_max_density() { neighbor_request_counts: BTreeMap::new(), }; - density_map.neighbor_request_counts.insert(Location::new(0.2), 1); - density_map.neighbor_request_counts.insert(Location::new(0.6), 2); - density_map.neighbor_request_counts.insert(Location::new(0.8), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.2), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.6), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.8), 2); let result = density_map.get_max_density(); assert!(result.is_ok()); @@ -123,10 +150,18 @@ fn test_get_max_density_2() { neighbor_request_counts: BTreeMap::new(), }; - density_map.neighbor_request_counts.insert(Location::new(0.2), 1); - density_map.neighbor_request_counts.insert(Location::new(0.6), 2); - density_map.neighbor_request_counts.insert(Location::new(0.8), 2); - density_map.neighbor_request_counts.insert(Location::new(0.9), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.2), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.6), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.8), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.9), 1); let result = density_map.get_max_density(); assert!(result.is_ok()); @@ -140,11 +175,21 @@ fn test_get_max_density_first_last() { neighbor_request_counts: BTreeMap::new(), }; - density_map.neighbor_request_counts.insert(Location::new(0.0), 2); - density_map.neighbor_request_counts.insert(Location::new(0.2), 1); - density_map.neighbor_request_counts.insert(Location::new(0.6), 1); - density_map.neighbor_request_counts.insert(Location::new(0.8), 1); - density_map.neighbor_request_counts.insert(Location::new(0.9), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.0), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.2), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.6), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.8), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.9), 2); let result = density_map.get_max_density(); assert!(result.is_ok()); @@ -159,11 +204,21 @@ fn test_get_max_density_first_last_2() { neighbor_request_counts: BTreeMap::new(), }; - density_map.neighbor_request_counts.insert(Location::new(0.3), 2); - density_map.neighbor_request_counts.insert(Location::new(0.4), 1); - density_map.neighbor_request_counts.insert(Location::new(0.6), 1); - density_map.neighbor_request_counts.insert(Location::new(0.8), 1); - density_map.neighbor_request_counts.insert(Location::new(0.9), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.3), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.4), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.6), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.8), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.9), 2); let result = density_map.get_max_density(); assert!(result.is_ok()); @@ -178,11 +233,21 @@ fn test_get_max_density_first_last_3() { neighbor_request_counts: BTreeMap::new(), }; - density_map.neighbor_request_counts.insert(Location::new(0.1), 2); - density_map.neighbor_request_counts.insert(Location::new(0.2), 1); - density_map.neighbor_request_counts.insert(Location::new(0.3), 1); - density_map.neighbor_request_counts.insert(Location::new(0.4), 1); - density_map.neighbor_request_counts.insert(Location::new(0.7), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.1), 2); + density_map + .neighbor_request_counts + .insert(Location::new(0.2), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.3), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.4), 1); + density_map + .neighbor_request_counts + .insert(Location::new(0.7), 2); let result = density_map.get_max_density(); assert!(result.is_ok()); diff --git a/crates/core/src/topology/small_world_rand.rs b/crates/core/src/topology/small_world_rand.rs index c0f84868d..57fb309fe 100644 --- a/crates/core/src/topology/small_world_rand.rs +++ b/crates/core/src/topology/small_world_rand.rs @@ -39,14 +39,14 @@ mod tests { } // Perform chi-squared test - let mut expected_counts = vec![0.0; num_bins]; - for i in 0..num_bins { - let lower = d_min + (d_max - d_min) * (i as f64 / num_bins as f64); - let upper = d_min + (d_max - d_min) * ((i as f64 + 1.0) / num_bins as f64); - expected_counts[i] = ((upper - lower) / (upper.powf(-1.0) - lower.powf(-1.0))).floor() - * n as f64 - / num_bins as f64; - } + let expected_counts: Vec<_> = (0..num_bins) + .map(|i| { + let lower = d_min + (d_max - d_min) * (i as f64 / num_bins as f64); + let upper = d_min + (d_max - d_min) * ((i as f64 + 1.0) / num_bins as f64); + ((upper - lower) / (upper.powf(-1.0) - lower.powf(-1.0))).floor() * n as f64 + / num_bins as f64 + }) + .collect(); let chi_squared = expected_counts .iter()