mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 08:14:42 +00:00
tree: propagate mid-chain ancestor swaps to leaves
A leaf node's my_coords could go stale after an upstream mid-chain ancestor swap, leaving non-parent destinations with 100% loss until either the parent or the depth also changed. The broadcast gate in handle_tree_announce's `else if !is_root && parent_id == from` branch compared only (root, depth). A swap that altered an interior ancestor without changing root or depth (e.g. A->B->C reorganizing to A->D->C while keeping (A, depth=2)) was silently dropped one hop below the swap node. Downstream nodes' coords paths then drifted from the real tree topology, defeating greedy distance routing for any destination whose path crossed the unrepresented section. Widen the gate to compare the full my_coords.node_addrs() so mid-chain swaps propagate to leaves the same way root/depth changes already did. The gate body's bloom-marking is adjusted in step so the wider gate doesn't generate empty/redundant FilterAnnounces to every peer on every mid-chain swap propagation: mark_changed_peers replaces mark_all_updates_needed in the gate body (parent_id is unchanged in this branch, so outgoing filter content is typically unchanged, and mark_changed_peers correctly marks zero peers in that case), and the unconditional mark_update_needed(*from) at the top of handle_tree_announce is removed (bloom exchange initiation is already handled at handshake completion, and ongoing content changes are picked up naturally by mark_changed_peers in handle_filter_announce when peers send their next filter). Required surface change: peer_inbound_filters in src/node/bloom.rs upgraded from private to pub(super) so the gate body can call it. Verified in a 6-node depth-4 docker reproduction under tc/netem-induced parent flapping: a depth-4 leaf's ancestry_changed counter advances with upstream parent switches while bloom_sent stays at zero matching the pre-change steady-state baseline.
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@ impl Node {
|
||||
///
|
||||
/// Returns a map of (peer_node_addr -> filter) for peers that
|
||||
/// have sent us a FilterAnnounce.
|
||||
fn peer_inbound_filters(&self) -> HashMap<NodeAddr, BloomFilter> {
|
||||
pub(super) fn peer_inbound_filters(&self) -> HashMap<NodeAddr, BloomFilter> {
|
||||
let mut filters = HashMap::new();
|
||||
for (addr, peer) in &self.peers {
|
||||
if self.is_tree_peer(addr)
|
||||
|
||||
+35
-12
@@ -216,10 +216,14 @@ impl Node {
|
||||
"Processed TreeAnnounce"
|
||||
);
|
||||
|
||||
// If this peer is (now) a tree peer, ensure bloom filter exchange
|
||||
if self.is_tree_peer(from) {
|
||||
self.bloom_state.mark_update_needed(*from);
|
||||
}
|
||||
// Bloom filter exchange initiation is handled at handshake completion
|
||||
// ([handshake.rs] mark_update_needed on the new peer) and on actual
|
||||
// content changes via [bloom.rs::handle_filter_announce]'s
|
||||
// `mark_changed_peers`. Marking the peer on every received TreeAnnounce
|
||||
// is redundant — and under high TreeAnnounce churn (rapid mid-chain
|
||||
// swap propagation) it amplifies bloom traffic proportionally with
|
||||
// the tree announce rate, even when the local outgoing filter
|
||||
// content has not changed.
|
||||
|
||||
// Re-evaluate parent selection with current link costs.
|
||||
// Exclude peers without MMP RTT data — they are not yet eligible
|
||||
@@ -299,8 +303,18 @@ impl Node {
|
||||
// Our parent's ancestry changed but we're keeping the same parent.
|
||||
// Recompute our own coordinates (which derive from parent's ancestry)
|
||||
// and re-announce so downstream nodes stay current.
|
||||
//
|
||||
// Compare the full address path (not just root + depth) so that a
|
||||
// mid-chain ancestor swap also triggers re-announce. A reroute that
|
||||
// replaces an interior ancestor without changing the root or the
|
||||
// path length leaves both `root` and `depth` unchanged but still
|
||||
// alters our coords; downstream peers must learn the new path or
|
||||
// they will route into a phantom intermediate that no longer
|
||||
// exists on our parent's tree.
|
||||
let old_root = *self.tree_state.root();
|
||||
let old_depth = self.tree_state.my_coords().depth();
|
||||
let old_addrs: Vec<NodeAddr> =
|
||||
self.tree_state.my_coords().node_addrs().copied().collect();
|
||||
|
||||
let new_seq = self.tree_state.my_declaration().sequence() + 1;
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
@@ -317,23 +331,32 @@ impl Node {
|
||||
self.coord_cache.clear();
|
||||
self.reset_discovery_backoff();
|
||||
|
||||
let new_root = *self.tree_state.root();
|
||||
let new_depth = self.tree_state.my_coords().depth();
|
||||
let new_addrs: Vec<NodeAddr> =
|
||||
self.tree_state.my_coords().node_addrs().copied().collect();
|
||||
|
||||
if new_root != old_root || new_depth != old_depth {
|
||||
if old_addrs != new_addrs {
|
||||
self.stats_mut().tree.ancestry_changed += 1;
|
||||
info!(
|
||||
parent = %self.peer_display_name(from),
|
||||
old_root = %old_root,
|
||||
new_root = %new_root,
|
||||
new_depth = new_depth,
|
||||
new_root = %self.tree_state.root(),
|
||||
old_depth = old_depth,
|
||||
new_depth = self.tree_state.my_coords().depth(),
|
||||
"Parent ancestry changed, re-announcing"
|
||||
);
|
||||
self.send_tree_announce_to_all().await;
|
||||
|
||||
// Coords changed — trigger bloom filter exchange with all peers
|
||||
let all_peers: Vec<NodeAddr> = self.peers.keys().copied().collect();
|
||||
self.bloom_state.mark_all_updates_needed(all_peers);
|
||||
// Bloom contents do not depend on path structure, only on
|
||||
// identity sets. Our parent_id is unchanged in this branch,
|
||||
// so our tree-peer set is unchanged and our outgoing filter
|
||||
// content is unchanged. Use mark_changed_peers, which
|
||||
// checks for actual content delta against last_sent_filters,
|
||||
// instead of mark_all_updates_needed, which marks
|
||||
// unconditionally regardless of whether content changed.
|
||||
let peer_addrs: Vec<NodeAddr> = self.peers.keys().copied().collect();
|
||||
let peer_filters = self.peer_inbound_filters();
|
||||
self.bloom_state
|
||||
.mark_changed_peers(from, &peer_addrs, &peer_filters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user