Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 113 additions & 1 deletion cmd/devp2p/discv5cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,37 @@
package main

import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"os"
"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/p2p/enr"
"github.com/ethereum/go-ethereum/rlp"
"github.com/urfave/cli/v2"
)

var (
discv5DumpFlag = &cli.BoolFlag{
Name: "dump",
Usage: "Dump all peers every 10 seconds",
}
opStackChainIDFlag = &cli.Uint64Flag{
Name: "opstack-chainid",
Usage: "Filter nodes by OP Stack chain ID (only nodes with matching opstack ENR entry will be accepted)",
}
chainIDFlag = &cli.Uint64Flag{
Name: "chainid",
Usage: "Filter nodes by chain ID (only nodes with matching chainID ENR entry will be accepted)",
}
discv5Command = &cli.Command{
Name: "discv5",
Usage: "Node Discovery v5 tools",
Expand Down Expand Up @@ -75,7 +94,7 @@ var (
Name: "listen",
Usage: "Runs a node",
Action: discv5Listen,
Flags: discoveryNodeFlags,
Flags: slices.Concat(discoveryNodeFlags, []cli.Flag{discv5DumpFlag, opStackChainIDFlag, chainIDFlag}),
}
)

Expand Down Expand Up @@ -136,12 +155,105 @@ func discv5Listen(ctx *cli.Context) error {
defer disc.Close()

fmt.Println(disc.Self())

if ctx.Bool(discv5DumpFlag.Name) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for range ticker.C {
nodes := disc.AllNodes()
fmt.Printf("\n--- Dumping %d peers ---\n", len(nodes))
for i, n := range nodes {
fmt.Printf("\n[%d] %s\n", i+1, n.String())
dumpRecord(os.Stdout, n.Record())
}
}
}

select {}
}

// opStackENRData is the ENR entry for OP Stack chain identification.
type opStackENRData struct {
chainID uint64
version uint64
}

func (o *opStackENRData) ENRKey() string { return "opstack" }

func (o *opStackENRData) DecodeRLP(s *rlp.Stream) error {
b, err := s.Bytes()
if err != nil {
return fmt.Errorf("failed to decode outer ENR entry: %w", err)
}
r := bytes.NewReader(b)
chainID, err := binary.ReadUvarint(r)
if err != nil {
return fmt.Errorf("failed to read chain ID var int: %w", err)
}
version, err := binary.ReadUvarint(r)
if err != nil {
return fmt.Errorf("failed to read version var int: %w", err)
}
o.chainID = chainID
o.version = version
return nil
}

var _ enr.Entry = (*opStackENRData)(nil)

// chainIDENRData is the ENR entry for chain ID identification.
type chainIDENRData uint64

func (c chainIDENRData) ENRKey() string { return "chainID" }

var _ enr.Entry = (*chainIDENRData)(nil)

// startV5 starts an ephemeral discovery v5 node.
func startV5(ctx *cli.Context) (*discover.UDPv5, discover.Config) {
ln, config := makeDiscoveryConfig(ctx)

// Set up OP Stack chain ID filter if specified
if ctx.IsSet(opStackChainIDFlag.Name) {
expectedChainID := ctx.Uint64(opStackChainIDFlag.Name)
config.NodeFilter = func(node *enode.Node) bool {
var dat opStackENRData
if err := node.Load(&dat); err != nil {
log.Debug("Node has no opstack ENR entry", "id", node.ID(), "ip", node.IP(), "err", err)
return false
}
if dat.chainID != expectedChainID {
log.Debug("Node has different chain ID", "id", node.ID(), "ip", node.IP(), "got", dat.chainID, "expected", expectedChainID)
return false
}
if dat.version != 0 {
log.Debug("Node has different version", "id", node.ID(), "ip", node.IP(), "got", dat.version, "expected", 0)
return false
}
log.Info("Node passed filter", "id", node.ID(), "ip", node.IP(), "chainID", dat.chainID)
return true
}
log.Info("OP Stack node filter enabled", "chainID", expectedChainID)
}

// Set up chain ID filter if specified
if ctx.IsSet(chainIDFlag.Name) {
expectedChainID := ctx.Uint64(chainIDFlag.Name)
config.NodeFilter = func(node *enode.Node) bool {
var dat chainIDENRData
if err := node.Load(&dat); err != nil {
log.Debug("Node has no chainID ENR entry", "id", node.ID(), "ip", node.IP(), "err", err)
return false
}
if uint64(dat) != expectedChainID {
log.Debug("Node has different chain ID", "id", node.ID(), "ip", node.IP(), "got", uint64(dat), "expected", expectedChainID)
return false
}
log.Info("Node passed chainID filter", "id", node.ID(), "ip", node.IP(), "chainID", uint64(dat))
return true
}
log.Info("Chain ID node filter enabled", "chainID", expectedChainID)
}

socket := listen(ctx, ln)
disc, err := discover.ListenV5(socket, ln, config)
if err != nil {
Expand Down
9 changes: 5 additions & 4 deletions p2p/discover/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ type Config struct {
V5RespTimeout time.Duration // timeout for v5 queries

// Node table configuration:
Bootnodes []*enode.Node // list of bootstrap nodes
PingInterval time.Duration // speed of node liveness check
RefreshInterval time.Duration // used in bucket refresh
NoFindnodeLivenessCheck bool // turns off validation of table nodes in FINDNODE handler
Bootnodes []*enode.Node // list of bootstrap nodes
PingInterval time.Duration // speed of node liveness check
RefreshInterval time.Duration // used in bucket refresh
NoFindnodeLivenessCheck bool // turns off validation of table nodes in FINDNODE handler
NodeFilter func(*enode.Node) bool // filter function for discovered nodes; if set, only nodes passing the filter are added to the table

// The options below are useful in very specific cases, like in unit tests.
V5ProtocolID *[6]byte
Expand Down
4 changes: 4 additions & 0 deletions p2p/discover/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,10 @@ func (tab *Table) handleAddNode(req addNodeOp) bool {
if req.isInbound && !tab.isInitDone() {
return false
}
// Apply node filter if configured.
if tab.cfg.NodeFilter != nil && !tab.cfg.NodeFilter(req.node) {
return false
}

b := tab.bucket(req.node.ID())
n, _ := tab.bumpInBucket(b, req.node, req.isInbound)
Expand Down
4 changes: 3 additions & 1 deletion p2p/discover/table_reval.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,13 @@ func (tr *tableRevalidation) handleResponse(tab *Table, resp revalidationRespons
return
}

// Store potential seeds in database.
// Store potential seeds in database and update last pong time.
// This is done via defer to avoid holding Table lock while writing to DB.
defer func() {
if n.isValidatedLive && n.livenessChecks > 5 {
tab.db.UpdateNode(resp.n.Node)
// Update last pong time so QuerySeeds can find this node
tab.db.UpdateLastPongReceived(resp.n.ID(), resp.n.IPAddr(), time.Now())
}
}()

Expand Down