Files
fips/src/proto/bloom/tests/state.rs
T
Johnathan Corgan 2b009196b5 proto: no_std hygiene for the fmt/alloc imports and the filter math
Bring the proto tree closer to a std+alloc-only posture, behavior unchanged on
every decision path:

- Sweep the remaining std::fmt and std::collections imports over to core::fmt
  and alloc::collections across the wire codecs and their tests. Subsystem files
  that shadow the core name with a child core module use the leading-colon
  ::core::fmt form. Imports only.

- Replace the two f64::powi calls (bloom false-positive-rate, FMP backoff timer)
  with a shared core-only square-and-multiply helper in proto/math, bit-identical
  to std::powi (a guard test pins this against every exponent the codecs reach),
  and route the diagnostic estimated-count natural log through libm::log. The FPR
  reject decision and the backoff timer are bit-for-bit unchanged; only the
  debug-only count estimate may differ by at most one ULP.
2026-07-08 19:00:25 +00:00

316 lines
9.5 KiB
Rust

//! Tests for `BloomState` (announcement state management).
use alloc::collections::BTreeMap;
use crate::proto::bloom::{BloomFilter, BloomState};
use crate::testutil::make_node_addr;
#[test]
fn test_bloom_state_new() {
let node = make_node_addr(0);
let state = BloomState::new(node);
assert_eq!(state.own_node_addr(), &node);
assert!(!state.is_leaf_only());
assert_eq!(state.sequence(), 0);
assert_eq!(state.leaf_dependent_count(), 0);
}
#[test]
fn test_bloom_state_leaf_only() {
let node = make_node_addr(0);
let state = BloomState::leaf_only(node);
assert!(state.is_leaf_only());
}
#[test]
fn test_bloom_state_leaf_dependents() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let leaf1 = make_node_addr(1);
let leaf2 = make_node_addr(2);
state.add_leaf_dependent(leaf1);
state.add_leaf_dependent(leaf2);
assert_eq!(state.leaf_dependent_count(), 2);
assert!(state.remove_leaf_dependent(&leaf1));
assert_eq!(state.leaf_dependent_count(), 1);
assert!(!state.remove_leaf_dependent(&leaf1)); // already removed
}
#[test]
fn test_bloom_state_debounce() {
let node = make_node_addr(0);
let peer = make_node_addr(1);
let mut state = BloomState::new(node);
state.set_update_debounce_ms(500);
state.mark_update_needed(peer);
// Should send initially
assert!(state.should_send_update(&peer, 1000));
// Record send
state.record_update_sent(peer, 1000);
state.mark_update_needed(peer);
// Should not send immediately (within debounce)
assert!(!state.should_send_update(&peer, 1200));
// Should send after debounce period
assert!(state.should_send_update(&peer, 1600));
}
#[test]
fn test_bloom_state_sequence() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
assert_eq!(state.sequence(), 0);
assert_eq!(state.next_sequence(), 1);
assert_eq!(state.next_sequence(), 2);
assert_eq!(state.sequence(), 2);
}
#[test]
fn test_bloom_state_pending_updates() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
assert!(!state.needs_update(&peer1));
state.mark_update_needed(peer1);
assert!(state.needs_update(&peer1));
assert!(!state.needs_update(&peer2));
state.mark_all_updates_needed(vec![peer1, peer2]);
assert!(state.needs_update(&peer1));
assert!(state.needs_update(&peer2));
state.clear_pending_updates();
assert!(!state.needs_update(&peer1));
assert!(!state.needs_update(&peer2));
}
#[test]
fn test_bloom_state_base_filter() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let leaf = make_node_addr(1);
state.add_leaf_dependent(leaf);
let filter = state.base_filter();
assert!(filter.contains(&node));
assert!(filter.contains(&leaf));
assert!(!filter.contains(&make_node_addr(99)));
}
#[test]
fn test_bloom_state_compute_outgoing_filter() {
let my_node = make_node_addr(0);
let mut state = BloomState::new(my_node);
let leaf = make_node_addr(1);
state.add_leaf_dependent(leaf);
let peer1 = make_node_addr(10);
let peer2 = make_node_addr(20);
// Create peer filters
let mut filter1 = BloomFilter::new();
filter1.insert(&make_node_addr(100));
filter1.insert(&make_node_addr(101));
let mut filter2 = BloomFilter::new();
filter2.insert(&make_node_addr(200));
let mut peer_filters = BTreeMap::new();
peer_filters.insert(peer1, filter1);
peer_filters.insert(peer2, filter2);
// Filter for peer1 should exclude peer1's contributions
let outgoing1 = state.compute_outgoing_filter(&peer1, &peer_filters);
assert!(outgoing1.contains(&my_node)); // self
assert!(outgoing1.contains(&leaf)); // leaf dependent
assert!(outgoing1.contains(&make_node_addr(200))); // from peer2
// peer1's nodes may or may not be present (depends on split brain)
// Filter for peer2 should exclude peer2's contributions
let outgoing2 = state.compute_outgoing_filter(&peer2, &peer_filters);
assert!(outgoing2.contains(&my_node));
assert!(outgoing2.contains(&leaf));
assert!(outgoing2.contains(&make_node_addr(100))); // from peer1
assert!(outgoing2.contains(&make_node_addr(101))); // from peer1
}
#[test]
fn test_bloom_state_leaf_dependents_accessor() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let leaf1 = make_node_addr(1);
let leaf2 = make_node_addr(2);
state.add_leaf_dependent(leaf1);
state.add_leaf_dependent(leaf2);
let deps = state.leaf_dependents();
assert!(deps.contains(&leaf1));
assert!(deps.contains(&leaf2));
assert!(!deps.contains(&make_node_addr(99)));
assert_eq!(deps.len(), 2);
}
#[test]
fn test_bloom_state_record_sent_filter() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let peer = make_node_addr(1);
let mut filter = BloomFilter::new();
filter.insert(&make_node_addr(42));
// Record a sent filter, then mark_changed_peers should detect no change
// when the outgoing filter matches what was recorded
state.record_sent_filter(peer, filter);
// Compute what would be sent to peer (just our own node, no peer filters)
let peer_filters = BTreeMap::new();
let peer_addrs = vec![peer];
state.mark_changed_peers(&make_node_addr(99), &peer_addrs, &peer_filters);
// Outgoing filter (just self) differs from recorded (self + node 42),
// so peer should be marked for update
assert!(state.needs_update(&peer));
}
#[test]
fn test_bloom_state_remove_peer_state() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let peer = make_node_addr(1);
// Populate all three internal maps for this peer
state.mark_update_needed(peer);
state.record_update_sent(peer, 1000);
state.mark_update_needed(peer); // re-mark after send
let filter = BloomFilter::new();
state.record_sent_filter(peer, filter);
assert!(state.needs_update(&peer));
// Remove all peer state
state.remove_peer_state(&peer);
// Pending updates cleared
assert!(!state.needs_update(&peer));
// Debounce state cleared — should be able to send immediately
state.mark_update_needed(peer);
assert!(state.should_send_update(&peer, 0));
// Sent filter cleared — mark_changed_peers should treat as "never sent"
state.clear_pending_updates();
let peer_filters = BTreeMap::new();
let peer_addrs = vec![peer];
state.mark_changed_peers(&make_node_addr(99), &peer_addrs, &peer_filters);
assert!(state.needs_update(&peer)); // never sent → must send
}
#[test]
fn test_bloom_state_mark_changed_peers_never_sent() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
let peer_filters = BTreeMap::new();
let peer_addrs = vec![peer1, peer2];
// No filters ever sent — all peers should be marked
state.mark_changed_peers(&make_node_addr(99), &peer_addrs, &peer_filters);
assert!(state.needs_update(&peer1));
assert!(state.needs_update(&peer2));
}
#[test]
fn test_bloom_state_mark_changed_peers_unchanged() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
let peer_filters = BTreeMap::new();
let peer_addrs = vec![peer1, peer2];
// Compute and record what would be sent to each peer
let outgoing1 = state.compute_outgoing_filter(&peer1, &peer_filters);
let outgoing2 = state.compute_outgoing_filter(&peer2, &peer_filters);
state.record_sent_filter(peer1, outgoing1);
state.record_sent_filter(peer2, outgoing2);
// Nothing changed — no peers should be marked
state.mark_changed_peers(&make_node_addr(99), &peer_addrs, &peer_filters);
assert!(!state.needs_update(&peer1));
assert!(!state.needs_update(&peer2));
}
#[test]
fn test_bloom_state_mark_changed_peers_one_changed() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
let peer_filters = BTreeMap::new();
let peer_addrs = vec![peer1, peer2];
// Record current outgoing filters for both peers
let outgoing1 = state.compute_outgoing_filter(&peer1, &peer_filters);
let outgoing2 = state.compute_outgoing_filter(&peer2, &peer_filters);
state.record_sent_filter(peer1, outgoing1);
state.record_sent_filter(peer2, outgoing2);
// Now peer1 sends us a filter with new entries
let mut inbound_from_peer1 = BloomFilter::new();
inbound_from_peer1.insert(&make_node_addr(100));
let mut updated_peer_filters = BTreeMap::new();
updated_peer_filters.insert(peer1, inbound_from_peer1);
// mark_changed_peers triggered by receiving from peer1
state.mark_changed_peers(&peer1, &peer_addrs, &updated_peer_filters);
// peer1 is excluded (it's the source), peer2's outgoing changed
// (now includes peer1's entries via split-horizon)
assert!(!state.needs_update(&peer1));
assert!(state.needs_update(&peer2));
}
#[test]
fn test_bloom_state_mark_changed_peers_excludes_source() {
let node = make_node_addr(0);
let mut state = BloomState::new(node);
let peer1 = make_node_addr(1);
let peer_filters = BTreeMap::new();
let peer_addrs = vec![peer1];
// peer1 is both the source and the only peer — should be skipped
state.mark_changed_peers(&peer1, &peer_addrs, &peer_filters);
assert!(!state.needs_update(&peer1));
}