fix(l1): announce local IP when --p2p.addr is unspecified#6713
Conversation
When `--p2p.addr=0.0.0.0` (or `::`) was passed without `--nat.extip`, ethrex used the bind address as the externally-announced address, ending up with `enode://...@0.0.0.0:30303` in the ENR. Peers can't dial back to that, so the node had 0 inbound peers. Now the announce address falls back to the auto-detected local IP when the bind is unspecified; `--nat.extip` still overrides. If no local IP is detectable, log a loud warning instead of silently advertising the unspecified address.
|
🤖 Claude Code ReviewHere is my review of PR #6713: PR Review:
|
🤖 Kimi Code ReviewThe PR refactors P2P endpoint resolution to prevent nodes from advertising Code Quality & Correctness
Testing The unit tests cover the main scenarios well. Consider adding coverage for:
Example test to add: #[test]
fn p2p_addr_unspecified_v6_prefers_v6() {
let local_v6 = ip("fe80::1");
let local_v4 = ip("10.0.0.5");
let (bind, ext) = resolve_p2p_endpoints(Some("::"), None, Some(local_v4), Some(local_v6));
assert_eq!(bind, ip("::"));
assert_eq!(ext, local_v6); // Should prefer IPv6 when bind is IPv6
}Documentation & Style
Security & Performance
Verdict: Approve with minor suggestion to expand IPv6 test coverage. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code Review
No other correctness or security issues stood out in this patch. I couldn’t run Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Lines of code reportTotal lines added: Detailed view |
Greptile SummaryFixes the long-standing issue where launching with
Confidence Score: 3/5The core fix is correct and well-tested for the common cases, but the cross-family fallback path can silently produce a broken bind/announce pair without any log warning. The primary bug (advertising cmd/ethrex/initializers.rs — the cross-family fallback in
|
| Filename | Overview |
|---|---|
| cmd/ethrex/initializers.rs | Extracts P2P address resolution into a testable resolve_p2p_endpoints function and fixes the 0.0.0.0/:: announce bug. The auto-detect fallback path can silently return a cross-family (bind, external) pair when the detected local IP family doesn't match the bind family. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["resolve_p2p_endpoints(p2p_addr, nat_extip, local_v4, local_v6)"] --> B{nat_extip set?}
B -- Yes --> C["external = nat_extip\nbind = p2p_addr (or UNSPECIFIED of same family)"]
B -- No --> D{p2p_addr set?}
D -- Yes --> E{bind.is_unspecified?}
E -- No --> F["return (bind, bind)"]
E -- Yes --> G{local IP of matching family?}
G -- Found --> H["INFO: announce auto-detected IP\nreturn (bind, local_ip)"]
G -- Not found, cross-family available --> I["⚠️ Silent mismatch:\nreturn (bind_v4, local_v6) or (bind_v6, local_v4)"]
G -- None at all --> J["WARN: announce unspecified\nreturn (bind, bind)"]
D -- No --> K["return (local_v4 or local_v6, same)"]
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
cmd/ethrex/initializers.rs:403-416
**Silent cross-family address mismatch in unspecified bind fallback**
When `--p2p.addr=0.0.0.0` is given and `local_v4` is `None` but `local_v6` is available, the code returns `(IpAddr::V4(0.0.0.0), IpAddr::V6(fe80::1))` — an IPv4 bind address paired with an IPv6 external address. The node would bind only on IPv4 sockets but advertise an IPv6 ENR, so peers would attempt IPv6 connections and find nothing listening. The symmetric case (`--p2p.addr=::` with only IPv4 local) produces the inverse mismatch. Neither case emits a warning (unlike the zero-addresses case on line 418). The `(_, Some(extip))` arm already guards this with an explicit `assert!` on family equality — the same guard (or at minimum a `warn!`) is needed here before returning the cross-family pair.
Reviews (1): Last reviewed commit: "fix(l1): announce local IP when --p2p.ad..." | Re-trigger Greptile
| if bind.is_unspecified() { | ||
| let external = if bind.is_ipv6() { | ||
| local_v6.or(local_v4) | ||
| } else { | ||
| local_v4.or(local_v6) | ||
| }; | ||
| match external { | ||
| Some(ext) => { | ||
| info!( | ||
| announced = %ext, | ||
| bind = %bind, | ||
| "--p2p.addr is unspecified; announcing auto-detected local IP. Set --nat.extip to override." | ||
| ); | ||
| (bind, ext) |
There was a problem hiding this comment.
Silent cross-family address mismatch in unspecified bind fallback
When --p2p.addr=0.0.0.0 is given and local_v4 is None but local_v6 is available, the code returns (IpAddr::V4(0.0.0.0), IpAddr::V6(fe80::1)) — an IPv4 bind address paired with an IPv6 external address. The node would bind only on IPv4 sockets but advertise an IPv6 ENR, so peers would attempt IPv6 connections and find nothing listening. The symmetric case (--p2p.addr=:: with only IPv4 local) produces the inverse mismatch. Neither case emits a warning (unlike the zero-addresses case on line 418). The (_, Some(extip)) arm already guards this with an explicit assert! on family equality — the same guard (or at minimum a warn!) is needed here before returning the cross-family pair.
Prompt To Fix With AI
This is a comment left during a code review.
Path: cmd/ethrex/initializers.rs
Line: 403-416
Comment:
**Silent cross-family address mismatch in unspecified bind fallback**
When `--p2p.addr=0.0.0.0` is given and `local_v4` is `None` but `local_v6` is available, the code returns `(IpAddr::V4(0.0.0.0), IpAddr::V6(fe80::1))` — an IPv4 bind address paired with an IPv6 external address. The node would bind only on IPv4 sockets but advertise an IPv6 ENR, so peers would attempt IPv6 connections and find nothing listening. The symmetric case (`--p2p.addr=::` with only IPv4 local) produces the inverse mismatch. Neither case emits a warning (unlike the zero-addresses case on line 418). The `(_, Some(extip))` arm already guards this with an explicit `assert!` on family equality — the same guard (or at minimum a `warn!`) is needed here before returning the cross-family pair.
How can I resolve this? If you propose a fix, please make it concise.Per review: an IPv4 socket can't accept inbound IPv6 connections (and vice versa), so falling back across families would advertise an unreachable address. Drop the cross-family or() in resolve_p2p_endpoints and fall through to the warn-then-bind path instead. Also widen the warning to mention --p2p.addr=<ip> as an alternative to --nat.extip, and add coverage for the IPv6 unspecified branch plus the family-mismatch assertion.
Summary
When ethrex is launched with
--p2p.addr=0.0.0.0(or::) and no--nat.extip, the announce/enode/ENR address ended up as the bind address verbatim, i.e.enode://…@0.0.0.0:30303. Peers cannot dial back to that, so the node sits at 0 inbound peers. Spotted on bal-devnet-7 (the deployment passes--p2p.addr=0.0.0.0without--nat.extip).This patch:
get_local_p2p_nodeinto a small pure functionresolve_p2p_endpoints, so it can be unit-tested.--p2p.addris given and resolves to an unspecified address, the announced address now falls back to the auto-detected local IP (the samelocal_ip()/local_ipv6()call the no-flag branch already used).--nat.extipstill overrides; specific bind addresses are unchanged.This is the minimal fix. A follow-up could implement devp2p endpoint prediction (learning the external IP from the
tofield on incoming PINGs/PONGs, à la geth'sUDPEndpointStatement), which would remove the need for operators to set--nat.extipat all on cloud hosts. Out of scope for this PR.Test plan
cargo fmt -p ethrexcargo clippy -p ethrex --all-targets -- -D warningscargo test -p ethrex --lib initializers::— 6 new unit tests pass--p2p.addr=0.0.0.0and confirmadmin_nodeInforeports the host IP in the enode/ENR and inbound peers > 0.