Skip to content

Commit cf6ebe0

Browse files
committed
add test
1 parent 708eff5 commit cf6ebe0

File tree

2 files changed

+77
-42
lines changed

2 files changed

+77
-42
lines changed

gossip/src/low_pass_filter.rs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
//! Fixed-point exponential smoothing filter for `alpha` update.
1+
//! Fixed-point IIR filter for smoothing `alpha` updates.
22
//!
33
//! Implements:
44
//! alpha_new = K * target + (1 - K) * previous
55
//!
6-
//! All math is unsigned integer fixed-point with `SCALE = 1000` (i.e. milli-units).
6+
//! All math is unsigned integer fixed-point with `SCALE = 1000`
77
//!
88
//! The filter constant K is derived from:
9-
//! K = W_C / (1 + W_C), where Wc = 2π * Fc / Fs
9+
//! K = W_C / (1 + W_C), where Wc = 2π * Fc * Fs
1010
//! Fc = 1 / TC (cutoff frequency)
1111
//! Fs = 1 / refresh interval
1212
use crate::cluster_info::REFRESH_PUSH_ACTIVE_SET_INTERVAL_MS;
@@ -20,12 +20,10 @@ const FS_MS: u64 = REFRESH_PUSH_ACTIVE_SET_INTERVAL_MS;
2020
// 2 * pi * SCALE
2121
const TWO_PI_SCALED: u64 = 6_283;
2222
const W_C_SCALED: u64 = (TWO_PI_SCALED * FS_MS) / TC_MS;
23-
/// Smoothing constant K = W_C / (1 + W_C)
24-
// K ~ 611
2523
const K: u64 = (W_C_SCALED * 1_000 + (SCALE / 2)) / (SCALE + W_C_SCALED);
2624

2725
/// Updates alpha with a first-order low-pass filter.
28-
/// ### Convergence Characteristics (assuming K = 0.611):
26+
/// ### Convergence Characteristics (w/ K = 0.611):
2927
///
3028
/// - From a step change in target, `alpha` reaches:
3129
/// - ~61% of the way to target after 1 update
@@ -44,18 +42,8 @@ pub fn filter_alpha(prev: u64, target: u64, min: u64, max: u64) -> u64 {
4442
updated.clamp(min, max)
4543
}
4644

