From 7251ed92191030f8d936288c35460d23d1fe5eb6 Mon Sep 17 00:00:00 2001 From: qima Date: Thu, 29 Jan 2026 05:14:04 +0800 Subject: [PATCH] fix: make trust scores effective in find_closest_nodes The previous implementation sorted candidates by full XOR distance first, then by trust score as a tiebreaker. Since peer IDs are unique, XOR distances are always unique, meaning trust scores were never actually used in selection. This fix introduces distance bucketing using leading zero bits as "distance magnitude". Nodes within the same magnitude (same number of leading zeros) are within a factor of 2 in actual distance, making them effectively equivalent from a Kademlia routing perspective. Within each magnitude bucket, nodes are now sorted by trust (descending) then RTT (ascending), allowing trust to meaningfully influence which nodes are selected while preserving Kademlia's convergence properties. --- src/dht/trust_weighted_kademlia.rs | 46 ++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/dht/trust_weighted_kademlia.rs b/src/dht/trust_weighted_kademlia.rs index 9a76d38c..09efef0c 100644 --- a/src/dht/trust_weighted_kademlia.rs +++ b/src/dht/trust_weighted_kademlia.rs @@ -226,32 +226,47 @@ impl TrustWeightedKademlia { } /// Find k closest nodes to target with trust bias + /// + /// Uses distance bucketing: nodes are grouped by "distance magnitude" (number of + /// leading zero bits in XOR distance). Within each magnitude group, nodes are + /// sorted by trust (descending) then RTT (ascending). + /// + /// This preserves Kademlia's convergence properties while preferring trusted nodes + /// among those at similar distances. Since distance magnitude groups nodes by + /// powers of 2, nodes within the same bucket are "close enough" from a routing + /// perspective, allowing trust to meaningfully influence selection. async fn find_closest_nodes(&self, target: &NodeId, k: usize) -> Vec { let routing_table = self.routing_table.read().await; let eigen_trust_scores = self.eigen_trust_scores.read().await; let mut candidates = Vec::new(); - // Collect candidates from appropriate buckets + // Collect candidates from all buckets for bucket in &*routing_table { for contact in &bucket.contacts { candidates.push(contact.clone()); } } - // Sort by (XOR distance, -trust_score, RTT) + // Sort by (distance_magnitude ASC, trust DESC, RTT ASC) candidates.sort_by(|a, b| { let a_distance = self.xor_distance(&a.peer, target); let b_distance = self.xor_distance(&b.peer, target); + // Use distance magnitude (inverted leading zeros) for coarse grouping + // Nodes within same magnitude are at similar distances (within factor of 2) + let a_magnitude = Self::distance_magnitude(&a_distance); + let b_magnitude = Self::distance_magnitude(&b_distance); + let a_trust = eigen_trust_scores.get(&a.peer).copied().unwrap_or(0.5); let b_trust = eigen_trust_scores.get(&b.peer).copied().unwrap_or(0.5); - a_distance - .cmp(&b_distance) + // Sort: closer first (smaller magnitude), then higher trust, then lower RTT + a_magnitude + .cmp(&b_magnitude) .then_with(|| { - (-a_trust) - .partial_cmp(&(-b_trust)) + b_trust + .partial_cmp(&a_trust) .unwrap_or(std::cmp::Ordering::Equal) }) .then_with(|| a.rtt_est.cmp(&b.rtt_est)) @@ -260,6 +275,25 @@ impl TrustWeightedKademlia { candidates.into_iter().take(k).collect() } + /// Calculate distance magnitude as inverted leading zeros count + /// + /// Returns a value where smaller = closer to target. + /// Nodes with the same magnitude are within a factor of 2 in actual distance, + /// making them effectively equivalent from a Kademlia routing perspective. + fn distance_magnitude(distance: &[u8; 32]) -> u16 { + let mut leading_zeros = 0u16; + for byte in distance { + if *byte == 0 { + leading_zeros += 8; + } else { + leading_zeros += byte.leading_zeros() as u16; + break; + } + } + // Invert: max 256 bits, so 256 - leading_zeros gives smaller = closer + 256 - leading_zeros + } + /// Calculate XOR distance between two node IDs fn xor_distance(&self, a: &NodeId, b: &NodeId) -> [u8; 32] { let mut result = [0u8; 32];