mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
cmd/devp2p: add combined discovery listener
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`.
This commit is contained in:
parent
3ab52d837d
commit
95a3b35e97
4 changed files with 157 additions and 2 deletions
|
|
@ -80,6 +80,13 @@ Run `devp2p discv5 listen` to run a Discovery v5 node.
|
|||
Run `devp2p discv5 crawl <nodes.json path>` to create or update a JSON node set containing
|
||||
discv5 nodes.
|
||||
|
||||
### Combined Discovery
|
||||
|
||||
Run `devp2p discovery listen` to run a node speaking all supported discovery protocol
|
||||
versions (currently discv4 and discv5) on a single UDP port. For a single-protocol node, use
|
||||
`devp2p discv4 listen` or `devp2p discv5 listen`. Add `--rpc <addr>` to expose the
|
||||
per-protocol HTTP API (`discv4_*`, `discv5_*`).
|
||||
|
||||
### Discovery Test Suites
|
||||
|
||||
The devp2p command also contains interactive test suites for Discovery v4 and Discovery
|
||||
|
|
|
|||
112
cmd/devp2p/discoverycmd.go
Normal file
112
cmd/devp2p/discoverycmd.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
discoveryCommand = &cli.Command{
|
||||
Name: "discovery",
|
||||
Usage: "Node Discovery tools",
|
||||
Subcommands: []*cli.Command{
|
||||
discoveryListenCommand,
|
||||
},
|
||||
}
|
||||
discoveryListenCommand = &cli.Command{
|
||||
Name: "listen",
|
||||
Usage: "Runs a discovery node speaking all supported protocol versions on one UDP port",
|
||||
Action: discoveryListen,
|
||||
Flags: slices.Concat(discoveryNodeFlags, []cli.Flag{
|
||||
httpAddrFlag,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
func discoveryListen(ctx *cli.Context) error {
|
||||
ln, config := makeDiscoveryConfig(ctx)
|
||||
socket := listen(ctx, ln)
|
||||
|
||||
// v4 is the primary listener on the real socket and forwards packets it
|
||||
// can't parse to v5 via the unhandled channel.
|
||||
unhandled := make(chan discover.ReadPacket, 100)
|
||||
config.Unhandled = unhandled
|
||||
v4, err := discover.ListenV4(socket, ln, config)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
config.Unhandled = nil
|
||||
v5, err := discover.ListenV5(&sharedUDPConn{socket, unhandled}, ln, config)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
// Close v4 before v5: when sharing the socket, v5's read loop only unblocks
|
||||
// once v4 closes the underlying socket and the unhandled channel.
|
||||
defer func() {
|
||||
v4.Close()
|
||||
v5.Close()
|
||||
}()
|
||||
|
||||
// v4 and v5 share the same local node, so this is the single record both announce.
|
||||
fmt.Println(ln.Node())
|
||||
|
||||
httpAddr := ctx.String(httpAddrFlag.Name)
|
||||
if httpAddr == "" {
|
||||
select {}
|
||||
}
|
||||
|
||||
srv := rpc.NewServer()
|
||||
srv.RegisterName("discv4", &discv4API{v4})
|
||||
srv.RegisterName("discv5", &discv5API{v5})
|
||||
log.Info("Starting RPC API server", "addr", httpAddr)
|
||||
http.DefaultServeMux.Handle("/", srv)
|
||||
httpsrv := http.Server{Addr: httpAddr, Handler: http.DefaultServeMux}
|
||||
return httpsrv.ListenAndServe()
|
||||
}
|
||||
|
||||
// sharedUDPConn implements a shared connection, identical to the one used by
|
||||
// p2p.Server: reads return packets the primary v4 listener found unprocessable
|
||||
// and forwarded on the unhandled channel.
|
||||
type sharedUDPConn struct {
|
||||
*net.UDPConn
|
||||
unhandled chan discover.ReadPacket
|
||||
}
|
||||
|
||||
func (s *sharedUDPConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
|
||||
packet, ok := <-s.unhandled
|
||||
if !ok {
|
||||
return 0, netip.AddrPort{}, errors.New("connection was closed")
|
||||
}
|
||||
l := min(len(packet.Data), len(b))
|
||||
copy(b[:l], packet.Data[:l])
|
||||
return l, packet.Addr, nil
|
||||
}
|
||||
|
||||
func (s *sharedUDPConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -19,12 +19,16 @@ package main
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -76,7 +80,9 @@ var (
|
|||
Name: "listen",
|
||||
Usage: "Runs a node",
|
||||
Action: discv5Listen,
|
||||
Flags: discoveryNodeFlags,
|
||||
Flags: slices.Concat(discoveryNodeFlags, []cli.Flag{
|
||||
httpAddrFlag,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -137,7 +143,20 @@ func discv5Listen(ctx *cli.Context) error {
|
|||
defer disc.Close()
|
||||
|
||||
fmt.Println(disc.Self())
|
||||
select {}
|
||||
|
||||
httpAddr := ctx.String(httpAddrFlag.Name)
|
||||
if httpAddr == "" {
|
||||
// Non-HTTP mode.
|
||||
select {}
|
||||
}
|
||||
|
||||
api := &discv5API{disc}
|
||||
log.Info("Starting RPC API server", "addr", httpAddr)
|
||||
srv := rpc.NewServer()
|
||||
srv.RegisterName("discv5", api)
|
||||
http.DefaultServeMux.Handle("/", srv)
|
||||
httpsrv := http.Server{Addr: httpAddr, Handler: http.DefaultServeMux}
|
||||
return httpsrv.ListenAndServe()
|
||||
}
|
||||
|
||||
// startV5 starts an ephemeral discovery v5 node.
|
||||
|
|
@ -150,3 +169,19 @@ func startV5(ctx *cli.Context) (*discover.UDPv5, discover.Config) {
|
|||
}
|
||||
return disc, config
|
||||
}
|
||||
|
||||
type discv5API struct {
|
||||
host *discover.UDPv5
|
||||
}
|
||||
|
||||
func (api *discv5API) LookupRandom(n int) (ns []*enode.Node) {
|
||||
it := api.host.RandomNodes()
|
||||
for len(ns) < n && it.Next() {
|
||||
ns = append(ns, it.Node())
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
func (api *discv5API) Self() *enode.Node {
|
||||
return api.host.Self()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ func init() {
|
|||
keyCommand,
|
||||
discv4Command,
|
||||
discv5Command,
|
||||
discoveryCommand,
|
||||
dnsCommand,
|
||||
nodesetCommand,
|
||||
rlpxCommand,
|
||||
|
|
|
|||
Loading…
Reference in a new issue