Skip to content

cmd/devp2p: add combined discovery listener#35254

Open
MysticRyuujin wants to merge 2 commits into
ethereum:masterfrom
MysticRyuujin:devp2p-combined-discovery-listener
Open

cmd/devp2p: add combined discovery listener#35254
MysticRyuujin wants to merge 2 commits into
ethereum:masterfrom
MysticRyuujin:devp2p-combined-discovery-listener

Conversation

@MysticRyuujin

Copy link
Copy Markdown
Contributor

What

Adds devp2p discovery listen, which runs a discovery-only node speaking all supported discovery protocol versions (discv4 and discv5) on a single UDP socket — the way a real node does via p2p.Server. Until now devp2p could only run one protocol per process (discv4 listen or discv5 listen), each on its own socket.

It also exposes the --rpc debug API on discv5 listen, for parity with discv4 listen.

Why

devp2p discv4 listen / discv5 listen is the documented replacement for the removed bootnode tool, but neither can run both protocols on one port. Other clients and the old bootnode tooling can serve discv4 and discv5 simultaneously on a single UDP port; this closes that gap.

The two protocols are distinguishable on the wire, so they share one socket: v4 is the primary listener and forwards packets it can't parse to v5 over an unhandled channel, wrapped in a sharedUDPConn — the same mechanism p2p.Server already uses.

Usage

# both discv4 and discv5 on one UDP port
devp2p discovery listen --addr [::]:30301

# with the HTTP debug API (discv4_* and discv5_*)
devp2p discovery listen --rpc 127.0.0.1:8080

Single-protocol nodes remain available via devp2p discv4 listen / devp2p discv5 listen. A future protocol version (e.g. v6) can be added as an opt-in flag without changing this surface.

Notes

  • discovery deliberately has no --v4/--v5 flags: single-protocol use is already covered by the discv4/discv5 command families, so the combined command just means "all supported versions."
  • v5's RPC API is a subset of v4's (self, lookupRandom) — UDPv5 has no routing-table accessor equivalent to UDPv4.TableBuckets, so there is no discv5_buckets.
  • Shared-socket shutdown closes v4 before v5: v5's read loop only unblocks once v4 closes the underlying socket and the unhandled channel.

Testing

Built and exercised manually:

  • discovery listen answers both discv4 ping and discv5 ping on one port.
  • discovery listen --rpc serves discv4_self/discv4_lookupRandom/discv4_buckets and discv5_self/discv5_lookupRandom; discv5_buckets correctly returns method-not-found.
  • New discv5 listen --rpc serves the discv5_* API standalone.
  • Forcing ListenAndServe to fail in combined mode exits cleanly instead of hanging (shutdown close-order).
  • Dual-stack default bind ([::]) works.

@MysticRyuujin

Copy link
Copy Markdown
Contributor Author

Pushed 4b7f221 with a few fixes on top of the original commit:

  • lookupRandom could hang forever and leaked per call. lookupIterator.Next never returns false while the node is up — on an empty or degraded table it just retries with backoff — so a discv5_lookupRandom request against an isolated node blocked its HTTP handler indefinitely, and the iterator (a cancel-context child of the transport's closeCtx) was never closed, leaking per request. The API methods now take the request context, close the iterator when the client disconnects, and drain through enode.ReadNodes. Two response-shape changes fall out of this: results are deduplicated (so fewer than n entries may come back) and empty results marshal as [] instead of null. The same defects exist in discv4_lookupRandom on master; fixed here too since the combined listener serves both APIs from one endpoint.

  • The RPC server no longer serves http.DefaultServeMux. cmd/devp2p blank-imports net/http/pprof via internal/debug, so mounting the RPC handler on the default mux exposed unauthenticated /debug/pprof/* on the RPC port. The rpc.Server is now the handler directly, and the three identical startup blocks (discv4 listen, discv5 listen, discovery listen) are collapsed into one runRPCServer helper.

  • Exported discover.SharedUDPConn. The command carried a copy of the unexported type in p2p/server.go and the two had already diverged textually; both p2p.Server and the combined listener now use the one in p2p/discover, which owns ReadPacket/UDPConn anyway.

  • discoveryListen builds a separate v4 config instead of mutate-then-restoring config.Unhandled, matching how p2p.Server sets up the shared socket.

Add `devp2p discovery listen`, which runs all supported discovery
protocol versions (discv4 and discv5) on a single UDP socket, the way a
real node does. For a single-protocol node, `discv4 listen` /
`discv5 listen` remain.

Also expose the --rpc debug API on `discv5 listen`, for parity with
`discv4 listen`.
Serve the RPC handler directly instead of via http.DefaultServeMux, which
also exposed /debug/pprof on the RPC port. lookupRandom now closes its
iterator and stops when the client disconnects, instead of hanging forever
on nodes with an empty table and leaking a context per call. Results go
through enode.ReadNodes: deduplicated (may return fewer than n) and empty
responses marshal as [] instead of null. The discv4 issues predate this
branch; fixed here because the combined listener exposes them.

Also export discover.SharedUDPConn and use it from p2p.Server and the
combined listener instead of keeping two diverging copies.
@MysticRyuujin MysticRyuujin force-pushed the devp2p-combined-discovery-listener branch from 4b7f221 to 9b57371 Compare July 10, 2026 15:32
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.

3 participants