Skip to content

feat(discv4): serve both IP families on the shared discovery UDP port#26402

Open
MysticRyuujin wants to merge 8 commits into
paradigmxyz:mainfrom
MysticRyuujin:feat/bootnode-dual-stack-discv4
Open

feat(discv4): serve both IP families on the shared discovery UDP port#26402
MysticRyuujin wants to merge 8 commits into
paradigmxyz:mainfrom
MysticRyuujin:feat/bootnode-dual-stack-discv4

Conversation

@MysticRyuujin

Copy link
Copy Markdown
Contributor

Follow-up to #26089.

In shared-port mode discv4 could only send through its primary-family socket, while discv5's forwarder fed it packets from both family sockets — opposite-family pings were received and answered, then every reply died in send_to with an address-family error. Outgoing discv4 packets are now routed to the socket matching the destination family, and both the bootnode command and the node's Discovery bind the dual-stack sibling socket before discv4 starts so it doubles as the secondary egress. Replies are source-addressed, so the node side needs no advertisement changes; the bootnode's EIP-868 ENR additionally advertises both families.

Also adds --bootnodes (enode URLs and signed ENRs) to the bootnode command — both discovery configs already support bootstrap nodes, there was just no CLI exposure. Seeds are validated and filtered against the families the node serves: an unservable ENR warns and is skipped instead of aborting startup via discv5's AddNodeFailed, ENR endpoints prefer a served family over the ip4-first conversion default, v4-mapped hosts are normalized, and zero-port or off-curve-id entries error instead of failing silently.

The test commit binds the discv4 test helpers to loopback: services dialed each other at 0.0.0.0, which only Linux delivers — on macOS/Windows every real UDP exchange failed with EHOSTUNREACH and hung through its timeout (the suite now runs in ~1s off-Linux). Also renames test_bootnode_not_in_update_stream, which has asserted the bootnode does appear since #14787.

Two behavior notes for review:

  • The EIP-868 ENR now omits an unspecified address's keys instead of advertising ip 0.0.0.0. This also applies to a full node before external-IP resolution completes; EIP-778 keys are optional, and resolution re-adds the endpoint once known.
  • Full nodes answer opposite-family discv4 but their discv4 ENR still advertises a single family — dual-stack advertisement for the node is discv5's job (enr_update), and wiring secondary_advertised_ip into NetworkConfig is left for a follow-up.

discv4 egress now routes each packet to the socket matching the
destination family; the --v5 sibling socket doubles as discv4's
secondary-family egress and the EIP-868 ENR advertises both families.
Previously opposite-family pings were received via the unrecognized-frame
forwarder but every reply died in send_to with an address-family error.

Also adds --bootnodes (enode URLs and signed ENRs) to seed both
discovery protocols; ENR entries seed discv5 signed-only so bootstrap
doesn't block on a redundant unsigned request_enr, and entries whose
family has no bound socket warn at startup.
The test helpers bound 0.0.0.0 and services then dialed each other at
that address, which only Linux delivers (aliased to loopback); macOS
and Windows reject the sends with EHOSTUNREACH, so every test doing a
real UDP exchange hung through its timeout or failed off-Linux.

Also renames test_bootnode_not_in_update_stream: it has asserted the
bootnode DOES appear since it was introduced in paradigmxyz#14787.
Review findings on the unreleased dual-stack/bootnodes work:

- discv5's bootstrap aborts on an ENR it cannot contact (AddNodeFailed):
  filter --bootnodes seeds by the families the listen config serves, so
  an unservable entry warns and is skipped instead of killing the node
- derive an ENR seed's discv4 endpoint from a served family instead of
  the ip4-first conversion default
- normalize v4-mapped enode hosts and reject seeds with a zero discovery
  port or a node id off the secp256k1 curve (discv5 dropped those
  silently)
- pin the record's IP family once a secondary is advertised so a
  periodic external-ip re-resolution can't flip it, and normalize the
  same-family filter once at service construction
- leave unspecified addresses out of the EIP-868 ENR rather than
  advertise 0.0.0.0 next to a valid opposite-family endpoint
- pass the primary socket's family into send_loop instead of a fallible
  re-query that fails open
In shared-port dual-stack mode the discv5 forwarder feeds both sockets'
unrecognized frames into discv4, so IPv6 discv4 pings were received and
processed but every pong died on the IPv4-only egress socket. Bind the
dual-stack sibling before discv4 starts and pass it as the secondary
egress — replies are source-addressed, so no advertisement changes are
needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant