Skip to content

fix(peers): omit the wildcard address for a relay-reached peer - #347

Merged
MichaelTaylor3d merged 3 commits into
mainfrom
loop/253-relay-peer-address
Aug 25, 2026
Merged

fix(peers): omit the wildcard address for a relay-reached peer#347
MichaelTaylor3d merged 3 commits into
mainfrom
loop/253-relay-peer-address

Conversation

@MichaelTaylor3d

@MichaelTaylor3d MichaelTaylor3d commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What

control.peerStatus emitted "address": "[::]:0" for a peer reached over a relay circuit with no
configured relay endpoint. [::]:0 is the IPv6 unspecified address on port 0 — the absence of an
endpoint, not one — so dign peers rendered it as if it were the peer's location:

    - 62ecc368...f2a5c  relay/outbound  [::]:0

The CLI's honest fallback at crates/dig-node-service/src/peers.rs:215
(p["address"].as_str().unwrap_or("(no address)")) is correct as written and was simply unreachable:
unwrap_or fires on a MISSING field, never on a present string that happens to be meaningless.

Fixed at the producer (crates/dig-node-core/src/peer.rs, connected_peers_json) by applying
net::is_usable_contact — the predicate that already guards the DHT-contact path (peer.rs
dht_contact_from_pool_addr), the download candidate path (download.rs) and the DHT seam
(seams/dig_peer/dht.rs), and was missing at this one enumeration site. No new predicate was written;
a second copy of "is this address meaningless?" would be the rival-implementation shape this ecosystem
keeps paying for.

The key is omitted, not placeholdered, so every consumer's own absence handling works.

Not fixed in the renderer by string-matching [::]:0: that would leave the producer emitting a falsehood
and add a second place that has to know about it.

Blast radius checked

connected_peers_json — the only producer of the peers[] array. Consumers of the address field,
measured repo-wide and ecosystem-wide:

  • dig-node-service/src/peers.rs:215format_peer, the fallback this fix makes reachable. Correct
    as written, unchanged.
  • dig-node-service/src/peers.rs:200 — the IPv6-first display sort. A missing field yields "",
    is_ipv4("") == false, so a no-address peer sorts with the IPv6 group. Stable, no change needed.
  • No consumer outside dig-node. A grep -rln 'peerStatus' across every submodule
    (--include=*.rs,*.ts,*.tsx) returns dig-node only. So this is a control-surface shape change with no
    external adopter today — address becomes OPTIONAL, and SPEC.md now states that a consumer MUST treat
    its absence as "no known dialable address" rather than as a malformed element.

Evidence

Red first, for the right reason — the failure printed the actual falsehood:

a wildcard pool address is not a destination and must be omitted, not rendered:
{"address":"[::]:0","direction":"outbound","peer_id":"abab...abab","via":"relay"}
test result: FAILED. 0 passed; 1 failed; 987 filtered out

Green: test result: ok. 988 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (full
dig-node-core --lib). cargo fmt --check clean, cargo clippy -p dig-node-core --lib --all-features
clean.

Placement revert proof — the guard call removed, is_usable_contact left intact, so it still
compiles and the failure is behavioural rather than a compile error:
test result: FAILED. 0 passed; 1 failed. Load-bearing.

Fixture design. Both peers are relay-reached; only the address varies, and the second is a
truthful control at a real destination. That separates this property from the two nearest wrong
implementations: emitting every address unconditionally fails the first assertion, and blanking every
relay peer's address fails the control. The assertion runs through connected_peers_json rather than a
pure helper, so a fix placed in the renderer — or a helper written and never called — leaves it red.

Version

0.149.0 -> 0.150.0 (minor). Behaviour is a fix, but the control-surface contract changes shape:
address becomes optional. Minor rather than patch because SPEC.md's documented element shape changed;
minor rather than major because omitting a field a consumer must already handle is compatible, and there
are no external consumers.

§2.4b — dep drift measured, NOT taken here, and why

dig-node-core is behind on five dig-* crates, every one a semver-incompatible 0.x step:

crate declared latest
dig-nat 0.18 0.21.0
dig-tls 0.3 0.4.0
dig-peer 0.11 0.12.0
dig-dht 0.11.1 0.12.1
dig-download 0.19 0.20.4

Plus dig-gossip is pinned by git rev (51054a41) rather than a published version — an NC-7
violation and invisible to any registry freshness check.

A finding worth recording: even the one apparently "free" patch bump is not free. cargo update -p dig-peer-selector (0.9.0 -> 0.9.2, caret-compatible, no manifest edit) rewrote 190 lock lines and left
the lock carrying two dig-nat lines (0.18.0 and 0.20.0) and two dig-tls lines (0.3.1 and 0.4.0)
while reporting success — 0.9.2 requires the new line. That is exactly the internally-split shape §2.4b
forbids, so it was reverted rather than shipped.

Taking the cascade would therefore mean adopting the whole dig-nat/dig-tls uplift inside a five-line
rendering fix, in a repo with two live lanes (#344, #237). Per §2.4b's scope limit, stated and split
rather than bridged with a shim. chia-* is untouched deliberately: the umbrella crate has no 0.36 line
(0.26 -> 0.44+), tracked on #308, and dig-wallet is stranded on 0.26.

Closes #253

Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/2760

MichaelTaylor3d and others added 3 commits August 24, 2026 19:31
…er (#253)

Co-Authored-By: Claude <noreply@anthropic.com>
`control.peerStatus` emitted `"address": "[::]:0"` for a peer reached over a relay
circuit with no configured relay endpoint — the IPv6 unspecified address on port 0, which
is the ABSENCE of an endpoint rather than one. The CLI's honest `(no address)` fallback
could not fire: `unwrap_or` only catches a MISSING field, never a present string that
happens to be meaningless, so `dign peers` rendered the wildcard as if it were a location.

Fixed at the producer by applying `net::is_usable_contact` — the predicate the DHT-contact
and download paths already trust (#1784) — at the one enumeration site that lacked it. The
key is omitted rather than placeholdered, so every consumer's own absence handling works.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>

@MichaelTaylor3d MichaelTaylor3d left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verdict: PASS (correctness gate)

Head read: 671b055. Read-only review; all probing done in a private worktree under C:\tmp\worktrees, no shared checkout mutated.

What I verified myself

1. Consumers of the per-peer address — the lane's claim holds, with one correction to its wording.
An ecosystem-wide grep for peerStatus returns matches in five repos (dig-app, dig-chrome-extension, dig-node, dig-node-control-interface, dig-rpc-protocol), so "no consumers outside dig-node" is too strong as stated. But the load-bearing claim is correct: nothing outside dig-node reads the per-peer address field.

  • dig-rpc-protocol's PeerStatusSnapshot (src/types.rs:1307) has no peers[] array at all — the typed shared contract never modelled the per-peer element, so a now-optional key cannot break a typed deserialize.
  • dig-app's gateway only names control.peerStatus / control.peers.* as method strings; it does not destructure peer rows.
  • dig-chrome-extension consumes a different peer shape ({peer_id, addresses[], connection_type, direction, latency_ms}, e2e/peers-tab.spec.ts:22), not this one.
  • In-repo the only readers are dig-node-service/src/peers.rs:200 (sort) and :215 (unwrap_or("(no address)")). A missing key yields "" at the sort, which groups the peer with IPv6 — harmless, and the renderer's fallback is now correct as written rather than dead.

2. is_usable_contact is the right predicate. seams/dig_peer/net.rs:104 rejects exactly an unspecified IP or port 0 and deliberately accepts loopback. Its documented question is "is this a destination", which is the same question as "should this be shown as an address". No drift; no fourth copy of the wildcard test was added.

3. Suite. cargo test -p dig-node-core --libtest result: ok. 988 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (454.99s). Parsed from the test result: line, single cargo process.

4. Placement revert — reproduced. With only the guard call removed and is_usable_contact still defined and compiling (grep -c "fn is_usable_contact" → 1, crate builds clean):
test result: FAILED. 0 passed; 1 failed; 0 ignored; 987 filtered out.
So the test is load-bearing behaviourally, not as a compile error — which is the distinction that matters.

5. Fixture discriminates. Both peers are TraversalKind::Relayed; only the address differs. "Emit everything" fails the wildcard assertion; "blank every relay peer" fails the 198.51.100.8:9444 control. It asserts through connected_peers_json, so a fix placed in the CLI renderer, or a helper written and never called, leaves it red. Both halves confirmed.

6. via/direction survive. The relay-reached row still carries via: "relay" and its direction, so (no address) reads as "no dialable endpoint", not "unreachable". The renderer prints id via/dir addr, so the relay fact stays on the line.

Non-gating findings (posted inline, resolved by me so they do not bar merge)

  • There is a FIFTH site, and it is peer-facingpeer.rs:1296 pool_peers. Pre-existing, out of #253's scope, so not gating, but it needs a ticket.
  • The superseded {peer_id, address, via, direction} phrasing survives in dig-node-service/src/peers.rs:209.
  • crates/dig-node-core/SPEC.md §7.2 (line 744) documents the peerStatus snapshot with no peers[] array at all — a pre-existing gap, not drift introduced here.

Out of scope, read and agreed

§2.4b is genuinely unsatisfiable here (five semver-incompatible dig-* steps); splitting rather than shimming is the right call. The reported cargo update -p dig-peer-selector result — 190 lock lines rewritten, two dig-nat and two dig-tls lines left while printing success — is exactly the internally-split shape §2.4b forbids, and reverting it was correct. The dig-gossip git-rev pin is a real NC-7 violation, invisible to any registry check, and belongs in its own ticket.

Do not merge on this comment alone — the PR is DRAFT by design and the orchestrator owns the merge. Note dig-node's cron cuts a stable tag unattended at midnight UTC, so main reaches users without a human.

Comment thread crates/dig-node-core/src/peer.rs
Comment thread SPEC.md
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.

fix(peers): a relay-reached peer renders as [::]:0 — an unspecified address shown where an endpoint goes

1 participant