mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 03:10:48 +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
|
Run `devp2p discv5 crawl <nodes.json path>` to create or update a JSON node set containing
|
||||||
discv5 nodes.
|
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
|
### Discovery Test Suites
|
||||||
|
|
||||||
The devp2p command also contains interactive test suites for Discovery v4 and Discovery
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -29,11 +29,9 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
|
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"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/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -176,19 +174,7 @@ func discv4Listen(ctx *cli.Context) error {
|
||||||
|
|
||||||
fmt.Println(disc.Self())
|
fmt.Println(disc.Self())
|
||||||
|
|
||||||
httpAddr := ctx.String(httpAddrFlag.Name)
|
return runRPCServer(ctx.String(httpAddrFlag.Name), map[string]any{"discv4": &discv4API{disc}})
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func discv4RequestRecord(ctx *cli.Context) error {
|
func discv4RequestRecord(ctx *cli.Context) error {
|
||||||
|
|
@ -428,13 +414,14 @@ type discv4API struct {
|
||||||
host *discover.UDPv4
|
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()
|
it := api.host.RandomNodes()
|
||||||
defer it.Close()
|
defer it.Close()
|
||||||
for len(ns) < n && it.Next() {
|
go func() {
|
||||||
ns = append(ns, it.Node())
|
<-ctx.Done()
|
||||||
}
|
it.Close()
|
||||||
return ns
|
}()
|
||||||
|
return enode.ReadNodes(it, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *discv4API) Buckets() [][]discover.BucketNode {
|
func (api *discv4API) Buckets() [][]discover.BucketNode {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -25,6 +26,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test"
|
"github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -76,7 +78,9 @@ var (
|
||||||
Name: "listen",
|
Name: "listen",
|
||||||
Usage: "Runs a node",
|
Usage: "Runs a node",
|
||||||
Action: discv5Listen,
|
Action: discv5Listen,
|
||||||
Flags: discoveryNodeFlags,
|
Flags: slices.Concat(discoveryNodeFlags, []cli.Flag{
|
||||||
|
httpAddrFlag,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -137,7 +141,8 @@ func discv5Listen(ctx *cli.Context) error {
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
|
|
||||||
fmt.Println(disc.Self())
|
fmt.Println(disc.Self())
|
||||||
select {}
|
|
||||||
|
return runRPCServer(ctx.String(httpAddrFlag.Name), map[string]any{"discv5": &discv5API{disc}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// startV5 starts an ephemeral discovery v5 node.
|
// startV5 starts an ephemeral discovery v5 node.
|
||||||
|
|
@ -150,3 +155,21 @@ func startV5(ctx *cli.Context) (*discover.UDPv5, discover.Config) {
|
||||||
}
|
}
|
||||||
return disc, 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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/internal/flags"
|
"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/p2p/enode"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -49,6 +52,7 @@ func init() {
|
||||||
keyCommand,
|
keyCommand,
|
||||||
discv4Command,
|
discv4Command,
|
||||||
discv5Command,
|
discv5Command,
|
||||||
|
discoveryCommand,
|
||||||
dnsCommand,
|
dnsCommand,
|
||||||
nodesetCommand,
|
nodesetCommand,
|
||||||
rlpxCommand,
|
rlpxCommand,
|
||||||
|
|
@ -92,6 +96,22 @@ func getNodeArg(ctx *cli.Context) *enode.Node {
|
||||||
return n
|
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{}) {
|
func exit(err interface{}) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"iter"
|
"iter"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -105,6 +106,30 @@ type ReadPacket struct {
|
||||||
Addr netip.AddrPort
|
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 {
|
type randomSource interface {
|
||||||
Intn(int) int
|
Intn(int) int
|
||||||
Int63n(int64) int64
|
Int63n(int64) int64
|
||||||
|
|
|
||||||
|
|
@ -330,32 +330,6 @@ func (srv *Server) Stop() {
|
||||||
srv.loopWG.Wait()
|
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.
|
// Start starts running the server.
|
||||||
// Servers can not be re-used after stopping.
|
// Servers can not be re-used after stopping.
|
||||||
func (srv *Server) Start() (err error) {
|
func (srv *Server) Start() (err error) {
|
||||||
|
|
@ -463,7 +437,7 @@ func (srv *Server) setupDiscovery() error {
|
||||||
// connection, so v5 can read unhandled messages from v4.
|
// connection, so v5 can read unhandled messages from v4.
|
||||||
if srv.Config.DiscoveryV4 && srv.Config.DiscoveryV5 {
|
if srv.Config.DiscoveryV4 && srv.Config.DiscoveryV5 {
|
||||||
unhandled = make(chan discover.ReadPacket, 100)
|
unhandled = make(chan discover.ReadPacket, 100)
|
||||||
sconn = &sharedUDPConn{conn, unhandled}
|
sconn = &discover.SharedUDPConn{UDPConn: conn, Unhandled: unhandled}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start discovery services.
|
// Start discovery services.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue