From 6bb35af2b0691bd36a7ea6343fbf40c1f698904f Mon Sep 17 00:00:00 2001 From: rayoo Date: Mon, 11 May 2026 18:20:15 +0800 Subject: [PATCH] p2p/discover: return errNoUDPEndpoint from RequestENR Mirror the guard applied to (*UDPv4).Dial in #34916: when the target node has no usable UDP endpoint, return errNoUDPEndpoint instead of silently sending the ENRRequest to an invalid AddrPort and waiting for a timeout. The other UDPEndpoint-using request paths in this file already do this: ping v4_udp.go:215 errNoUDPEndpoint Ping v4_udp.go:228 errNoUDPEndpoint newLookup v4_udp.go:309 errNoUDPEndpoint RequestENR v4_udp.go:358 addr, _ := n.UDPEndpoint() <-- outlier RequestENR is reachable from external callers like cmd/devp2p/crawl.go, which feeds in arbitrary nodes that may not have a UDP port set. Before this change, such nodes burn one full RPC timeout; after it, the caller gets a clean error immediately. The added test fails on master with "RPC timeout" and the trace logs "PING/v4 addr=invalid AddrPort", confirming packets are being written to an unspecified address; with the fix it returns errNoUDPEndpoint without doing any I/O. --- p2p/discover/v4_udp.go | 5 ++++- p2p/discover/v4_udp_test.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index ae8cbec3e2..28702f0fc6 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -355,7 +355,10 @@ func (t *UDPv4) findnode(toid enode.ID, toAddrPort netip.AddrPort, target v4wire // RequestENR sends ENRRequest to the given node and waits for a response. func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) { - addr, _ := n.UDPEndpoint() + addr, ok := n.UDPEndpoint() + if !ok { + return nil, errNoUDPEndpoint + } t.ensureBond(n.ID(), addr) req := &v4wire.ENRRequest{ diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 287f0c34fa..a4136c76d3 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -158,6 +158,23 @@ func TestUDPv4_pingTimeout(t *testing.T) { } } +// TestUDPv4_RequestENRNoUDPEndpoint verifies that RequestENR returns a clean +// errNoUDPEndpoint error for a node without a usable UDP endpoint, instead of +// silently sending a packet to the zero address and waiting for a timeout. +// This mirrors the existing guards in ping/Ping/findnode. +func TestUDPv4_RequestENRNoUDPEndpoint(t *testing.T) { + t.Parallel() + test := newUDPTest(t) + defer test.close() + + key := newkey() + // UDP port 0 makes UDPEndpoint return ok=false. + node := enode.NewV4(&key.PublicKey, net.ParseIP("1.2.3.4"), 2222, 0) + if _, err := test.udp.RequestENR(node); err != errNoUDPEndpoint { + t.Errorf("expected errNoUDPEndpoint, got %v", err) + } +} + type testPacket byte func (req testPacket) Kind() byte { return byte(req) }