mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 00:04:54 +00:00
Implement greedy routing with bloom filter priority
Add the full next-hop routing algorithm to Node::find_next_hop(): - Local delivery, direct peer, bloom filter candidates, greedy tree routing fallback, with (link_cost, tree_distance, node_addr) ordering - select_best_candidate() scores by peer→dest distance (not us→peer) with self-distance check to prevent routing loops - TreeState::find_next_hop() for greedy tree routing with progress guarantee - ActivePeer::link_cost() placeholder (constant 1.0) for future link quality metrics Add routing tests including 100-node all-pairs reachability simulation (9900/9900 delivered, 0 loops, avg 4.0 hops, max 8). Update fips-routing.md to reflect bloom filter routing as the primary forwarding mechanism, with greedy tree routing as fallback during convergence windows.
This commit is contained in:
+114
-6
@@ -706,14 +706,122 @@ impl Node {
|
||||
self.peers.values().filter(|p| p.can_send()).count()
|
||||
}
|
||||
|
||||
// === Routing (stubs) ===
|
||||
// === Routing ===
|
||||
|
||||
/// Find next hop for a destination (stub).
|
||||
/// Find next hop for a destination node address.
|
||||
///
|
||||
/// Returns the peer that minimizes tree distance to the destination.
|
||||
pub fn find_next_hop(&self, _dest_node_addr: &NodeAddr) -> Option<&ActivePeer> {
|
||||
// Stub: would implement greedy tree routing
|
||||
None
|
||||
/// Routing priority:
|
||||
/// 1. Destination is self → `None` (local delivery)
|
||||
/// 2. Destination is a direct peer → that peer
|
||||
/// 3. Bloom filter candidates + greedy tree routing → among peers whose
|
||||
/// bloom filter contains the destination, pick the one that minimizes
|
||||
/// tree distance to the destination (if dest coords are cached), with
|
||||
/// `(link_cost, tree_distance_to_dest, node_addr)` tie-breaking.
|
||||
/// Falls back to greedy tree routing if no bloom filter hits.
|
||||
/// 4. No route → `None`
|
||||
///
|
||||
/// The self-distance check from greedy routing also applies to bloom
|
||||
/// filter candidates: a peer is only selected if it is strictly closer
|
||||
/// to the destination than we are (prevents routing loops).
|
||||
pub fn find_next_hop(&self, dest_node_addr: &NodeAddr) -> Option<&ActivePeer> {
|
||||
// 1. Local delivery
|
||||
if dest_node_addr == self.node_addr() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 2. Direct peer
|
||||
if let Some(peer) = self.peers.get(dest_node_addr) {
|
||||
if peer.can_send() {
|
||||
return Some(peer);
|
||||
}
|
||||
}
|
||||
|
||||
// Look up destination coords (used by both bloom and tree paths)
|
||||
let now_ms = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis() as u64)
|
||||
.unwrap_or(0);
|
||||
let dest_coords = self.coord_cache.get(dest_node_addr, now_ms);
|
||||
|
||||
// 3. Bloom filter candidates, scored by tree distance to dest
|
||||
let candidates: Vec<&ActivePeer> = self.destination_in_filters(dest_node_addr);
|
||||
if !candidates.is_empty() {
|
||||
return self.select_best_candidate(&candidates, dest_coords);
|
||||
}
|
||||
|
||||
// 4. Greedy tree routing fallback
|
||||
let dest_coords = dest_coords?;
|
||||
let next_hop_id = self.tree_state.find_next_hop(dest_coords)?;
|
||||
|
||||
self.peers.get(&next_hop_id).filter(|p| p.can_send())
|
||||
}
|
||||
|
||||
/// Select the best peer from a set of bloom filter candidates.
|
||||
///
|
||||
/// When dest_coords are available, uses distance from each candidate's
|
||||
/// coordinates to the destination as the primary metric (after link_cost).
|
||||
/// Only selects peers that are strictly closer to the destination than
|
||||
/// we are (self-distance check prevents loops).
|
||||
///
|
||||
/// When dest_coords are not available, falls back to distance from us
|
||||
/// to the candidate peer (a weaker heuristic — prefers closer peers on
|
||||
/// the theory that shorter paths are better).
|
||||
///
|
||||
/// Ordering: `(link_cost, distance_to_dest, node_addr)`.
|
||||
fn select_best_candidate<'a>(
|
||||
&'a self,
|
||||
candidates: &[&'a ActivePeer],
|
||||
dest_coords: Option<&crate::tree::TreeCoordinate>,
|
||||
) -> Option<&'a ActivePeer> {
|
||||
let my_distance = dest_coords.map(|dc| self.tree_state.my_coords().distance_to(dc));
|
||||
|
||||
let mut best: Option<(&ActivePeer, f64, usize)> = None;
|
||||
|
||||
for &candidate in candidates {
|
||||
if !candidate.can_send() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cost = candidate.link_cost();
|
||||
|
||||
// Compute distance: peer→dest if coords available, else us→peer
|
||||
let dist = match dest_coords {
|
||||
Some(dc) => self
|
||||
.tree_state
|
||||
.peer_coords(candidate.node_addr())
|
||||
.map(|pc| pc.distance_to(dc))
|
||||
.unwrap_or(usize::MAX),
|
||||
None => self
|
||||
.tree_state
|
||||
.distance_to_peer(candidate.node_addr())
|
||||
.unwrap_or(usize::MAX),
|
||||
};
|
||||
|
||||
// Self-distance check: when dest coords are available,
|
||||
// only consider peers that are strictly closer than us
|
||||
if let Some(my_dist) = my_distance {
|
||||
if dist >= my_dist {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let dominated = match &best {
|
||||
None => true,
|
||||
Some((_, best_cost, best_dist)) => {
|
||||
cost < *best_cost
|
||||
|| (cost == *best_cost && dist < *best_dist)
|
||||
|| (cost == *best_cost
|
||||
&& dist == *best_dist
|
||||
&& candidate.node_addr() < best.as_ref().unwrap().0.node_addr())
|
||||
}
|
||||
};
|
||||
|
||||
if dominated {
|
||||
best = Some((candidate, cost, dist));
|
||||
}
|
||||
}
|
||||
|
||||
best.map(|(peer, _, _)| peer)
|
||||
}
|
||||
|
||||
/// Check if a destination is in any peer's bloom filter.
|
||||
|
||||
Reference in New Issue
Block a user