mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 19:00:46 +00:00
Merge 9b5737150a into dddbaa4bf3
This commit is contained in:
commit
6cdd6ce537
7 changed files with 162 additions and 50 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
|
||||
|
|
|
|||
76
cmd/devp2p/discoverycmd.go
Normal file
76
cmd/devp2p/discoverycmd.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"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)
|
||||
v4cfg := config
|
||||
v4cfg.Unhandled = unhandled
|
||||
v4, err := discover.ListenV4(socket, ln, v4cfg)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
v5, err := discover.ListenV5(&discover.SharedUDPConn{UDPConn: socket, Unhandled: 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())
|
||||
|
||||
return runRPCServer(ctx.String(httpAddrFlag.Name), map[string]any{
|
||||
"discv4": &discv4API{v4},
|
||||
"discv5": &discv5API{v5},
|
||||
})
|
||||
}
|
||||
|
|
@ -17,10 +17,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -29,11 +29,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"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/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -176,19 +174,7 @@ func discv4Listen(ctx *cli.Context) error {
|
|||
|
||||
fmt.Println(disc.Self())
|
||||
|
||||
httpAddr := ctx.String(httpAddrFlag.Name)
|
||||
if httpAddr == "" {
|
||||
// Non-HTTP mode.
|
||||
select {}
|
||||
}
|
||||
|
||||
api := &discv4API{disc}
|
||||
log.Info("Starting RPC API server", "addr", httpAddr)
|
||||
srv := rpc.NewServer()
|
||||
srv.RegisterName("discv4", api)
|
||||
http.DefaultServeMux.Handle("/", srv)
|
||||
httpsrv := http.Server{Addr: httpAddr, Handler: http.DefaultServeMux}
|
||||
return httpsrv.ListenAndServe()
|
||||
return runRPCServer(ctx.String(httpAddrFlag.Name), map[string]any{"discv4": &discv4API{disc}})
|
||||
}
|
||||
|
||||
func discv4RequestRecord(ctx *cli.Context) error {
|
||||
|
|
@ -428,13 +414,14 @@ type discv4API struct {
|
|||
host *discover.UDPv4
|
||||
}
|
||||
|
||||
func (api *discv4API) LookupRandom(n int) (ns []*enode.Node) {
|
||||
func (api *discv4API) LookupRandom(ctx context.Context, n int) []*enode.Node {
|
||||
it := api.host.RandomNodes()
|
||||
defer it.Close()
|
||||
for len(ns) < n && it.Next() {
|
||||
ns = append(ns, it.Node())
|
||||
}
|
||||
return ns
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
it.Close()
|
||||
}()
|
||||
return enode.ReadNodes(it, n)
|
||||
}
|
||||
|
||||
func (api *discv4API) Buckets() [][]discover.BucketNode {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
|
@ -25,6 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -76,7 +78,9 @@ var (
|
|||
Name: "listen",
|
||||
Usage: "Runs a node",
|
||||
Action: discv5Listen,
|
||||
Flags: discoveryNodeFlags,
|
||||
Flags: slices.Concat(discoveryNodeFlags, []cli.Flag{
|
||||
httpAddrFlag,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -137,7 +141,8 @@ func discv5Listen(ctx *cli.Context) error {
|
|||
defer disc.Close()
|
||||
|
||||
fmt.Println(disc.Self())
|
||||
select {}
|
||||
|
||||
return runRPCServer(ctx.String(httpAddrFlag.Name), map[string]any{"discv5": &discv5API{disc}})
|
||||
}
|
||||
|
||||
// startV5 starts an ephemeral discovery v5 node.
|
||||
|
|
@ -150,3 +155,21 @@ func startV5(ctx *cli.Context) (*discover.UDPv5, discover.Config) {
|
|||
}
|
||||
return disc, config
|
||||
}
|
||||
|
||||
type discv5API struct {
|
||||
host *discover.UDPv5
|
||||
}
|
||||
|
||||
func (api *discv5API) LookupRandom(ctx context.Context, n int) []*enode.Node {
|
||||
it := api.host.RandomNodes()
|
||||
defer it.Close()
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
it.Close()
|
||||
}()
|
||||
return enode.ReadNodes(it, n)
|
||||
}
|
||||
|
||||
func (api *discv5API) Self() *enode.Node {
|
||||
return api.host.Self()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -49,6 +52,7 @@ func init() {
|
|||
keyCommand,
|
||||
discv4Command,
|
||||
discv5Command,
|
||||
discoveryCommand,
|
||||
dnsCommand,
|
||||
nodesetCommand,
|
||||
rlpxCommand,
|
||||
|
|
@ -92,6 +96,22 @@ func getNodeArg(ctx *cli.Context) *enode.Node {
|
|||
return n
|
||||
}
|
||||
|
||||
// runRPCServer serves the given RPC APIs on httpAddr; if httpAddr is empty, it blocks forever without serving.
|
||||
func runRPCServer(httpAddr string, apis map[string]any) error {
|
||||
if httpAddr == "" {
|
||||
select {}
|
||||
}
|
||||
srv := rpc.NewServer()
|
||||
for name, api := range apis {
|
||||
if err := srv.RegisterName(name, api); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Info("Starting RPC API server", "addr", httpAddr)
|
||||
httpsrv := http.Server{Addr: httpAddr, Handler: srv}
|
||||
return httpsrv.ListenAndServe()
|
||||
}
|
||||
|
||||
func exit(err interface{}) {
|
||||
if err == nil {
|
||||
os.Exit(0)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"iter"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
|
@ -105,6 +106,30 @@ type ReadPacket struct {
|
|||
Addr netip.AddrPort
|
||||
}
|
||||
|
||||
// SharedUDPConn implements a shared connection. Write sends messages to the underlying
|
||||
// connection while read returns messages that were found unprocessable and sent to the
|
||||
// Unhandled channel by the primary listener.
|
||||
type SharedUDPConn struct {
|
||||
*net.UDPConn
|
||||
Unhandled chan ReadPacket
|
||||
}
|
||||
|
||||
// ReadFromUDPAddrPort implements UDPConn.
|
||||
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
|
||||
}
|
||||
|
||||
// Close implements UDPConn. It does not close the underlying connection.
|
||||
func (s *SharedUDPConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type randomSource interface {
|
||||
Intn(int) int
|
||||
Int63n(int64) int64
|
||||
|
|
|
|||
|
|
@ -330,32 +330,6 @@ func (srv *Server) Stop() {
|
|||
srv.loopWG.Wait()
|
||||
}
|
||||
|
||||
// sharedUDPConn implements a shared connection. Write sends messages to the underlying connection while read returns
|
||||
// messages that were found unprocessable and sent to the unhandled channel by the primary listener.
|
||||
type sharedUDPConn struct {
|
||||
*net.UDPConn
|
||||
unhandled chan discover.ReadPacket
|
||||
}
|
||||
|
||||
// ReadFromUDPAddrPort implements discover.UDPConn
|
||||
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 := len(packet.Data)
|
||||
if l > len(b) {
|
||||
l = len(b)
|
||||
}
|
||||
copy(b[:l], packet.Data[:l])
|
||||
return l, packet.Addr, nil
|
||||
}
|
||||
|
||||
// Close implements discover.UDPConn
|
||||
func (s *sharedUDPConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts running the server.
|
||||
// Servers can not be re-used after stopping.
|
||||
func (srv *Server) Start() (err error) {
|
||||
|
|
@ -463,7 +437,7 @@ func (srv *Server) setupDiscovery() error {
|
|||
// connection, so v5 can read unhandled messages from v4.
|
||||
if srv.Config.DiscoveryV4 && srv.Config.DiscoveryV5 {
|
||||
unhandled = make(chan discover.ReadPacket, 100)
|
||||
sconn = &sharedUDPConn{conn, unhandled}
|
||||
sconn = &discover.SharedUDPConn{UDPConn: conn, Unhandled: unhandled}
|
||||
}
|
||||
|
||||
// Start discovery services.
|
||||
|
|
|
|||
Loading…
Reference in a new issue