mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-11 17:17:54 +00:00
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.
316 lines
9.5 KiB
Rust
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));
|
|
}
|