fix: rank close-group/candidate selection by reachability#121
Conversation
Selection of both the close group and the Merkle candidate pool ranked purely by XOR distance, so an unreachable / relay-only / NAT-stuck peer that happened to be XOR-close was picked and then failed — costing a store slot and skewing closeness lookups. Rank by (reachability_tier, xor_distance) instead, preferring a directly-reachable peer over an XOR-equal unreachable one. Re-rank only, never filter: over-fetch, stable-sort, truncate — selection can never shrink a group below `count` (sparse-network-safe). - find_closest_nodes_local: over-fetch 2x, re-rank, truncate (E.1) - find_closest_nodes_local_with_self: reuse the reachability comparator - stamp DHTNode.reliability from trust_engine.score() instead of the hardcoded 1.0 placeholder, so ant-node consumers see a real signal (E.2) - apply_lookup_report_winners: reachability-aware final network-path re-rank, the sole final-ordering chokepoint for the candidate pool (E.3) Tier keys on absence of a Direct address, so a peer advertising both relay and direct stays tier 0. No wire-format or public-API change. Tested: cargo fmt clean; cargo clippy --lib (-D warnings -D unwrap_used -D expect_used) clean; cargo test --lib 485 passed/0 failed; new 2-node reliability integration test passed 3/3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dirvine
left a comment
There was a problem hiding this comment.
Review Summary
Overall: Sound fix, well-tested, CI all green. The re-rank-only approach (over-fetch → stable-sort → truncate) correctly preserves sparse-network safety. One minor doc fix needed.
Stale doc-comment (minor)
Greptile flagged this correctly — find_closest_nodes_local at line 1963-1964 still documents pure XOR-distance ordering. The sibling function find_closest_nodes_local_with_self was correctly updated. Suggest fixing before merging:
/// Results are sorted by `(reachability_tier, XOR distance)` to the key —
/// directly-reachable peers first — and truncated to `count`.
All CI passes
macOS/Ubuntu/Windows build, lint, security audit, unit tests, Greptile — all green.
👍 No blockers
Approving on the understanding the doc comment is fixed before merge.
The routing table now serves two consumers with opposite ordering needs. Close-group and candidate *selection* for storage benefits from the (reachability_tier, xor_distance) re-rank in find_closest_nodes_local — a directly-reachable peer is a better place to put data than an XOR-equal relay-only one. Closeness *verification* (ant-node's Merkle pay-yourself defence, WithAutonomi/ant-node#111) is the opposite: it must mirror the uploader's pure XOR-distance network view. If verification re-ranked by reachability it could demote an XOR-close relay-only peer out of the compared window and falsely reject an honest candidate pool that legitimately contains that peer. Add find_closest_nodes_local_by_distance: the distance-pure counterpart to find_closest_nodes_local. No over-fetch, no reachability re-rank — it returns the routing table's XOR-distance order as-is, still excluding self and stamping the real trust score. This gives the verification path the raw XOR ordering it needs while every selection caller keeps the reachability re-rank. No change to existing functions or public-API behaviour; purely additive. The ant-node closeness check will switch to this method once this PR (WithAutonomi#121) merges and ant-node bumps its saorsa-core pin (tracked on ant-node#111). Tested: cargo fmt clean; cargo clippy --lib + --test two_node_messaging (-D warnings -D unwrap_used -D expect_used) clean; cargo test --lib 485 passed/0 failed; new two-node integration test local_by_distance_returns_peer_and_stamps_neutral_trust passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Added:
|
Addresses the coordination concern raised in review (dirvine): the Merkle closeness check is a *verification* that must mirror the uploader's pure XOR-distance view, not the reachability re-rank used for storage selection. With saorsa-core's reachability-aware find_closest_nodes_local (WithAutonomi/saorsa-core#121), a re-rank could demote an XOR-close relay-only peer out of the compared window and falsely reject an honest candidate pool that legitimately contains that peer. Switch the closeness check to find_closest_nodes_local_by_distance, the XOR-only variant added to saorsa-core#121 for exactly this purpose. check_closeness_match (the set-membership helper) is unchanged. Repin ant-node to the coordinated rc-2026.5.4 line so the new function is available and the wire types stay unified: ant-protocol's rc-2026.5.4 branch (v2.1.2-rc.1) git-references the saorsa-core rc-2026.5.4 branch, so pointing ant-node's ant-protocol and saorsa-core deps at the same git sources resolves saorsa-core to a single 0.24.5-rc.1 (a plain [patch.crates-io] cannot work here — the rc is a prerelease that ant-protocol 2.1.1's ^0.24.4 requirement rejects). Revert both to the published crates once rc-2026.5.4 releases. Also rename the local variable network_peers -> closeness_peers for readability (review feedback, grumbach), since it now usually holds local-table results. Test results: - cargo fmt -- --check: clean - cargo clippy --lib --all-features (-D panic -D unwrap_used -D expect_used): no warnings - cargo test --lib payment::verifier: 67 passed, 0 failed - e2e test target (--test e2e --features test-utils): compiles against the rc deps - Pre-existing/environmental: config::tests::test_bootstrap_peers_discover_env_var fails on this machine because a real ~/.config/ant/bootstrap_peers.toml exists; unrelated to this change (the test does not isolate the platform config dir). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses the coordination concern raised in review (dirvine): the Merkle closeness check is a *verification* that must mirror the uploader's pure XOR-distance view, not the reachability re-rank used for storage selection. With saorsa-core's reachability-aware find_closest_nodes_local (WithAutonomi/saorsa-core#121), a re-rank could demote an XOR-close relay-only peer out of the compared window and falsely reject an honest candidate pool that legitimately contains that peer. Switch the closeness check to find_closest_nodes_local_by_distance, the XOR-only variant added to saorsa-core#121 for exactly this purpose. check_closeness_match (the set-membership helper) is unchanged. Also rename the local variable network_peers -> closeness_peers for readability (review feedback, grumbach), since it now usually holds local-table results. The rc-2026.5.4 dependency pins (saorsa-core, ant-protocol) come from the release-cut base commit; this commit only advances Cargo.lock to the rc-2026.5.4 tip so the pin includes the merged WithAutonomi#121 (find_closest_nodes_local_by_distance), which the base's release cut predated. Test results (against the rc-2026.5.4 deps): - cargo fmt -- --check: clean - cargo clippy --lib --all-features (-D panic -D unwrap_used -D expect_used): no warnings - cargo test --lib payment::verifier: 67 passed, 0 failed Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The routing table now serves two consumers with opposite ordering needs. Close-group and candidate *selection* for storage benefits from the (reachability_tier, xor_distance) re-rank in find_closest_nodes_local — a directly-reachable peer is a better place to put data than an XOR-equal relay-only one. Closeness *verification* (ant-node's Merkle pay-yourself defence, WithAutonomi/ant-node#111) is the opposite: it must mirror the uploader's pure XOR-distance network view. If verification re-ranked by reachability it could demote an XOR-close relay-only peer out of the compared window and falsely reject an honest candidate pool that legitimately contains that peer. Add find_closest_nodes_local_by_distance: the distance-pure counterpart to find_closest_nodes_local. No over-fetch, no reachability re-rank — it returns the routing table's XOR-distance order as-is, still excluding self and stamping the real trust score. This gives the verification path the raw XOR ordering it needs while every selection caller keeps the reachability re-rank. No change to existing functions or public-API behaviour; purely additive. The ant-node closeness check will switch to this method once this PR (#121) merges and ant-node bumps its saorsa-core pin (tracked on ant-node#111). Tested: cargo fmt clean; cargo clippy --lib + --test two_node_messaging (-D warnings -D unwrap_used -D expect_used) clean; cargo test --lib 485 passed/0 failed; new two-node integration test local_by_distance_returns_peer_and_stamps_neutral_trust passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`find_closest_nodes_local_with_self` ranks by `(reachability_tier, xor_distance)`, which is correct for storage *selection* but wrong for closeness *verification*: the reachability re-rank (added in #121) demotes XOR-close relay-only / NAT'd peers out of the compared window, so a verifier using it falsely rejects honest payments that legitimately quoted those peers. Add `find_closest_nodes_local_by_distance_with_self`: the self-inclusive counterpart of the XOR-only `find_closest_nodes_local_by_distance` (also added in #121). Same pure in-memory routing-table read, still includes self so callers can compute `IsResponsible(self, K)`, but orders purely by XOR distance with no reachability re-rank. Consumed by ant-node's single-node payment close-group verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The routing table now serves two consumers with opposite ordering needs. Close-group and candidate *selection* for storage benefits from the (reachability_tier, xor_distance) re-rank in find_closest_nodes_local — a directly-reachable peer is a better place to put data than an XOR-equal relay-only one. Closeness *verification* (ant-node's Merkle pay-yourself defence, WithAutonomi/ant-node#111) is the opposite: it must mirror the uploader's pure XOR-distance network view. If verification re-ranked by reachability it could demote an XOR-close relay-only peer out of the compared window and falsely reject an honest candidate pool that legitimately contains that peer. Add find_closest_nodes_local_by_distance: the distance-pure counterpart to find_closest_nodes_local. No over-fetch, no reachability re-rank — it returns the routing table's XOR-distance order as-is, still excluding self and stamping the real trust score. This gives the verification path the raw XOR ordering it needs while every selection caller keeps the reachability re-rank. No change to existing functions or public-API behaviour; purely additive. The ant-node closeness check will switch to this method once this PR (#121) merges and ant-node bumps its saorsa-core pin (tracked on ant-node#111). Tested: cargo fmt clean; cargo clippy --lib + --test two_node_messaging (-D warnings -D unwrap_used -D expect_used) clean; cargo test --lib 485 passed/0 failed; new two-node integration test local_by_distance_returns_peer_and_stamps_neutral_trust passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`find_closest_nodes_local_with_self` ranks by `(reachability_tier, xor_distance)`, which is correct for storage *selection* but wrong for closeness *verification*: the reachability re-rank (added in #121) demotes XOR-close relay-only / NAT'd peers out of the compared window, so a verifier using it falsely rejects honest payments that legitimately quoted those peers. Add `find_closest_nodes_local_by_distance_with_self`: the self-inclusive counterpart of the XOR-only `find_closest_nodes_local_by_distance` (also added in #121). Same pure in-memory routing-table read, still includes self so callers can compute `IsResponsible(self, K)`, but orders purely by XOR distance with no reachability re-rank. Consumed by ant-node's single-node payment close-group verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The routing table now serves two consumers with opposite ordering needs. Close-group and candidate *selection* for storage benefits from the (reachability_tier, xor_distance) re-rank in find_closest_nodes_local — a directly-reachable peer is a better place to put data than an XOR-equal relay-only one. Closeness *verification* (ant-node's Merkle pay-yourself defence, WithAutonomi/ant-node#111) is the opposite: it must mirror the uploader's pure XOR-distance network view. If verification re-ranked by reachability it could demote an XOR-close relay-only peer out of the compared window and falsely reject an honest candidate pool that legitimately contains that peer. Add find_closest_nodes_local_by_distance: the distance-pure counterpart to find_closest_nodes_local. No over-fetch, no reachability re-rank — it returns the routing table's XOR-distance order as-is, still excluding self and stamping the real trust score. This gives the verification path the raw XOR ordering it needs while every selection caller keeps the reachability re-rank. No change to existing functions or public-API behaviour; purely additive. The ant-node closeness check will switch to this method once this PR (#121) merges and ant-node bumps its saorsa-core pin (tracked on ant-node#111). Tested: cargo fmt clean; cargo clippy --lib + --test two_node_messaging (-D warnings -D unwrap_used -D expect_used) clean; cargo test --lib 485 passed/0 failed; new two-node integration test local_by_distance_returns_peer_and_stamps_neutral_trust passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`find_closest_nodes_local_with_self` ranks by `(reachability_tier, xor_distance)`, which is correct for storage *selection* but wrong for closeness *verification*: the reachability re-rank (added in #121) demotes XOR-close relay-only / NAT'd peers out of the compared window, so a verifier using it falsely rejects honest payments that legitimately quoted those peers. Add `find_closest_nodes_local_by_distance_with_self`: the self-inclusive counterpart of the XOR-only `find_closest_nodes_local_by_distance` (also added in #121). Same pure in-memory routing-table read, still includes self so callers can compute `IsResponsible(self, K)`, but orders purely by XOR distance with no reachability re-rank. Consumed by ant-node's single-node payment close-group verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Close-group (7 XOR-closest) and Merkle candidate-pool (16 XOR-closest) selection ranked purely by XOR distance, ignoring whether a peer is actually reachable. An unreachable / relay-only / NAT-stuck peer that happened to be XOR-close was picked, then failed — costing a store slot and skewing closeness lookups. This ranks by
(reachability_tier, xor_distance)so a directly-reachable peer is preferred over an XOR-equal unreachable one.Re-rank only, never filter: every path over-fetches, stable-sorts, then truncates, so a group can never shrink below
count(sparse-network-safe). Tier keys on the absence of aDirectaddress, so a peer advertising both relay and direct stays tier 0. No wire-format or public-API change.find_closest_nodes_local: over-fetch 2×, re-rank by(reachability_tier, xor_distance), truncate;find_closest_nodes_local_with_selfreuses the same comparator.DHTNode.reliabilityfromtrust_engine.score()instead of the hardcoded1.0placeholder, so ant-node consumers (replication, payment) see a real signal. Mirrors the existingrouting_table_peerspattern.apply_lookup_report_winners(the sole final-ordering chokepoint for the network/candidate-pool path) re-ranks reachability-first; iterative convergence and the dial-failure skip are left intact.Out of scope (per Fix E plan): saorsa-transport relay/holepunch work (E.4).
Test plan
cargo fmtcleancargo clippy --lib -- -D warnings -D clippy::unwrap_used -D clippy::expect_usedcleancargo test --lib— 485 passed, 0 failedcountin XOR order;reachability_tier0/1local_selection_stamps_neutral_trust_not_hardcoded_one) — passed 3/3 runs; verifies a learned peer's reliability is the neutral trust default, not the old hardcoded 1.0🤖 Generated with Claude Code
Greptile Summary
Re-ranks close-group and Merkle candidate-pool selection by
(reachability_tier, xor_distance)instead of pure XOR distance, so a directly-reachable peer beats an XOR-closer relay-only peer without ever filtering peers out. Also replaces the hardcodedreliability: 1.0placeholder infind_closest_nodes_localwith the real trust-engine score.find_closest_nodes_localover-fetches 2× from the routing table, re-ranks, and truncates;find_closest_nodes_local_with_selfandapply_lookup_report_winnersadopt the same comparator — all three selection chokepoints now consistently prefer reachable peers.find_closest_nodes_localnow callstrust_engine.score()(falling back toDEFAULT_NEUTRAL_TRUST) instead of the constantSELF_RELIABILITY_SCORE, mirroring the existingrouting_table_peerspattern; a new 2-node integration test verifies the0.5default for freshly-learned peers.Confidence Score: 4/5
The change is a re-rank-only fix with no wire-format or API changes; sparse-network safety is preserved by the over-fetch+truncate pattern.
The logic is straightforward and well-tested: the new comparator is exercised by four targeted unit tests and an integration test. The only gap is a stale doc-comment on find_closest_nodes_local that still describes pure XOR ordering.
src/dht_network_manager.rs — doc-comment on find_closest_nodes_local (line 1963) still describes pure XOR sort order.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[find_closest_nodes_local\ncalled with count] --> B[Over-fetch 2×count\nfrom routing table] B --> C[Filter out local peer ID] C --> D[Stamp reliability:\ntrust_engine.score OR\nDEFAULT_NEUTRAL_TRUST] D --> E[sort_by reachability_tier\nthen xor_distance] E --> F[truncate to count] F --> G[Return ≤count DHTNodes] G --> H{find_closest_nodes_local_with_self?} H -- Yes --> I[Push local_dht_node\nreliability = 1.0] I --> J[sort_by reachability_tier\nthen xor_distance] J --> K[truncate to count] H -- No --> L[Done] K --> L M[apply_lookup_report_winners\nnetwork/converged path] --> N[Deduplicate by peer_id\nkeep prefer_lookup_record winner] N --> O[sort_by reachability_tier\nthen xor_distance] O --> P[truncate to count] P --> L subgraph reachability_tier Q[DHTNode has Direct address?] -- Yes --> R[tier = 0] Q -- No --> S[tier = 1] endComments Outside Diff (1)
src/dht_network_manager.rs, line 1963-1964 (link)find_closest_nodes_localstill describes pure XOR-distance ordering, but the new implementation sorts by(reachability_tier, xor_distance). The sibling functionfind_closest_nodes_local_with_selfwas correctly updated; this one was missed. A caller reading only the doc-comment would assume XOR order and could be surprised by the reachability-based ranking.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "fix: rank close-group/candidate selectio..." | Re-trigger Greptile