Merge refactor-sans-io: converge next bloom onto v1-sans-IO

Forward-merge the master-line bloom relocation into the next line,
discarding next's v1.5 bloom draft (RLE codec, XOR-diff delta,
FilterNack/0x21, adaptive sizing) and converging both lines onto the
identical proto/bloom v1-sans-IO module. This is a deliberate,
temporary wire regression on the next line: the v2 bloom is rebuilt
fresh, sans-IO from day one, on both lines later. Nothing outside the
bloom feature depended on the v1.5-specific surface. proto/bloom is now
byte-identical across both integration lines.
This commit is contained in:
Johnathan Corgan
2026-07-07 23:22:22 +00:00
28 changed files with 1322 additions and 2598 deletions
+3 -3
View File
@@ -455,8 +455,8 @@ async fn test_bloom_filter_split_horizon() {
/// counted once, not the double-count fingerprint.
#[test]
fn compute_mesh_size_counts_each_peer_filter_once() {
use crate::bloom::BloomFilter;
use crate::peer::ActivePeer;
use crate::proto::bloom::BloomFilter;
use crate::proto::stp::ParentDeclaration;
let mut node = make_node();
@@ -550,8 +550,8 @@ fn compute_mesh_size_counts_each_peer_filter_once() {
/// `estimated_mesh_size` carries.
#[test]
fn compute_mesh_size_unions_overlapping_filters() {
use crate::bloom::BloomFilter;
use crate::peer::ActivePeer;
use crate::proto::bloom::BloomFilter;
let mut node = make_node();
@@ -632,8 +632,8 @@ fn compute_mesh_size_unions_overlapping_filters() {
/// removes the parent, and asserts the estimate does not collapse.
#[test]
fn compute_mesh_size_stable_across_parent_drop_with_cross_link() {
use crate::bloom::BloomFilter;
use crate::peer::ActivePeer;
use crate::proto::bloom::BloomFilter;
let mut node = make_node();
-102
View File
@@ -1,102 +0,0 @@
//! Registry-counter coverage tests for the bloom-v2 metric counters that
//! the mesh-lab suites do not reliably exercise.
//!
//! The send-path bloom counters (`deltas_sent`, `full_sends`,
//! `total_compressed_bytes`, `total_raw_bytes`) fire on every filter
//! announce and are covered by the steady-state suites. The three
//! condition-dependent counters (`nacks_sent`, `nacks_received`,
//! `size_changes`) only fire on out-of-sequence deltas, inbound NACKs,
//! and adaptive resizes — none of which occur in the stable, lossless
//! mesh-lab scenarios. These tests drive each of those paths directly and
//! assert the registry counter increments.
use super::*;
use crate::bloom::{BloomFilter, V1_SIZE_CLASS};
use crate::peer::ActivePeer;
use crate::protocol::{FilterAnnounce, FilterNack};
/// Inject a synthetic active peer with a known NodeAddr; returns it.
fn inject_peer(node: &mut Node) -> NodeAddr {
let peer_identity = make_peer_identity();
let peer_addr = *peer_identity.node_addr();
let peer = ActivePeer::new(peer_identity, LinkId::new(1), 0);
node.peers.insert(peer_addr, peer);
peer_addr
}
/// Encode a FilterAnnounce to the payload format handle_filter_announce
/// expects (msg_type byte stripped).
fn encode_announce(announce: &FilterAnnounce) -> Vec<u8> {
let (mut full, _stats) = announce.encode().unwrap();
full.remove(0); // strip msg_type byte
full
}
/// An out-of-sequence delta to a peer with no stored filter makes the node
/// send a NACK, bumping `nacks_sent`.
#[tokio::test]
async fn test_bloom_nacks_sent_counter() {
let mut node = make_node();
let peer_addr = inject_peer(&mut node);
// Fresh peer: filter_sequence == 0. A delta whose base_seq does not
// match the expected base (0) is out-of-sequence → NACK.
let announce = FilterAnnounce::delta(BloomFilter::new(), 2, 5, V1_SIZE_CLASS);
let payload = encode_announce(&announce);
node.handle_filter_announce(&peer_addr, &payload).await;
assert_eq!(
node.metrics().bloom.nacks_sent.get(),
1,
"registry nacks_sent must increment on out-of-sequence delta"
);
}
/// An inbound FilterNack bumps `nacks_received`.
#[tokio::test]
async fn test_bloom_nacks_received_counter() {
let mut node = make_node();
let peer_addr = inject_peer(&mut node);
let mut payload = FilterNack { expected_seq: 7 }.encode();
payload.remove(0); // strip msg_type byte (decode expects the seq only)
node.handle_filter_nack(&peer_addr, &payload).await;
assert_eq!(
node.metrics().bloom.nacks_received.get(),
1,
"registry nacks_received must increment on inbound NACK"
);
}
/// A fresh Full node starts at V1_SIZE_CLASS with a nearly empty outgoing
/// filter (just its own addr), so the first adaptive-sizing pass steps the
/// size class down, bumping `size_changes`.
#[tokio::test]
async fn test_bloom_size_changes_counter() {
let mut node = make_node();
// check_adaptive_sizing needs at least one peer for the representative
// outgoing-filter computation.
let _peer = inject_peer(&mut node);
assert_eq!(
node.bloom_state.size_class(),
V1_SIZE_CLASS,
"fresh node starts at the v1 size class"
);
node.check_bloom_state().await;
assert_eq!(
node.metrics().bloom.size_changes.get(),
1,
"registry size_changes must increment on adaptive resize"
);
assert_eq!(
node.bloom_state.size_class(),
V1_SIZE_CLASS - 1,
"near-empty outgoing filter steps the size class down"
);
}
+7 -7
View File
@@ -7,9 +7,9 @@
//! bloom.rs.
use super::*;
use crate::bloom::{BloomFilter, DEFAULT_FILTER_SIZE_BITS, DEFAULT_HASH_COUNT};
use crate::peer::ActivePeer;
use crate::protocol::FilterAnnounce;
use crate::proto::bloom::FilterAnnounce;
use crate::proto::bloom::{BloomFilter, DEFAULT_FILTER_SIZE_BITS, DEFAULT_HASH_COUNT};
/// Inject a synthetic active peer into the node with a known NodeAddr.
/// Returns the peer's NodeAddr.
@@ -24,7 +24,7 @@ fn inject_peer(node: &mut Node) -> NodeAddr {
/// Encode a FilterAnnounce to the payload format handle_filter_announce
/// expects (msg_type byte stripped).
fn encode_payload(announce: &FilterAnnounce) -> Vec<u8> {
let (mut full, _stats) = announce.encode().unwrap();
let mut full = announce.encode().unwrap();
full.remove(0); // strip msg_type byte
full
}
@@ -40,7 +40,7 @@ async fn test_m1_rejects_all_ones_filter_announce() {
DEFAULT_HASH_COUNT,
)
.unwrap();
let announce = FilterAnnounce::full(all_ones, 1, 1);
let announce = FilterAnnounce::new(all_ones, 1);
let payload = encode_payload(&announce);
let before_fill_exceeded = node.metrics().bloom.fill_exceeded.get();
@@ -88,7 +88,7 @@ async fn test_m1_accepts_sub_cap_filter() {
bytes[0] = i;
filter.insert(&NodeAddr::from_bytes(bytes));
}
let announce = FilterAnnounce::full(filter, 1, 1);
let announce = FilterAnnounce::new(filter, 1);
let payload = encode_payload(&announce);
let before_fill_exceeded = node.metrics().bloom.fill_exceeded.get();
@@ -135,7 +135,7 @@ async fn test_m1_sequence_not_advanced_allows_recovery() {
DEFAULT_HASH_COUNT,
)
.unwrap();
let bad_announce = FilterAnnounce::full(bad, 1, 1);
let bad_announce = FilterAnnounce::new(bad, 1);
node.handle_filter_announce(&peer_addr, &encode_payload(&bad_announce))
.await;
assert_eq!(
@@ -152,7 +152,7 @@ async fn test_m1_sequence_not_advanced_allows_recovery() {
bytes[0] = i;
good.insert(&NodeAddr::from_bytes(bytes));
}
let good_announce = FilterAnnounce::full(good, 1, 1);
let good_announce = FilterAnnounce::new(good, 1);
node.handle_filter_announce(&peer_addr, &encode_payload(&good_announce))
.await;
+1 -1
View File
@@ -1160,8 +1160,8 @@ async fn test_open_discovery_sweep_queues_eligible_skips_filtered() {
/// that `initiate_lookup` ran fresh on each attempt.
#[tokio::test]
async fn test_check_pending_lookups_default_sequence_unreachable() {
use crate::bloom::BloomFilter;
use crate::peer::ActivePeer;
use crate::proto::bloom::BloomFilter;
use crate::proto::discovery::PendingLookup;
use crate::transport::LinkId;
use std::sync::mpsc;
-1
View File
@@ -8,7 +8,6 @@ mod acl;
#[cfg(target_os = "linux")]
mod ble;
mod bloom;
mod bloom_metrics;
mod bloom_poison;
mod bootstrap;
mod decrypt_failure;
+1 -1
View File
@@ -4,7 +4,7 @@
//! filter priority, greedy tree routing, and tie-breaking.
use super::*;
use crate::bloom::BloomFilter;
use crate::proto::bloom::BloomFilter;
use crate::proto::stp::{ParentDeclaration, TreeCoordinate};
use spanning_tree::{
TestNode, cleanup_nodes, drain_all_packets, generate_random_edges, initiate_handshake,