47-
/// Approximates `base^alpha` using integer-only linear interpolation between `base^1` and `base^2`.
48-
///
49-
/// Caller supplies:
50-
/// - `base`: the value of `base^1`
51-
/// - `t`: a fixed-point interpolation factor in the range `[0, SCALE]`
52-
///
53-
/// This approximates:
54-
/// result ≈ (1 - x) * start + x * end
55-
/// where x = t / SCALE and 0 ≤ x ≤ 1
56-
///
57-
/// Returns:
58-
/// - Approximated value of `base^alpha`, rounded to nearest integer
45+
/// Approximates `base^alpha` rounded to nearest integer using
46+
/// integer-only linear interpolation between `base^1` and `base^2`.
5947
#[inline]
6048
pub fn interpolate(base: u64, t: u64) -> u64 {
6149
debug_assert!(t <= SCALE, "interpolation t={} > SCALE={}", t, SCALE);

gossip/src/push_active_set.rs

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use {
44
weighted_shuffle::WeightedShuffle,
55
},
66
indexmap::IndexMap,
7-
// log::error,
87
rand::Rng,
98
solana_bloom::bloom::{Bloom, ConcurrentBloom},
109
solana_native_token::LAMPORTS_PER_SOL,
@@ -15,7 +14,7 @@ use {
1514
const NUM_PUSH_ACTIVE_SET_ENTRIES: usize = 25;
1615
const ALPHA_MIN: u64 = SCALE;
1716
const ALPHA_MAX: u64 = 2 * SCALE;
18-
const DEFAULT_ALPHA: u64 = 2 * SCALE;
17+
const DEFAULT_ALPHA: u64 = ALPHA_MAX;
1918

2019
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2120
pub enum WeightingMode {
@@ -53,6 +52,17 @@ impl Default for PushActiveSet {
5352
}
5453
}
5554

55+
#[cfg(test)]
56+
impl PushActiveSet {
57+
fn new_static() -> Self {
58+
Self {
59+
entries: Default::default(),
60+
alpha: DEFAULT_ALPHA,
61+
mode: WeightingMode::Static,
62+
}
63+
}
64+
}
65+
5666
// Keys are gossip nodes to push messages to.
5767
// Values are which origins the node has pruned.
5868
#[derive(Default)]
@@ -148,7 +158,6 @@ impl PushActiveSet {
148158
let f_scaled = ((num_unstaked * SCALE as usize) + nodes.len() / 2) / nodes.len();
149159
let alpha_target = ALPHA_MIN + (f_scaled as u64).min(SCALE);
150160
self.alpha = filter_alpha(self.alpha, alpha_target, ALPHA_MIN, ALPHA_MAX);
151-
println!("alpha_target: {}, alpha: {}", alpha_target, self.alpha);
152161

153162
for (k, entry) in self.entries.iter_mut().enumerate() {
154163
let weights: Vec<u64> = buckets
@@ -297,15 +306,15 @@ mod tests {
297306
}
298307

299308
#[test]
300-
fn test_push_active_set() {
309+
fn test_push_active_set_static_weighting() {
301310
const CLUSTER_SIZE: usize = 117;
302311
let mut rng = ChaChaRng::from_seed([189u8; 32]);
303312
let pubkey = Pubkey::new_unique();
304313
let nodes: Vec<_> = repeat_with(Pubkey::new_unique).take(20).collect();
305314
let stakes = repeat_with(|| rng.gen_range(1..MAX_STAKE));
306315
let mut stakes: HashMap<_, _> = nodes.iter().copied().zip(stakes).collect();
307316
stakes.insert(pubkey, rng.gen_range(1..MAX_STAKE));
308-
let mut active_set = PushActiveSet::default();
317+
let mut active_set = PushActiveSet::new_static();
309318
assert!(active_set.entries.iter().all(|entry| entry.0.is_empty()));
310319
active_set.rotate(&mut rng, 5, CLUSTER_SIZE, &nodes, &stakes);
311320
assert!(active_set.entries.iter().all(|entry| entry.0.len() == 5));
@@ -317,28 +326,9 @@ mod tests {
317326
}
318327
let other = &nodes[5];
319328
let origin = &nodes[17];
320-
321-
// Debug: print expected nodes
322-
// let expected_indices = [13, 5, 18, 16, 0];
323-
// println!("Expected nodes:");
324-
// for (i, &k) in expected_indices.iter().enumerate() {
325-
// println!(" [{}] nodes[{}] = {:?}", i, k, nodes[k]);
326-
// }
327-
328-
// // Debug: print actual nodes returned by get_nodes
329-
// let actual_nodes: Vec<_> = active_set
330-
// .get_nodes(&pubkey, origin, |_| false, &stakes)
331-
// .collect();
332-
// println!("Actual nodes returned by get_nodes:");
333-
// for (i, node) in actual_nodes.iter().enumerate() {
334-
// println!(" [{}] = {:?}", i, node);
335-
// }
336-
337329
assert!(active_set
338330
.get_nodes(&pubkey, origin, |_| false, &stakes)
339331
.eq([13, 5, 18, 16, 0].into_iter().map(|k| &nodes[k])));
340-
341-
///////////////////////////
342332
assert!(active_set
343333
.get_nodes(&pubkey, other, |_| false, &stakes)
344334
.eq([13, 18, 16, 0].into_iter().map(|k| &nodes[k])));
@@ -371,6 +361,63 @@ mod tests {
371361
.eq([16, 7, 11].into_iter().map(|k| &nodes[k])));
372362
}
373363

364+
#[test]
365+
fn test_push_active_set_dynamic_weighting() {
366+
const CLUSTER_SIZE: usize = 117;
367+
let mut rng = ChaChaRng::from_seed([14u8; 32]);
368+
let pubkey = Pubkey::new_unique();
369+
let nodes: Vec<_> = repeat_with(Pubkey::new_unique).take(20).collect();
370+
let stakes = repeat_with(|| rng.gen_range(1..MAX_STAKE));
371+
let mut stakes: HashMap<_, _> = nodes.iter().copied().zip(stakes).collect();
372+
stakes.insert(pubkey, rng.gen_range(1..MAX_STAKE));
373+
let mut active_set = PushActiveSet::default();
374+
assert!(active_set.entries.iter().all(|entry| entry.0.is_empty()));
375+
active_set.rotate(&mut rng, 5, CLUSTER_SIZE, &nodes, &stakes);
376+
assert!(active_set.entries.iter().all(|entry| entry.0.len() == 5));
377+
// Assert that for all entries, each filter already prunes the key.
378+
for entry in &active_set.entries {
379+
for (node, filter) in entry.0.iter() {
380+
assert!(filter.contains(node));
381+
}
382+
}
383+
let other = &nodes[6];
384+
let origin = &nodes[17];
385+
assert!(active_set
386+
.get_nodes(&pubkey, origin, |_| false, &stakes)
387+
.eq([7, 6, 2, 4, 12].into_iter().map(|k| &nodes[k])));
388+
assert!(active_set
389+
.get_nodes(&pubkey, other, |_| false, &stakes)
390+
.eq([7, 2, 4, 12].into_iter().map(|k| &nodes[k])));
391+
392+
active_set.prune(&pubkey, &nodes[6], &[*origin], &stakes);
393+
active_set.prune(&pubkey, &nodes[11], &[*origin], &stakes);
394+
active_set.prune(&pubkey, &nodes[4], &[*origin], &stakes);
395+
assert!(active_set
396+
.get_nodes(&pubkey, origin, |_| false, &stakes)
397+
.eq([7, 2, 12].into_iter().map(|k| &nodes[k])));
398+
assert!(active_set
399+
.get_nodes(&pubkey, other, |_| false, &stakes)
400+
.eq([7, 2, 4, 12].into_iter().map(|k| &nodes[k])));
401+
active_set.rotate(&mut rng, 7, CLUSTER_SIZE, &nodes, &stakes);
402+
assert!(active_set.entries.iter().all(|entry| entry.0.len() == 7));
403+
assert!(active_set
404+
.get_nodes(&pubkey, origin, |_| false, &stakes)
405+
.eq([2, 12, 16, 9, 14].into_iter().map(|k| &nodes[k])));
406+
assert!(active_set
407+
.get_nodes(&pubkey, other, |_| false, &stakes)
408+
.eq([2, 4, 12, 16, 9, 14].into_iter().map(|k| &nodes[k])));
409+
let origins = [*origin, *other];
410+
active_set.prune(&pubkey, &nodes[2], &origins, &stakes);
411+
active_set.prune(&pubkey, &nodes[12], &origins, &stakes);
412+
active_set.prune(&pubkey, &nodes[9], &origins, &stakes);
413+
assert!(active_set
414+
.get_nodes(&pubkey, origin, |_| false, &stakes)
415+
.eq([16, 14].into_iter().map(|k| &nodes[k])));
416+
assert!(active_set
417+
.get_nodes(&pubkey, other, |_| false, &stakes)
418+
.eq([4, 16, 14].into_iter().map(|k| &nodes[k])));
419+
}
420+
374421
#[test]
375422
fn test_push_active_set_entry() {
376423
const NUM_BLOOM_FILTER_ITEMS: usize = 100;

0 commit comments

Comments
 (0)