Skip to content

fix: rank close-group/candidate selection by reachability#121

Merged
jacderida merged 2 commits into
WithAutonomi:rc-2026.5.4from
jacderida:fix/closegroup-reachability
May 26, 2026
Merged

fix: rank close-group/candidate selection by reachability#121
jacderida merged 2 commits into
WithAutonomi:rc-2026.5.4from
jacderida:fix/closegroup-reachability

Conversation

@jacderida

@jacderida jacderida commented May 24, 2026

Copy link
Copy Markdown
Collaborator

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 a Direct address, so a peer advertising both relay and direct stays tier 0. No wire-format or public-API change.

  • E.1 find_closest_nodes_local: over-fetch 2×, re-rank by (reachability_tier, xor_distance), truncate; find_closest_nodes_local_with_self reuses the same comparator.
  • E.2 Stamp DHTNode.reliability from trust_engine.score() instead of the hardcoded 1.0 placeholder, so ant-node consumers (replication, payment) see a real signal. Mirrors the existing routing_table_peers pattern.
  • E.3 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 fmt clean
  • cargo clippy --lib -- -D warnings -D clippy::unwrap_used -D clippy::expect_used clean
  • cargo test --lib — 485 passed, 0 failed
  • New unit tests: Direct outranks XOR-closer relay-only; sparse (all relay-only) keeps count in XOR order; reachability_tier 0/1
  • New 2-node integration test (local_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 hardcoded reliability: 1.0 placeholder in find_closest_nodes_local with the real trust-engine score.

  • E.1/E.3 re-rank: find_closest_nodes_local over-fetches 2× from the routing table, re-ranks, and truncates; find_closest_nodes_local_with_self and apply_lookup_report_winners adopt the same comparator — all three selection chokepoints now consistently prefer reachable peers.
  • E.2 trust stamping: find_closest_nodes_local now calls trust_engine.score() (falling back to DEFAULT_NEUTRAL_TRUST) instead of the constant SELF_RELIABILITY_SCORE, mirroring the existing routing_table_peers pattern; a new 2-node integration test verifies the 0.5 default 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

Filename Overview
src/dht_network_manager.rs Adds reachability-aware (reachability_tier, xor_distance) comparator; applies it in find_closest_nodes_local (with 2× over-fetch), find_closest_nodes_local_with_self, and apply_lookup_report_winners; stamps real trust scores instead of hardcoded 1.0. One stale doc-comment in find_closest_nodes_local (still describes pure XOR sort).
tests/two_node_messaging.rs Adds integration test verifying that a freshly-discovered peer's reliability is stamped as DEFAULT_NEUTRAL_TRUST (0.5) rather than the old hardcoded 1.0.

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]
    end
Loading

Comments Outside Diff (1)

  1. src/dht_network_manager.rs, line 1963-1964 (link)

    P2 The doc-comment for find_closest_nodes_local still describes pure XOR-distance ordering, but the new implementation sorts by (reachability_tier, xor_distance). The sibling function find_closest_nodes_local_with_self was 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
    This is a comment left during a code review.
    Path: src/dht_network_manager.rs
    Line: 1963-1964
    
    Comment:
    The doc-comment for `find_closest_nodes_local` still describes pure XOR-distance ordering, but the new implementation sorts by `(reachability_tier, xor_distance)`. The sibling function `find_closest_nodes_local_with_self` was 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.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/dht_network_manager.rs:1963-1964
The doc-comment for `find_closest_nodes_local` still describes pure XOR-distance ordering, but the new implementation sorts by `(reachability_tier, xor_distance)`. The sibling function `find_closest_nodes_local_with_self` was 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.

```suggestion
    /// Results are sorted by `(reachability_tier, XOR distance)` to the key —
    /// directly-reachable peers first — and truncated to `count`.
    pub async fn find_closest_nodes_local(&self, key: &Key, count: usize) -> Vec<DHTNode> {
```

Reviews (1): Last reviewed commit: "fix: rank close-group/candidate selectio..." | Re-trigger Greptile

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 dirvine left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@jacderida

Copy link
Copy Markdown
Collaborator Author

Added: find_closest_nodes_local_by_distance (commit a04f428)

This commit adds a distance-pure counterpart to find_closest_nodes_local, motivated by a coordination issue surfaced in WithAutonomi/ant-node#111.

Why this is needed. After this PR, the local routing table serves two consumers with opposite ordering needs:

  • Close-group / candidate selection for storage (the majority of callers) benefits from the (reachability_tier, xor_distance) re-rank introduced here — 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) is the opposite: it must mirror the uploader's view, which is built from a pure XOR-distance network lookup. If the verifier saw the reachability re-rank, the 2× over-fetch + reachability sort + truncate 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.

What the new function does. find_closest_nodes_local_by_distance returns the routing table's XOR-distance order as-is — no over-fetch, no reachability re-rank — while still excluding self and stamping the real trust score (so the E.2 reliability fix is preserved; only the ordering differs). Every existing selection caller keeps the reachability re-rank via find_closest_nodes_local; nothing else changes. Purely additive, no public-API or wire-format change.

Verification. cargo fmt clean; cargo clippy --lib and --test two_node_messaging (-D warnings -D clippy::unwrap_used -D clippy::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.

ant-node#111 will switch its Merkle closeness check to this method once this PR merges (see the note on that PR).

@jacderida
jacderida merged commit 1be7352 into WithAutonomi:rc-2026.5.4 May 26, 2026
9 of 10 checks passed
jacderida added a commit to jacderida/ant-node that referenced this pull request May 26, 2026
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>
jacderida added a commit to jacderida/ant-node that referenced this pull request May 26, 2026
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>
mickvandijke pushed a commit that referenced this pull request May 28, 2026
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>
mickvandijke pushed a commit that referenced this pull request May 28, 2026
`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>
jacderida added a commit that referenced this pull request Jun 4, 2026
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>
jacderida added a commit that referenced this pull request Jun 4, 2026
`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>
jacderida added a commit that referenced this pull request Jun 9, 2026
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>
jacderida added a commit that referenced this pull request Jun 9, 2026
`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>
@mickvandijke mickvandijke mentioned this pull request Jun 14, 2026
jacderida added a commit that referenced this pull request Jun 14, 2026
jacderida added a commit that referenced this pull request Jun 14, 2026
Re-cut after merging PR #131 (revert PR #121) for testnet validation.
saorsa-transport stays at crates.io 0.35.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants