Pin bloom-not-closer-than-tree-parent fall-through to greedy tree

Add test_routing_bloom_hit_not_closer_falls_through_to_tree to
src/node/tests/routing.rs covering the regression class fixed in
a859da7: bloom candidates exist BUT none are strictly closer to
destination than the tree parent. Pre-fix find_next_hop returned
None (NoRoute); post-fix it falls through to greedy tree routing.
Existing tests covered bloom-hit-closer, bloom-preferred-over-tree,
no-bloom-hit-tree-fallback, and bloom-hit-without-coords — but the
in-between case was unpinned.

Topology: self is tree root with two children (tree_peer at distance
1, bloom_peer at distance 3); destination is one hop below tree_peer
(self distance 2). Only bloom_peer advertises dest in its filter.
Distance of the only bloom candidate (3) is not strictly less than
self's (2), so select_best_candidate returns None and the call must
fall through to greedy tree routing returning tree_peer. Asserts
explicit identity (assert_eq! tree_peer_addr) and rules out the
wrong-peer regression (assert_ne! bloom_peer_addr).
This commit is contained in:
Johnathan Corgan
2026-05-03 21:02:51 +00:00
parent 9c96c9193d
commit 00f4a4c7af
+92
View File
@@ -201,6 +201,98 @@ fn test_routing_tree_fallback() {
assert_eq!(result.unwrap().node_addr(), &peer_addr);
}
/// Regression: bloom hit on a peer that is NOT strictly closer to dest
/// than we are must fall through to greedy tree routing rather than
/// returning None. Pinned by commit a859da7.
///
/// Pre-fix behavior: bloom candidates exist but `select_best_candidate`
/// rejects them all under the self-distance check (peer dist >= my dist),
/// and `find_next_hop` returned None — a NoRoute failure even though the
/// tree had a valid greedy next hop.
///
/// Post-fix behavior: same scenario falls through to greedy tree routing
/// and returns the tree-routing-selected next hop.
#[test]
fn test_routing_bloom_hit_not_closer_falls_through_to_tree() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let my_addr = *node.node_addr();
// tree_peer: child of self, on the path to dest (greedy tree pick).
let tree_link = LinkId::new(1);
let (tree_conn, tree_id) = make_completed_connection(&mut node, tree_link, transport_id, 1000);
let tree_peer_addr = *tree_id.node_addr();
node.add_connection(tree_conn).unwrap();
node.promote_connection(tree_link, tree_id, 2000).unwrap();
// bloom_peer: also a child of self, but with a stale/false-positive
// bloom hit for dest. Its tree distance to dest is NOT closer than
// ours, so the self-distance check in select_best_candidate excludes
// it — leaving zero viable bloom candidates.
let bloom_link = LinkId::new(2);
let (bloom_conn, bloom_id) =
make_completed_connection(&mut node, bloom_link, transport_id, 1000);
let bloom_peer_addr = *bloom_id.node_addr();
node.add_connection(bloom_conn).unwrap();
node.promote_connection(bloom_link, bloom_id, 2000).unwrap();
// Tree topology (we are root):
// self ── tree_peer ── dest
// └──── bloom_peer
//
// Distances to dest:
// self : 2 (root → tree_peer → dest)
// tree_peer : 1 (tree_peer → dest) ← greedy winner
// bloom_peer : 3 (bloom_peer → root → tree_peer → dest) ← NOT closer than self
let tree_peer_coords = TreeCoordinate::from_addrs(vec![tree_peer_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(tree_peer_addr, my_addr, 1, 1000),
tree_peer_coords,
);
let bloom_peer_coords = TreeCoordinate::from_addrs(vec![bloom_peer_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(bloom_peer_addr, my_addr, 1, 1000),
bloom_peer_coords,
);
// Destination is a child of tree_peer in the tree.
let dest = make_node_addr(99);
let dest_coords = TreeCoordinate::from_addrs(vec![dest, tree_peer_addr, my_addr]).unwrap();
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
node.coord_cache_mut().insert(dest, dest_coords, now_ms);
// dest is in bloom_peer's filter only (the "bloom hit" candidate),
// but bloom_peer's tree distance (3) is NOT strictly less than our
// distance (2), so select_best_candidate yields no winner.
// tree_peer has NO bloom entry for dest.
let bloom_peer = node.get_peer_mut(&bloom_peer_addr).unwrap();
let mut filter = BloomFilter::new();
filter.insert(&dest);
bloom_peer.update_filter(filter, 1, 3000);
// Pre-fix this returned None. Post-fix it falls through to greedy
// tree routing and picks tree_peer (distance 1 < self distance 2).
let result = node.find_next_hop(&dest);
assert!(
result.is_some(),
"find_next_hop must fall through to tree routing when bloom \
candidates exist but none are strictly closer than self"
);
let next_hop = result.unwrap().node_addr();
assert_eq!(
next_hop, &tree_peer_addr,
"tree-routing winner expected (tree_peer), got {:?}",
next_hop,
);
assert_ne!(
next_hop, &bloom_peer_addr,
"bloom_peer must be excluded by the self-distance check",
);
}
#[test]
fn test_routing_tree_no_coords_in_cache() {
let mut node = make_node();