From 4de78cea08dee5f049cdc67c4c8ba5ec2a0df69a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Isabel=20Sch=C3=B6ps=20Thiel=20=40IsabelSchoepd?=
<155141998+IST-Github@users.noreply.github.com>
Date: Thu, 4 Jan 2024 04:32:37 +0100
Subject: [PATCH] Delete p2p directory
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Isabel Schöps Thiel @IsabelSchoepd <155141998+IST-Github@users.noreply.github.com>
---
p2p/dial.go | 544 --------
p2p/dial_test.go | 672 ----------
p2p/discover/common.go | 101 --
p2p/discover/lookup.go | 227 ----
p2p/discover/metrics.go | 73 --
p2p/discover/node.go | 97 --
p2p/discover/ntp.go | 111 --
p2p/discover/table.go | 754 -----------
p2p/discover/table_test.go | 446 -------
p2p/discover/table_util_test.go | 258 ----
p2p/discover/v4_lookup_test.go | 347 -----
p2p/discover/v4_udp.go | 787 ------------
p2p/discover/v4_udp_test.go | 661 ----------
p2p/discover/v4wire/v4wire.go | 296 -----
p2p/discover/v4wire/v4wire_test.go | 132 --
p2p/discover/v5_talk.go | 113 --
p2p/discover/v5_udp.go | 910 -------------
p2p/discover/v5_udp_test.go | 859 -------------
p2p/discover/v5wire/crypto.go | 180 ---
p2p/discover/v5wire/crypto_test.go | 124 --
p2p/discover/v5wire/encoding.go | 672 ----------
p2p/discover/v5wire/encoding_test.go | 639 ----------
p2p/discover/v5wire/msg.go | 229 ----
p2p/discover/v5wire/session.go | 135 --
.../testdata/v5.1-ping-handshake-enr.txt | 27 -
.../v5wire/testdata/v5.1-ping-handshake.txt | 23 -
.../v5wire/testdata/v5.1-ping-message.txt | 10 -
.../v5wire/testdata/v5.1-whoareyou.txt | 9 -
p2p/dnsdisc/client.go | 389 ------
p2p/dnsdisc/client_test.go | 478 -------
p2p/dnsdisc/doc.go | 18 -
p2p/dnsdisc/error.go | 63 -
p2p/dnsdisc/sync.go | 329 -----
p2p/dnsdisc/sync_test.go | 83 --
p2p/dnsdisc/tree.go | 423 ------
p2p/dnsdisc/tree_test.go | 151 ---
p2p/enode/idscheme.go | 161 ---
p2p/enode/idscheme_test.go | 74 --
p2p/enode/iter.go | 295 -----
p2p/enode/iter_test.go | 291 -----
p2p/enode/localnode.go | 332 -----
p2p/enode/localnode_test.go | 129 --
p2p/enode/node.go | 279 ----
p2p/enode/node_test.go | 145 ---
p2p/enode/nodedb.go | 501 --------
p2p/enode/nodedb_test.go | 469 -------
p2p/enode/urlv4.go | 203 ---
p2p/enode/urlv4_test.go | 200 ---
p2p/enr/enr.go | 335 -----
p2p/enr/enr_test.go | 348 -----
p2p/enr/entries.go | 196 ---
p2p/message.go | 325 -----
p2p/message_test.go | 141 --
p2p/metrics.go | 132 --
p2p/msgrate/msgrate.go | 465 -------
p2p/msgrate/msgrate_test.go | 28 -
p2p/nat/nat.go | 240 ----
p2p/nat/nat_test.go | 63 -
p2p/nat/natpmp.go | 130 --
p2p/nat/natupnp.go | 250 ----
p2p/nat/natupnp_test.go | 249 ----
p2p/netutil/addrutil.go | 33 -
p2p/netutil/error.go | 33 -
p2p/netutil/error_test.go | 72 --
p2p/netutil/iptrack.go | 130 --
p2p/netutil/iptrack_test.go | 138 --
p2p/netutil/net.go | 322 -----
p2p/netutil/net_test.go | 262 ----
p2p/netutil/toobig_notwindows.go | 27 -
p2p/netutil/toobig_windows.go | 41 -
p2p/nodestate/nodestate.go | 1023 ---------------
p2p/nodestate/nodestate_test.go | 407 ------
p2p/peer.go | 548 --------
p2p/peer_error.go | 119 --
p2p/peer_test.go | 362 ------
p2p/protocol.go | 93 --
p2p/rlpx/buffer.go | 127 --
p2p/rlpx/buffer_test.go | 51 -
p2p/rlpx/rlpx.go | 676 ----------
p2p/rlpx/rlpx_test.go | 453 -------
p2p/server.go | 1134 -----------------
p2p/server_nat.go | 187 ---
p2p/server_nat_test.go | 102 --
p2p/server_test.go | 631 ---------
p2p/simulations/README.md | 169 ---
p2p/simulations/adapters/exec.go | 564 --------
p2p/simulations/adapters/inproc.go | 350 -----
p2p/simulations/adapters/inproc_test.go | 202 ---
p2p/simulations/adapters/types.go | 326 -----
p2p/simulations/connect.go | 153 ---
p2p/simulations/connect_test.go | 172 ---
p2p/simulations/events.go | 110 --
p2p/simulations/examples/README.md | 39 -
p2p/simulations/examples/ping-pong.go | 173 ---
p2p/simulations/examples/ping-pong.sh | 40 -
p2p/simulations/http.go | 743 -----------
p2p/simulations/http_test.go | 871 -------------
p2p/simulations/mocker.go | 191 ---
p2p/simulations/mocker_test.go | 174 ---
p2p/simulations/network.go | 1090 ----------------
p2p/simulations/network_test.go | 872 -------------
p2p/simulations/pipes/pipes.go | 55 -
p2p/simulations/simulation.go | 157 ---
p2p/simulations/test.go | 150 ---
p2p/tracker/tracker.go | 205 ---
p2p/transport.go | 182 ---
p2p/transport_test.go | 148 ---
p2p/util.go | 76 --
p2p/util_test.go | 56 -
109 files changed, 31590 deletions(-)
delete mode 100644 p2p/dial.go
delete mode 100644 p2p/dial_test.go
delete mode 100644 p2p/discover/common.go
delete mode 100644 p2p/discover/lookup.go
delete mode 100644 p2p/discover/metrics.go
delete mode 100644 p2p/discover/node.go
delete mode 100644 p2p/discover/ntp.go
delete mode 100644 p2p/discover/table.go
delete mode 100644 p2p/discover/table_test.go
delete mode 100644 p2p/discover/table_util_test.go
delete mode 100644 p2p/discover/v4_lookup_test.go
delete mode 100644 p2p/discover/v4_udp.go
delete mode 100644 p2p/discover/v4_udp_test.go
delete mode 100644 p2p/discover/v4wire/v4wire.go
delete mode 100644 p2p/discover/v4wire/v4wire_test.go
delete mode 100644 p2p/discover/v5_talk.go
delete mode 100644 p2p/discover/v5_udp.go
delete mode 100644 p2p/discover/v5_udp_test.go
delete mode 100644 p2p/discover/v5wire/crypto.go
delete mode 100644 p2p/discover/v5wire/crypto_test.go
delete mode 100644 p2p/discover/v5wire/encoding.go
delete mode 100644 p2p/discover/v5wire/encoding_test.go
delete mode 100644 p2p/discover/v5wire/msg.go
delete mode 100644 p2p/discover/v5wire/session.go
delete mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt
delete mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt
delete mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-message.txt
delete mode 100644 p2p/discover/v5wire/testdata/v5.1-whoareyou.txt
delete mode 100644 p2p/dnsdisc/client.go
delete mode 100644 p2p/dnsdisc/client_test.go
delete mode 100644 p2p/dnsdisc/doc.go
delete mode 100644 p2p/dnsdisc/error.go
delete mode 100644 p2p/dnsdisc/sync.go
delete mode 100644 p2p/dnsdisc/sync_test.go
delete mode 100644 p2p/dnsdisc/tree.go
delete mode 100644 p2p/dnsdisc/tree_test.go
delete mode 100644 p2p/enode/idscheme.go
delete mode 100644 p2p/enode/idscheme_test.go
delete mode 100644 p2p/enode/iter.go
delete mode 100644 p2p/enode/iter_test.go
delete mode 100644 p2p/enode/localnode.go
delete mode 100644 p2p/enode/localnode_test.go
delete mode 100644 p2p/enode/node.go
delete mode 100644 p2p/enode/node_test.go
delete mode 100644 p2p/enode/nodedb.go
delete mode 100644 p2p/enode/nodedb_test.go
delete mode 100644 p2p/enode/urlv4.go
delete mode 100644 p2p/enode/urlv4_test.go
delete mode 100644 p2p/enr/enr.go
delete mode 100644 p2p/enr/enr_test.go
delete mode 100644 p2p/enr/entries.go
delete mode 100644 p2p/message.go
delete mode 100644 p2p/message_test.go
delete mode 100644 p2p/metrics.go
delete mode 100644 p2p/msgrate/msgrate.go
delete mode 100644 p2p/msgrate/msgrate_test.go
delete mode 100644 p2p/nat/nat.go
delete mode 100644 p2p/nat/nat_test.go
delete mode 100644 p2p/nat/natpmp.go
delete mode 100644 p2p/nat/natupnp.go
delete mode 100644 p2p/nat/natupnp_test.go
delete mode 100644 p2p/netutil/addrutil.go
delete mode 100644 p2p/netutil/error.go
delete mode 100644 p2p/netutil/error_test.go
delete mode 100644 p2p/netutil/iptrack.go
delete mode 100644 p2p/netutil/iptrack_test.go
delete mode 100644 p2p/netutil/net.go
delete mode 100644 p2p/netutil/net_test.go
delete mode 100644 p2p/netutil/toobig_notwindows.go
delete mode 100644 p2p/netutil/toobig_windows.go
delete mode 100644 p2p/nodestate/nodestate.go
delete mode 100644 p2p/nodestate/nodestate_test.go
delete mode 100644 p2p/peer.go
delete mode 100644 p2p/peer_error.go
delete mode 100644 p2p/peer_test.go
delete mode 100644 p2p/protocol.go
delete mode 100644 p2p/rlpx/buffer.go
delete mode 100644 p2p/rlpx/buffer_test.go
delete mode 100644 p2p/rlpx/rlpx.go
delete mode 100644 p2p/rlpx/rlpx_test.go
delete mode 100644 p2p/server.go
delete mode 100644 p2p/server_nat.go
delete mode 100644 p2p/server_nat_test.go
delete mode 100644 p2p/server_test.go
delete mode 100644 p2p/simulations/README.md
delete mode 100644 p2p/simulations/adapters/exec.go
delete mode 100644 p2p/simulations/adapters/inproc.go
delete mode 100644 p2p/simulations/adapters/inproc_test.go
delete mode 100644 p2p/simulations/adapters/types.go
delete mode 100644 p2p/simulations/connect.go
delete mode 100644 p2p/simulations/connect_test.go
delete mode 100644 p2p/simulations/events.go
delete mode 100644 p2p/simulations/examples/README.md
delete mode 100644 p2p/simulations/examples/ping-pong.go
delete mode 100755 p2p/simulations/examples/ping-pong.sh
delete mode 100644 p2p/simulations/http.go
delete mode 100644 p2p/simulations/http_test.go
delete mode 100644 p2p/simulations/mocker.go
delete mode 100644 p2p/simulations/mocker_test.go
delete mode 100644 p2p/simulations/network.go
delete mode 100644 p2p/simulations/network_test.go
delete mode 100644 p2p/simulations/pipes/pipes.go
delete mode 100644 p2p/simulations/simulation.go
delete mode 100644 p2p/simulations/test.go
delete mode 100644 p2p/tracker/tracker.go
delete mode 100644 p2p/transport.go
delete mode 100644 p2p/transport_test.go
delete mode 100644 p2p/util.go
delete mode 100644 p2p/util_test.go
diff --git a/p2p/dial.go b/p2p/dial.go
deleted file mode 100644
index 5e4ab1d50d..0000000000
--- a/p2p/dial.go
+++ /dev/null
@@ -1,544 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "context"
- crand "crypto/rand"
- "encoding/binary"
- "errors"
- "fmt"
- mrand "math/rand"
- "net"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/netutil"
-)
-
-const (
- // This is the amount of time spent waiting in between redialing a certain node. The
- // limit is a bit higher than inboundThrottleTime to prevent failing dials in small
- // private networks.
- dialHistoryExpiration = inboundThrottleTime + 5*time.Second
-
- // Config for the "Looking for peers" message.
- dialStatsLogInterval = 10 * time.Second // printed at most this often
- dialStatsPeerLimit = 3 // but not if more than this many dialed peers
-
- // Endpoint resolution is throttled with bounded backoff.
- initialResolveDelay = 60 * time.Second
- maxResolveDelay = time.Hour
-)
-
-// NodeDialer is used to connect to nodes in the network, typically by using
-// an underlying net.Dialer but also using net.Pipe in tests.
-type NodeDialer interface {
- Dial(context.Context, *enode.Node) (net.Conn, error)
-}
-
-type nodeResolver interface {
- Resolve(*enode.Node) *enode.Node
-}
-
-// tcpDialer implements NodeDialer using real TCP connections.
-type tcpDialer struct {
- d *net.Dialer
-}
-
-func (t tcpDialer) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) {
- return t.d.DialContext(ctx, "tcp", nodeAddr(dest).String())
-}
-
-func nodeAddr(n *enode.Node) net.Addr {
- return &net.TCPAddr{IP: n.IP(), Port: n.TCP()}
-}
-
-// checkDial errors:
-var (
- errSelf = errors.New("is self")
- errAlreadyDialing = errors.New("already dialing")
- errAlreadyConnected = errors.New("already connected")
- errRecentlyDialed = errors.New("recently dialed")
- errNetRestrict = errors.New("not contained in netrestrict list")
- errNoPort = errors.New("node does not provide TCP port")
-)
-
-// dialer creates outbound connections and submits them into Server.
-// Two types of peer connections can be created:
-//
-// - static dials are pre-configured connections. The dialer attempts
-// keep these nodes connected at all times.
-//
-// - dynamic dials are created from node discovery results. The dialer
-// continuously reads candidate nodes from its input iterator and attempts
-// to create peer connections to nodes arriving through the iterator.
-type dialScheduler struct {
- dialConfig
- setupFunc dialSetupFunc
- wg sync.WaitGroup
- cancel context.CancelFunc
- ctx context.Context
- nodesIn chan *enode.Node
- doneCh chan *dialTask
- addStaticCh chan *enode.Node
- remStaticCh chan *enode.Node
- addPeerCh chan *conn
- remPeerCh chan *conn
-
- // Everything below here belongs to loop and
- // should only be accessed by code on the loop goroutine.
- dialing map[enode.ID]*dialTask // active tasks
- peers map[enode.ID]struct{} // all connected peers
- dialPeers int // current number of dialed peers
-
- // The static map tracks all static dial tasks. The subset of usable static dial tasks
- // (i.e. those passing checkDial) is kept in staticPool. The scheduler prefers
- // launching random static tasks from the pool over launching dynamic dials from the
- // iterator.
- static map[enode.ID]*dialTask
- staticPool []*dialTask
-
- // The dial history keeps recently dialed nodes. Members of history are not dialed.
- history expHeap
- historyTimer *mclock.Alarm
-
- // for logStats
- lastStatsLog mclock.AbsTime
- doneSinceLastLog int
-}
-
-type dialSetupFunc func(net.Conn, connFlag, *enode.Node) error
-
-type dialConfig struct {
- self enode.ID // our own ID
- maxDialPeers int // maximum number of dialed peers
- maxActiveDials int // maximum number of active dials
- netRestrict *netutil.Netlist // IP netrestrict list, disabled if nil
- resolver nodeResolver
- dialer NodeDialer
- log log.Logger
- clock mclock.Clock
- rand *mrand.Rand
-}
-
-func (cfg dialConfig) withDefaults() dialConfig {
- if cfg.maxActiveDials == 0 {
- cfg.maxActiveDials = defaultMaxPendingPeers
- }
- if cfg.log == nil {
- cfg.log = log.Root()
- }
- if cfg.clock == nil {
- cfg.clock = mclock.System{}
- }
- if cfg.rand == nil {
- seedb := make([]byte, 8)
- crand.Read(seedb)
- seed := int64(binary.BigEndian.Uint64(seedb))
- cfg.rand = mrand.New(mrand.NewSource(seed))
- }
- return cfg
-}
-
-func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler {
- cfg := config.withDefaults()
- d := &dialScheduler{
- dialConfig: cfg,
- historyTimer: mclock.NewAlarm(cfg.clock),
- setupFunc: setupFunc,
- dialing: make(map[enode.ID]*dialTask),
- static: make(map[enode.ID]*dialTask),
- peers: make(map[enode.ID]struct{}),
- doneCh: make(chan *dialTask),
- nodesIn: make(chan *enode.Node),
- addStaticCh: make(chan *enode.Node),
- remStaticCh: make(chan *enode.Node),
- addPeerCh: make(chan *conn),
- remPeerCh: make(chan *conn),
- }
- d.lastStatsLog = d.clock.Now()
- d.ctx, d.cancel = context.WithCancel(context.Background())
- d.wg.Add(2)
- go d.readNodes(it)
- go d.loop(it)
- return d
-}
-
-// stop shuts down the dialer, canceling all current dial tasks.
-func (d *dialScheduler) stop() {
- d.cancel()
- d.wg.Wait()
-}
-
-// addStatic adds a static dial candidate.
-func (d *dialScheduler) addStatic(n *enode.Node) {
- select {
- case d.addStaticCh <- n:
- case <-d.ctx.Done():
- }
-}
-
-// removeStatic removes a static dial candidate.
-func (d *dialScheduler) removeStatic(n *enode.Node) {
- select {
- case d.remStaticCh <- n:
- case <-d.ctx.Done():
- }
-}
-
-// peerAdded updates the peer set.
-func (d *dialScheduler) peerAdded(c *conn) {
- select {
- case d.addPeerCh <- c:
- case <-d.ctx.Done():
- }
-}
-
-// peerRemoved updates the peer set.
-func (d *dialScheduler) peerRemoved(c *conn) {
- select {
- case d.remPeerCh <- c:
- case <-d.ctx.Done():
- }
-}
-
-// loop is the main loop of the dialer.
-func (d *dialScheduler) loop(it enode.Iterator) {
- var (
- nodesCh chan *enode.Node
- )
-
-loop:
- for {
- // Launch new dials if slots are available.
- slots := d.freeDialSlots()
- slots -= d.startStaticDials(slots)
- if slots > 0 {
- nodesCh = d.nodesIn
- } else {
- nodesCh = nil
- }
- d.rearmHistoryTimer()
- d.logStats()
-
- select {
- case node := <-nodesCh:
- if err := d.checkDial(node); err != nil {
- d.log.Trace("Discarding dial candidate", "id", node.ID(), "ip", node.IP(), "reason", err)
- } else {
- d.startDial(newDialTask(node, dynDialedConn))
- }
-
- case task := <-d.doneCh:
- id := task.dest.ID()
- delete(d.dialing, id)
- d.updateStaticPool(id)
- d.doneSinceLastLog++
-
- case c := <-d.addPeerCh:
- if c.is(dynDialedConn) || c.is(staticDialedConn) {
- d.dialPeers++
- }
- id := c.node.ID()
- d.peers[id] = struct{}{}
- // Remove from static pool because the node is now connected.
- task := d.static[id]
- if task != nil && task.staticPoolIndex >= 0 {
- d.removeFromStaticPool(task.staticPoolIndex)
- }
- // TODO: cancel dials to connected peers
-
- case c := <-d.remPeerCh:
- if c.is(dynDialedConn) || c.is(staticDialedConn) {
- d.dialPeers--
- }
- delete(d.peers, c.node.ID())
- d.updateStaticPool(c.node.ID())
-
- case node := <-d.addStaticCh:
- id := node.ID()
- _, exists := d.static[id]
- d.log.Trace("Adding static node", "id", id, "ip", node.IP(), "added", !exists)
- if exists {
- continue loop
- }
- task := newDialTask(node, staticDialedConn)
- d.static[id] = task
- if d.checkDial(node) == nil {
- d.addToStaticPool(task)
- }
-
- case node := <-d.remStaticCh:
- id := node.ID()
- task := d.static[id]
- d.log.Trace("Removing static node", "id", id, "ok", task != nil)
- if task != nil {
- delete(d.static, id)
- if task.staticPoolIndex >= 0 {
- d.removeFromStaticPool(task.staticPoolIndex)
- }
- }
-
- case <-d.historyTimer.C():
- d.expireHistory()
-
- case <-d.ctx.Done():
- it.Close()
- break loop
- }
- }
-
- d.historyTimer.Stop()
- for range d.dialing {
- <-d.doneCh
- }
- d.wg.Done()
-}
-
-// readNodes runs in its own goroutine and delivers nodes from
-// the input iterator to the nodesIn channel.
-func (d *dialScheduler) readNodes(it enode.Iterator) {
- defer d.wg.Done()
-
- for it.Next() {
- select {
- case d.nodesIn <- it.Node():
- case <-d.ctx.Done():
- }
- }
-}
-
-// logStats prints dialer statistics to the log. The message is suppressed when enough
-// peers are connected because users should only see it while their client is starting up
-// or comes back online.
-func (d *dialScheduler) logStats() {
- now := d.clock.Now()
- if d.lastStatsLog.Add(dialStatsLogInterval) > now {
- return
- }
- if d.dialPeers < dialStatsPeerLimit && d.dialPeers < d.maxDialPeers {
- d.log.Info("Looking for peers", "peercount", len(d.peers), "tried", d.doneSinceLastLog, "static", len(d.static))
- }
- d.doneSinceLastLog = 0
- d.lastStatsLog = now
-}
-
-// rearmHistoryTimer configures d.historyTimer to fire when the
-// next item in d.history expires.
-func (d *dialScheduler) rearmHistoryTimer() {
- if len(d.history) == 0 {
- return
- }
- d.historyTimer.Schedule(d.history.nextExpiry())
-}
-
-// expireHistory removes expired items from d.history.
-func (d *dialScheduler) expireHistory() {
- d.history.expire(d.clock.Now(), func(hkey string) {
- var id enode.ID
- copy(id[:], hkey)
- d.updateStaticPool(id)
- })
-}
-
-// freeDialSlots returns the number of free dial slots. The result can be negative
-// when peers are connected while their task is still running.
-func (d *dialScheduler) freeDialSlots() int {
- slots := (d.maxDialPeers - d.dialPeers) * 2
- if slots > d.maxActiveDials {
- slots = d.maxActiveDials
- }
- free := slots - len(d.dialing)
- return free
-}
-
-// checkDial returns an error if node n should not be dialed.
-func (d *dialScheduler) checkDial(n *enode.Node) error {
- if n.ID() == d.self {
- return errSelf
- }
- if n.IP() != nil && n.TCP() == 0 {
- // This check can trigger if a non-TCP node is found
- // by discovery. If there is no IP, the node is a static
- // node and the actual endpoint will be resolved later in dialTask.
- return errNoPort
- }
- if _, ok := d.dialing[n.ID()]; ok {
- return errAlreadyDialing
- }
- if _, ok := d.peers[n.ID()]; ok {
- return errAlreadyConnected
- }
- if d.netRestrict != nil && !d.netRestrict.Contains(n.IP()) {
- return errNetRestrict
- }
- if d.history.contains(string(n.ID().Bytes())) {
- return errRecentlyDialed
- }
- return nil
-}
-
-// startStaticDials starts n static dial tasks.
-func (d *dialScheduler) startStaticDials(n int) (started int) {
- for started = 0; started < n && len(d.staticPool) > 0; started++ {
- idx := d.rand.Intn(len(d.staticPool))
- task := d.staticPool[idx]
- d.startDial(task)
- d.removeFromStaticPool(idx)
- }
- return started
-}
-
-// updateStaticPool attempts to move the given static dial back into staticPool.
-func (d *dialScheduler) updateStaticPool(id enode.ID) {
- task, ok := d.static[id]
- if ok && task.staticPoolIndex < 0 && d.checkDial(task.dest) == nil {
- d.addToStaticPool(task)
- }
-}
-
-func (d *dialScheduler) addToStaticPool(task *dialTask) {
- if task.staticPoolIndex >= 0 {
- panic("attempt to add task to staticPool twice")
- }
- d.staticPool = append(d.staticPool, task)
- task.staticPoolIndex = len(d.staticPool) - 1
-}
-
-// removeFromStaticPool removes the task at idx from staticPool. It does that by moving the
-// current last element of the pool to idx and then shortening the pool by one.
-func (d *dialScheduler) removeFromStaticPool(idx int) {
- task := d.staticPool[idx]
- end := len(d.staticPool) - 1
- d.staticPool[idx] = d.staticPool[end]
- d.staticPool[idx].staticPoolIndex = idx
- d.staticPool[end] = nil
- d.staticPool = d.staticPool[:end]
- task.staticPoolIndex = -1
-}
-
-// startDial runs the given dial task in a separate goroutine.
-func (d *dialScheduler) startDial(task *dialTask) {
- d.log.Trace("Starting p2p dial", "id", task.dest.ID(), "ip", task.dest.IP(), "flag", task.flags)
- hkey := string(task.dest.ID().Bytes())
- d.history.add(hkey, d.clock.Now().Add(dialHistoryExpiration))
- d.dialing[task.dest.ID()] = task
- go func() {
- task.run(d)
- d.doneCh <- task
- }()
-}
-
-// A dialTask generated for each node that is dialed.
-type dialTask struct {
- staticPoolIndex int
- flags connFlag
- // These fields are private to the task and should not be
- // accessed by dialScheduler while the task is running.
- dest *enode.Node
- lastResolved mclock.AbsTime
- resolveDelay time.Duration
-}
-
-func newDialTask(dest *enode.Node, flags connFlag) *dialTask {
- return &dialTask{dest: dest, flags: flags, staticPoolIndex: -1}
-}
-
-type dialError struct {
- error
-}
-
-func (t *dialTask) run(d *dialScheduler) {
- if t.needResolve() && !t.resolve(d) {
- return
- }
-
- err := t.dial(d, t.dest)
- if err != nil {
- // For static nodes, resolve one more time if dialing fails.
- if _, ok := err.(*dialError); ok && t.flags&staticDialedConn != 0 {
- if t.resolve(d) {
- t.dial(d, t.dest)
- }
- }
- }
-}
-
-func (t *dialTask) needResolve() bool {
- return t.flags&staticDialedConn != 0 && t.dest.IP() == nil
-}
-
-// resolve attempts to find the current endpoint for the destination
-// using discovery.
-//
-// Resolve operations are throttled with backoff to avoid flooding the
-// discovery network with useless queries for nodes that don't exist.
-// The backoff delay resets when the node is found.
-func (t *dialTask) resolve(d *dialScheduler) bool {
- if d.resolver == nil {
- return false
- }
- if t.resolveDelay == 0 {
- t.resolveDelay = initialResolveDelay
- }
- if t.lastResolved > 0 && time.Duration(d.clock.Now()-t.lastResolved) < t.resolveDelay {
- return false
- }
- resolved := d.resolver.Resolve(t.dest)
- t.lastResolved = d.clock.Now()
- if resolved == nil {
- t.resolveDelay *= 2
- if t.resolveDelay > maxResolveDelay {
- t.resolveDelay = maxResolveDelay
- }
- d.log.Debug("Resolving node failed", "id", t.dest.ID(), "newdelay", t.resolveDelay)
- return false
- }
- // The node was found.
- t.resolveDelay = initialResolveDelay
- t.dest = resolved
- d.log.Debug("Resolved node", "id", t.dest.ID(), "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()})
- return true
-}
-
-// dial performs the actual connection attempt.
-func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error {
- dialMeter.Mark(1)
- fd, err := d.dialer.Dial(d.ctx, t.dest)
- if err != nil {
- d.log.Trace("Dial error", "id", t.dest.ID(), "addr", nodeAddr(t.dest), "conn", t.flags, "err", cleanupDialErr(err))
- dialConnectionError.Mark(1)
- return &dialError{err}
- }
- return d.setupFunc(newMeteredConn(fd), t.flags, dest)
-}
-
-func (t *dialTask) String() string {
- id := t.dest.ID()
- return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], t.dest.IP(), t.dest.TCP())
-}
-
-func cleanupDialErr(err error) error {
- if netErr, ok := err.(*net.OpError); ok && netErr.Op == "dial" {
- return netErr.Err
- }
- return err
-}
diff --git a/p2p/dial_test.go b/p2p/dial_test.go
deleted file mode 100644
index 13908f11ea..0000000000
--- a/p2p/dial_test.go
+++ /dev/null
@@ -1,672 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "context"
- "errors"
- "fmt"
- "math/rand"
- "net"
- "reflect"
- "sync"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/internal/testlog"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/netutil"
-)
-
-// This test checks that dynamic dials are launched from discovery results.
-func TestDialSchedDynDial(t *testing.T) {
- t.Parallel()
-
- config := dialConfig{
- maxActiveDials: 5,
- maxDialPeers: 4,
- }
- runDialTest(t, config, []dialTestRound{
- // 3 out of 4 peers are connected, leaving 2 dial slots.
- // 9 nodes are discovered, but only 2 are dialed.
- {
- peersAdded: []*conn{
- {flags: staticDialedConn, node: newNode(uintID(0x00), "")},
- {flags: dynDialedConn, node: newNode(uintID(0x01), "")},
- {flags: dynDialedConn, node: newNode(uintID(0x02), "")},
- },
- discovered: []*enode.Node{
- newNode(uintID(0x00), "127.0.0.1:30303"), // not dialed because already connected as static peer
- newNode(uintID(0x02), "127.0.0.1:30303"), // ...
- newNode(uintID(0x03), "127.0.0.1:30303"),
- newNode(uintID(0x04), "127.0.0.1:30303"),
- newNode(uintID(0x05), "127.0.0.1:30303"), // not dialed because there are only two slots
- newNode(uintID(0x06), "127.0.0.1:30303"), // ...
- newNode(uintID(0x07), "127.0.0.1:30303"), // ...
- newNode(uintID(0x08), "127.0.0.1:30303"), // ...
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x03), "127.0.0.1:30303"),
- newNode(uintID(0x04), "127.0.0.1:30303"),
- },
- },
-
- // One dial completes, freeing one dial slot.
- {
- failed: []enode.ID{
- uintID(0x04),
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x05), "127.0.0.1:30303"),
- },
- },
-
- // Dial to 0x03 completes, filling the last remaining peer slot.
- {
- succeeded: []enode.ID{
- uintID(0x03),
- },
- failed: []enode.ID{
- uintID(0x05),
- },
- discovered: []*enode.Node{
- newNode(uintID(0x09), "127.0.0.1:30303"), // not dialed because there are no free slots
- },
- },
-
- // 3 peers drop off, creating 6 dial slots. Check that 5 of those slots
- // (i.e. up to maxActiveDialTasks) are used.
- {
- peersRemoved: []enode.ID{
- uintID(0x00),
- uintID(0x01),
- uintID(0x02),
- },
- discovered: []*enode.Node{
- newNode(uintID(0x0a), "127.0.0.1:30303"),
- newNode(uintID(0x0b), "127.0.0.1:30303"),
- newNode(uintID(0x0c), "127.0.0.1:30303"),
- newNode(uintID(0x0d), "127.0.0.1:30303"),
- newNode(uintID(0x0f), "127.0.0.1:30303"),
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x06), "127.0.0.1:30303"),
- newNode(uintID(0x07), "127.0.0.1:30303"),
- newNode(uintID(0x08), "127.0.0.1:30303"),
- newNode(uintID(0x09), "127.0.0.1:30303"),
- newNode(uintID(0x0a), "127.0.0.1:30303"),
- },
- },
- })
-}
-
-// This test checks that candidates that do not match the netrestrict list are not dialed.
-func TestDialSchedNetRestrict(t *testing.T) {
- t.Parallel()
-
- nodes := []*enode.Node{
- newNode(uintID(0x01), "127.0.0.1:30303"),
- newNode(uintID(0x02), "127.0.0.2:30303"),
- newNode(uintID(0x03), "127.0.0.3:30303"),
- newNode(uintID(0x04), "127.0.0.4:30303"),
- newNode(uintID(0x05), "127.0.2.5:30303"),
- newNode(uintID(0x06), "127.0.2.6:30303"),
- newNode(uintID(0x07), "127.0.2.7:30303"),
- newNode(uintID(0x08), "127.0.2.8:30303"),
- }
- config := dialConfig{
- netRestrict: new(netutil.Netlist),
- maxActiveDials: 10,
- maxDialPeers: 10,
- }
- config.netRestrict.Add("127.0.2.0/24")
- runDialTest(t, config, []dialTestRound{
- {
- discovered: nodes,
- wantNewDials: nodes[4:8],
- },
- {
- succeeded: []enode.ID{
- nodes[4].ID(),
- nodes[5].ID(),
- nodes[6].ID(),
- nodes[7].ID(),
- },
- },
- })
-}
-
-// This test checks that static dials work and obey the limits.
-func TestDialSchedStaticDial(t *testing.T) {
- t.Parallel()
-
- config := dialConfig{
- maxActiveDials: 5,
- maxDialPeers: 4,
- }
- runDialTest(t, config, []dialTestRound{
- // Static dials are launched for the nodes that
- // aren't yet connected.
- {
- peersAdded: []*conn{
- {flags: dynDialedConn, node: newNode(uintID(0x01), "127.0.0.1:30303")},
- {flags: dynDialedConn, node: newNode(uintID(0x02), "127.0.0.2:30303")},
- },
- update: func(d *dialScheduler) {
- // These two are not dialed because they're already connected
- // as dynamic peers.
- d.addStatic(newNode(uintID(0x01), "127.0.0.1:30303"))
- d.addStatic(newNode(uintID(0x02), "127.0.0.2:30303"))
- // These nodes will be dialed:
- d.addStatic(newNode(uintID(0x03), "127.0.0.3:30303"))
- d.addStatic(newNode(uintID(0x04), "127.0.0.4:30303"))
- d.addStatic(newNode(uintID(0x05), "127.0.0.5:30303"))
- d.addStatic(newNode(uintID(0x06), "127.0.0.6:30303"))
- d.addStatic(newNode(uintID(0x07), "127.0.0.7:30303"))
- d.addStatic(newNode(uintID(0x08), "127.0.0.8:30303"))
- d.addStatic(newNode(uintID(0x09), "127.0.0.9:30303"))
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x03), "127.0.0.3:30303"),
- newNode(uintID(0x04), "127.0.0.4:30303"),
- newNode(uintID(0x05), "127.0.0.5:30303"),
- newNode(uintID(0x06), "127.0.0.6:30303"),
- },
- },
- // Dial to 0x03 completes, filling a peer slot. One slot remains,
- // two dials are launched to attempt to fill it.
- {
- succeeded: []enode.ID{
- uintID(0x03),
- },
- failed: []enode.ID{
- uintID(0x04),
- uintID(0x05),
- uintID(0x06),
- },
- wantResolves: map[enode.ID]*enode.Node{
- uintID(0x04): nil,
- uintID(0x05): nil,
- uintID(0x06): nil,
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x08), "127.0.0.8:30303"),
- newNode(uintID(0x09), "127.0.0.9:30303"),
- },
- },
- // Peer 0x01 drops and 0x07 connects as inbound peer.
- // Only 0x01 is dialed.
- {
- peersAdded: []*conn{
- {flags: inboundConn, node: newNode(uintID(0x07), "127.0.0.7:30303")},
- },
- peersRemoved: []enode.ID{
- uintID(0x01),
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x01), "127.0.0.1:30303"),
- },
- },
- })
-}
-
-// This test checks that removing static nodes stops connecting to them.
-func TestDialSchedRemoveStatic(t *testing.T) {
- t.Parallel()
-
- config := dialConfig{
- maxActiveDials: 1,
- maxDialPeers: 1,
- }
- runDialTest(t, config, []dialTestRound{
- // Add static nodes.
- {
- update: func(d *dialScheduler) {
- d.addStatic(newNode(uintID(0x01), "127.0.0.1:30303"))
- d.addStatic(newNode(uintID(0x02), "127.0.0.2:30303"))
- d.addStatic(newNode(uintID(0x03), "127.0.0.3:30303"))
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x01), "127.0.0.1:30303"),
- },
- },
- // Dial to 0x01 fails.
- {
- failed: []enode.ID{
- uintID(0x01),
- },
- wantResolves: map[enode.ID]*enode.Node{
- uintID(0x01): nil,
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x02), "127.0.0.2:30303"),
- },
- },
- // All static nodes are removed. 0x01 is in history, 0x02 is being
- // dialed, 0x03 is in staticPool.
- {
- update: func(d *dialScheduler) {
- d.removeStatic(newNode(uintID(0x01), "127.0.0.1:30303"))
- d.removeStatic(newNode(uintID(0x02), "127.0.0.2:30303"))
- d.removeStatic(newNode(uintID(0x03), "127.0.0.3:30303"))
- },
- failed: []enode.ID{
- uintID(0x02),
- },
- wantResolves: map[enode.ID]*enode.Node{
- uintID(0x02): nil,
- },
- },
- // Since all static nodes are removed, they should not be dialed again.
- {}, {}, {},
- })
-}
-
-// This test checks that static dials are selected at random.
-func TestDialSchedManyStaticNodes(t *testing.T) {
- t.Parallel()
-
- config := dialConfig{maxDialPeers: 2}
- runDialTest(t, config, []dialTestRound{
- {
- peersAdded: []*conn{
- {flags: dynDialedConn, node: newNode(uintID(0xFFFE), "")},
- {flags: dynDialedConn, node: newNode(uintID(0xFFFF), "")},
- },
- update: func(d *dialScheduler) {
- for id := uint16(0); id < 2000; id++ {
- n := newNode(uintID(id), "127.0.0.1:30303")
- d.addStatic(n)
- }
- },
- },
- {
- peersRemoved: []enode.ID{
- uintID(0xFFFE),
- uintID(0xFFFF),
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x0085), "127.0.0.1:30303"),
- newNode(uintID(0x02dc), "127.0.0.1:30303"),
- newNode(uintID(0x0285), "127.0.0.1:30303"),
- newNode(uintID(0x00cb), "127.0.0.1:30303"),
- },
- },
- })
-}
-
-// This test checks that past dials are not retried for some time.
-func TestDialSchedHistory(t *testing.T) {
- t.Parallel()
-
- config := dialConfig{
- maxActiveDials: 3,
- maxDialPeers: 3,
- }
- runDialTest(t, config, []dialTestRound{
- {
- update: func(d *dialScheduler) {
- d.addStatic(newNode(uintID(0x01), "127.0.0.1:30303"))
- d.addStatic(newNode(uintID(0x02), "127.0.0.2:30303"))
- d.addStatic(newNode(uintID(0x03), "127.0.0.3:30303"))
- },
- wantNewDials: []*enode.Node{
- newNode(uintID(0x01), "127.0.0.1:30303"),
- newNode(uintID(0x02), "127.0.0.2:30303"),
- newNode(uintID(0x03), "127.0.0.3:30303"),
- },
- },
- // No new tasks are launched in this round because all static
- // nodes are either connected or still being dialed.
- {
- succeeded: []enode.ID{
- uintID(0x01),
- uintID(0x02),
- },
- failed: []enode.ID{
- uintID(0x03),
- },
- wantResolves: map[enode.ID]*enode.Node{
- uintID(0x03): nil,
- },
- },
- // Nothing happens in this round because we're waiting for
- // node 0x3's history entry to expire.
- {},
- // The cache entry for node 0x03 has expired and is retried.
- {
- wantNewDials: []*enode.Node{
- newNode(uintID(0x03), "127.0.0.3:30303"),
- },
- },
- })
-}
-
-func TestDialSchedResolve(t *testing.T) {
- t.Parallel()
-
- config := dialConfig{
- maxActiveDials: 1,
- maxDialPeers: 1,
- }
- node := newNode(uintID(0x01), "")
- resolved := newNode(uintID(0x01), "127.0.0.1:30303")
- resolved2 := newNode(uintID(0x01), "127.0.0.55:30303")
- runDialTest(t, config, []dialTestRound{
- {
- update: func(d *dialScheduler) {
- d.addStatic(node)
- },
- wantResolves: map[enode.ID]*enode.Node{
- uintID(0x01): resolved,
- },
- wantNewDials: []*enode.Node{
- resolved,
- },
- },
- {
- failed: []enode.ID{
- uintID(0x01),
- },
- wantResolves: map[enode.ID]*enode.Node{
- uintID(0x01): resolved2,
- },
- wantNewDials: []*enode.Node{
- resolved2,
- },
- },
- })
-}
-
-// -------
-// Code below here is the framework for the tests above.
-
-type dialTestRound struct {
- peersAdded []*conn
- peersRemoved []enode.ID
- update func(*dialScheduler) // called at beginning of round
- discovered []*enode.Node // newly discovered nodes
- succeeded []enode.ID // dials which succeed this round
- failed []enode.ID // dials which fail this round
- wantResolves map[enode.ID]*enode.Node
- wantNewDials []*enode.Node // dials that should be launched in this round
-}
-
-func runDialTest(t *testing.T, config dialConfig, rounds []dialTestRound) {
- var (
- clock = new(mclock.Simulated)
- iterator = newDialTestIterator()
- dialer = newDialTestDialer()
- resolver = new(dialTestResolver)
- peers = make(map[enode.ID]*conn)
- setupCh = make(chan *conn)
- )
-
- // Override config.
- config.clock = clock
- config.dialer = dialer
- config.resolver = resolver
- config.log = testlog.Logger(t, log.LvlTrace)
- config.rand = rand.New(rand.NewSource(0x1111))
-
- // Set up the dialer. The setup function below runs on the dialTask
- // goroutine and adds the peer.
- var dialsched *dialScheduler
- setup := func(fd net.Conn, f connFlag, node *enode.Node) error {
- conn := &conn{flags: f, node: node}
- dialsched.peerAdded(conn)
- setupCh <- conn
- return nil
- }
- dialsched = newDialScheduler(config, iterator, setup)
- defer dialsched.stop()
-
- for i, round := range rounds {
- // Apply peer set updates.
- for _, c := range round.peersAdded {
- if peers[c.node.ID()] != nil {
- t.Fatalf("round %d: peer %v already connected", i, c.node.ID())
- }
- dialsched.peerAdded(c)
- peers[c.node.ID()] = c
- }
- for _, id := range round.peersRemoved {
- c := peers[id]
- if c == nil {
- t.Fatalf("round %d: can't remove non-existent peer %v", i, id)
- }
- dialsched.peerRemoved(c)
- }
-
- // Init round.
- t.Logf("round %d (%d peers)", i, len(peers))
- resolver.setAnswers(round.wantResolves)
- if round.update != nil {
- round.update(dialsched)
- }
- iterator.addNodes(round.discovered)
-
- // Unblock dialTask goroutines.
- if err := dialer.completeDials(round.succeeded, nil); err != nil {
- t.Fatalf("round %d: %v", i, err)
- }
- for range round.succeeded {
- conn := <-setupCh
- peers[conn.node.ID()] = conn
- }
- if err := dialer.completeDials(round.failed, errors.New("oops")); err != nil {
- t.Fatalf("round %d: %v", i, err)
- }
-
- // Wait for new tasks.
- if err := dialer.waitForDials(round.wantNewDials); err != nil {
- t.Fatalf("round %d: %v", i, err)
- }
- if !resolver.checkCalls() {
- t.Fatalf("unexpected calls to Resolve: %v", resolver.calls)
- }
-
- clock.Run(16 * time.Second)
- }
-}
-
-// dialTestIterator is the input iterator for dialer tests. This works a bit like a channel
-// with infinite buffer: nodes are added to the buffer with addNodes, which unblocks Next
-// and returns them from the iterator.
-type dialTestIterator struct {
- cur *enode.Node
-
- mu sync.Mutex
- buf []*enode.Node
- cond *sync.Cond
- closed bool
-}
-
-func newDialTestIterator() *dialTestIterator {
- it := &dialTestIterator{}
- it.cond = sync.NewCond(&it.mu)
- return it
-}
-
-// addNodes adds nodes to the iterator buffer and unblocks Next.
-func (it *dialTestIterator) addNodes(nodes []*enode.Node) {
- it.mu.Lock()
- defer it.mu.Unlock()
-
- it.buf = append(it.buf, nodes...)
- it.cond.Signal()
-}
-
-// Node returns the current node.
-func (it *dialTestIterator) Node() *enode.Node {
- return it.cur
-}
-
-// Next moves to the next node.
-func (it *dialTestIterator) Next() bool {
- it.mu.Lock()
- defer it.mu.Unlock()
-
- it.cur = nil
- for len(it.buf) == 0 && !it.closed {
- it.cond.Wait()
- }
- if it.closed {
- return false
- }
- it.cur = it.buf[0]
- copy(it.buf[:], it.buf[1:])
- it.buf = it.buf[:len(it.buf)-1]
- return true
-}
-
-// Close ends the iterator, unblocking Next.
-func (it *dialTestIterator) Close() {
- it.mu.Lock()
- defer it.mu.Unlock()
-
- it.closed = true
- it.buf = nil
- it.cond.Signal()
-}
-
-// dialTestDialer is the NodeDialer used by runDialTest.
-type dialTestDialer struct {
- init chan *dialTestReq
- blocked map[enode.ID]*dialTestReq
-}
-
-type dialTestReq struct {
- n *enode.Node
- unblock chan error
-}
-
-func newDialTestDialer() *dialTestDialer {
- return &dialTestDialer{
- init: make(chan *dialTestReq),
- blocked: make(map[enode.ID]*dialTestReq),
- }
-}
-
-// Dial implements NodeDialer.
-func (d *dialTestDialer) Dial(ctx context.Context, n *enode.Node) (net.Conn, error) {
- req := &dialTestReq{n: n, unblock: make(chan error, 1)}
- select {
- case d.init <- req:
- select {
- case err := <-req.unblock:
- pipe, _ := net.Pipe()
- return pipe, err
- case <-ctx.Done():
- return nil, ctx.Err()
- }
- case <-ctx.Done():
- return nil, ctx.Err()
- }
-}
-
-// waitForDials waits for calls to Dial with the given nodes as argument.
-// Those calls will be held blocking until completeDials is called with the same nodes.
-func (d *dialTestDialer) waitForDials(nodes []*enode.Node) error {
- waitset := make(map[enode.ID]*enode.Node, len(nodes))
- for _, n := range nodes {
- waitset[n.ID()] = n
- }
- timeout := time.NewTimer(1 * time.Second)
- defer timeout.Stop()
-
- for len(waitset) > 0 {
- select {
- case req := <-d.init:
- want, ok := waitset[req.n.ID()]
- if !ok {
- return fmt.Errorf("attempt to dial unexpected node %v", req.n.ID())
- }
- if !reflect.DeepEqual(req.n, want) {
- return fmt.Errorf("ENR of dialed node %v does not match test", req.n.ID())
- }
- delete(waitset, req.n.ID())
- d.blocked[req.n.ID()] = req
- case <-timeout.C:
- var waitlist []enode.ID
- for id := range waitset {
- waitlist = append(waitlist, id)
- }
- return fmt.Errorf("timed out waiting for dials to %v", waitlist)
- }
- }
-
- return d.checkUnexpectedDial()
-}
-
-func (d *dialTestDialer) checkUnexpectedDial() error {
- select {
- case req := <-d.init:
- return fmt.Errorf("attempt to dial unexpected node %v", req.n.ID())
- case <-time.After(150 * time.Millisecond):
- return nil
- }
-}
-
-// completeDials unblocks calls to Dial for the given nodes.
-func (d *dialTestDialer) completeDials(ids []enode.ID, err error) error {
- for _, id := range ids {
- req := d.blocked[id]
- if req == nil {
- return fmt.Errorf("can't complete dial to %v", id)
- }
- req.unblock <- err
- }
- return nil
-}
-
-// dialTestResolver tracks calls to resolve.
-type dialTestResolver struct {
- mu sync.Mutex
- calls []enode.ID
- answers map[enode.ID]*enode.Node
-}
-
-func (t *dialTestResolver) setAnswers(m map[enode.ID]*enode.Node) {
- t.mu.Lock()
- defer t.mu.Unlock()
-
- t.answers = m
- t.calls = nil
-}
-
-func (t *dialTestResolver) checkCalls() bool {
- t.mu.Lock()
- defer t.mu.Unlock()
-
- for _, id := range t.calls {
- if _, ok := t.answers[id]; !ok {
- return false
- }
- }
- return true
-}
-
-func (t *dialTestResolver) Resolve(n *enode.Node) *enode.Node {
- t.mu.Lock()
- defer t.mu.Unlock()
-
- t.calls = append(t.calls, n.ID())
- return t.answers[n.ID()]
-}
diff --git a/p2p/discover/common.go b/p2p/discover/common.go
deleted file mode 100644
index c9f0477def..0000000000
--- a/p2p/discover/common.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "crypto/ecdsa"
- "net"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/netutil"
-)
-
-// UDPConn is a network connection on which discovery can operate.
-type UDPConn interface {
- ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
- WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error)
- Close() error
- LocalAddr() net.Addr
-}
-
-// Config holds settings for the discovery listener.
-type Config struct {
- // These settings are required and configure the UDP listener:
- PrivateKey *ecdsa.PrivateKey
-
- // All remaining settings are optional.
-
- // Packet handling configuration:
- NetRestrict *netutil.Netlist // list of allowed IP networks
- Unhandled chan<- ReadPacket // unhandled packets are sent on this channel
-
- // 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
-
- // The options below are useful in very specific cases, like in unit tests.
- V5ProtocolID *[6]byte
- Log log.Logger // if set, log messages go here
- ValidSchemes enr.IdentityScheme // allowed identity schemes
- Clock mclock.Clock
-}
-
-func (cfg Config) withDefaults() Config {
- // Node table configuration:
- if cfg.PingInterval == 0 {
- cfg.PingInterval = 10 * time.Second
- }
- if cfg.RefreshInterval == 0 {
- cfg.RefreshInterval = 30 * time.Minute
- }
-
- // Debug/test settings:
- if cfg.Log == nil {
- cfg.Log = log.Root()
- }
- if cfg.ValidSchemes == nil {
- cfg.ValidSchemes = enode.ValidSchemes
- }
- if cfg.Clock == nil {
- cfg.Clock = mclock.System{}
- }
- return cfg
-}
-
-// ListenUDP starts listening for discovery packets on the given UDP socket.
-func ListenUDP(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
- return ListenV4(c, ln, cfg)
-}
-
-// ReadPacket is a packet that couldn't be handled. Those packets are sent to the unhandled
-// channel if configured.
-type ReadPacket struct {
- Data []byte
- Addr *net.UDPAddr
-}
-
-func min(x, y int) int {
- if x > y {
- return y
- }
- return x
-}
diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go
deleted file mode 100644
index b8d97b44e1..0000000000
--- a/p2p/discover/lookup.go
+++ /dev/null
@@ -1,227 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "context"
- "errors"
- "time"
-
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// lookup performs a network search for nodes close to the given target. It approaches the
-// target by querying nodes that are closer to it on each iteration. The given target does
-// not need to be an actual node identifier.
-type lookup struct {
- tab *Table
- queryfunc func(*node) ([]*node, error)
- replyCh chan []*node
- cancelCh <-chan struct{}
- asked, seen map[enode.ID]bool
- result nodesByDistance
- replyBuffer []*node
- queries int
-}
-
-type queryFunc func(*node) ([]*node, error)
-
-func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup {
- it := &lookup{
- tab: tab,
- queryfunc: q,
- asked: make(map[enode.ID]bool),
- seen: make(map[enode.ID]bool),
- result: nodesByDistance{target: target},
- replyCh: make(chan []*node, alpha),
- cancelCh: ctx.Done(),
- queries: -1,
- }
- // Don't query further if we hit ourself.
- // Unlikely to happen often in practice.
- it.asked[tab.self().ID()] = true
- return it
-}
-
-// run runs the lookup to completion and returns the closest nodes found.
-func (it *lookup) run() []*enode.Node {
- for it.advance() {
- }
- return unwrapNodes(it.result.entries)
-}
-
-// advance advances the lookup until any new nodes have been found.
-// It returns false when the lookup has ended.
-func (it *lookup) advance() bool {
- for it.startQueries() {
- select {
- case nodes := <-it.replyCh:
- it.replyBuffer = it.replyBuffer[:0]
- for _, n := range nodes {
- if n != nil && !it.seen[n.ID()] {
- it.seen[n.ID()] = true
- it.result.push(n, bucketSize)
- it.replyBuffer = append(it.replyBuffer, n)
- }
- }
- it.queries--
- if len(it.replyBuffer) > 0 {
- return true
- }
- case <-it.cancelCh:
- it.shutdown()
- }
- }
- return false
-}
-
-func (it *lookup) shutdown() {
- for it.queries > 0 {
- <-it.replyCh
- it.queries--
- }
- it.queryfunc = nil
- it.replyBuffer = nil
-}
-
-func (it *lookup) startQueries() bool {
- if it.queryfunc == nil {
- return false
- }
-
- // The first query returns nodes from the local table.
- if it.queries == -1 {
- closest := it.tab.findnodeByID(it.result.target, bucketSize, false)
- // Avoid finishing the lookup too quickly if table is empty. It'd be better to wait
- // for the table to fill in this case, but there is no good mechanism for that
- // yet.
- if len(closest.entries) == 0 {
- it.slowdown()
- }
- it.queries = 1
- it.replyCh <- closest.entries
- return true
- }
-
- // Ask the closest nodes that we haven't asked yet.
- for i := 0; i < len(it.result.entries) && it.queries < alpha; i++ {
- n := it.result.entries[i]
- if !it.asked[n.ID()] {
- it.asked[n.ID()] = true
- it.queries++
- go it.query(n, it.replyCh)
- }
- }
- // The lookup ends when no more nodes can be asked.
- return it.queries > 0
-}
-
-func (it *lookup) slowdown() {
- sleep := time.NewTimer(1 * time.Second)
- defer sleep.Stop()
- select {
- case <-sleep.C:
- case <-it.tab.closeReq:
- }
-}
-
-func (it *lookup) query(n *node, reply chan<- []*node) {
- fails := it.tab.db.FindFails(n.ID(), n.IP())
- r, err := it.queryfunc(n)
- if errors.Is(err, errClosed) {
- // Avoid recording failures on shutdown.
- reply <- nil
- return
- } else if len(r) == 0 {
- fails++
- it.tab.db.UpdateFindFails(n.ID(), n.IP(), fails)
- // Remove the node from the local table if it fails to return anything useful too
- // many times, but only if there are enough other nodes in the bucket.
- dropped := false
- if fails >= maxFindnodeFailures && it.tab.bucketLen(n.ID()) >= bucketSize/2 {
- dropped = true
- it.tab.delete(n)
- }
- it.tab.log.Trace("FINDNODE failed", "id", n.ID(), "failcount", fails, "dropped", dropped, "err", err)
- } else if fails > 0 {
- // Reset failure counter because it counts _consecutive_ failures.
- it.tab.db.UpdateFindFails(n.ID(), n.IP(), 0)
- }
-
- // Grab as many nodes as possible. Some of them might not be alive anymore, but we'll
- // just remove those again during revalidation.
- for _, n := range r {
- it.tab.addSeenNode(n)
- }
- reply <- r
-}
-
-// lookupIterator performs lookup operations and iterates over all seen nodes.
-// When a lookup finishes, a new one is created through nextLookup.
-type lookupIterator struct {
- buffer []*node
- nextLookup lookupFunc
- ctx context.Context
- cancel func()
- lookup *lookup
-}
-
-type lookupFunc func(ctx context.Context) *lookup
-
-func newLookupIterator(ctx context.Context, next lookupFunc) *lookupIterator {
- ctx, cancel := context.WithCancel(ctx)
- return &lookupIterator{ctx: ctx, cancel: cancel, nextLookup: next}
-}
-
-// Node returns the current node.
-func (it *lookupIterator) Node() *enode.Node {
- if len(it.buffer) == 0 {
- return nil
- }
- return unwrapNode(it.buffer[0])
-}
-
-// Next moves to the next node.
-func (it *lookupIterator) Next() bool {
- // Consume next node in buffer.
- if len(it.buffer) > 0 {
- it.buffer = it.buffer[1:]
- }
- // Advance the lookup to refill the buffer.
- for len(it.buffer) == 0 {
- if it.ctx.Err() != nil {
- it.lookup = nil
- it.buffer = nil
- return false
- }
- if it.lookup == nil {
- it.lookup = it.nextLookup(it.ctx)
- continue
- }
- if !it.lookup.advance() {
- it.lookup = nil
- continue
- }
- it.buffer = it.lookup.replyBuffer
- }
- return true
-}
-
-// Close ends the iterator.
-func (it *lookupIterator) Close() {
- it.cancel()
-}
diff --git a/p2p/discover/metrics.go b/p2p/discover/metrics.go
deleted file mode 100644
index da8e9cb817..0000000000
--- a/p2p/discover/metrics.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "fmt"
- "net"
-
- "github.com/ethereum/go-ethereum/metrics"
-)
-
-const (
- moduleName = "discover"
- // ingressMeterName is the prefix of the per-packet inbound metrics.
- ingressMeterName = moduleName + "/ingress"
-
- // egressMeterName is the prefix of the per-packet outbound metrics.
- egressMeterName = moduleName + "/egress"
-)
-
-var (
- bucketsCounter []metrics.Counter
- ingressTrafficMeter = metrics.NewRegisteredMeter(ingressMeterName, nil)
- egressTrafficMeter = metrics.NewRegisteredMeter(egressMeterName, nil)
-)
-
-func init() {
- for i := 0; i < nBuckets; i++ {
- bucketsCounter = append(bucketsCounter, metrics.NewRegisteredCounter(fmt.Sprintf("%s/bucket/%d/count", moduleName, i), nil))
- }
-}
-
-// meteredConn is a wrapper around a net.UDPConn that meters both the
-// inbound and outbound network traffic.
-type meteredUdpConn struct {
- UDPConn
-}
-
-func newMeteredConn(conn UDPConn) UDPConn {
- // Short circuit if metrics are disabled
- if !metrics.Enabled {
- return conn
- }
- return &meteredUdpConn{UDPConn: conn}
-}
-
-// Read delegates a network read to the underlying connection, bumping the udp ingress traffic meter along the way.
-func (c *meteredUdpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
- n, addr, err = c.UDPConn.ReadFromUDP(b)
- ingressTrafficMeter.Mark(int64(n))
- return n, addr, err
-}
-
-// Write delegates a network write to the underlying connection, bumping the udp egress traffic meter along the way.
-func (c *meteredUdpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
- n, err = c.UDPConn.WriteToUDP(b, addr)
- egressTrafficMeter.Mark(int64(n))
- return n, err
-}
diff --git a/p2p/discover/node.go b/p2p/discover/node.go
deleted file mode 100644
index 9ffe101ccf..0000000000
--- a/p2p/discover/node.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "crypto/ecdsa"
- "crypto/elliptic"
- "errors"
- "math/big"
- "net"
- "time"
-
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// node represents a host on the network.
-// The fields of Node may not be modified.
-type node struct {
- enode.Node
- addedAt time.Time // time when the node was added to the table
- livenessChecks uint // how often liveness was checked
-}
-
-type encPubkey [64]byte
-
-func encodePubkey(key *ecdsa.PublicKey) encPubkey {
- var e encPubkey
- math.ReadBits(key.X, e[:len(e)/2])
- math.ReadBits(key.Y, e[len(e)/2:])
- return e
-}
-
-func decodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) {
- if len(e) != len(encPubkey{}) {
- return nil, errors.New("wrong size public key data")
- }
- p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}
- half := len(e) / 2
- p.X.SetBytes(e[:half])
- p.Y.SetBytes(e[half:])
- if !p.Curve.IsOnCurve(p.X, p.Y) {
- return nil, errors.New("invalid curve point")
- }
- return p, nil
-}
-
-func (e encPubkey) id() enode.ID {
- return enode.ID(crypto.Keccak256Hash(e[:]))
-}
-
-func wrapNode(n *enode.Node) *node {
- return &node{Node: *n}
-}
-
-func wrapNodes(ns []*enode.Node) []*node {
- result := make([]*node, len(ns))
- for i, n := range ns {
- result[i] = wrapNode(n)
- }
- return result
-}
-
-func unwrapNode(n *node) *enode.Node {
- return &n.Node
-}
-
-func unwrapNodes(ns []*node) []*enode.Node {
- result := make([]*enode.Node, len(ns))
- for i, n := range ns {
- result[i] = unwrapNode(n)
- }
- return result
-}
-
-func (n *node) addr() *net.UDPAddr {
- return &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
-}
-
-func (n *node) String() string {
- return n.Node.String()
-}
diff --git a/p2p/discover/ntp.go b/p2p/discover/ntp.go
deleted file mode 100644
index 3f9157808f..0000000000
--- a/p2p/discover/ntp.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Contains the NTP time drift detection via the SNTP protocol:
-// https://tools.ietf.org/html/rfc4330
-
-package discover
-
-import (
- "fmt"
- "net"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "golang.org/x/exp/slices"
-)
-
-const (
- ntpPool = "pool.ntp.org" // ntpPool is the NTP server to query for the current time
- ntpChecks = 3 // Number of measurements to do against the NTP server
-)
-
-// checkClockDrift queries an NTP server for clock drifts and warns the user if
-// one large enough is detected.
-func checkClockDrift() {
- drift, err := sntpDrift(ntpChecks)
- if err != nil {
- return
- }
- if drift < -driftThreshold || drift > driftThreshold {
- log.Warn(fmt.Sprintf("System clock seems off by %v, which can prevent network connectivity", drift))
- log.Warn("Please enable network time synchronisation in system settings.")
- } else {
- log.Debug("NTP sanity check done", "drift", drift)
- }
-}
-
-// sntpDrift does a naive time resolution against an NTP server and returns the
-// measured drift. This method uses the simple version of NTP. It's not precise
-// but should be fine for these purposes.
-//
-// Note, it executes two extra measurements compared to the number of requested
-// ones to be able to discard the two extremes as outliers.
-func sntpDrift(measurements int) (time.Duration, error) {
- // Resolve the address of the NTP server
- addr, err := net.ResolveUDPAddr("udp", ntpPool+":123")
- if err != nil {
- return 0, err
- }
- // Construct the time request (empty package with only 2 fields set):
- // Bits 3-5: Protocol version, 3
- // Bits 6-8: Mode of operation, client, 3
- request := make([]byte, 48)
- request[0] = 3<<3 | 3
-
- // Execute each of the measurements
- drifts := []time.Duration{}
- for i := 0; i < measurements+2; i++ {
- // Dial the NTP server and send the time retrieval request
- conn, err := net.DialUDP("udp", nil, addr)
- if err != nil {
- return 0, err
- }
- defer conn.Close()
-
- sent := time.Now()
- if _, err = conn.Write(request); err != nil {
- return 0, err
- }
- // Retrieve the reply and calculate the elapsed time
- conn.SetDeadline(time.Now().Add(5 * time.Second))
-
- reply := make([]byte, 48)
- if _, err = conn.Read(reply); err != nil {
- return 0, err
- }
- elapsed := time.Since(sent)
-
- // Reconstruct the time from the reply data
- sec := uint64(reply[43]) | uint64(reply[42])<<8 | uint64(reply[41])<<16 | uint64(reply[40])<<24
- frac := uint64(reply[47]) | uint64(reply[46])<<8 | uint64(reply[45])<<16 | uint64(reply[44])<<24
-
- nanosec := sec*1e9 + (frac*1e9)>>32
-
- t := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nanosec)).Local()
-
- // Calculate the drift based on an assumed answer time of RRT/2
- drifts = append(drifts, sent.Sub(t)+elapsed/2)
- }
- // Calculate average drift (drop two extremities to avoid outliers)
- slices.Sort(drifts)
-
- drift := time.Duration(0)
- for i := 1; i < len(drifts)-1; i++ {
- drift += drifts[i]
- }
- return drift / time.Duration(measurements), nil
-}
diff --git a/p2p/discover/table.go b/p2p/discover/table.go
deleted file mode 100644
index 2b7a28708b..0000000000
--- a/p2p/discover/table.go
+++ /dev/null
@@ -1,754 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package discover implements the Node Discovery Protocol.
-//
-// The Node Discovery protocol provides a way to find RLPx nodes that
-// can be connected to. It uses a Kademlia-like protocol to maintain a
-// distributed database of the IDs and endpoints of all listening
-// nodes.
-package discover
-
-import (
- "context"
- crand "crypto/rand"
- "encoding/binary"
- "fmt"
- mrand "math/rand"
- "net"
- "sort"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/netutil"
-)
-
-const (
- alpha = 3 // Kademlia concurrency factor
- bucketSize = 16 // Kademlia bucket size
- maxReplacements = 10 // Size of per-bucket replacement list
-
- // We keep buckets for the upper 1/15 of distances because
- // it's very unlikely we'll ever encounter a node that's closer.
- hashBits = len(common.Hash{}) * 8
- nBuckets = hashBits / 15 // Number of buckets
- bucketMinDistance = hashBits - nBuckets // Log distance of closest bucket
-
- // IP address limits.
- bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
- tableIPLimit, tableSubnet = 10, 24
-
- copyNodesInterval = 30 * time.Second
- seedMinTableTime = 5 * time.Minute
- seedCount = 30
- seedMaxAge = 5 * 24 * time.Hour
-)
-
-// Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps
-// itself up-to-date by verifying the liveness of neighbors and requesting their node
-// records when announcements of a new record version are received.
-type Table struct {
- mutex sync.Mutex // protects buckets, bucket content, nursery, rand
- buckets [nBuckets]*bucket // index of known nodes by distance
- nursery []*node // bootstrap nodes
- rand *mrand.Rand // source of randomness, periodically reseeded
- ips netutil.DistinctNetSet
-
- db *enode.DB // database of known nodes
- net transport
- cfg Config
- log log.Logger
-
- // loop channels
- refreshReq chan chan struct{}
- initDone chan struct{}
- closeReq chan struct{}
- closed chan struct{}
-
- nodeAddedHook func(*bucket, *node)
- nodeRemovedHook func(*bucket, *node)
-}
-
-// transport is implemented by the UDP transports.
-type transport interface {
- Self() *enode.Node
- RequestENR(*enode.Node) (*enode.Node, error)
- lookupRandom() []*enode.Node
- lookupSelf() []*enode.Node
- ping(*enode.Node) (seq uint64, err error)
-}
-
-// bucket contains nodes, ordered by their last activity. the entry
-// that was most recently active is the first element in entries.
-type bucket struct {
- entries []*node // live entries, sorted by time of last contact
- replacements []*node // recently seen nodes to be used if revalidation fails
- ips netutil.DistinctNetSet
- index int
-}
-
-func newTable(t transport, db *enode.DB, cfg Config) (*Table, error) {
- cfg = cfg.withDefaults()
- tab := &Table{
- net: t,
- db: db,
- cfg: cfg,
- log: cfg.Log,
- refreshReq: make(chan chan struct{}),
- initDone: make(chan struct{}),
- closeReq: make(chan struct{}),
- closed: make(chan struct{}),
- rand: mrand.New(mrand.NewSource(0)),
- ips: netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit},
- }
- if err := tab.setFallbackNodes(cfg.Bootnodes); err != nil {
- return nil, err
- }
- for i := range tab.buckets {
- tab.buckets[i] = &bucket{
- index: i,
- ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
- }
- }
- tab.seedRand()
- tab.loadSeedNodes()
-
- return tab, nil
-}
-
-func newMeteredTable(t transport, db *enode.DB, cfg Config) (*Table, error) {
- tab, err := newTable(t, db, cfg)
- if err != nil {
- return nil, err
- }
- if metrics.Enabled {
- tab.nodeAddedHook = func(b *bucket, n *node) {
- bucketsCounter[b.index].Inc(1)
- }
- tab.nodeRemovedHook = func(b *bucket, n *node) {
- bucketsCounter[b.index].Dec(1)
- }
- }
- return tab, nil
-}
-
-// Nodes returns all nodes contained in the table.
-func (tab *Table) Nodes() []*enode.Node {
- if !tab.isInitDone() {
- return nil
- }
-
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- var nodes []*enode.Node
- for _, b := range &tab.buckets {
- for _, n := range b.entries {
- nodes = append(nodes, unwrapNode(n))
- }
- }
- return nodes
-}
-
-func (tab *Table) self() *enode.Node {
- return tab.net.Self()
-}
-
-func (tab *Table) seedRand() {
- var b [8]byte
- crand.Read(b[:])
-
- tab.mutex.Lock()
- tab.rand.Seed(int64(binary.BigEndian.Uint64(b[:])))
- tab.mutex.Unlock()
-}
-
-// getNode returns the node with the given ID or nil if it isn't in the table.
-func (tab *Table) getNode(id enode.ID) *enode.Node {
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- b := tab.bucket(id)
- for _, e := range b.entries {
- if e.ID() == id {
- return unwrapNode(e)
- }
- }
- return nil
-}
-
-// close terminates the network listener and flushes the node database.
-func (tab *Table) close() {
- close(tab.closeReq)
- <-tab.closed
-}
-
-// setFallbackNodes sets the initial points of contact. These nodes
-// are used to connect to the network if the table is empty and there
-// are no known nodes in the database.
-func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
- nursery := make([]*node, 0, len(nodes))
- for _, n := range nodes {
- if err := n.ValidateComplete(); err != nil {
- return fmt.Errorf("bad bootstrap node %q: %v", n, err)
- }
- if tab.cfg.NetRestrict != nil && !tab.cfg.NetRestrict.Contains(n.IP()) {
- tab.log.Error("Bootstrap node filtered by netrestrict", "id", n.ID(), "ip", n.IP())
- continue
- }
- nursery = append(nursery, wrapNode(n))
- }
- tab.nursery = nursery
- return nil
-}
-
-// isInitDone returns whether the table's initial seeding procedure has completed.
-func (tab *Table) isInitDone() bool {
- select {
- case <-tab.initDone:
- return true
- default:
- return false
- }
-}
-
-func (tab *Table) refresh() <-chan struct{} {
- done := make(chan struct{})
- select {
- case tab.refreshReq <- done:
- case <-tab.closeReq:
- close(done)
- }
- return done
-}
-
-// loop schedules runs of doRefresh, doRevalidate and copyLiveNodes.
-func (tab *Table) loop() {
- var (
- revalidate = time.NewTimer(tab.nextRevalidateTime())
- refresh = time.NewTimer(tab.nextRefreshTime())
- copyNodes = time.NewTicker(copyNodesInterval)
- refreshDone = make(chan struct{}) // where doRefresh reports completion
- revalidateDone chan struct{} // where doRevalidate reports completion
- waiting = []chan struct{}{tab.initDone} // holds waiting callers while doRefresh runs
- )
- defer refresh.Stop()
- defer revalidate.Stop()
- defer copyNodes.Stop()
-
- // Start initial refresh.
- go tab.doRefresh(refreshDone)
-
-loop:
- for {
- select {
- case <-refresh.C:
- tab.seedRand()
- if refreshDone == nil {
- refreshDone = make(chan struct{})
- go tab.doRefresh(refreshDone)
- }
- case req := <-tab.refreshReq:
- waiting = append(waiting, req)
- if refreshDone == nil {
- refreshDone = make(chan struct{})
- go tab.doRefresh(refreshDone)
- }
- case <-refreshDone:
- for _, ch := range waiting {
- close(ch)
- }
- waiting, refreshDone = nil, nil
- refresh.Reset(tab.nextRefreshTime())
- case <-revalidate.C:
- revalidateDone = make(chan struct{})
- go tab.doRevalidate(revalidateDone)
- case <-revalidateDone:
- revalidate.Reset(tab.nextRevalidateTime())
- revalidateDone = nil
- case <-copyNodes.C:
- go tab.copyLiveNodes()
- case <-tab.closeReq:
- break loop
- }
- }
-
- if refreshDone != nil {
- <-refreshDone
- }
- for _, ch := range waiting {
- close(ch)
- }
- if revalidateDone != nil {
- <-revalidateDone
- }
- close(tab.closed)
-}
-
-// doRefresh performs a lookup for a random target to keep buckets full. seed nodes are
-// inserted if the table is empty (initial bootstrap or discarded faulty peers).
-func (tab *Table) doRefresh(done chan struct{}) {
- defer close(done)
-
- // Load nodes from the database and insert
- // them. This should yield a few previously seen nodes that are
- // (hopefully) still alive.
- tab.loadSeedNodes()
-
- // Run self lookup to discover new neighbor nodes.
- tab.net.lookupSelf()
-
- // The Kademlia paper specifies that the bucket refresh should
- // perform a lookup in the least recently used bucket. We cannot
- // adhere to this because the findnode target is a 512bit value
- // (not hash-sized) and it is not easily possible to generate a
- // sha3 preimage that falls into a chosen bucket.
- // We perform a few lookups with a random target instead.
- for i := 0; i < 3; i++ {
- tab.net.lookupRandom()
- }
-}
-
-func (tab *Table) loadSeedNodes() {
- seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge))
- seeds = append(seeds, tab.nursery...)
- for i := range seeds {
- seed := seeds[i]
- if tab.log.Enabled(context.Background(), log.LevelTrace) {
- age := time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP()))
- tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
- }
- tab.addSeenNode(seed)
- }
-}
-
-// doRevalidate checks that the last node in a random bucket is still live and replaces or
-// deletes the node if it isn't.
-func (tab *Table) doRevalidate(done chan<- struct{}) {
- defer func() { done <- struct{}{} }()
-
- last, bi := tab.nodeToRevalidate()
- if last == nil {
- // No non-empty bucket found.
- return
- }
-
- // Ping the selected node and wait for a pong.
- remoteSeq, err := tab.net.ping(unwrapNode(last))
-
- // Also fetch record if the node replied and returned a higher sequence number.
- if last.Seq() < remoteSeq {
- n, err := tab.net.RequestENR(unwrapNode(last))
- if err != nil {
- tab.log.Debug("ENR request failed", "id", last.ID(), "addr", last.addr(), "err", err)
- } else {
- last = &node{Node: *n, addedAt: last.addedAt, livenessChecks: last.livenessChecks}
- }
- }
-
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
- b := tab.buckets[bi]
- if err == nil {
- // The node responded, move it to the front.
- last.livenessChecks++
- tab.log.Debug("Revalidated node", "b", bi, "id", last.ID(), "checks", last.livenessChecks)
- tab.bumpInBucket(b, last)
- return
- }
- // No reply received, pick a replacement or delete the node if there aren't
- // any replacements.
- if r := tab.replace(b, last); r != nil {
- tab.log.Debug("Replaced dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks, "r", r.ID(), "rip", r.IP())
- } else {
- tab.log.Debug("Removed dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks)
- }
-}
-
-// nodeToRevalidate returns the last node in a random, non-empty bucket.
-func (tab *Table) nodeToRevalidate() (n *node, bi int) {
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- for _, bi = range tab.rand.Perm(len(tab.buckets)) {
- b := tab.buckets[bi]
- if len(b.entries) > 0 {
- last := b.entries[len(b.entries)-1]
- return last, bi
- }
- }
- return nil, 0
-}
-
-func (tab *Table) nextRevalidateTime() time.Duration {
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- return time.Duration(tab.rand.Int63n(int64(tab.cfg.PingInterval)))
-}
-
-func (tab *Table) nextRefreshTime() time.Duration {
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- half := tab.cfg.RefreshInterval / 2
- return half + time.Duration(tab.rand.Int63n(int64(half)))
-}
-
-// copyLiveNodes adds nodes from the table to the database if they have been in the table
-// longer than seedMinTableTime.
-func (tab *Table) copyLiveNodes() {
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- now := time.Now()
- for _, b := range &tab.buckets {
- for _, n := range b.entries {
- if n.livenessChecks > 0 && now.Sub(n.addedAt) >= seedMinTableTime {
- tab.db.UpdateNode(unwrapNode(n))
- }
- }
- }
-}
-
-// findnodeByID returns the n nodes in the table that are closest to the given id.
-// This is used by the FINDNODE/v4 handler.
-//
-// The preferLive parameter says whether the caller wants liveness-checked results. If
-// preferLive is true and the table contains any verified nodes, the result will not
-// contain unverified nodes. However, if there are no verified nodes at all, the result
-// will contain unverified nodes.
-func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *nodesByDistance {
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- // Scan all buckets. There might be a better way to do this, but there aren't that many
- // buckets, so this solution should be fine. The worst-case complexity of this loop
- // is O(tab.len() * nresults).
- nodes := &nodesByDistance{target: target}
- liveNodes := &nodesByDistance{target: target}
- for _, b := range &tab.buckets {
- for _, n := range b.entries {
- nodes.push(n, nresults)
- if preferLive && n.livenessChecks > 0 {
- liveNodes.push(n, nresults)
- }
- }
- }
-
- if preferLive && len(liveNodes.entries) > 0 {
- return liveNodes
- }
- return nodes
-}
-
-// appendLiveNodes adds nodes at the given distance to the result slice.
-func (tab *Table) appendLiveNodes(dist uint, result []*enode.Node) []*enode.Node {
- if dist > 256 {
- return result
- }
- if dist == 0 {
- return append(result, tab.self())
- }
-
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
- for _, n := range tab.bucketAtDistance(int(dist)).entries {
- if n.livenessChecks >= 1 {
- node := n.Node // avoid handing out pointer to struct field
- result = append(result, &node)
- }
- }
- return result
-}
-
-// len returns the number of nodes in the table.
-func (tab *Table) len() (n int) {
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- for _, b := range &tab.buckets {
- n += len(b.entries)
- }
- return n
-}
-
-// bucketLen returns the number of nodes in the bucket for the given ID.
-func (tab *Table) bucketLen(id enode.ID) int {
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- return len(tab.bucket(id).entries)
-}
-
-// bucket returns the bucket for the given node ID hash.
-func (tab *Table) bucket(id enode.ID) *bucket {
- d := enode.LogDist(tab.self().ID(), id)
- return tab.bucketAtDistance(d)
-}
-
-func (tab *Table) bucketAtDistance(d int) *bucket {
- if d <= bucketMinDistance {
- return tab.buckets[0]
- }
- return tab.buckets[d-bucketMinDistance-1]
-}
-
-// addSeenNode adds a node which may or may not be live to the end of a bucket. If the
-// bucket has space available, adding the node succeeds immediately. Otherwise, the node is
-// added to the replacements list.
-//
-// The caller must not hold tab.mutex.
-func (tab *Table) addSeenNode(n *node) {
- if n.ID() == tab.self().ID() {
- return
- }
-
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
- b := tab.bucket(n.ID())
- if contains(b.entries, n.ID()) {
- // Already in bucket, don't add.
- return
- }
- if len(b.entries) >= bucketSize {
- // Bucket full, maybe add as replacement.
- tab.addReplacement(b, n)
- return
- }
- if !tab.addIP(b, n.IP()) {
- // Can't add: IP limit reached.
- return
- }
-
- // Add to end of bucket:
- b.entries = append(b.entries, n)
- b.replacements = deleteNode(b.replacements, n)
- n.addedAt = time.Now()
-
- if tab.nodeAddedHook != nil {
- tab.nodeAddedHook(b, n)
- }
-}
-
-// addVerifiedNode adds a node whose existence has been verified recently to the front of a
-// bucket. If the node is already in the bucket, it is moved to the front. If the bucket
-// has no space, the node is added to the replacements list.
-//
-// There is an additional safety measure: if the table is still initializing the node
-// is not added. This prevents an attack where the table could be filled by just sending
-// ping repeatedly.
-//
-// The caller must not hold tab.mutex.
-func (tab *Table) addVerifiedNode(n *node) {
- if !tab.isInitDone() {
- return
- }
- if n.ID() == tab.self().ID() {
- return
- }
-
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
- b := tab.bucket(n.ID())
- if tab.bumpInBucket(b, n) {
- // Already in bucket, moved to front.
- return
- }
- if len(b.entries) >= bucketSize {
- // Bucket full, maybe add as replacement.
- tab.addReplacement(b, n)
- return
- }
- if !tab.addIP(b, n.IP()) {
- // Can't add: IP limit reached.
- return
- }
-
- // Add to front of bucket.
- b.entries, _ = pushNode(b.entries, n, bucketSize)
- b.replacements = deleteNode(b.replacements, n)
- n.addedAt = time.Now()
-
- if tab.nodeAddedHook != nil {
- tab.nodeAddedHook(b, n)
- }
-}
-
-// delete removes an entry from the node table. It is used to evacuate dead nodes.
-func (tab *Table) delete(node *node) {
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
-
- tab.deleteInBucket(tab.bucket(node.ID()), node)
-}
-
-func (tab *Table) addIP(b *bucket, ip net.IP) bool {
- if len(ip) == 0 {
- return false // Nodes without IP cannot be added.
- }
- if netutil.IsLAN(ip) {
- return true
- }
- if !tab.ips.Add(ip) {
- tab.log.Debug("IP exceeds table limit", "ip", ip)
- return false
- }
- if !b.ips.Add(ip) {
- tab.log.Debug("IP exceeds bucket limit", "ip", ip)
- tab.ips.Remove(ip)
- return false
- }
- return true
-}
-
-func (tab *Table) removeIP(b *bucket, ip net.IP) {
- if netutil.IsLAN(ip) {
- return
- }
- tab.ips.Remove(ip)
- b.ips.Remove(ip)
-}
-
-func (tab *Table) addReplacement(b *bucket, n *node) {
- for _, e := range b.replacements {
- if e.ID() == n.ID() {
- return // already in list
- }
- }
- if !tab.addIP(b, n.IP()) {
- return
- }
- var removed *node
- b.replacements, removed = pushNode(b.replacements, n, maxReplacements)
- if removed != nil {
- tab.removeIP(b, removed.IP())
- }
-}
-
-// replace removes n from the replacement list and replaces 'last' with it if it is the
-// last entry in the bucket. If 'last' isn't the last entry, it has either been replaced
-// with someone else or became active.
-func (tab *Table) replace(b *bucket, last *node) *node {
- if len(b.entries) == 0 || b.entries[len(b.entries)-1].ID() != last.ID() {
- // Entry has moved, don't replace it.
- return nil
- }
- // Still the last entry.
- if len(b.replacements) == 0 {
- tab.deleteInBucket(b, last)
- return nil
- }
- r := b.replacements[tab.rand.Intn(len(b.replacements))]
- b.replacements = deleteNode(b.replacements, r)
- b.entries[len(b.entries)-1] = r
- tab.removeIP(b, last.IP())
- return r
-}
-
-// bumpInBucket moves the given node to the front of the bucket entry list
-// if it is contained in that list.
-func (tab *Table) bumpInBucket(b *bucket, n *node) bool {
- for i := range b.entries {
- if b.entries[i].ID() == n.ID() {
- if !n.IP().Equal(b.entries[i].IP()) {
- // Endpoint has changed, ensure that the new IP fits into table limits.
- tab.removeIP(b, b.entries[i].IP())
- if !tab.addIP(b, n.IP()) {
- // It doesn't, put the previous one back.
- tab.addIP(b, b.entries[i].IP())
- return false
- }
- }
- // Move it to the front.
- copy(b.entries[1:], b.entries[:i])
- b.entries[0] = n
- return true
- }
- }
- return false
-}
-
-func (tab *Table) deleteInBucket(b *bucket, n *node) {
- // Check if the node is actually in the bucket so the removed hook
- // isn't called multiple times for the same node.
- if !contains(b.entries, n.ID()) {
- return
- }
- b.entries = deleteNode(b.entries, n)
- tab.removeIP(b, n.IP())
- if tab.nodeRemovedHook != nil {
- tab.nodeRemovedHook(b, n)
- }
-}
-
-func contains(ns []*node, id enode.ID) bool {
- for _, n := range ns {
- if n.ID() == id {
- return true
- }
- }
- return false
-}
-
-// pushNode adds n to the front of list, keeping at most max items.
-func pushNode(list []*node, n *node, max int) ([]*node, *node) {
- if len(list) < max {
- list = append(list, nil)
- }
- removed := list[len(list)-1]
- copy(list[1:], list)
- list[0] = n
- return list, removed
-}
-
-// deleteNode removes n from list.
-func deleteNode(list []*node, n *node) []*node {
- for i := range list {
- if list[i].ID() == n.ID() {
- return append(list[:i], list[i+1:]...)
- }
- }
- return list
-}
-
-// nodesByDistance is a list of nodes, ordered by distance to target.
-type nodesByDistance struct {
- entries []*node
- target enode.ID
-}
-
-// push adds the given node to the list, keeping the total size below maxElems.
-func (h *nodesByDistance) push(n *node, maxElems int) {
- ix := sort.Search(len(h.entries), func(i int) bool {
- return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
- })
-
- end := len(h.entries)
- if len(h.entries) < maxElems {
- h.entries = append(h.entries, n)
- }
- if ix < end {
- // Slide existing entries down to make room.
- // This will overwrite the entry we just appended.
- copy(h.entries[ix+1:], h.entries[ix:])
- h.entries[ix] = n
- }
-}
diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go
deleted file mode 100644
index 3ba3422251..0000000000
--- a/p2p/discover/table_test.go
+++ /dev/null
@@ -1,446 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "crypto/ecdsa"
- "fmt"
- "math/rand"
-
- "net"
- "reflect"
- "testing"
- "testing/quick"
- "time"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/netutil"
-)
-
-func TestTable_pingReplace(t *testing.T) {
- run := func(newNodeResponding, lastInBucketResponding bool) {
- name := fmt.Sprintf("newNodeResponding=%t/lastInBucketResponding=%t", newNodeResponding, lastInBucketResponding)
- t.Run(name, func(t *testing.T) {
- t.Parallel()
- testPingReplace(t, newNodeResponding, lastInBucketResponding)
- })
- }
-
- run(true, true)
- run(false, true)
- run(true, false)
- run(false, false)
-}
-
-func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) {
- transport := newPingRecorder()
- tab, db := newTestTable(transport)
- defer db.Close()
- defer tab.close()
-
- <-tab.initDone
-
- // Fill up the sender's bucket.
- pingKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
- pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99))
- last := fillBucket(tab, pingSender)
-
- // Add the sender as if it just pinged us. Revalidate should replace the last node in
- // its bucket if it is unresponsive. Revalidate again to ensure that
- transport.dead[last.ID()] = !lastInBucketIsResponding
- transport.dead[pingSender.ID()] = !newNodeIsResponding
- tab.addSeenNode(pingSender)
- tab.doRevalidate(make(chan struct{}, 1))
- tab.doRevalidate(make(chan struct{}, 1))
-
- if !transport.pinged[last.ID()] {
- // Oldest node in bucket is pinged to see whether it is still alive.
- t.Error("table did not ping last node in bucket")
- }
-
- tab.mutex.Lock()
- defer tab.mutex.Unlock()
- wantSize := bucketSize
- if !lastInBucketIsResponding && !newNodeIsResponding {
- wantSize--
- }
- if l := len(tab.bucket(pingSender.ID()).entries); l != wantSize {
- t.Errorf("wrong bucket size after bond: got %d, want %d", l, wantSize)
- }
- if found := contains(tab.bucket(pingSender.ID()).entries, last.ID()); found != lastInBucketIsResponding {
- t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding)
- }
- wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding
- if found := contains(tab.bucket(pingSender.ID()).entries, pingSender.ID()); found != wantNewEntry {
- t.Errorf("new entry found: %t, want: %t", found, wantNewEntry)
- }
-}
-
-func TestBucket_bumpNoDuplicates(t *testing.T) {
- t.Parallel()
- cfg := &quick.Config{
- MaxCount: 1000,
- Rand: rand.New(rand.NewSource(time.Now().Unix())),
- Values: func(args []reflect.Value, rand *rand.Rand) {
- // generate a random list of nodes. this will be the content of the bucket.
- n := rand.Intn(bucketSize-1) + 1
- nodes := make([]*node, n)
- for i := range nodes {
- nodes[i] = nodeAtDistance(enode.ID{}, 200, intIP(200))
- }
- args[0] = reflect.ValueOf(nodes)
- // generate random bump positions.
- bumps := make([]int, rand.Intn(100))
- for i := range bumps {
- bumps[i] = rand.Intn(len(nodes))
- }
- args[1] = reflect.ValueOf(bumps)
- },
- }
-
- prop := func(nodes []*node, bumps []int) (ok bool) {
- tab, db := newTestTable(newPingRecorder())
- defer db.Close()
- defer tab.close()
-
- b := &bucket{entries: make([]*node, len(nodes))}
- copy(b.entries, nodes)
- for i, pos := range bumps {
- tab.bumpInBucket(b, b.entries[pos])
- if hasDuplicates(b.entries) {
- t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps))
- for _, n := range b.entries {
- t.Logf(" %p", n)
- }
- return false
- }
- }
- checkIPLimitInvariant(t, tab)
- return true
- }
- if err := quick.Check(prop, cfg); err != nil {
- t.Error(err)
- }
-}
-
-// This checks that the table-wide IP limit is applied correctly.
-func TestTable_IPLimit(t *testing.T) {
- transport := newPingRecorder()
- tab, db := newTestTable(transport)
- defer db.Close()
- defer tab.close()
-
- for i := 0; i < tableIPLimit+1; i++ {
- n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)})
- tab.addSeenNode(n)
- }
- if tab.len() > tableIPLimit {
- t.Errorf("too many nodes in table")
- }
- checkIPLimitInvariant(t, tab)
-}
-
-// This checks that the per-bucket IP limit is applied correctly.
-func TestTable_BucketIPLimit(t *testing.T) {
- transport := newPingRecorder()
- tab, db := newTestTable(transport)
- defer db.Close()
- defer tab.close()
-
- d := 3
- for i := 0; i < bucketIPLimit+1; i++ {
- n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)})
- tab.addSeenNode(n)
- }
- if tab.len() > bucketIPLimit {
- t.Errorf("too many nodes in table")
- }
- checkIPLimitInvariant(t, tab)
-}
-
-// checkIPLimitInvariant checks that ip limit sets contain an entry for every
-// node in the table and no extra entries.
-func checkIPLimitInvariant(t *testing.T, tab *Table) {
- t.Helper()
-
- tabset := netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit}
- for _, b := range tab.buckets {
- for _, n := range b.entries {
- tabset.Add(n.IP())
- }
- }
- if tabset.String() != tab.ips.String() {
- t.Errorf("table IP set is incorrect:\nhave: %v\nwant: %v", tab.ips, tabset)
- }
-}
-
-func TestTable_findnodeByID(t *testing.T) {
- t.Parallel()
-
- test := func(test *closeTest) bool {
- // for any node table, Target and N
- transport := newPingRecorder()
- tab, db := newTestTable(transport)
- defer db.Close()
- defer tab.close()
- fillTable(tab, test.All, true)
-
- // check that closest(Target, N) returns nodes
- result := tab.findnodeByID(test.Target, test.N, false).entries
- if hasDuplicates(result) {
- t.Errorf("result contains duplicates")
- return false
- }
- if !sortedByDistanceTo(test.Target, result) {
- t.Errorf("result is not sorted by distance to target")
- return false
- }
-
- // check that the number of results is min(N, tablen)
- wantN := test.N
- if tlen := tab.len(); tlen < test.N {
- wantN = tlen
- }
- if len(result) != wantN {
- t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN)
- return false
- } else if len(result) == 0 {
- return true // no need to check distance
- }
-
- // check that the result nodes have minimum distance to target.
- for _, b := range tab.buckets {
- for _, n := range b.entries {
- if contains(result, n.ID()) {
- continue // don't run the check below for nodes in result
- }
- farthestResult := result[len(result)-1].ID()
- if enode.DistCmp(test.Target, n.ID(), farthestResult) < 0 {
- t.Errorf("table contains node that is closer to target but it's not in result")
- t.Logf(" Target: %v", test.Target)
- t.Logf(" Farthest Result: %v", farthestResult)
- t.Logf(" ID: %v", n.ID())
- return false
- }
- }
- }
- return true
- }
- if err := quick.Check(test, quickcfg()); err != nil {
- t.Error(err)
- }
-}
-
-type closeTest struct {
- Self enode.ID
- Target enode.ID
- All []*node
- N int
-}
-
-func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
- t := &closeTest{
- Self: gen(enode.ID{}, rand).(enode.ID),
- Target: gen(enode.ID{}, rand).(enode.ID),
- N: rand.Intn(bucketSize),
- }
- for _, id := range gen([]enode.ID{}, rand).([]enode.ID) {
- r := new(enr.Record)
- r.Set(enr.IP(genIP(rand)))
- n := wrapNode(enode.SignNull(r, id))
- n.livenessChecks = 1
- t.All = append(t.All, n)
- }
- return reflect.ValueOf(t)
-}
-
-func TestTable_addVerifiedNode(t *testing.T) {
- tab, db := newTestTable(newPingRecorder())
- <-tab.initDone
- defer db.Close()
- defer tab.close()
-
- // Insert two nodes.
- n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
- n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
- tab.addSeenNode(n1)
- tab.addSeenNode(n2)
-
- // Verify bucket content:
- bcontent := []*node{n1, n2}
- if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
- t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries)
- }
-
- // Add a changed version of n2.
- newrec := n2.Record()
- newrec.Set(enr.IP{99, 99, 99, 99})
- newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
- tab.addVerifiedNode(newn2)
-
- // Check that bucket is updated correctly.
- newBcontent := []*node{newn2, n1}
- if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, newBcontent) {
- t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries)
- }
- checkIPLimitInvariant(t, tab)
-}
-
-func TestTable_addSeenNode(t *testing.T) {
- tab, db := newTestTable(newPingRecorder())
- <-tab.initDone
- defer db.Close()
- defer tab.close()
-
- // Insert two nodes.
- n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
- n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
- tab.addSeenNode(n1)
- tab.addSeenNode(n2)
-
- // Verify bucket content:
- bcontent := []*node{n1, n2}
- if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
- t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries)
- }
-
- // Add a changed version of n2.
- newrec := n2.Record()
- newrec.Set(enr.IP{99, 99, 99, 99})
- newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
- tab.addSeenNode(newn2)
-
- // Check that bucket content is unchanged.
- if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
- t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries)
- }
- checkIPLimitInvariant(t, tab)
-}
-
-// This test checks that ENR updates happen during revalidation. If a node in the table
-// announces a new sequence number, the new record should be pulled.
-func TestTable_revalidateSyncRecord(t *testing.T) {
- transport := newPingRecorder()
- tab, db := newTestTable(transport)
- <-tab.initDone
- defer db.Close()
- defer tab.close()
-
- // Insert a node.
- var r enr.Record
- r.Set(enr.IP(net.IP{127, 0, 0, 1}))
- id := enode.ID{1}
- n1 := wrapNode(enode.SignNull(&r, id))
- tab.addSeenNode(n1)
-
- // Update the node record.
- r.Set(enr.WithEntry("foo", "bar"))
- n2 := enode.SignNull(&r, id)
- transport.updateRecord(n2)
-
- tab.doRevalidate(make(chan struct{}, 1))
- intable := tab.getNode(id)
- if !reflect.DeepEqual(intable, n2) {
- t.Fatalf("table contains old record with seq %d, want seq %d", intable.Seq(), n2.Seq())
- }
-}
-
-func TestNodesPush(t *testing.T) {
- var target enode.ID
- n1 := nodeAtDistance(target, 255, intIP(1))
- n2 := nodeAtDistance(target, 254, intIP(2))
- n3 := nodeAtDistance(target, 253, intIP(3))
- perm := [][]*node{
- {n3, n2, n1},
- {n3, n1, n2},
- {n2, n3, n1},
- {n2, n1, n3},
- {n1, n3, n2},
- {n1, n2, n3},
- }
-
- // Insert all permutations into lists with size limit 3.
- for _, nodes := range perm {
- list := nodesByDistance{target: target}
- for _, n := range nodes {
- list.push(n, 3)
- }
- if !slicesEqual(list.entries, perm[0], nodeIDEqual) {
- t.Fatal("not equal")
- }
- }
-
- // Insert all permutations into lists with size limit 2.
- for _, nodes := range perm {
- list := nodesByDistance{target: target}
- for _, n := range nodes {
- list.push(n, 2)
- }
- if !slicesEqual(list.entries, perm[0][:2], nodeIDEqual) {
- t.Fatal("not equal")
- }
- }
-}
-
-func nodeIDEqual(n1, n2 *node) bool {
- return n1.ID() == n2.ID()
-}
-
-func slicesEqual[T any](s1, s2 []T, check func(e1, e2 T) bool) bool {
- if len(s1) != len(s2) {
- return false
- }
- for i := range s1 {
- if !check(s1[i], s2[i]) {
- return false
- }
- }
- return true
-}
-
-// gen wraps quick.Value so it's easier to use.
-// it generates a random value of the given value's type.
-func gen(typ interface{}, rand *rand.Rand) interface{} {
- v, ok := quick.Value(reflect.TypeOf(typ), rand)
- if !ok {
- panic(fmt.Sprintf("couldn't generate random value of type %T", typ))
- }
- return v.Interface()
-}
-
-func genIP(rand *rand.Rand) net.IP {
- ip := make(net.IP, 4)
- rand.Read(ip)
- return ip
-}
-
-func quickcfg() *quick.Config {
- return &quick.Config{
- MaxCount: 5000,
- Rand: rand.New(rand.NewSource(time.Now().Unix())),
- }
-}
-
-func newkey() *ecdsa.PrivateKey {
- key, err := crypto.GenerateKey()
- if err != nil {
- panic("couldn't generate key: " + err.Error())
- }
- return key
-}
diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go
deleted file mode 100644
index d6309dfd6c..0000000000
--- a/p2p/discover/table_util_test.go
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "bytes"
- "crypto/ecdsa"
- "encoding/hex"
- "errors"
- "fmt"
- "math/rand"
- "net"
- "sync"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "golang.org/x/exp/slices"
-)
-
-var nullNode *enode.Node
-
-func init() {
- var r enr.Record
- r.Set(enr.IP{0, 0, 0, 0})
- nullNode = enode.SignNull(&r, enode.ID{})
-}
-
-func newTestTable(t transport) (*Table, *enode.DB) {
- cfg := Config{}
- db, _ := enode.OpenDB("")
- tab, _ := newTable(t, db, cfg)
- go tab.loop()
- return tab, db
-}
-
-// nodeAtDistance creates a node for which enode.LogDist(base, n.id) == ld.
-func nodeAtDistance(base enode.ID, ld int, ip net.IP) *node {
- var r enr.Record
- r.Set(enr.IP(ip))
- r.Set(enr.UDP(30303))
- return wrapNode(enode.SignNull(&r, idAtDistance(base, ld)))
-}
-
-// nodesAtDistance creates n nodes for which enode.LogDist(base, node.ID()) == ld.
-func nodesAtDistance(base enode.ID, ld int, n int) []*enode.Node {
- results := make([]*enode.Node, n)
- for i := range results {
- results[i] = unwrapNode(nodeAtDistance(base, ld, intIP(i)))
- }
- return results
-}
-
-func nodesToRecords(nodes []*enode.Node) []*enr.Record {
- records := make([]*enr.Record, len(nodes))
- for i := range nodes {
- records[i] = nodes[i].Record()
- }
- return records
-}
-
-// idAtDistance returns a random hash such that enode.LogDist(a, b) == n
-func idAtDistance(a enode.ID, n int) (b enode.ID) {
- if n == 0 {
- return a
- }
- // flip bit at position n, fill the rest with random bits
- b = a
- pos := len(a) - n/8 - 1
- bit := byte(0x01) << (byte(n%8) - 1)
- if bit == 0 {
- pos++
- bit = 0x80
- }
- b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits
- for i := pos + 1; i < len(a); i++ {
- b[i] = byte(rand.Intn(255))
- }
- return b
-}
-
-func intIP(i int) net.IP {
- return net.IP{byte(i), 0, 2, byte(i)}
-}
-
-// fillBucket inserts nodes into the given bucket until it is full.
-func fillBucket(tab *Table, n *node) (last *node) {
- ld := enode.LogDist(tab.self().ID(), n.ID())
- b := tab.bucket(n.ID())
- for len(b.entries) < bucketSize {
- b.entries = append(b.entries, nodeAtDistance(tab.self().ID(), ld, intIP(ld)))
- }
- return b.entries[bucketSize-1]
-}
-
-// fillTable adds nodes the table to the end of their corresponding bucket
-// if the bucket is not full. The caller must not hold tab.mutex.
-func fillTable(tab *Table, nodes []*node, setLive bool) {
- for _, n := range nodes {
- if setLive {
- n.livenessChecks = 1
- }
- tab.addSeenNode(n)
- }
-}
-
-type pingRecorder struct {
- mu sync.Mutex
- dead, pinged map[enode.ID]bool
- records map[enode.ID]*enode.Node
- n *enode.Node
-}
-
-func newPingRecorder() *pingRecorder {
- var r enr.Record
- r.Set(enr.IP{0, 0, 0, 0})
- n := enode.SignNull(&r, enode.ID{})
-
- return &pingRecorder{
- dead: make(map[enode.ID]bool),
- pinged: make(map[enode.ID]bool),
- records: make(map[enode.ID]*enode.Node),
- n: n,
- }
-}
-
-// updateRecord updates a node record. Future calls to ping and
-// RequestENR will return this record.
-func (t *pingRecorder) updateRecord(n *enode.Node) {
- t.mu.Lock()
- defer t.mu.Unlock()
- t.records[n.ID()] = n
-}
-
-// Stubs to satisfy the transport interface.
-func (t *pingRecorder) Self() *enode.Node { return nullNode }
-func (t *pingRecorder) lookupSelf() []*enode.Node { return nil }
-func (t *pingRecorder) lookupRandom() []*enode.Node { return nil }
-
-// ping simulates a ping request.
-func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) {
- t.mu.Lock()
- defer t.mu.Unlock()
-
- t.pinged[n.ID()] = true
- if t.dead[n.ID()] {
- return 0, errTimeout
- }
- if t.records[n.ID()] != nil {
- seq = t.records[n.ID()].Seq()
- }
- return seq, nil
-}
-
-// RequestENR simulates an ENR request.
-func (t *pingRecorder) RequestENR(n *enode.Node) (*enode.Node, error) {
- t.mu.Lock()
- defer t.mu.Unlock()
-
- if t.dead[n.ID()] || t.records[n.ID()] == nil {
- return nil, errTimeout
- }
- return t.records[n.ID()], nil
-}
-
-func hasDuplicates(slice []*node) bool {
- seen := make(map[enode.ID]bool, len(slice))
- for i, e := range slice {
- if e == nil {
- panic(fmt.Sprintf("nil *Node at %d", i))
- }
- if seen[e.ID()] {
- return true
- }
- seen[e.ID()] = true
- }
- return false
-}
-
-// checkNodesEqual checks whether the two given node lists contain the same nodes.
-func checkNodesEqual(got, want []*enode.Node) error {
- if len(got) == len(want) {
- for i := range got {
- if !nodeEqual(got[i], want[i]) {
- goto NotEqual
- }
- }
- }
- return nil
-
-NotEqual:
- output := new(bytes.Buffer)
- fmt.Fprintf(output, "got %d nodes:\n", len(got))
- for _, n := range got {
- fmt.Fprintf(output, " %v %v\n", n.ID(), n)
- }
- fmt.Fprintf(output, "want %d:\n", len(want))
- for _, n := range want {
- fmt.Fprintf(output, " %v %v\n", n.ID(), n)
- }
- return errors.New(output.String())
-}
-
-func nodeEqual(n1 *enode.Node, n2 *enode.Node) bool {
- return n1.ID() == n2.ID() && n1.IP().Equal(n2.IP())
-}
-
-func sortByID(nodes []*enode.Node) {
- slices.SortFunc(nodes, func(a, b *enode.Node) int {
- return bytes.Compare(a.ID().Bytes(), b.ID().Bytes())
- })
-}
-
-func sortedByDistanceTo(distbase enode.ID, slice []*node) bool {
- return slices.IsSortedFunc(slice, func(a, b *node) int {
- return enode.DistCmp(distbase, a.ID(), b.ID())
- })
-}
-
-// hexEncPrivkey decodes h as a private key.
-func hexEncPrivkey(h string) *ecdsa.PrivateKey {
- b, err := hex.DecodeString(h)
- if err != nil {
- panic(err)
- }
- key, err := crypto.ToECDSA(b)
- if err != nil {
- panic(err)
- }
- return key
-}
-
-// hexEncPubkey decodes h as a public key.
-func hexEncPubkey(h string) (ret encPubkey) {
- b, err := hex.DecodeString(h)
- if err != nil {
- panic(err)
- }
- if len(b) != len(ret) {
- panic("invalid length")
- }
- copy(ret[:], b)
- return ret
-}
diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go
deleted file mode 100644
index 8867a5a8ac..0000000000
--- a/p2p/discover/v4_lookup_test.go
+++ /dev/null
@@ -1,347 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "crypto/ecdsa"
- "fmt"
- "net"
- "testing"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/discover/v4wire"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "golang.org/x/exp/slices"
-)
-
-func TestUDPv4_Lookup(t *testing.T) {
- t.Parallel()
- test := newUDPTest(t)
-
- // Lookup on empty table returns no nodes.
- targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target[:])
- if results := test.udp.LookupPubkey(targetKey); len(results) > 0 {
- t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results)
- }
-
- // Seed table with initial node.
- fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}, true)
-
- // Start the lookup.
- resultC := make(chan []*enode.Node, 1)
- go func() {
- resultC <- test.udp.LookupPubkey(targetKey)
- test.close()
- }()
-
- // Answer lookup packets.
- serveTestnet(test, lookupTestnet)
-
- // Verify result nodes.
- results := <-resultC
- t.Logf("results:")
- for _, e := range results {
- t.Logf(" ld=%d, %x", enode.LogDist(lookupTestnet.target.id(), e.ID()), e.ID().Bytes())
- }
- if len(results) != bucketSize {
- t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize)
- }
- checkLookupResults(t, lookupTestnet, results)
-}
-
-func TestUDPv4_LookupIterator(t *testing.T) {
- t.Parallel()
- test := newUDPTest(t)
- defer test.close()
-
- // Seed table with initial nodes.
- bootnodes := make([]*node, len(lookupTestnet.dists[256]))
- for i := range lookupTestnet.dists[256] {
- bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
- }
- fillTable(test.table, bootnodes, true)
- go serveTestnet(test, lookupTestnet)
-
- // Create the iterator and collect the nodes it yields.
- iter := test.udp.RandomNodes()
- seen := make(map[enode.ID]*enode.Node)
- for limit := lookupTestnet.len(); iter.Next() && len(seen) < limit; {
- seen[iter.Node().ID()] = iter.Node()
- }
- iter.Close()
-
- // Check that all nodes in lookupTestnet were seen by the iterator.
- results := make([]*enode.Node, 0, len(seen))
- for _, n := range seen {
- results = append(results, n)
- }
- sortByID(results)
- want := lookupTestnet.nodes()
- if err := checkNodesEqual(results, want); err != nil {
- t.Fatal(err)
- }
-}
-
-// TestUDPv4_LookupIteratorClose checks that lookupIterator ends when its Close
-// method is called.
-func TestUDPv4_LookupIteratorClose(t *testing.T) {
- t.Parallel()
- test := newUDPTest(t)
- defer test.close()
-
- // Seed table with initial nodes.
- bootnodes := make([]*node, len(lookupTestnet.dists[256]))
- for i := range lookupTestnet.dists[256] {
- bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
- }
- fillTable(test.table, bootnodes, true)
- go serveTestnet(test, lookupTestnet)
-
- it := test.udp.RandomNodes()
- if ok := it.Next(); !ok || it.Node() == nil {
- t.Fatalf("iterator didn't return any node")
- }
-
- it.Close()
-
- ncalls := 0
- for ; ncalls < 100 && it.Next(); ncalls++ {
- if it.Node() == nil {
- t.Error("iterator returned Node() == nil node after Next() == true")
- }
- }
- t.Logf("iterator returned %d nodes after close", ncalls)
- if it.Next() {
- t.Errorf("Next() == true after close and %d more calls", ncalls)
- }
- if n := it.Node(); n != nil {
- t.Errorf("iterator returned non-nil node after close and %d more calls", ncalls)
- }
-}
-
-func serveTestnet(test *udpTest, testnet *preminedTestnet) {
- for done := false; !done; {
- done = test.waitPacketOut(func(p v4wire.Packet, to *net.UDPAddr, hash []byte) {
- n, key := testnet.nodeByAddr(to)
- switch p.(type) {
- case *v4wire.Ping:
- test.packetInFrom(nil, key, to, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash})
- case *v4wire.Findnode:
- dist := enode.LogDist(n.ID(), testnet.target.id())
- nodes := testnet.nodesAtDistance(dist - 1)
- test.packetInFrom(nil, key, to, &v4wire.Neighbors{Expiration: futureExp, Nodes: nodes})
- }
- })
- }
-}
-
-// checkLookupResults verifies that the results of a lookup are the closest nodes to
-// the testnet's target.
-func checkLookupResults(t *testing.T, tn *preminedTestnet, results []*enode.Node) {
- t.Helper()
- t.Logf("results:")
- for _, e := range results {
- t.Logf(" ld=%d, %x", enode.LogDist(tn.target.id(), e.ID()), e.ID().Bytes())
- }
- if hasDuplicates(wrapNodes(results)) {
- t.Errorf("result set contains duplicate entries")
- }
- if !sortedByDistanceTo(tn.target.id(), wrapNodes(results)) {
- t.Errorf("result set not sorted by distance to target")
- }
- wantNodes := tn.closest(len(results))
- if err := checkNodesEqual(results, wantNodes); err != nil {
- t.Error(err)
- }
-}
-
-// This is the test network for the Lookup test.
-// The nodes were obtained by running lookupTestnet.mine with a random NodeID as target.
-var lookupTestnet = &preminedTestnet{
- target: hexEncPubkey("5d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125"),
- dists: [257][]*ecdsa.PrivateKey{
- 251: {
- hexEncPrivkey("29738ba0c1a4397d6a65f292eee07f02df8e58d41594ba2be3cf84ce0fc58169"),
- hexEncPrivkey("511b1686e4e58a917f7f848e9bf5539d206a68f5ad6b54b552c2399fe7d174ae"),
- hexEncPrivkey("d09e5eaeec0fd596236faed210e55ef45112409a5aa7f3276d26646080dcfaeb"),
- hexEncPrivkey("c1e20dbbf0d530e50573bd0a260b32ec15eb9190032b4633d44834afc8afe578"),
- hexEncPrivkey("ed5f38f5702d92d306143e5d9154fb21819777da39af325ea359f453d179e80b"),
- },
- 252: {
- hexEncPrivkey("1c9b1cafbec00848d2c174b858219914b42a7d5c9359b1ca03fd650e8239ae94"),
- hexEncPrivkey("e0e1e8db4a6f13c1ffdd3e96b72fa7012293ced187c9dcdcb9ba2af37a46fa10"),
- hexEncPrivkey("3d53823e0a0295cb09f3e11d16c1b44d07dd37cec6f739b8df3a590189fe9fb9"),
- },
- 253: {
- hexEncPrivkey("2d0511ae9bf590166597eeab86b6f27b1ab761761eaea8965487b162f8703847"),
- hexEncPrivkey("6cfbd7b8503073fc3dbdb746a7c672571648d3bd15197ccf7f7fef3d904f53a2"),
- hexEncPrivkey("a30599b12827b69120633f15b98a7f6bc9fc2e9a0fd6ae2ebb767c0e64d743ab"),
- hexEncPrivkey("14a98db9b46a831d67eff29f3b85b1b485bb12ae9796aea98d91be3dc78d8a91"),
- hexEncPrivkey("2369ff1fc1ff8ca7d20b17e2673adc3365c3674377f21c5d9dafaff21fe12e24"),
- hexEncPrivkey("9ae91101d6b5048607f41ec0f690ef5d09507928aded2410aabd9237aa2727d7"),
- hexEncPrivkey("05e3c59090a3fd1ae697c09c574a36fcf9bedd0afa8fe3946f21117319ca4973"),
- hexEncPrivkey("06f31c5ea632658f718a91a1b1b9ae4b7549d7b3bc61cbc2be5f4a439039f3ad"),
- },
- 254: {
- hexEncPrivkey("dec742079ec00ff4ec1284d7905bc3de2366f67a0769431fd16f80fd68c58a7c"),
- hexEncPrivkey("ff02c8861fa12fbd129d2a95ea663492ef9c1e51de19dcfbbfe1c59894a28d2b"),
- hexEncPrivkey("4dded9e4eefcbce4262be4fd9e8a773670ab0b5f448f286ec97dfc8cf681444a"),
- hexEncPrivkey("750d931e2a8baa2c9268cb46b7cd851f4198018bed22f4dceb09dd334a2395f6"),
- hexEncPrivkey("ce1435a956a98ffec484cd11489c4f165cf1606819ab6b521cee440f0c677e9e"),
- hexEncPrivkey("996e7f8d1638be92d7328b4770f47e5420fc4bafecb4324fd33b1f5d9f403a75"),
- hexEncPrivkey("ebdc44e77a6cc0eb622e58cf3bb903c3da4c91ca75b447b0168505d8fc308b9c"),
- hexEncPrivkey("46bd1eddcf6431bea66fc19ebc45df191c1c7d6ed552dcdc7392885009c322f0"),
- },
- 255: {
- hexEncPrivkey("da8645f90826e57228d9ea72aff84500060ad111a5d62e4af831ed8e4b5acfb8"),
- hexEncPrivkey("3c944c5d9af51d4c1d43f5d0f3a1a7ef65d5e82744d669b58b5fed242941a566"),
- hexEncPrivkey("5ebcde76f1d579eebf6e43b0ffe9157e65ffaa391175d5b9aa988f47df3e33da"),
- hexEncPrivkey("97f78253a7d1d796e4eaabce721febcc4550dd68fb11cc818378ba807a2cb7de"),
- hexEncPrivkey("a38cd7dc9b4079d1c0406afd0fdb1165c285f2c44f946eca96fc67772c988c7d"),
- hexEncPrivkey("d64cbb3ffdf712c372b7a22a176308ef8f91861398d5dbaf326fd89c6eaeef1c"),
- hexEncPrivkey("d269609743ef29d6446e3355ec647e38d919c82a4eb5837e442efd7f4218944f"),
- hexEncPrivkey("d8f7bcc4a530efde1d143717007179e0d9ace405ddaaf151c4d863753b7fd64c"),
- },
- 256: {
- hexEncPrivkey("8c5b422155d33ea8e9d46f71d1ad3e7b24cb40051413ffa1a81cff613d243ba9"),
- hexEncPrivkey("937b1af801def4e8f5a3a8bd225a8bcff1db764e41d3e177f2e9376e8dd87233"),
- hexEncPrivkey("120260dce739b6f71f171da6f65bc361b5fad51db74cf02d3e973347819a6518"),
- hexEncPrivkey("1fa56cf25d4b46c2bf94e82355aa631717b63190785ac6bae545a88aadc304a9"),
- hexEncPrivkey("3c38c503c0376f9b4adcbe935d5f4b890391741c764f61b03cd4d0d42deae002"),
- hexEncPrivkey("3a54af3e9fa162bc8623cdf3e5d9b70bf30ade1d54cc3abea8659aba6cff471f"),
- hexEncPrivkey("6799a02ea1999aefdcbcc4d3ff9544478be7365a328d0d0f37c26bd95ade0cda"),
- hexEncPrivkey("e24a7bc9051058f918646b0f6e3d16884b2a55a15553b89bab910d55ebc36116"),
- },
- },
-}
-
-type preminedTestnet struct {
- target encPubkey
- dists [hashBits + 1][]*ecdsa.PrivateKey
-}
-
-func (tn *preminedTestnet) len() int {
- n := 0
- for _, keys := range tn.dists {
- n += len(keys)
- }
- return n
-}
-
-func (tn *preminedTestnet) nodes() []*enode.Node {
- result := make([]*enode.Node, 0, tn.len())
- for dist, keys := range tn.dists {
- for index := range keys {
- result = append(result, tn.node(dist, index))
- }
- }
- sortByID(result)
- return result
-}
-
-func (tn *preminedTestnet) node(dist, index int) *enode.Node {
- key := tn.dists[dist][index]
- rec := new(enr.Record)
- rec.Set(enr.IP{127, byte(dist >> 8), byte(dist), byte(index)})
- rec.Set(enr.UDP(5000))
- enode.SignV4(rec, key)
- n, _ := enode.New(enode.ValidSchemes, rec)
- return n
-}
-
-func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.PrivateKey) {
- dist := int(addr.IP[1])<<8 + int(addr.IP[2])
- index := int(addr.IP[3])
- key := tn.dists[dist][index]
- return tn.node(dist, index), key
-}
-
-func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node {
- result := make([]v4wire.Node, len(tn.dists[dist]))
- for i := range result {
- result[i] = nodeToRPC(wrapNode(tn.node(dist, i)))
- }
- return result
-}
-
-func (tn *preminedTestnet) neighborsAtDistances(base *enode.Node, distances []uint, elems int) []*enode.Node {
- var result []*enode.Node
- for d := range lookupTestnet.dists {
- for i := range lookupTestnet.dists[d] {
- n := lookupTestnet.node(d, i)
- d := enode.LogDist(base.ID(), n.ID())
- if containsUint(uint(d), distances) {
- result = append(result, n)
- if len(result) >= elems {
- return result
- }
- }
- }
- }
- return result
-}
-
-func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) {
- for d := range tn.dists {
- for i := range tn.dists[d] {
- nodes = append(nodes, tn.node(d, i))
- }
- }
- slices.SortFunc(nodes, func(a, b *enode.Node) int {
- return enode.DistCmp(tn.target.id(), a.ID(), b.ID())
- })
- return nodes[:n]
-}
-
-var _ = (*preminedTestnet).mine // avoid linter warning about mine being dead code.
-
-// mine generates a testnet struct literal with nodes at
-// various distances to the network's target.
-func (tn *preminedTestnet) mine() {
- // Clear existing slices first (useful when re-mining).
- for i := range tn.dists {
- tn.dists[i] = nil
- }
-
- targetSha := tn.target.id()
- found, need := 0, 40
- for found < need {
- k := newkey()
- ld := enode.LogDist(targetSha, encodePubkey(&k.PublicKey).id())
- if len(tn.dists[ld]) < 8 {
- tn.dists[ld] = append(tn.dists[ld], k)
- found++
- fmt.Printf("found ID with ld %d (%d/%d)\n", ld, found, need)
- }
- }
- fmt.Printf("&preminedTestnet{\n")
- fmt.Printf(" target: hexEncPubkey(\"%x\"),\n", tn.target[:])
- fmt.Printf(" dists: [%d][]*ecdsa.PrivateKey{\n", len(tn.dists))
- for ld, ns := range tn.dists {
- if len(ns) == 0 {
- continue
- }
- fmt.Printf(" %d: {\n", ld)
- for _, key := range ns {
- fmt.Printf(" hexEncPrivkey(\"%x\"),\n", crypto.FromECDSA(key))
- }
- fmt.Printf(" },\n")
- }
- fmt.Printf(" },\n")
- fmt.Printf("}\n")
-}
diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go
deleted file mode 100644
index 988f16b01d..0000000000
--- a/p2p/discover/v4_udp.go
+++ /dev/null
@@ -1,787 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "bytes"
- "container/list"
- "context"
- "crypto/ecdsa"
- crand "crypto/rand"
- "errors"
- "fmt"
- "io"
- "net"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/discover/v4wire"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/netutil"
-)
-
-// Errors
-var (
- errExpired = errors.New("expired")
- errUnsolicitedReply = errors.New("unsolicited reply")
- errUnknownNode = errors.New("unknown node")
- errTimeout = errors.New("RPC timeout")
- errClockWarp = errors.New("reply deadline too far in the future")
- errClosed = errors.New("socket closed")
- errLowPort = errors.New("low port")
-)
-
-const (
- respTimeout = 500 * time.Millisecond
- expiration = 20 * time.Second
- bondExpiration = 24 * time.Hour
-
- maxFindnodeFailures = 5 // nodes exceeding this limit are dropped
- ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP
- ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning
- driftThreshold = 10 * time.Second // Allowed clock drift before warning user
-
- // Discovery packets are defined to be no larger than 1280 bytes.
- // Packets larger than this size will be cut at the end and treated
- // as invalid because their hash won't match.
- maxPacketSize = 1280
-)
-
-// UDPv4 implements the v4 wire protocol.
-type UDPv4 struct {
- conn UDPConn
- log log.Logger
- netrestrict *netutil.Netlist
- priv *ecdsa.PrivateKey
- localNode *enode.LocalNode
- db *enode.DB
- tab *Table
- closeOnce sync.Once
- wg sync.WaitGroup
-
- addReplyMatcher chan *replyMatcher
- gotreply chan reply
- closeCtx context.Context
- cancelCloseCtx context.CancelFunc
-}
-
-// replyMatcher represents a pending reply.
-//
-// Some implementations of the protocol wish to send more than one
-// reply packet to findnode. In general, any neighbors packet cannot
-// be matched up with a specific findnode packet.
-//
-// Our implementation handles this by storing a callback function for
-// each pending reply. Incoming packets from a node are dispatched
-// to all callback functions for that node.
-type replyMatcher struct {
- // these fields must match in the reply.
- from enode.ID
- ip net.IP
- ptype byte
-
- // time when the request must complete
- deadline time.Time
-
- // callback is called when a matching reply arrives. If it returns matched == true, the
- // reply was acceptable. The second return value indicates whether the callback should
- // be removed from the pending reply queue. If it returns false, the reply is considered
- // incomplete and the callback will be invoked again for the next matching reply.
- callback replyMatchFunc
-
- // errc receives nil when the callback indicates completion or an
- // error if no further reply is received within the timeout.
- errc chan error
-
- // reply contains the most recent reply. This field is safe for reading after errc has
- // received a value.
- reply v4wire.Packet
-}
-
-type replyMatchFunc func(v4wire.Packet) (matched bool, requestDone bool)
-
-// reply is a reply packet from a certain node.
-type reply struct {
- from enode.ID
- ip net.IP
- data v4wire.Packet
- // loop indicates whether there was
- // a matching request by sending on this channel.
- matched chan<- bool
-}
-
-func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
- cfg = cfg.withDefaults()
- closeCtx, cancel := context.WithCancel(context.Background())
- t := &UDPv4{
- conn: newMeteredConn(c),
- priv: cfg.PrivateKey,
- netrestrict: cfg.NetRestrict,
- localNode: ln,
- db: ln.Database(),
- gotreply: make(chan reply),
- addReplyMatcher: make(chan *replyMatcher),
- closeCtx: closeCtx,
- cancelCloseCtx: cancel,
- log: cfg.Log,
- }
-
- tab, err := newMeteredTable(t, ln.Database(), cfg)
- if err != nil {
- return nil, err
- }
- t.tab = tab
- go tab.loop()
-
- t.wg.Add(2)
- go t.loop()
- go t.readLoop(cfg.Unhandled)
- return t, nil
-}
-
-// Self returns the local node.
-func (t *UDPv4) Self() *enode.Node {
- return t.localNode.Node()
-}
-
-// Close shuts down the socket and aborts any running queries.
-func (t *UDPv4) Close() {
- t.closeOnce.Do(func() {
- t.cancelCloseCtx()
- t.conn.Close()
- t.wg.Wait()
- t.tab.close()
- })
-}
-
-// Resolve searches for a specific node with the given ID and tries to get the most recent
-// version of the node record for it. It returns n if the node could not be resolved.
-func (t *UDPv4) Resolve(n *enode.Node) *enode.Node {
- // Try asking directly. This works if the node is still responding on the endpoint we have.
- if rn, err := t.RequestENR(n); err == nil {
- return rn
- }
- // Check table for the ID, we might have a newer version there.
- if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
- n = intable
- if rn, err := t.RequestENR(n); err == nil {
- return rn
- }
- }
- // Otherwise perform a network lookup.
- var key enode.Secp256k1
- if n.Load(&key) != nil {
- return n // no secp256k1 key
- }
- result := t.LookupPubkey((*ecdsa.PublicKey)(&key))
- for _, rn := range result {
- if rn.ID() == n.ID() {
- if rn, err := t.RequestENR(rn); err == nil {
- return rn
- }
- }
- }
- return n
-}
-
-func (t *UDPv4) ourEndpoint() v4wire.Endpoint {
- n := t.Self()
- a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
- return v4wire.NewEndpoint(a, uint16(n.TCP()))
-}
-
-// Ping sends a ping message to the given node.
-func (t *UDPv4) Ping(n *enode.Node) error {
- _, err := t.ping(n)
- return err
-}
-
-// ping sends a ping message to the given node and waits for a reply.
-func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
- rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil)
- if err = <-rm.errc; err == nil {
- seq = rm.reply.(*v4wire.Pong).ENRSeq
- }
- return seq, err
-}
-
-// sendPing sends a ping message to the given node and invokes the callback
-// when the reply arrives.
-func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher {
- req := t.makePing(toaddr)
- packet, hash, err := v4wire.Encode(t.priv, req)
- if err != nil {
- errc := make(chan error, 1)
- errc <- err
- return &replyMatcher{errc: errc}
- }
- // Add a matcher for the reply to the pending reply queue. Pongs are matched if they
- // reference the ping we're about to send.
- rm := t.pending(toid, toaddr.IP, v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
- matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash)
- if matched && callback != nil {
- callback()
- }
- return matched, matched
- })
- // Send the packet.
- t.localNode.UDPContact(toaddr)
- t.write(toaddr, toid, req.Name(), packet)
- return rm
-}
-
-func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping {
- return &v4wire.Ping{
- Version: 4,
- From: t.ourEndpoint(),
- To: v4wire.NewEndpoint(toaddr, 0),
- Expiration: uint64(time.Now().Add(expiration).Unix()),
- ENRSeq: t.localNode.Node().Seq(),
- }
-}
-
-// LookupPubkey finds the closest nodes to the given public key.
-func (t *UDPv4) LookupPubkey(key *ecdsa.PublicKey) []*enode.Node {
- if t.tab.len() == 0 {
- // All nodes were dropped, refresh. The very first query will hit this
- // case and run the bootstrapping logic.
- <-t.tab.refresh()
- }
- return t.newLookup(t.closeCtx, encodePubkey(key)).run()
-}
-
-// RandomNodes is an iterator yielding nodes from a random walk of the DHT.
-func (t *UDPv4) RandomNodes() enode.Iterator {
- return newLookupIterator(t.closeCtx, t.newRandomLookup)
-}
-
-// lookupRandom implements transport.
-func (t *UDPv4) lookupRandom() []*enode.Node {
- return t.newRandomLookup(t.closeCtx).run()
-}
-
-// lookupSelf implements transport.
-func (t *UDPv4) lookupSelf() []*enode.Node {
- return t.newLookup(t.closeCtx, encodePubkey(&t.priv.PublicKey)).run()
-}
-
-func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup {
- var target encPubkey
- crand.Read(target[:])
- return t.newLookup(ctx, target)
-}
-
-func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup {
- target := enode.ID(crypto.Keccak256Hash(targetKey[:]))
- ekey := v4wire.Pubkey(targetKey)
- it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
- return t.findnode(n.ID(), n.addr(), ekey)
- })
- return it
-}
-
-// findnode sends a findnode request to the given node and waits until
-// the node has sent up to k neighbors.
-func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubkey) ([]*node, error) {
- t.ensureBond(toid, toaddr)
-
- // Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is
- // active until enough nodes have been received.
- nodes := make([]*node, 0, bucketSize)
- nreceived := 0
- rm := t.pending(toid, toaddr.IP, v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
- reply := r.(*v4wire.Neighbors)
- for _, rn := range reply.Nodes {
- nreceived++
- n, err := t.nodeFromRPC(toaddr, rn)
- if err != nil {
- t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err)
- continue
- }
- nodes = append(nodes, n)
- }
- return true, nreceived >= bucketSize
- })
- t.send(toaddr, toid, &v4wire.Findnode{
- Target: target,
- Expiration: uint64(time.Now().Add(expiration).Unix()),
- })
- // Ensure that callers don't see a timeout if the node actually responded. Since
- // findnode can receive more than one neighbors response, the reply matcher will be
- // active until the remote node sends enough nodes. If the remote end doesn't have
- // enough nodes the reply matcher will time out waiting for the second reply, but
- // there's no need for an error in that case.
- err := <-rm.errc
- if errors.Is(err, errTimeout) && rm.reply != nil {
- err = nil
- }
- return nodes, err
-}
-
-// RequestENR sends ENRRequest to the given node and waits for a response.
-func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
- addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
- t.ensureBond(n.ID(), addr)
-
- req := &v4wire.ENRRequest{
- Expiration: uint64(time.Now().Add(expiration).Unix()),
- }
- packet, hash, err := v4wire.Encode(t.priv, req)
- if err != nil {
- return nil, err
- }
-
- // Add a matcher for the reply to the pending reply queue. Responses are matched if
- // they reference the request we're about to send.
- rm := t.pending(n.ID(), addr.IP, v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
- matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash)
- return matched, matched
- })
- // Send the packet and wait for the reply.
- t.write(addr, n.ID(), req.Name(), packet)
- if err := <-rm.errc; err != nil {
- return nil, err
- }
- // Verify the response record.
- respN, err := enode.New(enode.ValidSchemes, &rm.reply.(*v4wire.ENRResponse).Record)
- if err != nil {
- return nil, err
- }
- if respN.ID() != n.ID() {
- return nil, fmt.Errorf("invalid ID in response record")
- }
- if respN.Seq() < n.Seq() {
- return n, nil // response record is older
- }
- if err := netutil.CheckRelayIP(addr.IP, respN.IP()); err != nil {
- return nil, fmt.Errorf("invalid IP in response record: %v", err)
- }
- return respN, nil
-}
-
-// pending adds a reply matcher to the pending reply queue.
-// see the documentation of type replyMatcher for a detailed explanation.
-func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) *replyMatcher {
- ch := make(chan error, 1)
- p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch}
- select {
- case t.addReplyMatcher <- p:
- // loop will handle it
- case <-t.closeCtx.Done():
- ch <- errClosed
- }
- return p
-}
-
-// handleReply dispatches a reply packet, invoking reply matchers. It returns
-// whether any matcher considered the packet acceptable.
-func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req v4wire.Packet) bool {
- matched := make(chan bool, 1)
- select {
- case t.gotreply <- reply{from, fromIP, req, matched}:
- // loop will handle it
- return <-matched
- case <-t.closeCtx.Done():
- return false
- }
-}
-
-// loop runs in its own goroutine. it keeps track of
-// the refresh timer and the pending reply queue.
-func (t *UDPv4) loop() {
- defer t.wg.Done()
-
- var (
- plist = list.New()
- timeout = time.NewTimer(0)
- nextTimeout *replyMatcher // head of plist when timeout was last reset
- contTimeouts = 0 // number of continuous timeouts to do NTP checks
- ntpWarnTime = time.Unix(0, 0)
- )
- <-timeout.C // ignore first timeout
- defer timeout.Stop()
-
- resetTimeout := func() {
- if plist.Front() == nil || nextTimeout == plist.Front().Value {
- return
- }
- // Start the timer so it fires when the next pending reply has expired.
- now := time.Now()
- for el := plist.Front(); el != nil; el = el.Next() {
- nextTimeout = el.Value.(*replyMatcher)
- if dist := nextTimeout.deadline.Sub(now); dist < 2*respTimeout {
- timeout.Reset(dist)
- return
- }
- // Remove pending replies whose deadline is too far in the
- // future. These can occur if the system clock jumped
- // backwards after the deadline was assigned.
- nextTimeout.errc <- errClockWarp
- plist.Remove(el)
- }
- nextTimeout = nil
- timeout.Stop()
- }
-
- for {
- resetTimeout()
-
- select {
- case <-t.closeCtx.Done():
- for el := plist.Front(); el != nil; el = el.Next() {
- el.Value.(*replyMatcher).errc <- errClosed
- }
- return
-
- case p := <-t.addReplyMatcher:
- p.deadline = time.Now().Add(respTimeout)
- plist.PushBack(p)
-
- case r := <-t.gotreply:
- var matched bool // whether any replyMatcher considered the reply acceptable.
- for el := plist.Front(); el != nil; el = el.Next() {
- p := el.Value.(*replyMatcher)
- if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) {
- ok, requestDone := p.callback(r.data)
- matched = matched || ok
- p.reply = r.data
- // Remove the matcher if callback indicates that all replies have been received.
- if requestDone {
- p.errc <- nil
- plist.Remove(el)
- }
- // Reset the continuous timeout counter (time drift detection)
- contTimeouts = 0
- }
- }
- r.matched <- matched
-
- case now := <-timeout.C:
- nextTimeout = nil
-
- // Notify and remove callbacks whose deadline is in the past.
- for el := plist.Front(); el != nil; el = el.Next() {
- p := el.Value.(*replyMatcher)
- if now.After(p.deadline) || now.Equal(p.deadline) {
- p.errc <- errTimeout
- plist.Remove(el)
- contTimeouts++
- }
- }
- // If we've accumulated too many timeouts, do an NTP time sync check
- if contTimeouts > ntpFailureThreshold {
- if time.Since(ntpWarnTime) >= ntpWarningCooldown {
- ntpWarnTime = time.Now()
- go checkClockDrift()
- }
- contTimeouts = 0
- }
- }
- }
-}
-
-func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]byte, error) {
- packet, hash, err := v4wire.Encode(t.priv, req)
- if err != nil {
- return hash, err
- }
- return hash, t.write(toaddr, toid, req.Name(), packet)
-}
-
-func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error {
- _, err := t.conn.WriteToUDP(packet, toaddr)
- t.log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err)
- return err
-}
-
-// readLoop runs in its own goroutine. it handles incoming UDP packets.
-func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
- defer t.wg.Done()
- if unhandled != nil {
- defer close(unhandled)
- }
-
- buf := make([]byte, maxPacketSize)
- for {
- nbytes, from, err := t.conn.ReadFromUDP(buf)
- if netutil.IsTemporaryError(err) {
- // Ignore temporary read errors.
- t.log.Debug("Temporary UDP read error", "err", err)
- continue
- } else if err != nil {
- // Shut down the loop for permanent errors.
- if !errors.Is(err, io.EOF) {
- t.log.Debug("UDP read error", "err", err)
- }
- return
- }
- if t.handlePacket(from, buf[:nbytes]) != nil && unhandled != nil {
- select {
- case unhandled <- ReadPacket{buf[:nbytes], from}:
- default:
- }
- }
- }
-}
-
-func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
- rawpacket, fromKey, hash, err := v4wire.Decode(buf)
- if err != nil {
- t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
- return err
- }
- packet := t.wrapPacket(rawpacket)
- fromID := fromKey.ID()
- if err == nil && packet.preverify != nil {
- err = packet.preverify(packet, from, fromID, fromKey)
- }
- t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", from, "err", err)
- if err == nil && packet.handle != nil {
- packet.handle(packet, from, fromID, hash)
- }
- return err
-}
-
-// checkBond checks if the given node has a recent enough endpoint proof.
-func (t *UDPv4) checkBond(id enode.ID, ip net.IP) bool {
- return time.Since(t.db.LastPongReceived(id, ip)) < bondExpiration
-}
-
-// ensureBond solicits a ping from a node if we haven't seen a ping from it for a while.
-// This ensures there is a valid endpoint proof on the remote end.
-func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) {
- tooOld := time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration
- if tooOld || t.db.FindFails(toid, toaddr.IP) > maxFindnodeFailures {
- rm := t.sendPing(toid, toaddr, nil)
- <-rm.errc
- // Wait for them to ping back and process our pong.
- time.Sleep(respTimeout)
- }
-}
-
-func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) {
- if rn.UDP <= 1024 {
- return nil, errLowPort
- }
- if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
- return nil, err
- }
- if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
- return nil, errors.New("not contained in netrestrict list")
- }
- key, err := v4wire.DecodePubkey(crypto.S256(), rn.ID)
- if err != nil {
- return nil, err
- }
- n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP)))
- err = n.ValidateComplete()
- return n, err
-}
-
-func nodeToRPC(n *node) v4wire.Node {
- var key ecdsa.PublicKey
- var ekey v4wire.Pubkey
- if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
- ekey = v4wire.EncodePubkey(&key)
- }
- return v4wire.Node{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())}
-}
-
-// wrapPacket returns the handler functions applicable to a packet.
-func (t *UDPv4) wrapPacket(p v4wire.Packet) *packetHandlerV4 {
- var h packetHandlerV4
- h.Packet = p
- switch p.(type) {
- case *v4wire.Ping:
- h.preverify = t.verifyPing
- h.handle = t.handlePing
- case *v4wire.Pong:
- h.preverify = t.verifyPong
- case *v4wire.Findnode:
- h.preverify = t.verifyFindnode
- h.handle = t.handleFindnode
- case *v4wire.Neighbors:
- h.preverify = t.verifyNeighbors
- case *v4wire.ENRRequest:
- h.preverify = t.verifyENRRequest
- h.handle = t.handleENRRequest
- case *v4wire.ENRResponse:
- h.preverify = t.verifyENRResponse
- }
- return &h
-}
-
-// packetHandlerV4 wraps a packet with handler functions.
-type packetHandlerV4 struct {
- v4wire.Packet
- senderKey *ecdsa.PublicKey // used for ping
-
- // preverify checks whether the packet is valid and should be handled at all.
- preverify func(p *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error
- // handle handles the packet.
- handle func(req *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte)
-}
-
-// PING/v4
-
-func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
- req := h.Packet.(*v4wire.Ping)
-
- if v4wire.Expired(req.Expiration) {
- return errExpired
- }
- senderKey, err := v4wire.DecodePubkey(crypto.S256(), fromKey)
- if err != nil {
- return err
- }
- h.senderKey = senderKey
- return nil
-}
-
-func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
- req := h.Packet.(*v4wire.Ping)
-
- // Reply.
- t.send(from, fromID, &v4wire.Pong{
- To: v4wire.NewEndpoint(from, req.From.TCP),
- ReplyTok: mac,
- Expiration: uint64(time.Now().Add(expiration).Unix()),
- ENRSeq: t.localNode.Node().Seq(),
- })
-
- // Ping back if our last pong on file is too far in the past.
- n := wrapNode(enode.NewV4(h.senderKey, from.IP, int(req.From.TCP), from.Port))
- if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration {
- t.sendPing(fromID, from, func() {
- t.tab.addVerifiedNode(n)
- })
- } else {
- t.tab.addVerifiedNode(n)
- }
-
- // Update node database and endpoint predictor.
- t.db.UpdateLastPingReceived(n.ID(), from.IP, time.Now())
- t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
-}
-
-// PONG/v4
-
-func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
- req := h.Packet.(*v4wire.Pong)
-
- if v4wire.Expired(req.Expiration) {
- return errExpired
- }
- if !t.handleReply(fromID, from.IP, req) {
- return errUnsolicitedReply
- }
- t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
- t.db.UpdateLastPongReceived(fromID, from.IP, time.Now())
- return nil
-}
-
-// FINDNODE/v4
-
-func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
- req := h.Packet.(*v4wire.Findnode)
-
- if v4wire.Expired(req.Expiration) {
- return errExpired
- }
- if !t.checkBond(fromID, from.IP) {
- // No endpoint proof pong exists, we don't process the packet. This prevents an
- // attack vector where the discovery protocol could be used to amplify traffic in a
- // DDOS attack. A malicious actor would send a findnode request with the IP address
- // and UDP port of the target as the source address. The recipient of the findnode
- // packet would then send a neighbors packet (which is a much bigger packet than
- // findnode) to the victim.
- return errUnknownNode
- }
- return nil
-}
-
-func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
- req := h.Packet.(*v4wire.Findnode)
-
- // Determine closest nodes.
- target := enode.ID(crypto.Keccak256Hash(req.Target[:]))
- closest := t.tab.findnodeByID(target, bucketSize, true).entries
-
- // Send neighbors in chunks with at most maxNeighbors per packet
- // to stay below the packet size limit.
- p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
- var sent bool
- for _, n := range closest {
- if netutil.CheckRelayIP(from.IP, n.IP()) == nil {
- p.Nodes = append(p.Nodes, nodeToRPC(n))
- }
- if len(p.Nodes) == v4wire.MaxNeighbors {
- t.send(from, fromID, &p)
- p.Nodes = p.Nodes[:0]
- sent = true
- }
- }
- if len(p.Nodes) > 0 || !sent {
- t.send(from, fromID, &p)
- }
-}
-
-// NEIGHBORS/v4
-
-func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
- req := h.Packet.(*v4wire.Neighbors)
-
- if v4wire.Expired(req.Expiration) {
- return errExpired
- }
- if !t.handleReply(fromID, from.IP, h.Packet) {
- return errUnsolicitedReply
- }
- return nil
-}
-
-// ENRREQUEST/v4
-
-func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
- req := h.Packet.(*v4wire.ENRRequest)
-
- if v4wire.Expired(req.Expiration) {
- return errExpired
- }
- if !t.checkBond(fromID, from.IP) {
- return errUnknownNode
- }
- return nil
-}
-
-func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
- t.send(from, fromID, &v4wire.ENRResponse{
- ReplyTok: mac,
- Record: *t.localNode.Node().Record(),
- })
-}
-
-// ENRRESPONSE/v4
-
-func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
- if !t.handleReply(fromID, from.IP, h.Packet) {
- return errUnsolicitedReply
- }
- return nil
-}
diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go
deleted file mode 100644
index 361e379626..0000000000
--- a/p2p/discover/v4_udp_test.go
+++ /dev/null
@@ -1,661 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "bytes"
- "crypto/ecdsa"
- crand "crypto/rand"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
- "math/rand"
- "net"
- "reflect"
- "sync"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/internal/testlog"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/discover/v4wire"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
-)
-
-// shared test variables
-var (
- futureExp = uint64(time.Now().Add(10 * time.Hour).Unix())
- testTarget = v4wire.Pubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
- testRemote = v4wire.Endpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2}
- testLocalAnnounced = v4wire.Endpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4}
- testLocal = v4wire.Endpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6}
-)
-
-type udpTest struct {
- t *testing.T
- pipe *dgramPipe
- table *Table
- db *enode.DB
- udp *UDPv4
- sent [][]byte
- localkey, remotekey *ecdsa.PrivateKey
- remoteaddr *net.UDPAddr
-}
-
-func newUDPTest(t *testing.T) *udpTest {
- test := &udpTest{
- t: t,
- pipe: newpipe(),
- localkey: newkey(),
- remotekey: newkey(),
- remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303},
- }
-
- test.db, _ = enode.OpenDB("")
- ln := enode.NewLocalNode(test.db, test.localkey)
- test.udp, _ = ListenV4(test.pipe, ln, Config{
- PrivateKey: test.localkey,
- Log: testlog.Logger(t, log.LvlTrace),
- })
- test.table = test.udp.tab
- // Wait for initial refresh so the table doesn't send unexpected findnode.
- <-test.table.initDone
- return test
-}
-
-func (test *udpTest) close() {
- test.udp.Close()
- test.db.Close()
-}
-
-// handles a packet as if it had been sent to the transport.
-func (test *udpTest) packetIn(wantError error, data v4wire.Packet) {
- test.t.Helper()
-
- test.packetInFrom(wantError, test.remotekey, test.remoteaddr, data)
-}
-
-// handles a packet as if it had been sent to the transport by the key/endpoint.
-func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, data v4wire.Packet) {
- test.t.Helper()
-
- enc, _, err := v4wire.Encode(key, data)
- if err != nil {
- test.t.Errorf("%s encode error: %v", data.Name(), err)
- }
- test.sent = append(test.sent, enc)
- if err = test.udp.handlePacket(addr, enc); err != wantError {
- test.t.Errorf("error mismatch: got %q, want %q", err, wantError)
- }
-}
-
-// waits for a packet to be sent by the transport.
-// validate should have type func(X, *net.UDPAddr, []byte), where X is a packet type.
-func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
- test.t.Helper()
-
- dgram, err := test.pipe.receive()
- if err == errClosed {
- return true
- } else if err != nil {
- test.t.Error("packet receive error:", err)
- return false
- }
- p, _, hash, err := v4wire.Decode(dgram.data)
- if err != nil {
- test.t.Errorf("sent packet decode error: %v", err)
- return false
- }
- fn := reflect.ValueOf(validate)
- exptype := fn.Type().In(0)
- if !reflect.TypeOf(p).AssignableTo(exptype) {
- test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
- return false
- }
- fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(hash)})
- return false
-}
-
-func TestUDPv4_packetErrors(t *testing.T) {
- test := newUDPTest(t)
- defer test.close()
-
- test.packetIn(errExpired, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4})
- test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: []byte{}, Expiration: futureExp})
- test.packetIn(errUnknownNode, &v4wire.Findnode{Expiration: futureExp})
- test.packetIn(errUnsolicitedReply, &v4wire.Neighbors{Expiration: futureExp})
-}
-
-func TestUDPv4_pingTimeout(t *testing.T) {
- t.Parallel()
- test := newUDPTest(t)
- defer test.close()
-
- key := newkey()
- toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
- node := enode.NewV4(&key.PublicKey, toaddr.IP, 0, toaddr.Port)
- if _, err := test.udp.ping(node); err != errTimeout {
- t.Error("expected timeout error, got", err)
- }
-}
-
-type testPacket byte
-
-func (req testPacket) Kind() byte { return byte(req) }
-func (req testPacket) Name() string { return "" }
-
-func TestUDPv4_responseTimeouts(t *testing.T) {
- t.Parallel()
- test := newUDPTest(t)
- defer test.close()
-
- randomDuration := func(max time.Duration) time.Duration {
- return time.Duration(rand.Int63n(int64(max)))
- }
-
- var (
- nReqs = 200
- nTimeouts = 0 // number of requests with ptype > 128
- nilErr = make(chan error, nReqs) // for requests that get a reply
- timeoutErr = make(chan error, nReqs) // for requests that time out
- )
- for i := 0; i < nReqs; i++ {
- // Create a matcher for a random request in udp.loop. Requests
- // with ptype <= 128 will not get a reply and should time out.
- // For all other requests, a reply is scheduled to arrive
- // within the timeout window.
- p := &replyMatcher{
- ptype: byte(rand.Intn(255)),
- callback: func(v4wire.Packet) (bool, bool) { return true, true },
- }
- binary.BigEndian.PutUint64(p.from[:], uint64(i))
- if p.ptype <= 128 {
- p.errc = timeoutErr
- test.udp.addReplyMatcher <- p
- nTimeouts++
- } else {
- p.errc = nilErr
- test.udp.addReplyMatcher <- p
- time.AfterFunc(randomDuration(60*time.Millisecond), func() {
- if !test.udp.handleReply(p.from, p.ip, testPacket(p.ptype)) {
- t.Logf("not matched: %v", p)
- }
- })
- }
- time.Sleep(randomDuration(30 * time.Millisecond))
- }
-
- // Check that all timeouts were delivered and that the rest got nil errors.
- // The replies must be delivered.
- var (
- recvDeadline = time.After(20 * time.Second)
- nTimeoutsRecv, nNil = 0, 0
- )
- for i := 0; i < nReqs; i++ {
- select {
- case err := <-timeoutErr:
- if err != errTimeout {
- t.Fatalf("got non-timeout error on timeoutErr %d: %v", i, err)
- }
- nTimeoutsRecv++
- case err := <-nilErr:
- if err != nil {
- t.Fatalf("got non-nil error on nilErr %d: %v", i, err)
- }
- nNil++
- case <-recvDeadline:
- t.Fatalf("exceeded recv deadline")
- }
- }
- if nTimeoutsRecv != nTimeouts {
- t.Errorf("wrong number of timeout errors received: got %d, want %d", nTimeoutsRecv, nTimeouts)
- }
- if nNil != nReqs-nTimeouts {
- t.Errorf("wrong number of successful replies: got %d, want %d", nNil, nReqs-nTimeouts)
- }
-}
-
-func TestUDPv4_findnodeTimeout(t *testing.T) {
- t.Parallel()
- test := newUDPTest(t)
- defer test.close()
-
- toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
- toid := enode.ID{1, 2, 3, 4}
- target := v4wire.Pubkey{4, 5, 6, 7}
- result, err := test.udp.findnode(toid, toaddr, target)
- if err != errTimeout {
- t.Error("expected timeout error, got", err)
- }
- if len(result) > 0 {
- t.Error("expected empty result, got", result)
- }
-}
-
-func TestUDPv4_findnode(t *testing.T) {
- test := newUDPTest(t)
- defer test.close()
-
- // put a few nodes into the table. their exact
- // distribution shouldn't matter much, although we need to
- // take care not to overflow any bucket.
- nodes := &nodesByDistance{target: testTarget.ID()}
- live := make(map[enode.ID]bool)
- numCandidates := 2 * bucketSize
- for i := 0; i < numCandidates; i++ {
- key := newkey()
- ip := net.IP{10, 13, 0, byte(i)}
- n := wrapNode(enode.NewV4(&key.PublicKey, ip, 0, 2000))
- // Ensure half of table content isn't verified live yet.
- if i > numCandidates/2 {
- n.livenessChecks = 1
- live[n.ID()] = true
- }
- nodes.push(n, numCandidates)
- }
- fillTable(test.table, nodes.entries, false)
-
- // ensure there's a bond with the test node,
- // findnode won't be accepted otherwise.
- remoteID := v4wire.EncodePubkey(&test.remotekey.PublicKey).ID()
- test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.IP, time.Now())
-
- // check that closest neighbors are returned.
- expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true)
- test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp})
- waitNeighbors := func(want []*node) {
- test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) {
- if len(p.Nodes) != len(want) {
- t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize)
- return
- }
- for i, n := range p.Nodes {
- if n.ID.ID() != want[i].ID() {
- t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, n, expected.entries[i])
- }
- if !live[n.ID.ID()] {
- t.Errorf("result includes dead node %v", n.ID.ID())
- }
- }
- })
- }
- // Receive replies.
- want := expected.entries
- if len(want) > v4wire.MaxNeighbors {
- waitNeighbors(want[:v4wire.MaxNeighbors])
- want = want[v4wire.MaxNeighbors:]
- }
- waitNeighbors(want)
-}
-
-func TestUDPv4_findnodeMultiReply(t *testing.T) {
- test := newUDPTest(t)
- defer test.close()
-
- rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey)
- test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.IP, time.Now())
-
- // queue a pending findnode request
- resultc, errc := make(chan []*node, 1), make(chan error, 1)
- go func() {
- rid := encodePubkey(&test.remotekey.PublicKey).id()
- ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget)
- if err != nil && len(ns) == 0 {
- errc <- err
- } else {
- resultc <- ns
- }
- }()
-
- // wait for the findnode to be sent.
- // after it is sent, the transport is waiting for a reply
- test.waitPacketOut(func(p *v4wire.Findnode, to *net.UDPAddr, hash []byte) {
- if p.Target != testTarget {
- t.Errorf("wrong target: got %v, want %v", p.Target, testTarget)
- }
- })
-
- // send the reply as two packets.
- list := []*node{
- wrapNode(enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")),
- wrapNode(enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")),
- wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")),
- wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")),
- }
- rpclist := make([]v4wire.Node, len(list))
- for i := range list {
- rpclist[i] = nodeToRPC(list[i])
- }
- test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[:2]})
- test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[2:]})
-
- // check that the sent neighbors are all returned by findnode
- select {
- case result := <-resultc:
- want := append(list[:2], list[3:]...)
- if !reflect.DeepEqual(result, want) {
- t.Errorf("neighbors mismatch:\n got: %v\n want: %v", result, want)
- }
- case err := <-errc:
- t.Errorf("findnode error: %v", err)
- case <-time.After(5 * time.Second):
- t.Error("findnode did not return within 5 seconds")
- }
-}
-
-// This test checks that reply matching of pong verifies the ping hash.
-func TestUDPv4_pingMatch(t *testing.T) {
- test := newUDPTest(t)
- defer test.close()
-
- randToken := make([]byte, 32)
- crand.Read(randToken)
-
- test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
- test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
- test.waitPacketOut(func(*v4wire.Ping, *net.UDPAddr, []byte) {})
- test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp})
-}
-
-// This test checks that reply matching of pong verifies the sender IP address.
-func TestUDPv4_pingMatchIP(t *testing.T) {
- test := newUDPTest(t)
- defer test.close()
-
- test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
- test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
-
- test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
- wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000}
- test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &v4wire.Pong{
- ReplyTok: hash,
- To: testLocalAnnounced,
- Expiration: futureExp,
- })
- })
-}
-
-func TestUDPv4_successfulPing(t *testing.T) {
- test := newUDPTest(t)
- added := make(chan *node, 1)
- test.table.nodeAddedHook = func(b *bucket, n *node) { added <- n }
- defer test.close()
-
- // The remote side sends a ping packet to initiate the exchange.
- go test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
-
- // The ping is replied to.
- test.waitPacketOut(func(p *v4wire.Pong, to *net.UDPAddr, hash []byte) {
- pinghash := test.sent[0][:32]
- if !bytes.Equal(p.ReplyTok, pinghash) {
- t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash)
- }
- wantTo := v4wire.Endpoint{
- // The mirrored UDP address is the UDP packet sender
- IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port),
- // The mirrored TCP port is the one from the ping packet
- TCP: testRemote.TCP,
- }
- if !reflect.DeepEqual(p.To, wantTo) {
- t.Errorf("got pong.To %v, want %v", p.To, wantTo)
- }
- })
-
- // Remote is unknown, the table pings back.
- test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
- if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) {
- t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint())
- }
- wantTo := v4wire.Endpoint{
- // The mirrored UDP address is the UDP packet sender.
- IP: test.remoteaddr.IP,
- UDP: uint16(test.remoteaddr.Port),
- TCP: 0,
- }
- if !reflect.DeepEqual(p.To, wantTo) {
- t.Errorf("got ping.To %v, want %v", p.To, wantTo)
- }
- test.packetIn(nil, &v4wire.Pong{ReplyTok: hash, Expiration: futureExp})
- })
-
- // The node should be added to the table shortly after getting the
- // pong packet.
- select {
- case n := <-added:
- rid := encodePubkey(&test.remotekey.PublicKey).id()
- if n.ID() != rid {
- t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid)
- }
- if !n.IP().Equal(test.remoteaddr.IP) {
- t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.IP)
- }
- if n.UDP() != test.remoteaddr.Port {
- t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port)
- }
- if n.TCP() != int(testRemote.TCP) {
- t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP(), testRemote.TCP)
- }
- case <-time.After(2 * time.Second):
- t.Errorf("node was not added within 2 seconds")
- }
-}
-
-// This test checks that EIP-868 requests work.
-func TestUDPv4_EIP868(t *testing.T) {
- test := newUDPTest(t)
- defer test.close()
-
- test.udp.localNode.Set(enr.WithEntry("foo", "bar"))
- wantNode := test.udp.localNode.Node()
-
- // ENR requests aren't allowed before endpoint proof.
- test.packetIn(errUnknownNode, &v4wire.ENRRequest{Expiration: futureExp})
-
- // Perform endpoint proof and check for sequence number in packet tail.
- test.packetIn(nil, &v4wire.Ping{Expiration: futureExp})
- test.waitPacketOut(func(p *v4wire.Pong, addr *net.UDPAddr, hash []byte) {
- if p.ENRSeq != wantNode.Seq() {
- t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq, wantNode.Seq())
- }
- })
- test.waitPacketOut(func(p *v4wire.Ping, addr *net.UDPAddr, hash []byte) {
- if p.ENRSeq != wantNode.Seq() {
- t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq, wantNode.Seq())
- }
- test.packetIn(nil, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash})
- })
-
- // Request should work now.
- test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp})
- test.waitPacketOut(func(p *v4wire.ENRResponse, addr *net.UDPAddr, hash []byte) {
- n, err := enode.New(enode.ValidSchemes, &p.Record)
- if err != nil {
- t.Fatalf("invalid record: %v", err)
- }
- if !reflect.DeepEqual(n, wantNode) {
- t.Fatalf("wrong node in ENRResponse: %v", n)
- }
- })
-}
-
-// This test verifies that a small network of nodes can boot up into a healthy state.
-func TestUDPv4_smallNetConvergence(t *testing.T) {
- t.Parallel()
-
- // Start the network.
- nodes := make([]*UDPv4, 4)
- for i := range nodes {
- var cfg Config
- if i > 0 {
- bn := nodes[0].Self()
- cfg.Bootnodes = []*enode.Node{bn}
- }
- nodes[i] = startLocalhostV4(t, cfg)
- defer nodes[i].Close()
- }
-
- // Run through the iterator on all nodes until
- // they have all found each other.
- status := make(chan error, len(nodes))
- for i := range nodes {
- node := nodes[i]
- go func() {
- found := make(map[enode.ID]bool, len(nodes))
- it := node.RandomNodes()
- for it.Next() {
- found[it.Node().ID()] = true
- if len(found) == len(nodes) {
- status <- nil
- return
- }
- }
- status <- fmt.Errorf("node %s didn't find all nodes", node.Self().ID().TerminalString())
- }()
- }
-
- // Wait for all status reports.
- timeout := time.NewTimer(30 * time.Second)
- defer timeout.Stop()
- for received := 0; received < len(nodes); {
- select {
- case <-timeout.C:
- for _, node := range nodes {
- node.Close()
- }
- case err := <-status:
- received++
- if err != nil {
- t.Error("ERROR:", err)
- return
- }
- }
- }
-}
-
-func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 {
- t.Helper()
-
- cfg.PrivateKey = newkey()
- db, _ := enode.OpenDB("")
- ln := enode.NewLocalNode(db, cfg.PrivateKey)
-
- // Prefix logs with node ID.
- lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString())
- cfg.Log = testlog.Logger(t, log.LevelTrace).With("node-id", lprefix)
-
- // Listen.
- socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}})
- if err != nil {
- t.Fatal(err)
- }
- realaddr := socket.LocalAddr().(*net.UDPAddr)
- ln.SetStaticIP(realaddr.IP)
- ln.SetFallbackUDP(realaddr.Port)
- udp, err := ListenV4(socket, ln, cfg)
- if err != nil {
- t.Fatal(err)
- }
- return udp
-}
-
-// dgramPipe is a fake UDP socket. It queues all sent datagrams.
-type dgramPipe struct {
- mu *sync.Mutex
- cond *sync.Cond
- closing chan struct{}
- closed bool
- queue []dgram
-}
-
-type dgram struct {
- to net.UDPAddr
- data []byte
-}
-
-func newpipe() *dgramPipe {
- mu := new(sync.Mutex)
- return &dgramPipe{
- closing: make(chan struct{}),
- cond: &sync.Cond{L: mu},
- mu: mu,
- }
-}
-
-// WriteToUDP queues a datagram.
-func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) {
- msg := make([]byte, len(b))
- copy(msg, b)
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.closed {
- return 0, errors.New("closed")
- }
- c.queue = append(c.queue, dgram{*to, b})
- c.cond.Signal()
- return len(b), nil
-}
-
-// ReadFromUDP just hangs until the pipe is closed.
-func (c *dgramPipe) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
- <-c.closing
- return 0, nil, io.EOF
-}
-
-func (c *dgramPipe) Close() error {
- c.mu.Lock()
- defer c.mu.Unlock()
- if !c.closed {
- close(c.closing)
- c.closed = true
- }
- c.cond.Broadcast()
- return nil
-}
-
-func (c *dgramPipe) LocalAddr() net.Addr {
- return &net.UDPAddr{IP: testLocal.IP, Port: int(testLocal.UDP)}
-}
-
-func (c *dgramPipe) receive() (dgram, error) {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- var timedOut bool
- timer := time.AfterFunc(3*time.Second, func() {
- c.mu.Lock()
- timedOut = true
- c.mu.Unlock()
- c.cond.Broadcast()
- })
- defer timer.Stop()
-
- for len(c.queue) == 0 && !c.closed && !timedOut {
- c.cond.Wait()
- }
- if c.closed {
- return dgram{}, errClosed
- }
- if timedOut {
- return dgram{}, errTimeout
- }
- p := c.queue[0]
- copy(c.queue, c.queue[1:])
- c.queue = c.queue[:len(c.queue)-1]
- return p, nil
-}
diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go
deleted file mode 100644
index 9c59359fb2..0000000000
--- a/p2p/discover/v4wire/v4wire.go
+++ /dev/null
@@ -1,296 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package v4wire implements the Discovery v4 Wire Protocol.
-package v4wire
-
-import (
- "bytes"
- "crypto/ecdsa"
- "crypto/elliptic"
- "errors"
- "fmt"
- "math/big"
- "net"
- "time"
-
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// RPC packet types
-const (
- PingPacket = iota + 1 // zero is 'reserved'
- PongPacket
- FindnodePacket
- NeighborsPacket
- ENRRequestPacket
- ENRResponsePacket
-)
-
-// RPC request structures
-type (
- Ping struct {
- Version uint
- From, To Endpoint
- Expiration uint64
- ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868.
-
- // Ignore additional fields (for forward compatibility).
- Rest []rlp.RawValue `rlp:"tail"`
- }
-
- // Pong is the reply to ping.
- Pong struct {
- // This field should mirror the UDP envelope address
- // of the ping packet, which provides a way to discover the
- // external address (after NAT).
- To Endpoint
- ReplyTok []byte // This contains the hash of the ping packet.
- Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
- ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868.
-
- // Ignore additional fields (for forward compatibility).
- Rest []rlp.RawValue `rlp:"tail"`
- }
-
- // Findnode is a query for nodes close to the given target.
- Findnode struct {
- Target Pubkey
- Expiration uint64
- // Ignore additional fields (for forward compatibility).
- Rest []rlp.RawValue `rlp:"tail"`
- }
-
- // Neighbors is the reply to findnode.
- Neighbors struct {
- Nodes []Node
- Expiration uint64
- // Ignore additional fields (for forward compatibility).
- Rest []rlp.RawValue `rlp:"tail"`
- }
-
- // ENRRequest queries for the remote node's record.
- ENRRequest struct {
- Expiration uint64
- // Ignore additional fields (for forward compatibility).
- Rest []rlp.RawValue `rlp:"tail"`
- }
-
- // ENRResponse is the reply to ENRRequest.
- ENRResponse struct {
- ReplyTok []byte // Hash of the ENRRequest packet.
- Record enr.Record
- // Ignore additional fields (for forward compatibility).
- Rest []rlp.RawValue `rlp:"tail"`
- }
-)
-
-// MaxNeighbors is the maximum number of neighbor nodes in a Neighbors packet.
-const MaxNeighbors = 12
-
-// This code computes the MaxNeighbors constant value.
-
-// func init() {
-// var maxNeighbors int
-// p := Neighbors{Expiration: ^uint64(0)}
-// maxSizeNode := Node{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
-// for n := 0; ; n++ {
-// p.Nodes = append(p.Nodes, maxSizeNode)
-// size, _, err := rlp.EncodeToReader(p)
-// if err != nil {
-// // If this ever happens, it will be caught by the unit tests.
-// panic("cannot encode: " + err.Error())
-// }
-// if headSize+size+1 >= 1280 {
-// maxNeighbors = n
-// break
-// }
-// }
-// fmt.Println("maxNeighbors", maxNeighbors)
-// }
-
-// Pubkey represents an encoded 64-byte secp256k1 public key.
-type Pubkey [64]byte
-
-// ID returns the node ID corresponding to the public key.
-func (e Pubkey) ID() enode.ID {
- return enode.ID(crypto.Keccak256Hash(e[:]))
-}
-
-// Node represents information about a node.
-type Node struct {
- IP net.IP // len 4 for IPv4 or 16 for IPv6
- UDP uint16 // for discovery protocol
- TCP uint16 // for RLPx protocol
- ID Pubkey
-}
-
-// Endpoint represents a network endpoint.
-type Endpoint struct {
- IP net.IP // len 4 for IPv4 or 16 for IPv6
- UDP uint16 // for discovery protocol
- TCP uint16 // for RLPx protocol
-}
-
-// NewEndpoint creates an endpoint.
-func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint {
- ip := net.IP{}
- if ip4 := addr.IP.To4(); ip4 != nil {
- ip = ip4
- } else if ip6 := addr.IP.To16(); ip6 != nil {
- ip = ip6
- }
- return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
-}
-
-type Packet interface {
- // Name is the name of the package, for logging purposes.
- Name() string
- // Kind is the packet type, for logging purposes.
- Kind() byte
-}
-
-func (req *Ping) Name() string { return "PING/v4" }
-func (req *Ping) Kind() byte { return PingPacket }
-
-func (req *Pong) Name() string { return "PONG/v4" }
-func (req *Pong) Kind() byte { return PongPacket }
-
-func (req *Findnode) Name() string { return "FINDNODE/v4" }
-func (req *Findnode) Kind() byte { return FindnodePacket }
-
-func (req *Neighbors) Name() string { return "NEIGHBORS/v4" }
-func (req *Neighbors) Kind() byte { return NeighborsPacket }
-
-func (req *ENRRequest) Name() string { return "ENRREQUEST/v4" }
-func (req *ENRRequest) Kind() byte { return ENRRequestPacket }
-
-func (req *ENRResponse) Name() string { return "ENRRESPONSE/v4" }
-func (req *ENRResponse) Kind() byte { return ENRResponsePacket }
-
-// Expired checks whether the given UNIX time stamp is in the past.
-func Expired(ts uint64) bool {
- return time.Unix(int64(ts), 0).Before(time.Now())
-}
-
-// Encoder/decoder.
-
-const (
- macSize = 32
- sigSize = crypto.SignatureLength
- headSize = macSize + sigSize // space of packet frame data
-)
-
-var (
- ErrPacketTooSmall = errors.New("too small")
- ErrBadHash = errors.New("bad hash")
- ErrBadPoint = errors.New("invalid curve point")
-)
-
-var headSpace = make([]byte, headSize)
-
-// Decode reads a discovery v4 packet.
-func Decode(input []byte) (Packet, Pubkey, []byte, error) {
- if len(input) < headSize+1 {
- return nil, Pubkey{}, nil, ErrPacketTooSmall
- }
- hash, sig, sigdata := input[:macSize], input[macSize:headSize], input[headSize:]
- shouldhash := crypto.Keccak256(input[macSize:])
- if !bytes.Equal(hash, shouldhash) {
- return nil, Pubkey{}, nil, ErrBadHash
- }
- fromKey, err := recoverNodeKey(crypto.Keccak256(input[headSize:]), sig)
- if err != nil {
- return nil, fromKey, hash, err
- }
-
- var req Packet
- switch ptype := sigdata[0]; ptype {
- case PingPacket:
- req = new(Ping)
- case PongPacket:
- req = new(Pong)
- case FindnodePacket:
- req = new(Findnode)
- case NeighborsPacket:
- req = new(Neighbors)
- case ENRRequestPacket:
- req = new(ENRRequest)
- case ENRResponsePacket:
- req = new(ENRResponse)
- default:
- return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype)
- }
- // Here we use NewStream to allow for additional data after the first
- // RLP object (forward-compatibility).
- s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
- err = s.Decode(req)
- return req, fromKey, hash, err
-}
-
-// Encode encodes a discovery packet.
-func Encode(priv *ecdsa.PrivateKey, req Packet) (packet, hash []byte, err error) {
- b := new(bytes.Buffer)
- b.Write(headSpace)
- b.WriteByte(req.Kind())
- if err := rlp.Encode(b, req); err != nil {
- return nil, nil, err
- }
- packet = b.Bytes()
- sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv)
- if err != nil {
- return nil, nil, err
- }
- copy(packet[macSize:], sig)
- // Add the hash to the front. Note: this doesn't protect the packet in any way.
- hash = crypto.Keccak256(packet[macSize:])
- copy(packet, hash)
- return packet, hash, nil
-}
-
-// recoverNodeKey computes the public key used to sign the given hash from the signature.
-func recoverNodeKey(hash, sig []byte) (key Pubkey, err error) {
- pubkey, err := crypto.Ecrecover(hash, sig)
- if err != nil {
- return key, err
- }
- copy(key[:], pubkey[1:])
- return key, nil
-}
-
-// EncodePubkey encodes a secp256k1 public key.
-func EncodePubkey(key *ecdsa.PublicKey) Pubkey {
- var e Pubkey
- math.ReadBits(key.X, e[:len(e)/2])
- math.ReadBits(key.Y, e[len(e)/2:])
- return e
-}
-
-// DecodePubkey reads an encoded secp256k1 public key.
-func DecodePubkey(curve elliptic.Curve, e Pubkey) (*ecdsa.PublicKey, error) {
- p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}
- half := len(e) / 2
- p.X.SetBytes(e[:half])
- p.Y.SetBytes(e[half:])
- if !p.Curve.IsOnCurve(p.X, p.Y) {
- return nil, ErrBadPoint
- }
- return p, nil
-}
diff --git a/p2p/discover/v4wire/v4wire_test.go b/p2p/discover/v4wire/v4wire_test.go
deleted file mode 100644
index 38820f3b48..0000000000
--- a/p2p/discover/v4wire/v4wire_test.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package v4wire
-
-import (
- "encoding/hex"
- "net"
- "reflect"
- "testing"
-
- "github.com/davecgh/go-spew/spew"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// EIP-8 test vectors.
-var testPackets = []struct {
- input string
- wantPacket interface{}
-}{
- {
- input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355",
- wantPacket: &Ping{
- Version: 4,
- From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544},
- To: Endpoint{net.ParseIP("::1"), 2222, 3333},
- Expiration: 1136239445,
- },
- },
- {
- input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102",
- wantPacket: &Ping{
- Version: 4,
- From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544},
- To: Endpoint{net.ParseIP("::1"), 2222, 3333},
- Expiration: 1136239445,
- ENRSeq: 1,
- Rest: []rlp.RawValue{{0x02}},
- },
- },
- {
- input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396",
- wantPacket: &Findnode{
- Target: hexPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"),
- Expiration: 1136239445,
- Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}},
- },
- },
- {
- input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0",
- wantPacket: &Neighbors{
- Nodes: []Node{
- {
- ID: hexPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
- IP: net.ParseIP("99.33.22.55").To4(),
- UDP: 4444,
- TCP: 4445,
- },
- {
- ID: hexPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
- IP: net.ParseIP("1.2.3.4").To4(),
- UDP: 1,
- TCP: 1,
- },
- {
- ID: hexPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
- IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
- UDP: 3333,
- TCP: 3333,
- },
- {
- ID: hexPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
- IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"),
- UDP: 999,
- TCP: 1000,
- },
- },
- Expiration: 1136239445,
- Rest: []rlp.RawValue{{0x01}, {0x02}, {0x03}},
- },
- },
-}
-
-// This test checks that the decoder accepts packets according to EIP-8.
-func TestForwardCompatibility(t *testing.T) {
- testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
- wantNodeKey := EncodePubkey(&testkey.PublicKey)
-
- for _, test := range testPackets {
- input, err := hex.DecodeString(test.input)
- if err != nil {
- t.Fatalf("invalid hex: %s", test.input)
- }
- packet, nodekey, _, err := Decode(input)
- if err != nil {
- t.Errorf("did not accept packet %s\n%v", test.input, err)
- continue
- }
- if !reflect.DeepEqual(packet, test.wantPacket) {
- t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket))
- }
- if nodekey != wantNodeKey {
- t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey)
- }
- }
-}
-
-func hexPubkey(h string) (ret Pubkey) {
- b, err := hex.DecodeString(h)
- if err != nil {
- panic(err)
- }
- if len(b) != len(ret) {
- panic("invalid length")
- }
- copy(ret[:], b)
- return ret
-}
diff --git a/p2p/discover/v5_talk.go b/p2p/discover/v5_talk.go
deleted file mode 100644
index c1f6787940..0000000000
--- a/p2p/discover/v5_talk.go
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "net"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/discover/v5wire"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// This is a limit for the number of concurrent talk requests.
-const maxActiveTalkRequests = 1024
-
-// This is the timeout for acquiring a handler execution slot for a talk request.
-// The timeout should be short enough to fit within the request timeout.
-const talkHandlerLaunchTimeout = 400 * time.Millisecond
-
-// TalkRequestHandler callback processes a talk request and returns a response.
-//
-// Note that talk handlers are expected to come up with a response very quickly, within at
-// most 200ms or so. If the handler takes longer than that, the remote end may time out
-// and wont receive the response.
-type TalkRequestHandler func(enode.ID, *net.UDPAddr, []byte) []byte
-
-type talkSystem struct {
- transport *UDPv5
-
- mutex sync.Mutex
- handlers map[string]TalkRequestHandler
- slots chan struct{}
- lastLog time.Time
- dropCount int
-}
-
-func newTalkSystem(transport *UDPv5) *talkSystem {
- t := &talkSystem{
- transport: transport,
- handlers: make(map[string]TalkRequestHandler),
- slots: make(chan struct{}, maxActiveTalkRequests),
- }
- for i := 0; i < cap(t.slots); i++ {
- t.slots <- struct{}{}
- }
- return t
-}
-
-// register adds a protocol handler.
-func (t *talkSystem) register(protocol string, handler TalkRequestHandler) {
- t.mutex.Lock()
- t.handlers[protocol] = handler
- t.mutex.Unlock()
-}
-
-// handleRequest handles a talk request.
-func (t *talkSystem) handleRequest(id enode.ID, addr *net.UDPAddr, req *v5wire.TalkRequest) {
- t.mutex.Lock()
- handler, ok := t.handlers[req.Protocol]
- t.mutex.Unlock()
-
- if !ok {
- resp := &v5wire.TalkResponse{ReqID: req.ReqID}
- t.transport.sendResponse(id, addr, resp)
- return
- }
-
- // Wait for a slot to become available, then run the handler.
- timeout := time.NewTimer(talkHandlerLaunchTimeout)
- defer timeout.Stop()
- select {
- case <-t.slots:
- go func() {
- defer func() { t.slots <- struct{}{} }()
- respMessage := handler(id, addr, req.Message)
- resp := &v5wire.TalkResponse{ReqID: req.ReqID, Message: respMessage}
- t.transport.sendFromAnotherThread(id, addr, resp)
- }()
- case <-timeout.C:
- // Couldn't get it in time, drop the request.
- if time.Since(t.lastLog) > 5*time.Second {
- log.Warn("Dropping TALKREQ due to overload", "ndrop", t.dropCount)
- t.lastLog = time.Now()
- t.dropCount++
- }
- case <-t.transport.closeCtx.Done():
- // Transport closed, drop the request.
- }
-}
-
-// wait blocks until all active requests have finished, and prevents new request
-// handlers from being launched.
-func (t *talkSystem) wait() {
- for i := 0; i < cap(t.slots); i++ {
- <-t.slots
- }
-}
diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go
deleted file mode 100644
index 8b3e33d37c..0000000000
--- a/p2p/discover/v5_udp.go
+++ /dev/null
@@ -1,910 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "bytes"
- "context"
- "crypto/ecdsa"
- crand "crypto/rand"
- "errors"
- "fmt"
- "io"
- "net"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/discover/v5wire"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/netutil"
-)
-
-const (
- lookupRequestLimit = 3 // max requests against a single node during lookup
- findnodeResultLimit = 16 // applies in FINDNODE handler
- totalNodesResponseLimit = 5 // applies in waitForNodes
-
- respTimeoutV5 = 700 * time.Millisecond
-)
-
-// codecV5 is implemented by v5wire.Codec (and testCodec).
-//
-// The UDPv5 transport is split into two objects: the codec object deals with
-// encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns.
-type codecV5 interface {
- // Encode encodes a packet.
- Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error)
-
- // Decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails.
- // The *enode.Node return value is non-nil when the input contains a handshake response.
- Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error)
-}
-
-// UDPv5 is the implementation of protocol version 5.
-type UDPv5 struct {
- // static fields
- conn UDPConn
- tab *Table
- netrestrict *netutil.Netlist
- priv *ecdsa.PrivateKey
- localNode *enode.LocalNode
- db *enode.DB
- log log.Logger
- clock mclock.Clock
- validSchemes enr.IdentityScheme
-
- // misc buffers used during message handling
- logcontext []interface{}
-
- // talkreq handler registry
- talk *talkSystem
-
- // channels into dispatch
- packetInCh chan ReadPacket
- readNextCh chan struct{}
- callCh chan *callV5
- callDoneCh chan *callV5
- respTimeoutCh chan *callTimeout
- sendCh chan sendRequest
- unhandled chan<- ReadPacket
-
- // state of dispatch
- codec codecV5
- activeCallByNode map[enode.ID]*callV5
- activeCallByAuth map[v5wire.Nonce]*callV5
- callQueue map[enode.ID][]*callV5
-
- // shutdown stuff
- closeOnce sync.Once
- closeCtx context.Context
- cancelCloseCtx context.CancelFunc
- wg sync.WaitGroup
-}
-
-type sendRequest struct {
- destID enode.ID
- destAddr *net.UDPAddr
- msg v5wire.Packet
-}
-
-// callV5 represents a remote procedure call against another node.
-type callV5 struct {
- id enode.ID
- addr *net.UDPAddr
- node *enode.Node // This is required to perform handshakes.
-
- packet v5wire.Packet
- responseType byte // expected packet type of response
- reqid []byte
- ch chan v5wire.Packet // responses sent here
- err chan error // errors sent here
-
- // Valid for active calls only:
- nonce v5wire.Nonce // nonce of request packet
- handshakeCount int // # times we attempted handshake for this call
- challenge *v5wire.Whoareyou // last sent handshake challenge
- timeout mclock.Timer
-}
-
-// callTimeout is the response timeout event of a call.
-type callTimeout struct {
- c *callV5
- timer mclock.Timer
-}
-
-// ListenV5 listens on the given connection.
-func ListenV5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
- t, err := newUDPv5(conn, ln, cfg)
- if err != nil {
- return nil, err
- }
- go t.tab.loop()
- t.wg.Add(2)
- go t.readLoop()
- go t.dispatch()
- return t, nil
-}
-
-// newUDPv5 creates a UDPv5 transport, but doesn't start any goroutines.
-func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
- closeCtx, cancelCloseCtx := context.WithCancel(context.Background())
- cfg = cfg.withDefaults()
- t := &UDPv5{
- // static fields
- conn: newMeteredConn(conn),
- localNode: ln,
- db: ln.Database(),
- netrestrict: cfg.NetRestrict,
- priv: cfg.PrivateKey,
- log: cfg.Log,
- validSchemes: cfg.ValidSchemes,
- clock: cfg.Clock,
- // channels into dispatch
- packetInCh: make(chan ReadPacket, 1),
- readNextCh: make(chan struct{}, 1),
- callCh: make(chan *callV5),
- callDoneCh: make(chan *callV5),
- sendCh: make(chan sendRequest),
- respTimeoutCh: make(chan *callTimeout),
- unhandled: cfg.Unhandled,
- // state of dispatch
- codec: v5wire.NewCodec(ln, cfg.PrivateKey, cfg.Clock, cfg.V5ProtocolID),
- activeCallByNode: make(map[enode.ID]*callV5),
- activeCallByAuth: make(map[v5wire.Nonce]*callV5),
- callQueue: make(map[enode.ID][]*callV5),
- // shutdown
- closeCtx: closeCtx,
- cancelCloseCtx: cancelCloseCtx,
- }
- t.talk = newTalkSystem(t)
- tab, err := newMeteredTable(t, t.db, cfg)
- if err != nil {
- return nil, err
- }
- t.tab = tab
- return t, nil
-}
-
-// Self returns the local node record.
-func (t *UDPv5) Self() *enode.Node {
- return t.localNode.Node()
-}
-
-// Close shuts down packet processing.
-func (t *UDPv5) Close() {
- t.closeOnce.Do(func() {
- t.cancelCloseCtx()
- t.conn.Close()
- t.talk.wait()
- t.wg.Wait()
- t.tab.close()
- })
-}
-
-// Ping sends a ping message to the given node.
-func (t *UDPv5) Ping(n *enode.Node) error {
- _, err := t.ping(n)
- return err
-}
-
-// Resolve searches for a specific node with the given ID and tries to get the most recent
-// version of the node record for it. It returns n if the node could not be resolved.
-func (t *UDPv5) Resolve(n *enode.Node) *enode.Node {
- if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
- n = intable
- }
- // Try asking directly. This works if the node is still responding on the endpoint we have.
- if resp, err := t.RequestENR(n); err == nil {
- return resp
- }
- // Otherwise do a network lookup.
- result := t.Lookup(n.ID())
- for _, rn := range result {
- if rn.ID() == n.ID() && rn.Seq() > n.Seq() {
- return rn
- }
- }
- return n
-}
-
-// AllNodes returns all the nodes stored in the local table.
-func (t *UDPv5) AllNodes() []*enode.Node {
- t.tab.mutex.Lock()
- defer t.tab.mutex.Unlock()
- nodes := make([]*enode.Node, 0)
-
- for _, b := range &t.tab.buckets {
- for _, n := range b.entries {
- nodes = append(nodes, unwrapNode(n))
- }
- }
- return nodes
-}
-
-// LocalNode returns the current local node running the
-// protocol.
-func (t *UDPv5) LocalNode() *enode.LocalNode {
- return t.localNode
-}
-
-// RegisterTalkHandler adds a handler for 'talk requests'. The handler function is called
-// whenever a request for the given protocol is received and should return the response
-// data or nil.
-func (t *UDPv5) RegisterTalkHandler(protocol string, handler TalkRequestHandler) {
- t.talk.register(protocol, handler)
-}
-
-// TalkRequest sends a talk request to a node and waits for a response.
-func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]byte, error) {
- req := &v5wire.TalkRequest{Protocol: protocol, Message: request}
- resp := t.callToNode(n, v5wire.TalkResponseMsg, req)
- defer t.callDone(resp)
- select {
- case respMsg := <-resp.ch:
- return respMsg.(*v5wire.TalkResponse).Message, nil
- case err := <-resp.err:
- return nil, err
- }
-}
-
-// TalkRequestToID sends a talk request to a node and waits for a response.
-func (t *UDPv5) TalkRequestToID(id enode.ID, addr *net.UDPAddr, protocol string, request []byte) ([]byte, error) {
- req := &v5wire.TalkRequest{Protocol: protocol, Message: request}
- resp := t.callToID(id, addr, v5wire.TalkResponseMsg, req)
- defer t.callDone(resp)
- select {
- case respMsg := <-resp.ch:
- return respMsg.(*v5wire.TalkResponse).Message, nil
- case err := <-resp.err:
- return nil, err
- }
-}
-
-// RandomNodes returns an iterator that finds random nodes in the DHT.
-func (t *UDPv5) RandomNodes() enode.Iterator {
- if t.tab.len() == 0 {
- // All nodes were dropped, refresh. The very first query will hit this
- // case and run the bootstrapping logic.
- <-t.tab.refresh()
- }
-
- return newLookupIterator(t.closeCtx, t.newRandomLookup)
-}
-
-// Lookup performs a recursive lookup for the given target.
-// It returns the closest nodes to target.
-func (t *UDPv5) Lookup(target enode.ID) []*enode.Node {
- return t.newLookup(t.closeCtx, target).run()
-}
-
-// lookupRandom looks up a random target.
-// This is needed to satisfy the transport interface.
-func (t *UDPv5) lookupRandom() []*enode.Node {
- return t.newRandomLookup(t.closeCtx).run()
-}
-
-// lookupSelf looks up our own node ID.
-// This is needed to satisfy the transport interface.
-func (t *UDPv5) lookupSelf() []*enode.Node {
- return t.newLookup(t.closeCtx, t.Self().ID()).run()
-}
-
-func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup {
- var target enode.ID
- crand.Read(target[:])
- return t.newLookup(ctx, target)
-}
-
-func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
- return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
- return t.lookupWorker(n, target)
- })
-}
-
-// lookupWorker performs FINDNODE calls against a single node during lookup.
-func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
- var (
- dists = lookupDistances(target, destNode.ID())
- nodes = nodesByDistance{target: target}
- err error
- )
- var r []*enode.Node
- r, err = t.findnode(unwrapNode(destNode), dists)
- if errors.Is(err, errClosed) {
- return nil, err
- }
- for _, n := range r {
- if n.ID() != t.Self().ID() {
- nodes.push(wrapNode(n), findnodeResultLimit)
- }
- }
- return nodes.entries, err
-}
-
-// lookupDistances computes the distance parameter for FINDNODE calls to dest.
-// It chooses distances adjacent to logdist(target, dest), e.g. for a target
-// with logdist(target, dest) = 255 the result is [255, 256, 254].
-func lookupDistances(target, dest enode.ID) (dists []uint) {
- td := enode.LogDist(target, dest)
- dists = append(dists, uint(td))
- for i := 1; len(dists) < lookupRequestLimit; i++ {
- if td+i <= 256 {
- dists = append(dists, uint(td+i))
- }
- if td-i > 0 {
- dists = append(dists, uint(td-i))
- }
- }
- return dists
-}
-
-// ping calls PING on a node and waits for a PONG response.
-func (t *UDPv5) ping(n *enode.Node) (uint64, error) {
- req := &v5wire.Ping{ENRSeq: t.localNode.Node().Seq()}
- resp := t.callToNode(n, v5wire.PongMsg, req)
- defer t.callDone(resp)
-
- select {
- case pong := <-resp.ch:
- return pong.(*v5wire.Pong).ENRSeq, nil
- case err := <-resp.err:
- return 0, err
- }
-}
-
-// RequestENR requests n's record.
-func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) {
- nodes, err := t.findnode(n, []uint{0})
- if err != nil {
- return nil, err
- }
- if len(nodes) != 1 {
- return nil, fmt.Errorf("%d nodes in response for distance zero", len(nodes))
- }
- return nodes[0], nil
-}
-
-// findnode calls FINDNODE on a node and waits for responses.
-func (t *UDPv5) findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) {
- resp := t.callToNode(n, v5wire.NodesMsg, &v5wire.Findnode{Distances: distances})
- return t.waitForNodes(resp, distances)
-}
-
-// waitForNodes waits for NODES responses to the given call.
-func (t *UDPv5) waitForNodes(c *callV5, distances []uint) ([]*enode.Node, error) {
- defer t.callDone(c)
-
- var (
- nodes []*enode.Node
- seen = make(map[enode.ID]struct{})
- received, total = 0, -1
- )
- for {
- select {
- case responseP := <-c.ch:
- response := responseP.(*v5wire.Nodes)
- for _, record := range response.Nodes {
- node, err := t.verifyResponseNode(c, record, distances, seen)
- if err != nil {
- t.log.Debug("Invalid record in "+response.Name(), "id", c.node.ID(), "err", err)
- continue
- }
- nodes = append(nodes, node)
- }
- if total == -1 {
- total = min(int(response.RespCount), totalNodesResponseLimit)
- }
- if received++; received == total {
- return nodes, nil
- }
- case err := <-c.err:
- return nodes, err
- }
- }
-}
-
-// verifyResponseNode checks validity of a record in a NODES response.
-func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, seen map[enode.ID]struct{}) (*enode.Node, error) {
- node, err := enode.New(t.validSchemes, r)
- if err != nil {
- return nil, err
- }
- if err := netutil.CheckRelayIP(c.addr.IP, node.IP()); err != nil {
- return nil, err
- }
- if t.netrestrict != nil && !t.netrestrict.Contains(node.IP()) {
- return nil, errors.New("not contained in netrestrict list")
- }
- if node.UDP() <= 1024 {
- return nil, errLowPort
- }
- if distances != nil {
- nd := enode.LogDist(c.id, node.ID())
- if !containsUint(uint(nd), distances) {
- return nil, errors.New("does not match any requested distance")
- }
- }
- if _, ok := seen[node.ID()]; ok {
- return nil, fmt.Errorf("duplicate record")
- }
- seen[node.ID()] = struct{}{}
- return node, nil
-}
-
-func containsUint(x uint, xs []uint) bool {
- for _, v := range xs {
- if x == v {
- return true
- }
- }
- return false
-}
-
-// callToNode sends the given call and sets up a handler for response packets (of message
-// type responseType). Responses are dispatched to the call's response channel.
-func (t *UDPv5) callToNode(n *enode.Node, responseType byte, req v5wire.Packet) *callV5 {
- addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
- c := &callV5{id: n.ID(), addr: addr, node: n}
- t.initCall(c, responseType, req)
- return c
-}
-
-// callToID is like callToNode, but for cases where the node record is not available.
-func (t *UDPv5) callToID(id enode.ID, addr *net.UDPAddr, responseType byte, req v5wire.Packet) *callV5 {
- c := &callV5{id: id, addr: addr}
- t.initCall(c, responseType, req)
- return c
-}
-
-func (t *UDPv5) initCall(c *callV5, responseType byte, packet v5wire.Packet) {
- c.packet = packet
- c.responseType = responseType
- c.reqid = make([]byte, 8)
- c.ch = make(chan v5wire.Packet, 1)
- c.err = make(chan error, 1)
- // Assign request ID.
- crand.Read(c.reqid)
- packet.SetRequestID(c.reqid)
- // Send call to dispatch.
- select {
- case t.callCh <- c:
- case <-t.closeCtx.Done():
- c.err <- errClosed
- }
-}
-
-// callDone tells dispatch that the active call is done.
-func (t *UDPv5) callDone(c *callV5) {
- // This needs a loop because further responses may be incoming until the
- // send to callDoneCh has completed. Such responses need to be discarded
- // in order to avoid blocking the dispatch loop.
- for {
- select {
- case <-c.ch:
- // late response, discard.
- case <-c.err:
- // late error, discard.
- case t.callDoneCh <- c:
- return
- case <-t.closeCtx.Done():
- return
- }
- }
-}
-
-// dispatch runs in its own goroutine, handles incoming packets and deals with calls.
-//
-// For any destination node there is at most one 'active call', stored in the t.activeCall*
-// maps. A call is made active when it is sent. The active call can be answered by a
-// matching response, in which case c.ch receives the response; or by timing out, in which case
-// c.err receives the error. When the function that created the call signals the active
-// call is done through callDone, the next call from the call queue is started.
-//
-// Calls may also be answered by a WHOAREYOU packet referencing the call packet's authTag.
-// When that happens the call is simply re-sent to complete the handshake. We allow one
-// handshake attempt per call.
-func (t *UDPv5) dispatch() {
- defer t.wg.Done()
-
- // Arm first read.
- t.readNextCh <- struct{}{}
-
- for {
- select {
- case c := <-t.callCh:
- t.callQueue[c.id] = append(t.callQueue[c.id], c)
- t.sendNextCall(c.id)
-
- case ct := <-t.respTimeoutCh:
- active := t.activeCallByNode[ct.c.id]
- if ct.c == active && ct.timer == active.timeout {
- ct.c.err <- errTimeout
- }
-
- case c := <-t.callDoneCh:
- active := t.activeCallByNode[c.id]
- if active != c {
- panic("BUG: callDone for inactive call")
- }
- c.timeout.Stop()
- delete(t.activeCallByAuth, c.nonce)
- delete(t.activeCallByNode, c.id)
- t.sendNextCall(c.id)
-
- case r := <-t.sendCh:
- t.send(r.destID, r.destAddr, r.msg, nil)
-
- case p := <-t.packetInCh:
- t.handlePacket(p.Data, p.Addr)
- // Arm next read.
- t.readNextCh <- struct{}{}
-
- case <-t.closeCtx.Done():
- close(t.readNextCh)
- for id, queue := range t.callQueue {
- for _, c := range queue {
- c.err <- errClosed
- }
- delete(t.callQueue, id)
- }
- for id, c := range t.activeCallByNode {
- c.err <- errClosed
- delete(t.activeCallByNode, id)
- delete(t.activeCallByAuth, c.nonce)
- }
- return
- }
- }
-}
-
-// startResponseTimeout sets the response timer for a call.
-func (t *UDPv5) startResponseTimeout(c *callV5) {
- if c.timeout != nil {
- c.timeout.Stop()
- }
- var (
- timer mclock.Timer
- done = make(chan struct{})
- )
- timer = t.clock.AfterFunc(respTimeoutV5, func() {
- <-done
- select {
- case t.respTimeoutCh <- &callTimeout{c, timer}:
- case <-t.closeCtx.Done():
- }
- })
- c.timeout = timer
- close(done)
-}
-
-// sendNextCall sends the next call in the call queue if there is no active call.
-func (t *UDPv5) sendNextCall(id enode.ID) {
- queue := t.callQueue[id]
- if len(queue) == 0 || t.activeCallByNode[id] != nil {
- return
- }
- t.activeCallByNode[id] = queue[0]
- t.sendCall(t.activeCallByNode[id])
- if len(queue) == 1 {
- delete(t.callQueue, id)
- } else {
- copy(queue, queue[1:])
- t.callQueue[id] = queue[:len(queue)-1]
- }
-}
-
-// sendCall encodes and sends a request packet to the call's recipient node.
-// This performs a handshake if needed.
-func (t *UDPv5) sendCall(c *callV5) {
- // The call might have a nonce from a previous handshake attempt. Remove the entry for
- // the old nonce because we're about to generate a new nonce for this call.
- if c.nonce != (v5wire.Nonce{}) {
- delete(t.activeCallByAuth, c.nonce)
- }
-
- newNonce, _ := t.send(c.id, c.addr, c.packet, c.challenge)
- c.nonce = newNonce
- t.activeCallByAuth[newNonce] = c
- t.startResponseTimeout(c)
-}
-
-// sendResponse sends a response packet to the given node.
-// This doesn't trigger a handshake even if no keys are available.
-func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error {
- _, err := t.send(toID, toAddr, packet, nil)
- return err
-}
-
-func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) {
- select {
- case t.sendCh <- sendRequest{toID, toAddr, packet}:
- case <-t.closeCtx.Done():
- }
-}
-
-// send sends a packet to the given node.
-func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
- addr := toAddr.String()
- t.logcontext = append(t.logcontext[:0], "id", toID, "addr", addr)
- t.logcontext = packet.AppendLogInfo(t.logcontext)
-
- enc, nonce, err := t.codec.Encode(toID, addr, packet, c)
- if err != nil {
- t.logcontext = append(t.logcontext, "err", err)
- t.log.Warn(">> "+packet.Name(), t.logcontext...)
- return nonce, err
- }
-
- _, err = t.conn.WriteToUDP(enc, toAddr)
- t.log.Trace(">> "+packet.Name(), t.logcontext...)
- return nonce, err
-}
-
-// readLoop runs in its own goroutine and reads packets from the network.
-func (t *UDPv5) readLoop() {
- defer t.wg.Done()
-
- buf := make([]byte, maxPacketSize)
- for range t.readNextCh {
- nbytes, from, err := t.conn.ReadFromUDP(buf)
- if netutil.IsTemporaryError(err) {
- // Ignore temporary read errors.
- t.log.Debug("Temporary UDP read error", "err", err)
- continue
- } else if err != nil {
- // Shut down the loop for permanent errors.
- if !errors.Is(err, io.EOF) {
- t.log.Debug("UDP read error", "err", err)
- }
- return
- }
- t.dispatchReadPacket(from, buf[:nbytes])
- }
-}
-
-// dispatchReadPacket sends a packet into the dispatch loop.
-func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
- select {
- case t.packetInCh <- ReadPacket{content, from}:
- return true
- case <-t.closeCtx.Done():
- return false
- }
-}
-
-// handlePacket decodes and processes an incoming packet from the network.
-func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
- addr := fromAddr.String()
- fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr)
- if err != nil {
- if t.unhandled != nil && v5wire.IsInvalidHeader(err) {
- // The packet seems unrelated to discv5, send it to the next protocol.
- // t.log.Trace("Unhandled discv5 packet", "id", fromID, "addr", addr, "err", err)
- up := ReadPacket{Data: make([]byte, len(rawpacket)), Addr: fromAddr}
- copy(up.Data, rawpacket)
- t.unhandled <- up
- return nil
- }
- t.log.Debug("Bad discv5 packet", "id", fromID, "addr", addr, "err", err)
- return err
- }
- if fromNode != nil {
- // Handshake succeeded, add to table.
- t.tab.addSeenNode(wrapNode(fromNode))
- }
- if packet.Kind() != v5wire.WhoareyouPacket {
- // WHOAREYOU logged separately to report errors.
- t.logcontext = append(t.logcontext[:0], "id", fromID, "addr", addr)
- t.logcontext = packet.AppendLogInfo(t.logcontext)
- t.log.Trace("<< "+packet.Name(), t.logcontext...)
- }
- t.handle(packet, fromID, fromAddr)
- return nil
-}
-
-// handleCallResponse dispatches a response packet to the call waiting for it.
-func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool {
- ac := t.activeCallByNode[fromID]
- if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) {
- t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr)
- return false
- }
- if !fromAddr.IP.Equal(ac.addr.IP) || fromAddr.Port != ac.addr.Port {
- t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr)
- return false
- }
- if p.Kind() != ac.responseType {
- t.log.Debug(fmt.Sprintf("Wrong discv5 response type %s", p.Name()), "id", fromID, "addr", fromAddr)
- return false
- }
- t.startResponseTimeout(ac)
- ac.ch <- p
- return true
-}
-
-// getNode looks for a node record in table and database.
-func (t *UDPv5) getNode(id enode.ID) *enode.Node {
- if n := t.tab.getNode(id); n != nil {
- return n
- }
- if n := t.localNode.Database().Node(id); n != nil {
- return n
- }
- return nil
-}
-
-// handle processes incoming packets according to their message type.
-func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) {
- switch p := p.(type) {
- case *v5wire.Unknown:
- t.handleUnknown(p, fromID, fromAddr)
- case *v5wire.Whoareyou:
- t.handleWhoareyou(p, fromID, fromAddr)
- case *v5wire.Ping:
- t.handlePing(p, fromID, fromAddr)
- case *v5wire.Pong:
- if t.handleCallResponse(fromID, fromAddr, p) {
- t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)})
- }
- case *v5wire.Findnode:
- t.handleFindnode(p, fromID, fromAddr)
- case *v5wire.Nodes:
- t.handleCallResponse(fromID, fromAddr, p)
- case *v5wire.TalkRequest:
- t.talk.handleRequest(fromID, fromAddr, p)
- case *v5wire.TalkResponse:
- t.handleCallResponse(fromID, fromAddr, p)
- }
-}
-
-// handleUnknown initiates a handshake by responding with WHOAREYOU.
-func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) {
- challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
- crand.Read(challenge.IDNonce[:])
- if n := t.getNode(fromID); n != nil {
- challenge.Node = n
- challenge.RecordSeq = n.Seq()
- }
- t.sendResponse(fromID, fromAddr, challenge)
-}
-
-var (
- errChallengeNoCall = errors.New("no matching call")
- errChallengeTwice = errors.New("second handshake")
-)
-
-// handleWhoareyou resends the active call as a handshake packet.
-func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) {
- c, err := t.matchWithCall(fromID, p.Nonce)
- if err != nil {
- t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err)
- return
- }
-
- if c.node == nil {
- // Can't perform handshake because we don't have the ENR.
- t.log.Debug("Can't handle "+p.Name(), "addr", fromAddr, "err", "call has no ENR")
- c.err <- errors.New("remote wants handshake, but call has no ENR")
- return
- }
- // Resend the call that was answered by WHOAREYOU.
- t.log.Trace("<< "+p.Name(), "id", c.node.ID(), "addr", fromAddr)
- c.handshakeCount++
- c.challenge = p
- p.Node = c.node
- t.sendCall(c)
-}
-
-// matchWithCall checks whether a handshake attempt matches the active call.
-func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, error) {
- c := t.activeCallByAuth[nonce]
- if c == nil {
- return nil, errChallengeNoCall
- }
- if c.handshakeCount > 0 {
- return nil, errChallengeTwice
- }
- return c, nil
-}
-
-// handlePing sends a PONG response.
-func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) {
- remoteIP := fromAddr.IP
- // Handle IPv4 mapped IPv6 addresses in the
- // event the local node is binded to an
- // ipv6 interface.
- if remoteIP.To4() != nil {
- remoteIP = remoteIP.To4()
- }
- t.sendResponse(fromID, fromAddr, &v5wire.Pong{
- ReqID: p.ReqID,
- ToIP: remoteIP,
- ToPort: uint16(fromAddr.Port),
- ENRSeq: t.localNode.Node().Seq(),
- })
-}
-
-// handleFindnode returns nodes to the requester.
-func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) {
- nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit)
- for _, resp := range packNodes(p.ReqID, nodes) {
- t.sendResponse(fromID, fromAddr, resp)
- }
-}
-
-// collectTableNodes creates a FINDNODE result set for the given distances.
-func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node {
- var bn []*enode.Node
- var nodes []*enode.Node
- var processed = make(map[uint]struct{})
- for _, dist := range distances {
- // Reject duplicate / invalid distances.
- _, seen := processed[dist]
- if seen || dist > 256 {
- continue
- }
- processed[dist] = struct{}{}
-
- for _, n := range t.tab.appendLiveNodes(dist, bn[:0]) {
- // Apply some pre-checks to avoid sending invalid nodes.
- // Note liveness is checked by appendLiveNodes.
- if netutil.CheckRelayIP(rip, n.IP()) != nil {
- continue
- }
- nodes = append(nodes, n)
- if len(nodes) >= limit {
- return nodes
- }
- }
- }
- return nodes
-}
-
-// packNodes creates NODES response packets for the given node list.
-func packNodes(reqid []byte, nodes []*enode.Node) []*v5wire.Nodes {
- if len(nodes) == 0 {
- return []*v5wire.Nodes{{ReqID: reqid, RespCount: 1}}
- }
-
- // This limit represents the available space for nodes in output packets. Maximum
- // packet size is 1280, and out of this ~80 bytes will be taken up by the packet
- // frame. So limiting to 1000 bytes here leaves 200 bytes for other fields of the
- // NODES message, which is a lot.
- const sizeLimit = 1000
-
- var resp []*v5wire.Nodes
- for len(nodes) > 0 {
- p := &v5wire.Nodes{ReqID: reqid}
- size := uint64(0)
- for len(nodes) > 0 {
- r := nodes[0].Record()
- if size += r.Size(); size > sizeLimit {
- break
- }
- p.Nodes = append(p.Nodes, r)
- nodes = nodes[1:]
- }
- resp = append(resp, p)
- }
- for _, msg := range resp {
- msg.RespCount = uint8(len(resp))
- }
- return resp
-}
diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go
deleted file mode 100644
index eaa969ea8b..0000000000
--- a/p2p/discover/v5_udp_test.go
+++ /dev/null
@@ -1,859 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
- "bytes"
- "crypto/ecdsa"
- "encoding/binary"
- "fmt"
- "math/rand"
- "net"
- "reflect"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/internal/testlog"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/discover/v5wire"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/stretchr/testify/require"
- "golang.org/x/exp/slices"
-)
-
-// Real sockets, real crypto: this test checks end-to-end connectivity for UDPv5.
-func TestUDPv5_lookupE2E(t *testing.T) {
- t.Parallel()
-
- const N = 5
- var nodes []*UDPv5
- for i := 0; i < N; i++ {
- var cfg Config
- if len(nodes) > 0 {
- bn := nodes[0].Self()
- cfg.Bootnodes = []*enode.Node{bn}
- }
- node := startLocalhostV5(t, cfg)
- nodes = append(nodes, node)
- defer node.Close()
- }
- last := nodes[N-1]
- target := nodes[rand.Intn(N-2)].Self()
-
- // It is expected that all nodes can be found.
- expectedResult := make([]*enode.Node, len(nodes))
- for i := range nodes {
- expectedResult[i] = nodes[i].Self()
- }
- slices.SortFunc(expectedResult, func(a, b *enode.Node) int {
- return enode.DistCmp(target.ID(), a.ID(), b.ID())
- })
-
- // Do the lookup.
- results := last.Lookup(target.ID())
- if err := checkNodesEqual(results, expectedResult); err != nil {
- t.Fatalf("lookup returned wrong results: %v", err)
- }
-}
-
-func startLocalhostV5(t *testing.T, cfg Config) *UDPv5 {
- cfg.PrivateKey = newkey()
- db, _ := enode.OpenDB("")
- ln := enode.NewLocalNode(db, cfg.PrivateKey)
-
- // Prefix logs with node ID.
- lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString())
- cfg.Log = testlog.Logger(t, log.LevelTrace).With("node-id", lprefix)
-
- // Listen.
- socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}})
- if err != nil {
- t.Fatal(err)
- }
- realaddr := socket.LocalAddr().(*net.UDPAddr)
- ln.SetStaticIP(realaddr.IP)
- ln.Set(enr.UDP(realaddr.Port))
- udp, err := ListenV5(socket, ln, cfg)
- if err != nil {
- t.Fatal(err)
- }
- return udp
-}
-
-// This test checks that incoming PING calls are handled correctly.
-func TestUDPv5_pingHandling(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- test.packetIn(&v5wire.Ping{ReqID: []byte("foo")})
- test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
- if !bytes.Equal(p.ReqID, []byte("foo")) {
- t.Error("wrong request ID in response:", p.ReqID)
- }
- if p.ENRSeq != test.table.self().Seq() {
- t.Error("wrong ENR sequence number in response:", p.ENRSeq)
- }
- })
-}
-
-// This test checks that incoming 'unknown' packets trigger the handshake.
-func TestUDPv5_unknownPacket(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- nonce := v5wire.Nonce{1, 2, 3}
- check := func(p *v5wire.Whoareyou, wantSeq uint64) {
- t.Helper()
- if p.Nonce != nonce {
- t.Error("wrong nonce in WHOAREYOU:", p.Nonce, nonce)
- }
- if p.IDNonce == ([16]byte{}) {
- t.Error("all zero ID nonce")
- }
- if p.RecordSeq != wantSeq {
- t.Errorf("wrong record seq %d in WHOAREYOU, want %d", p.RecordSeq, wantSeq)
- }
- }
-
- // Unknown packet from unknown node.
- test.packetIn(&v5wire.Unknown{Nonce: nonce})
- test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
- check(p, 0)
- })
-
- // Make node known.
- n := test.getNode(test.remotekey, test.remoteaddr).Node()
- test.table.addSeenNode(wrapNode(n))
-
- test.packetIn(&v5wire.Unknown{Nonce: nonce})
- test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
- check(p, n.Seq())
- })
-}
-
-// This test checks that incoming FINDNODE calls are handled correctly.
-func TestUDPv5_findnodeHandling(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- // Create test nodes and insert them into the table.
- nodes253 := nodesAtDistance(test.table.self().ID(), 253, 16)
- nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4)
- nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10)
- fillTable(test.table, wrapNodes(nodes253), true)
- fillTable(test.table, wrapNodes(nodes249), true)
- fillTable(test.table, wrapNodes(nodes248), true)
-
- // Requesting with distance zero should return the node's own record.
- test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}})
- test.expectNodes([]byte{0}, 1, []*enode.Node{test.udp.Self()})
-
- // Requesting with distance > 256 shouldn't crash.
- test.packetIn(&v5wire.Findnode{ReqID: []byte{1}, Distances: []uint{4234098}})
- test.expectNodes([]byte{1}, 1, nil)
-
- // Requesting with empty distance list shouldn't crash either.
- test.packetIn(&v5wire.Findnode{ReqID: []byte{2}, Distances: []uint{}})
- test.expectNodes([]byte{2}, 1, nil)
-
- // This request gets no nodes because the corresponding bucket is empty.
- test.packetIn(&v5wire.Findnode{ReqID: []byte{3}, Distances: []uint{254}})
- test.expectNodes([]byte{3}, 1, nil)
-
- // This request gets all the distance-253 nodes.
- test.packetIn(&v5wire.Findnode{ReqID: []byte{4}, Distances: []uint{253}})
- test.expectNodes([]byte{4}, 2, nodes253)
-
- // This request gets all the distance-249 nodes and some more at 248 because
- // the bucket at 249 is not full.
- test.packetIn(&v5wire.Findnode{ReqID: []byte{5}, Distances: []uint{249, 248}})
- var nodes []*enode.Node
- nodes = append(nodes, nodes249...)
- nodes = append(nodes, nodes248[:10]...)
- test.expectNodes([]byte{5}, 1, nodes)
-}
-
-func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes []*enode.Node) {
- nodeSet := make(map[enode.ID]*enr.Record, len(wantNodes))
- for _, n := range wantNodes {
- nodeSet[n.ID()] = n.Record()
- }
-
- for {
- test.waitPacketOut(func(p *v5wire.Nodes, addr *net.UDPAddr, _ v5wire.Nonce) {
- if !bytes.Equal(p.ReqID, wantReqID) {
- test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID)
- }
- if p.RespCount != wantTotal {
- test.t.Fatalf("wrong total response count %d, want %d", p.RespCount, wantTotal)
- }
- for _, record := range p.Nodes {
- n, _ := enode.New(enode.ValidSchemesForTesting, record)
- want := nodeSet[n.ID()]
- if want == nil {
- test.t.Fatalf("unexpected node in response: %v", n)
- }
- if !reflect.DeepEqual(record, want) {
- test.t.Fatalf("wrong record in response: %v", n)
- }
- delete(nodeSet, n.ID())
- }
- })
- if len(nodeSet) == 0 {
- return
- }
- }
-}
-
-// This test checks that outgoing PING calls work.
-func TestUDPv5_pingCall(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- remote := test.getNode(test.remotekey, test.remoteaddr).Node()
- done := make(chan error, 1)
-
- // This ping times out.
- go func() {
- _, err := test.udp.ping(remote)
- done <- err
- }()
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {})
- if err := <-done; err != errTimeout {
- t.Fatalf("want errTimeout, got %q", err)
- }
-
- // This ping works.
- go func() {
- _, err := test.udp.ping(remote)
- done <- err
- }()
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
- test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID})
- })
- if err := <-done; err != nil {
- t.Fatal(err)
- }
-
- // This ping gets a reply from the wrong endpoint.
- go func() {
- _, err := test.udp.ping(remote)
- done <- err
- }()
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
- wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101}
- test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID})
- })
- if err := <-done; err != errTimeout {
- t.Fatalf("want errTimeout for reply from wrong IP, got %q", err)
- }
-}
-
-// This test checks that outgoing FINDNODE calls work and multiple NODES
-// replies are aggregated.
-func TestUDPv5_findnodeCall(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- // Launch the request:
- var (
- distances = []uint{230}
- remote = test.getNode(test.remotekey, test.remoteaddr).Node()
- nodes = nodesAtDistance(remote.ID(), int(distances[0]), 8)
- done = make(chan error, 1)
- response []*enode.Node
- )
- go func() {
- var err error
- response, err = test.udp.findnode(remote, distances)
- done <- err
- }()
-
- // Serve the responses:
- test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
- if !reflect.DeepEqual(p.Distances, distances) {
- t.Fatalf("wrong distances in request: %v", p.Distances)
- }
- test.packetIn(&v5wire.Nodes{
- ReqID: p.ReqID,
- RespCount: 2,
- Nodes: nodesToRecords(nodes[:4]),
- })
- test.packetIn(&v5wire.Nodes{
- ReqID: p.ReqID,
- RespCount: 2,
- Nodes: nodesToRecords(nodes[4:]),
- })
- })
-
- // Check results:
- if err := <-done; err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if !reflect.DeepEqual(response, nodes) {
- t.Fatalf("wrong nodes in response")
- }
-
- // TODO: check invalid IPs
- // TODO: check invalid/unsigned record
-}
-
-// This test checks that pending calls are re-sent when a handshake happens.
-func TestUDPv5_callResend(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- remote := test.getNode(test.remotekey, test.remoteaddr).Node()
- done := make(chan error, 2)
- go func() {
- _, err := test.udp.ping(remote)
- done <- err
- }()
- go func() {
- _, err := test.udp.ping(remote)
- done <- err
- }()
-
- // Ping answered by WHOAREYOU.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
- test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
- })
- // Ping should be re-sent.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
- test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
- })
- // Answer the other ping.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
- test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
- })
- if err := <-done; err != nil {
- t.Fatalf("unexpected ping error: %v", err)
- }
- if err := <-done; err != nil {
- t.Fatalf("unexpected ping error: %v", err)
- }
-}
-
-// This test ensures we don't allow multiple rounds of WHOAREYOU for a single call.
-func TestUDPv5_multipleHandshakeRounds(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- remote := test.getNode(test.remotekey, test.remoteaddr).Node()
- done := make(chan error, 1)
- go func() {
- _, err := test.udp.ping(remote)
- done <- err
- }()
-
- // Ping answered by WHOAREYOU.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
- test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
- })
- // Ping answered by WHOAREYOU again.
- test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
- test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
- })
- if err := <-done; err != errTimeout {
- t.Fatalf("unexpected ping error: %q", err)
- }
-}
-
-// This test checks that calls with n replies may take up to n * respTimeout.
-func TestUDPv5_callTimeoutReset(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- // Launch the request:
- var (
- distance = uint(230)
- remote = test.getNode(test.remotekey, test.remoteaddr).Node()
- nodes = nodesAtDistance(remote.ID(), int(distance), 8)
- done = make(chan error, 1)
- )
- go func() {
- _, err := test.udp.findnode(remote, []uint{distance})
- done <- err
- }()
-
- // Serve two responses, slowly.
- test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
- time.Sleep(respTimeout - 50*time.Millisecond)
- test.packetIn(&v5wire.Nodes{
- ReqID: p.ReqID,
- RespCount: 2,
- Nodes: nodesToRecords(nodes[:4]),
- })
-
- time.Sleep(respTimeout - 50*time.Millisecond)
- test.packetIn(&v5wire.Nodes{
- ReqID: p.ReqID,
- RespCount: 2,
- Nodes: nodesToRecords(nodes[4:]),
- })
- })
- if err := <-done; err != nil {
- t.Fatalf("unexpected error: %q", err)
- }
-}
-
-// This test checks that TALKREQ calls the registered handler function.
-func TestUDPv5_talkHandling(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- var recvMessage []byte
- test.udp.RegisterTalkHandler("test", func(id enode.ID, addr *net.UDPAddr, message []byte) []byte {
- recvMessage = message
- return []byte("test response")
- })
-
- // Successful case:
- test.packetIn(&v5wire.TalkRequest{
- ReqID: []byte("foo"),
- Protocol: "test",
- Message: []byte("test request"),
- })
- test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
- if !bytes.Equal(p.ReqID, []byte("foo")) {
- t.Error("wrong request ID in response:", p.ReqID)
- }
- if string(p.Message) != "test response" {
- t.Errorf("wrong talk response message: %q", p.Message)
- }
- if string(recvMessage) != "test request" {
- t.Errorf("wrong message received in handler: %q", recvMessage)
- }
- })
-
- // Check that empty response is returned for unregistered protocols.
- recvMessage = nil
- test.packetIn(&v5wire.TalkRequest{
- ReqID: []byte("2"),
- Protocol: "wrong",
- Message: []byte("test request"),
- })
- test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
- if !bytes.Equal(p.ReqID, []byte("2")) {
- t.Error("wrong request ID in response:", p.ReqID)
- }
- if string(p.Message) != "" {
- t.Errorf("wrong talk response message: %q", p.Message)
- }
- if recvMessage != nil {
- t.Errorf("handler was called for wrong protocol: %q", recvMessage)
- }
- })
-}
-
-// This test checks that outgoing TALKREQ calls work.
-func TestUDPv5_talkRequest(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- remote := test.getNode(test.remotekey, test.remoteaddr).Node()
- done := make(chan error, 1)
-
- // This request times out.
- go func() {
- _, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
- done <- err
- }()
- test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {})
- if err := <-done; err != errTimeout {
- t.Fatalf("want errTimeout, got %q", err)
- }
-
- // This request works.
- go func() {
- _, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
- done <- err
- }()
- test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {
- if p.Protocol != "test" {
- t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
- }
- if string(p.Message) != "test request" {
- t.Errorf("wrong message talk request: %q", p.Message)
- }
- test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.TalkResponse{
- ReqID: p.ReqID,
- Message: []byte("test response"),
- })
- })
- if err := <-done; err != nil {
- t.Fatal(err)
- }
-
- // Also check requesting without ENR.
- go func() {
- _, err := test.udp.TalkRequestToID(remote.ID(), test.remoteaddr, "test", []byte("test request 2"))
- done <- err
- }()
- test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {
- if p.Protocol != "test" {
- t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
- }
- if string(p.Message) != "test request 2" {
- t.Errorf("wrong message talk request: %q", p.Message)
- }
- test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.TalkResponse{
- ReqID: p.ReqID,
- Message: []byte("test response 2"),
- })
- })
- if err := <-done; err != nil {
- t.Fatal(err)
- }
-}
-
-// This test checks that lookupDistances works.
-func TestUDPv5_lookupDistances(t *testing.T) {
- test := newUDPV5Test(t)
- lnID := test.table.self().ID()
-
- t.Run("target distance of 1", func(t *testing.T) {
- node := nodeAtDistance(lnID, 1, intIP(0))
- dists := lookupDistances(lnID, node.ID())
- require.Equal(t, []uint{1, 2, 3}, dists)
- })
-
- t.Run("target distance of 2", func(t *testing.T) {
- node := nodeAtDistance(lnID, 2, intIP(0))
- dists := lookupDistances(lnID, node.ID())
- require.Equal(t, []uint{2, 3, 1}, dists)
- })
-
- t.Run("target distance of 128", func(t *testing.T) {
- node := nodeAtDistance(lnID, 128, intIP(0))
- dists := lookupDistances(lnID, node.ID())
- require.Equal(t, []uint{128, 129, 127}, dists)
- })
-
- t.Run("target distance of 255", func(t *testing.T) {
- node := nodeAtDistance(lnID, 255, intIP(0))
- dists := lookupDistances(lnID, node.ID())
- require.Equal(t, []uint{255, 256, 254}, dists)
- })
-
- t.Run("target distance of 256", func(t *testing.T) {
- node := nodeAtDistance(lnID, 256, intIP(0))
- dists := lookupDistances(lnID, node.ID())
- require.Equal(t, []uint{256, 255, 254}, dists)
- })
-}
-
-// This test checks that lookup works.
-func TestUDPv5_lookup(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
-
- // Lookup on empty table returns no nodes.
- if results := test.udp.Lookup(lookupTestnet.target.id()); len(results) > 0 {
- t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results)
- }
-
- // Ensure the tester knows all nodes in lookupTestnet by IP.
- for d, nn := range lookupTestnet.dists {
- for i, key := range nn {
- n := lookupTestnet.node(d, i)
- test.getNode(key, &net.UDPAddr{IP: n.IP(), Port: n.UDP()})
- }
- }
-
- // Seed table with initial node.
- initialNode := lookupTestnet.node(256, 0)
- fillTable(test.table, []*node{wrapNode(initialNode)}, true)
-
- // Start the lookup.
- resultC := make(chan []*enode.Node, 1)
- go func() {
- resultC <- test.udp.Lookup(lookupTestnet.target.id())
- test.close()
- }()
-
- // Answer lookup packets.
- asked := make(map[enode.ID]bool)
- for done := false; !done; {
- done = test.waitPacketOut(func(p v5wire.Packet, to *net.UDPAddr, _ v5wire.Nonce) {
- recipient, key := lookupTestnet.nodeByAddr(to)
- switch p := p.(type) {
- case *v5wire.Ping:
- test.packetInFrom(key, to, &v5wire.Pong{ReqID: p.ReqID})
- case *v5wire.Findnode:
- if asked[recipient.ID()] {
- t.Error("Asked node", recipient.ID(), "twice")
- }
- asked[recipient.ID()] = true
- nodes := lookupTestnet.neighborsAtDistances(recipient, p.Distances, 16)
- t.Logf("Got FINDNODE for %v, returning %d nodes", p.Distances, len(nodes))
- for _, resp := range packNodes(p.ReqID, nodes) {
- test.packetInFrom(key, to, resp)
- }
- }
- })
- }
-
- // Verify result nodes.
- results := <-resultC
- checkLookupResults(t, lookupTestnet, results)
-}
-
-// This test checks the local node can be utilised to set key-values.
-func TestUDPv5_LocalNode(t *testing.T) {
- t.Parallel()
- var cfg Config
- node := startLocalhostV5(t, cfg)
- defer node.Close()
- localNd := node.LocalNode()
-
- // set value in node's local record
- testVal := [4]byte{'A', 'B', 'C', 'D'}
- localNd.Set(enr.WithEntry("testing", &testVal))
-
- // retrieve the value from self to make sure it matches.
- outputVal := [4]byte{}
- if err := node.Self().Load(enr.WithEntry("testing", &outputVal)); err != nil {
- t.Errorf("Could not load value from record: %v", err)
- }
- if testVal != outputVal {
- t.Errorf("Wanted %#x to be retrieved from the record but instead got %#x", testVal, outputVal)
- }
-}
-
-func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) {
- t.Parallel()
- test := newUDPV5Test(t)
- defer test.close()
-
- rawIP := net.IPv4(0xFF, 0x12, 0x33, 0xE5)
- test.remoteaddr = &net.UDPAddr{
- IP: rawIP.To16(),
- Port: 0,
- }
- remote := test.getNode(test.remotekey, test.remoteaddr).Node()
- done := make(chan struct{}, 1)
-
- // This handler will truncate the ipv4-mapped in ipv6 address.
- go func() {
- test.udp.handlePing(&v5wire.Ping{ENRSeq: 1}, remote.ID(), test.remoteaddr)
- done <- struct{}{}
- }()
- test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
- if len(p.ToIP) == net.IPv6len {
- t.Error("Received untruncated ip address")
- }
- if len(p.ToIP) != net.IPv4len {
- t.Errorf("Received ip address with incorrect length: %d", len(p.ToIP))
- }
- if !p.ToIP.Equal(rawIP) {
- t.Errorf("Received incorrect ip address: wanted %s but received %s", rawIP.String(), p.ToIP.String())
- }
- })
- <-done
-}
-
-// udpV5Test is the framework for all tests above.
-// It runs the UDPv5 transport on a virtual socket and allows testing outgoing packets.
-type udpV5Test struct {
- t *testing.T
- pipe *dgramPipe
- table *Table
- db *enode.DB
- udp *UDPv5
- localkey, remotekey *ecdsa.PrivateKey
- remoteaddr *net.UDPAddr
- nodesByID map[enode.ID]*enode.LocalNode
- nodesByIP map[string]*enode.LocalNode
-}
-
-// testCodec is the packet encoding used by protocol tests. This codec does not perform encryption.
-type testCodec struct {
- test *udpV5Test
- id enode.ID
- ctr uint64
-}
-
-type testCodecFrame struct {
- NodeID enode.ID
- AuthTag v5wire.Nonce
- Ptype byte
- Packet rlp.RawValue
-}
-
-func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) {
- c.ctr++
- var authTag v5wire.Nonce
- binary.BigEndian.PutUint64(authTag[:], c.ctr)
-
- penc, _ := rlp.EncodeToBytes(p)
- frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc})
- return frame, authTag, err
-}
-
-func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error) {
- frame, p, err := c.decodeFrame(input)
- if err != nil {
- return enode.ID{}, nil, nil, err
- }
- return frame.NodeID, nil, p, nil
-}
-
-func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Packet, err error) {
- if err = rlp.DecodeBytes(input, &frame); err != nil {
- return frame, nil, fmt.Errorf("invalid frame: %v", err)
- }
- switch frame.Ptype {
- case v5wire.UnknownPacket:
- dec := new(v5wire.Unknown)
- err = rlp.DecodeBytes(frame.Packet, &dec)
- p = dec
- case v5wire.WhoareyouPacket:
- dec := new(v5wire.Whoareyou)
- err = rlp.DecodeBytes(frame.Packet, &dec)
- p = dec
- default:
- p, err = v5wire.DecodeMessage(frame.Ptype, frame.Packet)
- }
- return frame, p, err
-}
-
-func newUDPV5Test(t *testing.T) *udpV5Test {
- test := &udpV5Test{
- t: t,
- pipe: newpipe(),
- localkey: newkey(),
- remotekey: newkey(),
- remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303},
- nodesByID: make(map[enode.ID]*enode.LocalNode),
- nodesByIP: make(map[string]*enode.LocalNode),
- }
- test.db, _ = enode.OpenDB("")
- ln := enode.NewLocalNode(test.db, test.localkey)
- ln.SetStaticIP(net.IP{10, 0, 0, 1})
- ln.Set(enr.UDP(30303))
- test.udp, _ = ListenV5(test.pipe, ln, Config{
- PrivateKey: test.localkey,
- Log: testlog.Logger(t, log.LvlTrace),
- ValidSchemes: enode.ValidSchemesForTesting,
- })
- test.udp.codec = &testCodec{test: test, id: ln.ID()}
- test.table = test.udp.tab
- test.nodesByID[ln.ID()] = ln
- // Wait for initial refresh so the table doesn't send unexpected findnode.
- <-test.table.initDone
- return test
-}
-
-// handles a packet as if it had been sent to the transport.
-func (test *udpV5Test) packetIn(packet v5wire.Packet) {
- test.t.Helper()
- test.packetInFrom(test.remotekey, test.remoteaddr, packet)
-}
-
-// handles a packet as if it had been sent to the transport by the key/endpoint.
-func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet v5wire.Packet) {
- test.t.Helper()
-
- ln := test.getNode(key, addr)
- codec := &testCodec{test: test, id: ln.ID()}
- enc, _, err := codec.Encode(test.udp.Self().ID(), addr.String(), packet, nil)
- if err != nil {
- test.t.Errorf("%s encode error: %v", packet.Name(), err)
- }
- if test.udp.dispatchReadPacket(addr, enc) {
- <-test.udp.readNextCh // unblock UDPv5.dispatch
- }
-}
-
-// getNode ensures the test knows about a node at the given endpoint.
-func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode.LocalNode {
- id := encodePubkey(&key.PublicKey).id()
- ln := test.nodesByID[id]
- if ln == nil {
- db, _ := enode.OpenDB("")
- ln = enode.NewLocalNode(db, key)
- ln.SetStaticIP(addr.IP)
- ln.Set(enr.UDP(addr.Port))
- test.nodesByID[id] = ln
- }
- test.nodesByIP[string(addr.IP)] = ln
- return ln
-}
-
-// waitPacketOut waits for the next output packet and handles it using the given 'validate'
-// function. The function must be of type func (X, *net.UDPAddr, v5wire.Nonce) where X is
-// assignable to packetV5.
-func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
- test.t.Helper()
-
- fn := reflect.ValueOf(validate)
- exptype := fn.Type().In(0)
-
- dgram, err := test.pipe.receive()
- if err == errClosed {
- return true
- }
- if err == errTimeout {
- test.t.Fatalf("timed out waiting for %v", exptype)
- return false
- }
- ln := test.nodesByIP[string(dgram.to.IP)]
- if ln == nil {
- test.t.Fatalf("attempt to send to non-existing node %v", &dgram.to)
- return false
- }
- codec := &testCodec{test: test, id: ln.ID()}
- frame, p, err := codec.decodeFrame(dgram.data)
- if err != nil {
- test.t.Errorf("sent packet decode error: %v", err)
- return false
- }
- if !reflect.TypeOf(p).AssignableTo(exptype) {
- test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
- return false
- }
- fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(frame.AuthTag)})
- return false
-}
-
-func (test *udpV5Test) close() {
- test.t.Helper()
-
- test.udp.Close()
- test.db.Close()
- for id, n := range test.nodesByID {
- if id != test.udp.Self().ID() {
- n.Database().Close()
- }
- }
- if len(test.pipe.queue) != 0 {
- test.t.Fatalf("%d unmatched UDP packets in queue", len(test.pipe.queue))
- }
-}
diff --git a/p2p/discover/v5wire/crypto.go b/p2p/discover/v5wire/crypto.go
deleted file mode 100644
index fc0a0edef5..0000000000
--- a/p2p/discover/v5wire/crypto.go
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package v5wire
-
-import (
- "crypto/aes"
- "crypto/cipher"
- "crypto/ecdsa"
- "crypto/elliptic"
- "errors"
- "fmt"
- "hash"
-
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "golang.org/x/crypto/hkdf"
-)
-
-const (
- // Encryption/authentication parameters.
- aesKeySize = 16
- gcmNonceSize = 12
-)
-
-// Nonce represents a nonce used for AES/GCM.
-type Nonce [gcmNonceSize]byte
-
-// EncodePubkey encodes a public key.
-func EncodePubkey(key *ecdsa.PublicKey) []byte {
- switch key.Curve {
- case crypto.S256():
- return crypto.CompressPubkey(key)
- default:
- panic("unsupported curve " + key.Curve.Params().Name + " in EncodePubkey")
- }
-}
-
-// DecodePubkey decodes a public key in compressed format.
-func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) {
- switch curve {
- case crypto.S256():
- if len(e) != 33 {
- return nil, errors.New("wrong size public key data")
- }
- return crypto.DecompressPubkey(e)
- default:
- return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name)
- }
-}
-
-// idNonceHash computes the ID signature hash used in the handshake.
-func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte {
- h.Reset()
- h.Write([]byte("discovery v5 identity proof"))
- h.Write(challenge)
- h.Write(ephkey)
- h.Write(destID[:])
- return h.Sum(nil)
-}
-
-// makeIDSignature creates the ID nonce signature.
-func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) {
- input := idNonceHash(hash, challenge, ephkey, destID)
- switch key.Curve {
- case crypto.S256():
- idsig, err := crypto.Sign(input, key)
- if err != nil {
- return nil, err
- }
- return idsig[:len(idsig)-1], nil // remove recovery ID
- default:
- return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name)
- }
-}
-
-// s256raw is an unparsed secp256k1 public key ENR entry.
-type s256raw []byte
-
-func (s256raw) ENRKey() string { return "secp256k1" }
-
-// verifyIDSignature checks that signature over idnonce was made by the given node.
-func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, ephkey []byte, destID enode.ID) error {
- switch idscheme := n.Record().IdentityScheme(); idscheme {
- case "v4":
- var pubkey s256raw
- if n.Load(&pubkey) != nil {
- return errors.New("no secp256k1 public key in record")
- }
- input := idNonceHash(hash, challenge, ephkey, destID)
- if !crypto.VerifySignature(pubkey, input, sig) {
- return errInvalidNonceSig
- }
- return nil
- default:
- return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme)
- }
-}
-
-type hashFn func() hash.Hash
-
-// deriveKeys creates the session keys.
-func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session {
- const text = "discovery v5 key agreement"
- var info = make([]byte, 0, len(text)+len(n1)+len(n2))
- info = append(info, text...)
- info = append(info, n1[:]...)
- info = append(info, n2[:]...)
-
- eph := ecdh(priv, pub)
- if eph == nil {
- return nil
- }
- kdf := hkdf.New(hash, eph, challenge, info)
- sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)}
- kdf.Read(sec.writeKey)
- kdf.Read(sec.readKey)
- for i := range eph {
- eph[i] = 0
- }
- return &sec
-}
-
-// ecdh creates a shared secret.
-func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte {
- secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
- if secX == nil {
- return nil
- }
- sec := make([]byte, 33)
- sec[0] = 0x02 | byte(secY.Bit(0))
- math.ReadBits(secX, sec[1:])
- return sec
-}
-
-// encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is
-// appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16
-// bytes longer than plaintext because it contains an authentication tag.
-func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) {
- block, err := aes.NewCipher(key)
- if err != nil {
- panic(fmt.Errorf("can't create block cipher: %v", err))
- }
- aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
- if err != nil {
- panic(fmt.Errorf("can't create GCM: %v", err))
- }
- return aesgcm.Seal(dest, nonce, plaintext, authData), nil
-}
-
-// decryptGCM decrypts ct using AES-GCM with the given key and nonce.
-func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) {
- block, err := aes.NewCipher(key)
- if err != nil {
- return nil, fmt.Errorf("can't create block cipher: %v", err)
- }
- if len(nonce) != gcmNonceSize {
- return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce))
- }
- aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
- if err != nil {
- return nil, fmt.Errorf("can't create GCM: %v", err)
- }
- pt := make([]byte, 0, len(ct))
- return aesgcm.Open(pt, nonce, ct, authData)
-}
diff --git a/p2p/discover/v5wire/crypto_test.go b/p2p/discover/v5wire/crypto_test.go
deleted file mode 100644
index 72169b4314..0000000000
--- a/p2p/discover/v5wire/crypto_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package v5wire
-
-import (
- "bytes"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/sha256"
- "reflect"
- "strings"
- "testing"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-func TestVector_ECDH(t *testing.T) {
- var (
- staticKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736")
- publicKey = hexPubkey(crypto.S256(), "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231")
- want = hexutil.MustDecode("0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e")
- )
- result := ecdh(staticKey, publicKey)
- check(t, "shared-secret", result, want)
-}
-
-func TestVector_KDF(t *testing.T) {
- var (
- ephKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736")
- cdata = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000")
- net = newHandshakeTest()
- )
- defer net.close()
-
- destKey := &testKeyB.PublicKey
- s := deriveKeys(sha256.New, ephKey, destKey, net.nodeA.id(), net.nodeB.id(), cdata)
- t.Logf("ephemeral-key = %#x", ephKey.D)
- t.Logf("dest-pubkey = %#x", EncodePubkey(destKey))
- t.Logf("node-id-a = %#x", net.nodeA.id().Bytes())
- t.Logf("node-id-b = %#x", net.nodeB.id().Bytes())
- t.Logf("challenge-data = %#x", cdata)
- check(t, "initiator-key", s.writeKey, hexutil.MustDecode("0xdccc82d81bd610f4f76d3ebe97a40571"))
- check(t, "recipient-key", s.readKey, hexutil.MustDecode("0xac74bb8773749920b0d3a8881c173ec5"))
-}
-
-func TestVector_IDSignature(t *testing.T) {
- var (
- key = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736")
- destID = enode.HexID("0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9")
- ephkey = hexutil.MustDecode("0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231")
- cdata = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000")
- )
-
- sig, err := makeIDSignature(sha256.New(), key, cdata, ephkey, destID)
- if err != nil {
- t.Fatal(err)
- }
- t.Logf("static-key = %#x", key.D)
- t.Logf("challenge-data = %#x", cdata)
- t.Logf("ephemeral-pubkey = %#x", ephkey)
- t.Logf("node-id-B = %#x", destID.Bytes())
- expected := "0x94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6"
- check(t, "id-signature", sig, hexutil.MustDecode(expected))
-}
-
-func TestDeriveKeys(t *testing.T) {
- t.Parallel()
-
- var (
- n1 = enode.ID{1}
- n2 = enode.ID{2}
- cdata = []byte{1, 2, 3, 4}
- )
- sec1 := deriveKeys(sha256.New, testKeyA, &testKeyB.PublicKey, n1, n2, cdata)
- sec2 := deriveKeys(sha256.New, testKeyB, &testKeyA.PublicKey, n1, n2, cdata)
- if sec1 == nil || sec2 == nil {
- t.Fatal("key agreement failed")
- }
- if !reflect.DeepEqual(sec1, sec2) {
- t.Fatalf("keys not equal:\n %+v\n %+v", sec1, sec2)
- }
-}
-
-func check(t *testing.T, what string, x, y []byte) {
- t.Helper()
-
- if !bytes.Equal(x, y) {
- t.Errorf("wrong %s: %#x != %#x", what, x, y)
- } else {
- t.Logf("%s = %#x", what, x)
- }
-}
-
-func hexPrivkey(input string) *ecdsa.PrivateKey {
- key, err := crypto.HexToECDSA(strings.TrimPrefix(input, "0x"))
- if err != nil {
- panic(err)
- }
- return key
-}
-
-func hexPubkey(curve elliptic.Curve, input string) *ecdsa.PublicKey {
- key, err := DecodePubkey(curve, hexutil.MustDecode(input))
- if err != nil {
- panic(err)
- }
- return key
-}
diff --git a/p2p/discover/v5wire/encoding.go b/p2p/discover/v5wire/encoding.go
deleted file mode 100644
index 5108910620..0000000000
--- a/p2p/discover/v5wire/encoding.go
+++ /dev/null
@@ -1,672 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package v5wire
-
-import (
- "bytes"
- "crypto/aes"
- "crypto/cipher"
- "crypto/ecdsa"
- crand "crypto/rand"
- "crypto/sha256"
- "encoding/binary"
- "errors"
- "fmt"
- "hash"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// TODO concurrent WHOAREYOU tie-breaker
-// TODO rehandshake after X packets
-
-// Header represents a packet header.
-type Header struct {
- IV [sizeofMaskingIV]byte
- StaticHeader
- AuthData []byte
-
- src enode.ID // used by decoder
-}
-
-// StaticHeader contains the static fields of a packet header.
-type StaticHeader struct {
- ProtocolID [6]byte
- Version uint16
- Flag byte
- Nonce Nonce
- AuthSize uint16
-}
-
-// Authdata layouts.
-type (
- whoareyouAuthData struct {
- IDNonce [16]byte // ID proof data
- RecordSeq uint64 // highest known ENR sequence of requester
- }
-
- handshakeAuthData struct {
- h struct {
- SrcID enode.ID
- SigSize byte // signature data
- PubkeySize byte // offset of
- }
- // Trailing variable-size data.
- signature, pubkey, record []byte
- }
-
- messageAuthData struct {
- SrcID enode.ID
- }
-)
-
-// Packet header flag values.
-const (
- flagMessage = iota
- flagWhoareyou
- flagHandshake
-)
-
-// Protocol constants.
-const (
- version = 1
- minVersion = 1
- sizeofMaskingIV = 16
-
- // The minimum size of any Discovery v5 packet is 63 bytes.
- // Should reject packets smaller than minPacketSize.
- minPacketSize = 63
-
- maxPacketSize = 1280
-
- minMessageSize = 48 // this refers to data after static headers
- randomPacketMsgSize = 20
-)
-
-var DefaultProtocolID = [6]byte{'d', 'i', 's', 'c', 'v', '5'}
-
-// Errors.
-var (
- errTooShort = errors.New("packet too short")
- errInvalidHeader = errors.New("invalid packet header")
- errInvalidFlag = errors.New("invalid flag value in header")
- errMinVersion = errors.New("version of packet header below minimum")
- errMsgTooShort = errors.New("message/handshake packet below minimum size")
- errAuthSize = errors.New("declared auth size is beyond packet length")
- errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake")
- errInvalidAuthKey = errors.New("invalid ephemeral pubkey")
- errNoRecord = errors.New("expected ENR in handshake but none sent")
- errInvalidNonceSig = errors.New("invalid ID nonce signature")
- errMessageTooShort = errors.New("message contains no data")
- errMessageDecrypt = errors.New("cannot decrypt message")
-)
-
-// Public errors.
-var (
- // ErrInvalidReqID represents error when the ID is invalid.
- ErrInvalidReqID = errors.New("request ID larger than 8 bytes")
-)
-
-// IsInvalidHeader reports whether 'err' is related to an invalid packet header. When it
-// returns false, it is pretty certain that the packet causing the error does not belong
-// to discv5.
-func IsInvalidHeader(err error) bool {
- return err == errTooShort || err == errInvalidHeader || err == errMsgTooShort
-}
-
-// Packet sizes.
-var (
- sizeofStaticHeader = binary.Size(StaticHeader{})
- sizeofWhoareyouAuthData = binary.Size(whoareyouAuthData{})
- sizeofHandshakeAuthData = binary.Size(handshakeAuthData{}.h)
- sizeofMessageAuthData = binary.Size(messageAuthData{})
- sizeofStaticPacketData = sizeofMaskingIV + sizeofStaticHeader
-)
-
-// Codec encodes and decodes Discovery v5 packets.
-// This type is not safe for concurrent use.
-type Codec struct {
- sha256 hash.Hash
- localnode *enode.LocalNode
- privkey *ecdsa.PrivateKey
- sc *SessionCache
- protocolID [6]byte
-
- // encoder buffers
- buf bytes.Buffer // whole packet
- headbuf bytes.Buffer // packet header
- msgbuf bytes.Buffer // message RLP plaintext
- msgctbuf []byte // message data ciphertext
-
- // decoder buffer
- decbuf []byte
- reader bytes.Reader
-}
-
-// NewCodec creates a wire codec.
-func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock, protocolID *[6]byte) *Codec {
- c := &Codec{
- sha256: sha256.New(),
- localnode: ln,
- privkey: key,
- sc: NewSessionCache(1024, clock),
- protocolID: DefaultProtocolID,
- decbuf: make([]byte, maxPacketSize),
- }
- if protocolID != nil {
- c.protocolID = *protocolID
- }
- return c
-}
-
-// Encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The
-// 'challenge' parameter should be the most recently received WHOAREYOU packet from that
-// node.
-func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoareyou) ([]byte, Nonce, error) {
- // Create the packet header.
- var (
- head Header
- session *session
- msgData []byte
- err error
- )
- switch {
- case packet.Kind() == WhoareyouPacket:
- head, err = c.encodeWhoareyou(id, packet.(*Whoareyou))
- case challenge != nil:
- // We have an unanswered challenge, send handshake.
- head, session, err = c.encodeHandshakeHeader(id, addr, challenge)
- default:
- session = c.sc.session(id, addr)
- if session != nil {
- // There is a session, use it.
- head, err = c.encodeMessageHeader(id, session)
- } else {
- // No keys, send random data to kick off the handshake.
- head, msgData, err = c.encodeRandom(id)
- }
- }
- if err != nil {
- return nil, Nonce{}, err
- }
-
- // Generate masking IV.
- if err := c.sc.maskingIVGen(head.IV[:]); err != nil {
- return nil, Nonce{}, fmt.Errorf("can't generate masking IV: %v", err)
- }
-
- // Encode header data.
- c.writeHeaders(&head)
-
- // Store sent WHOAREYOU challenges.
- if challenge, ok := packet.(*Whoareyou); ok {
- challenge.ChallengeData = bytesCopy(&c.buf)
- c.sc.storeSentHandshake(id, addr, challenge)
- } else if msgData == nil {
- headerData := c.buf.Bytes()
- msgData, err = c.encryptMessage(session, packet, &head, headerData)
- if err != nil {
- return nil, Nonce{}, err
- }
- }
-
- enc, err := c.EncodeRaw(id, head, msgData)
- return enc, head.Nonce, err
-}
-
-// EncodeRaw encodes a packet with the given header.
-func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, error) {
- c.writeHeaders(&head)
-
- // Apply masking.
- masked := c.buf.Bytes()[sizeofMaskingIV:]
- mask := head.mask(id)
- mask.XORKeyStream(masked[:], masked[:])
-
- // Write message data.
- c.buf.Write(msgdata)
- return c.buf.Bytes(), nil
-}
-
-func (c *Codec) writeHeaders(head *Header) {
- c.buf.Reset()
- c.buf.Write(head.IV[:])
- binary.Write(&c.buf, binary.BigEndian, &head.StaticHeader)
- c.buf.Write(head.AuthData)
-}
-
-// makeHeader creates a packet header.
-func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header {
- var authsize int
- switch flag {
- case flagMessage:
- authsize = sizeofMessageAuthData
- case flagWhoareyou:
- authsize = sizeofWhoareyouAuthData
- case flagHandshake:
- authsize = sizeofHandshakeAuthData
- default:
- panic(fmt.Errorf("BUG: invalid packet header flag %x", flag))
- }
- authsize += authsizeExtra
- if authsize > int(^uint16(0)) {
- panic(fmt.Errorf("BUG: auth size %d overflows uint16", authsize))
- }
- return Header{
- StaticHeader: StaticHeader{
- ProtocolID: c.protocolID,
- Version: version,
- Flag: flag,
- AuthSize: uint16(authsize),
- },
- }
-}
-
-// encodeRandom encodes a packet with random content.
-func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) {
- head := c.makeHeader(toID, flagMessage, 0)
-
- // Encode auth data.
- auth := messageAuthData{SrcID: c.localnode.ID()}
- if _, err := crand.Read(head.Nonce[:]); err != nil {
- return head, nil, fmt.Errorf("can't get random data: %v", err)
- }
- c.headbuf.Reset()
- binary.Write(&c.headbuf, binary.BigEndian, auth)
- head.AuthData = c.headbuf.Bytes()
-
- // Fill message ciphertext buffer with random bytes.
- c.msgctbuf = append(c.msgctbuf[:0], make([]byte, randomPacketMsgSize)...)
- crand.Read(c.msgctbuf)
- return head, c.msgctbuf, nil
-}
-
-// encodeWhoareyou encodes a WHOAREYOU packet.
-func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error) {
- // Sanity check node field to catch misbehaving callers.
- if packet.RecordSeq > 0 && packet.Node == nil {
- panic("BUG: missing node in whoareyou with non-zero seq")
- }
-
- // Create header.
- head := c.makeHeader(toID, flagWhoareyou, 0)
- head.AuthData = bytesCopy(&c.buf)
- head.Nonce = packet.Nonce
-
- // Encode auth data.
- auth := &whoareyouAuthData{
- IDNonce: packet.IDNonce,
- RecordSeq: packet.RecordSeq,
- }
- c.headbuf.Reset()
- binary.Write(&c.headbuf, binary.BigEndian, auth)
- head.AuthData = c.headbuf.Bytes()
- return head, nil
-}
-
-// encodeHandshakeHeader encodes the handshake message packet header.
-func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Whoareyou) (Header, *session, error) {
- // Ensure calling code sets challenge.node.
- if challenge.Node == nil {
- panic("BUG: missing challenge.Node in encode")
- }
-
- // Generate new secrets.
- auth, session, err := c.makeHandshakeAuth(toID, addr, challenge)
- if err != nil {
- return Header{}, nil, err
- }
-
- // Generate nonce for message.
- nonce, err := c.sc.nextNonce(session)
- if err != nil {
- return Header{}, nil, fmt.Errorf("can't generate nonce: %v", err)
- }
-
- // TODO: this should happen when the first authenticated message is received
- c.sc.storeNewSession(toID, addr, session)
-
- // Encode the auth header.
- var (
- authsizeExtra = len(auth.pubkey) + len(auth.signature) + len(auth.record)
- head = c.makeHeader(toID, flagHandshake, authsizeExtra)
- )
- c.headbuf.Reset()
- binary.Write(&c.headbuf, binary.BigEndian, &auth.h)
- c.headbuf.Write(auth.signature)
- c.headbuf.Write(auth.pubkey)
- c.headbuf.Write(auth.record)
- head.AuthData = c.headbuf.Bytes()
- head.Nonce = nonce
- return head, session, err
-}
-
-// makeHandshakeAuth creates the auth header on a request packet following WHOAREYOU.
-func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoareyou) (*handshakeAuthData, *session, error) {
- auth := new(handshakeAuthData)
- auth.h.SrcID = c.localnode.ID()
-
- // Create the ephemeral key. This needs to be first because the
- // key is part of the ID nonce signature.
- var remotePubkey = new(ecdsa.PublicKey)
- if err := challenge.Node.Load((*enode.Secp256k1)(remotePubkey)); err != nil {
- return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient")
- }
- ephkey, err := c.sc.ephemeralKeyGen()
- if err != nil {
- return nil, nil, fmt.Errorf("can't generate ephemeral key")
- }
- ephpubkey := EncodePubkey(&ephkey.PublicKey)
- auth.pubkey = ephpubkey[:]
- auth.h.PubkeySize = byte(len(auth.pubkey))
-
- // Add ID nonce signature to response.
- cdata := challenge.ChallengeData
- idsig, err := makeIDSignature(c.sha256, c.privkey, cdata, ephpubkey[:], toID)
- if err != nil {
- return nil, nil, fmt.Errorf("can't sign: %v", err)
- }
- auth.signature = idsig
- auth.h.SigSize = byte(len(auth.signature))
-
- // Add our record to response if it's newer than what remote side has.
- ln := c.localnode.Node()
- if challenge.RecordSeq < ln.Seq() {
- auth.record, _ = rlp.EncodeToBytes(ln.Record())
- }
-
- // Create session keys.
- sec := deriveKeys(sha256.New, ephkey, remotePubkey, c.localnode.ID(), challenge.Node.ID(), cdata)
- if sec == nil {
- return nil, nil, fmt.Errorf("key derivation failed")
- }
- return auth, sec, err
-}
-
-// encodeMessageHeader encodes an encrypted message packet.
-func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) {
- head := c.makeHeader(toID, flagMessage, 0)
-
- // Create the header.
- nonce, err := c.sc.nextNonce(s)
- if err != nil {
- return Header{}, fmt.Errorf("can't generate nonce: %v", err)
- }
- auth := messageAuthData{SrcID: c.localnode.ID()}
- c.buf.Reset()
- binary.Write(&c.buf, binary.BigEndian, &auth)
- head.AuthData = bytesCopy(&c.buf)
- head.Nonce = nonce
- return head, err
-}
-
-func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData []byte) ([]byte, error) {
- // Encode message plaintext.
- c.msgbuf.Reset()
- c.msgbuf.WriteByte(p.Kind())
- if err := rlp.Encode(&c.msgbuf, p); err != nil {
- return nil, err
- }
- messagePT := c.msgbuf.Bytes()
-
- // Encrypt into message ciphertext buffer.
- messageCT, err := encryptGCM(c.msgctbuf[:0], s.writeKey, head.Nonce[:], messagePT, headerData)
- if err == nil {
- c.msgctbuf = messageCT
- }
- return messageCT, err
-}
-
-// Decode decodes a discovery packet.
-func (c *Codec) Decode(inputData []byte, addr string) (src enode.ID, n *enode.Node, p Packet, err error) {
- if len(inputData) < minPacketSize {
- return enode.ID{}, nil, nil, errTooShort
- }
- // Copy the packet to a tmp buffer to avoid modifying it.
- c.decbuf = append(c.decbuf[:0], inputData...)
- input := c.decbuf
- // Unmask the static header.
- var head Header
- copy(head.IV[:], input[:sizeofMaskingIV])
- mask := head.mask(c.localnode.ID())
- staticHeader := input[sizeofMaskingIV:sizeofStaticPacketData]
- mask.XORKeyStream(staticHeader, staticHeader)
-
- // Decode and verify the static header.
- c.reader.Reset(staticHeader)
- binary.Read(&c.reader, binary.BigEndian, &head.StaticHeader)
- remainingInput := len(input) - sizeofStaticPacketData
- if err := head.checkValid(remainingInput, c.protocolID); err != nil {
- return enode.ID{}, nil, nil, err
- }
-
- // Unmask auth data.
- authDataEnd := sizeofStaticPacketData + int(head.AuthSize)
- authData := input[sizeofStaticPacketData:authDataEnd]
- mask.XORKeyStream(authData, authData)
- head.AuthData = authData
-
- // Delete timed-out handshakes. This must happen before decoding to avoid
- // processing the same handshake twice.
- c.sc.handshakeGC()
-
- // Decode auth part and message.
- headerData := input[:authDataEnd]
- msgData := input[authDataEnd:]
- switch head.Flag {
- case flagWhoareyou:
- p, err = c.decodeWhoareyou(&head, headerData)
- case flagHandshake:
- n, p, err = c.decodeHandshakeMessage(addr, &head, headerData, msgData)
- case flagMessage:
- p, err = c.decodeMessage(addr, &head, headerData, msgData)
- default:
- err = errInvalidFlag
- }
- return head.src, n, p, err
-}
-
-// decodeWhoareyou reads packet data after the header as a WHOAREYOU packet.
-func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) {
- if len(head.AuthData) != sizeofWhoareyouAuthData {
- return nil, fmt.Errorf("invalid auth size %d for WHOAREYOU", len(head.AuthData))
- }
- var auth whoareyouAuthData
- c.reader.Reset(head.AuthData)
- binary.Read(&c.reader, binary.BigEndian, &auth)
- p := &Whoareyou{
- Nonce: head.Nonce,
- IDNonce: auth.IDNonce,
- RecordSeq: auth.RecordSeq,
- ChallengeData: make([]byte, len(headerData)),
- }
- copy(p.ChallengeData, headerData)
- return p, nil
-}
-
-func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData, msgData []byte) (n *enode.Node, p Packet, err error) {
- node, auth, session, err := c.decodeHandshake(fromAddr, head)
- if err != nil {
- c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
- return nil, nil, err
- }
-
- // Decrypt the message using the new session keys.
- msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, session.readKey)
- if err != nil {
- c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
- return node, msg, err
- }
-
- // Handshake OK, drop the challenge and store the new session keys.
- c.sc.storeNewSession(auth.h.SrcID, fromAddr, session)
- c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
- return node, msg, nil
-}
-
-func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, auth handshakeAuthData, s *session, err error) {
- if auth, err = c.decodeHandshakeAuthData(head); err != nil {
- return nil, auth, nil, err
- }
-
- // Verify against our last WHOAREYOU.
- challenge := c.sc.getHandshake(auth.h.SrcID, fromAddr)
- if challenge == nil {
- return nil, auth, nil, errUnexpectedHandshake
- }
- // Get node record.
- n, err = c.decodeHandshakeRecord(challenge.Node, auth.h.SrcID, auth.record)
- if err != nil {
- return nil, auth, nil, err
- }
- // Verify ID nonce signature.
- sig := auth.signature
- cdata := challenge.ChallengeData
- err = verifyIDSignature(c.sha256, sig, n, cdata, auth.pubkey, c.localnode.ID())
- if err != nil {
- return nil, auth, nil, err
- }
- // Verify ephemeral key is on curve.
- ephkey, err := DecodePubkey(c.privkey.Curve, auth.pubkey)
- if err != nil {
- return nil, auth, nil, errInvalidAuthKey
- }
- // Derive session keys.
- session := deriveKeys(sha256.New, c.privkey, ephkey, auth.h.SrcID, c.localnode.ID(), cdata)
- session = session.keysFlipped()
- return n, auth, session, nil
-}
-
-// decodeHandshakeAuthData reads the authdata section of a handshake packet.
-func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, err error) {
- // Decode fixed size part.
- if len(head.AuthData) < sizeofHandshakeAuthData {
- return auth, fmt.Errorf("header authsize %d too low for handshake", head.AuthSize)
- }
- c.reader.Reset(head.AuthData)
- binary.Read(&c.reader, binary.BigEndian, &auth.h)
- head.src = auth.h.SrcID
-
- // Decode variable-size part.
- var (
- vardata = head.AuthData[sizeofHandshakeAuthData:]
- sigAndKeySize = int(auth.h.SigSize) + int(auth.h.PubkeySize)
- keyOffset = int(auth.h.SigSize)
- recOffset = keyOffset + int(auth.h.PubkeySize)
- )
- if len(vardata) < sigAndKeySize {
- return auth, errTooShort
- }
- auth.signature = vardata[:keyOffset]
- auth.pubkey = vardata[keyOffset:recOffset]
- auth.record = vardata[recOffset:]
- return auth, nil
-}
-
-// decodeHandshakeRecord verifies the node record contained in a handshake packet. The
-// remote node should include the record if we don't have one or if ours is older than the
-// latest sequence number.
-func (c *Codec) decodeHandshakeRecord(local *enode.Node, wantID enode.ID, remote []byte) (*enode.Node, error) {
- node := local
- if len(remote) > 0 {
- var record enr.Record
- if err := rlp.DecodeBytes(remote, &record); err != nil {
- return nil, err
- }
- if local == nil || local.Seq() < record.Seq() {
- n, err := enode.New(enode.ValidSchemes, &record)
- if err != nil {
- return nil, fmt.Errorf("invalid node record: %v", err)
- }
- if n.ID() != wantID {
- return nil, fmt.Errorf("record in handshake has wrong ID: %v", n.ID())
- }
- node = n
- }
- }
- if node == nil {
- return nil, errNoRecord
- }
- return node, nil
-}
-
-// decodeMessage reads packet data following the header as an ordinary message packet.
-func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData []byte) (Packet, error) {
- if len(head.AuthData) != sizeofMessageAuthData {
- return nil, fmt.Errorf("invalid auth size %d for message packet", len(head.AuthData))
- }
- var auth messageAuthData
- c.reader.Reset(head.AuthData)
- binary.Read(&c.reader, binary.BigEndian, &auth)
- head.src = auth.SrcID
-
- // Try decrypting the message.
- key := c.sc.readKey(auth.SrcID, fromAddr)
- msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, key)
- if errors.Is(err, errMessageDecrypt) {
- // It didn't work. Start the handshake since this is an ordinary message packet.
- return &Unknown{Nonce: head.Nonce}, nil
- }
- return msg, err
-}
-
-func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet, error) {
- msgdata, err := decryptGCM(readKey, nonce, input, headerData)
- if err != nil {
- return nil, errMessageDecrypt
- }
- if len(msgdata) == 0 {
- return nil, errMessageTooShort
- }
- return DecodeMessage(msgdata[0], msgdata[1:])
-}
-
-// checkValid performs some basic validity checks on the header.
-// The packetLen here is the length remaining after the static header.
-func (h *StaticHeader) checkValid(packetLen int, protocolID [6]byte) error {
- if h.ProtocolID != protocolID {
- return errInvalidHeader
- }
- if h.Version < minVersion {
- return errMinVersion
- }
- if h.Flag != flagWhoareyou && packetLen < minMessageSize {
- return errMsgTooShort
- }
- if int(h.AuthSize) > packetLen {
- return errAuthSize
- }
- return nil
-}
-
-// mask returns a cipher for 'masking' / 'unmasking' packet headers.
-func (h *Header) mask(destID enode.ID) cipher.Stream {
- block, err := aes.NewCipher(destID[:16])
- if err != nil {
- panic("can't create cipher")
- }
- return cipher.NewCTR(block, h.IV[:])
-}
-
-func bytesCopy(r *bytes.Buffer) []byte {
- b := make([]byte, r.Len())
- copy(b, r.Bytes())
- return b
-}
diff --git a/p2p/discover/v5wire/encoding_test.go b/p2p/discover/v5wire/encoding_test.go
deleted file mode 100644
index a5387311a5..0000000000
--- a/p2p/discover/v5wire/encoding_test.go
+++ /dev/null
@@ -1,639 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package v5wire
-
-import (
- "bytes"
- "crypto/ecdsa"
- "encoding/hex"
- "errors"
- "flag"
- "fmt"
- "net"
- "os"
- "path/filepath"
- "strings"
- "testing"
-
- "github.com/davecgh/go-spew/spew"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// To regenerate discv5 test vectors, run
-//
-// go test -run TestVectors -write-test-vectors
-var writeTestVectorsFlag = flag.Bool("write-test-vectors", false, "Overwrite discv5 test vectors in testdata/")
-
-var (
- testKeyA, _ = crypto.HexToECDSA("eef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f")
- testKeyB, _ = crypto.HexToECDSA("66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628")
- testEphKey, _ = crypto.HexToECDSA("0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6")
- testIDnonce = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
-)
-
-// This test checks that the minPacketSize and randomPacketMsgSize constants are well-defined.
-func TestMinSizes(t *testing.T) {
- var (
- gcmTagSize = 16
- emptyMsg = sizeofMessageAuthData + gcmTagSize
- )
- t.Log("static header size", sizeofStaticPacketData)
- t.Log("whoareyou size", sizeofStaticPacketData+sizeofWhoareyouAuthData)
- t.Log("empty msg size", sizeofStaticPacketData+emptyMsg)
- if want := emptyMsg; minMessageSize != want {
- t.Fatalf("wrong minMessageSize %d, want %d", minMessageSize, want)
- }
- if sizeofMessageAuthData+randomPacketMsgSize < minMessageSize {
- t.Fatalf("randomPacketMsgSize %d too small", randomPacketMsgSize)
- }
-}
-
-// This test checks the basic handshake flow where A talks to B and A has no secrets.
-func TestHandshake(t *testing.T) {
- t.Parallel()
- net := newHandshakeTest()
- defer net.close()
-
- // A -> B RANDOM PACKET
- packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{})
- resp := net.nodeB.expectDecode(t, UnknownPacket, packet)
-
- // A <- B WHOAREYOU
- challenge := &Whoareyou{
- Nonce: resp.(*Unknown).Nonce,
- IDNonce: testIDnonce,
- RecordSeq: 0,
- }
- whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
- net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
-
- // A -> B FINDNODE (handshake packet)
- findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
- net.nodeB.expectDecode(t, FindnodeMsg, findnode)
- if len(net.nodeB.c.sc.handshakes) > 0 {
- t.Fatalf("node B didn't remove handshake from challenge map")
- }
-
- // A <- B NODES
- nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{RespCount: 1})
- net.nodeA.expectDecode(t, NodesMsg, nodes)
-}
-
-// This test checks that handshake attempts are removed within the timeout.
-func TestHandshake_timeout(t *testing.T) {
- t.Parallel()
- net := newHandshakeTest()
- defer net.close()
-
- // A -> B RANDOM PACKET
- packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{})
- resp := net.nodeB.expectDecode(t, UnknownPacket, packet)
-
- // A <- B WHOAREYOU
- challenge := &Whoareyou{
- Nonce: resp.(*Unknown).Nonce,
- IDNonce: testIDnonce,
- RecordSeq: 0,
- }
- whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
- net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
-
- // A -> B FINDNODE (handshake packet) after timeout
- net.clock.Run(handshakeTimeout + 1)
- findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
- net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode)
-}
-
-// This test checks handshake behavior when no record is sent in the auth response.
-func TestHandshake_norecord(t *testing.T) {
- t.Parallel()
- net := newHandshakeTest()
- defer net.close()
-
- // A -> B RANDOM PACKET
- packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{})
- resp := net.nodeB.expectDecode(t, UnknownPacket, packet)
-
- // A <- B WHOAREYOU
- nodeA := net.nodeA.n()
- if nodeA.Seq() == 0 {
- t.Fatal("need non-zero sequence number")
- }
- challenge := &Whoareyou{
- Nonce: resp.(*Unknown).Nonce,
- IDNonce: testIDnonce,
- RecordSeq: nodeA.Seq(),
- Node: nodeA,
- }
- whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
- net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
-
- // A -> B FINDNODE
- findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
- net.nodeB.expectDecode(t, FindnodeMsg, findnode)
-
- // A <- B NODES
- nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{RespCount: 1})
- net.nodeA.expectDecode(t, NodesMsg, nodes)
-}
-
-// In this test, A tries to send FINDNODE with existing secrets but B doesn't know
-// anything about A.
-func TestHandshake_rekey(t *testing.T) {
- t.Parallel()
- net := newHandshakeTest()
- defer net.close()
-
- session := &session{
- readKey: []byte("BBBBBBBBBBBBBBBB"),
- writeKey: []byte("AAAAAAAAAAAAAAAA"),
- }
- net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session)
-
- // A -> B FINDNODE (encrypted with zero keys)
- findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{})
- net.nodeB.expectDecode(t, UnknownPacket, findnode)
-
- // A <- B WHOAREYOU
- challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce}
- whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
- net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
-
- // Check that new keys haven't been stored yet.
- sa := net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr())
- if !bytes.Equal(sa.writeKey, session.writeKey) || !bytes.Equal(sa.readKey, session.readKey) {
- t.Fatal("node A stored keys too early")
- }
- if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil {
- t.Fatal("node B stored keys too early")
- }
-
- // A -> B FINDNODE encrypted with new keys
- findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
- net.nodeB.expectDecode(t, FindnodeMsg, findnode)
-
- // A <- B NODES
- nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{RespCount: 1})
- net.nodeA.expectDecode(t, NodesMsg, nodes)
-}
-
-// In this test A and B have different keys before the handshake.
-func TestHandshake_rekey2(t *testing.T) {
- t.Parallel()
- net := newHandshakeTest()
- defer net.close()
-
- initKeysA := &session{
- readKey: []byte("BBBBBBBBBBBBBBBB"),
- writeKey: []byte("AAAAAAAAAAAAAAAA"),
- }
- initKeysB := &session{
- readKey: []byte("CCCCCCCCCCCCCCCC"),
- writeKey: []byte("DDDDDDDDDDDDDDDD"),
- }
- net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA)
- net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB)
-
- // A -> B FINDNODE encrypted with initKeysA
- findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}})
- net.nodeB.expectDecode(t, UnknownPacket, findnode)
-
- // A <- B WHOAREYOU
- challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce}
- whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
- net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
-
- // A -> B FINDNODE (handshake packet)
- findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
- net.nodeB.expectDecode(t, FindnodeMsg, findnode)
-
- // A <- B NODES
- nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{RespCount: 1})
- net.nodeA.expectDecode(t, NodesMsg, nodes)
-}
-
-func TestHandshake_BadHandshakeAttack(t *testing.T) {
- t.Parallel()
- net := newHandshakeTest()
- defer net.close()
-
- // A -> B RANDOM PACKET
- packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{})
- resp := net.nodeB.expectDecode(t, UnknownPacket, packet)
-
- // A <- B WHOAREYOU
- challenge := &Whoareyou{
- Nonce: resp.(*Unknown).Nonce,
- IDNonce: testIDnonce,
- RecordSeq: 0,
- }
- whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
- net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
-
- // A -> B FINDNODE
- incorrect_challenge := &Whoareyou{
- IDNonce: [16]byte{5, 6, 7, 8, 9, 6, 11, 12},
- RecordSeq: challenge.RecordSeq,
- Node: challenge.Node,
- sent: challenge.sent,
- }
- incorrect_findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, incorrect_challenge, &Findnode{})
- incorrect_findnode2 := make([]byte, len(incorrect_findnode))
- copy(incorrect_findnode2, incorrect_findnode)
-
- net.nodeB.expectDecodeErr(t, errInvalidNonceSig, incorrect_findnode)
-
- // Reject new findnode as previous handshake is now deleted.
- net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, incorrect_findnode2)
-
- // The findnode packet is again rejected even with a valid challenge this time.
- findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
- net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode)
-}
-
-// This test checks some malformed packets.
-func TestDecodeErrorsV5(t *testing.T) {
- t.Parallel()
- net := newHandshakeTest()
- defer net.close()
-
- b := make([]byte, 0)
- net.nodeA.expectDecodeErr(t, errTooShort, b)
-
- b = make([]byte, 62)
- net.nodeA.expectDecodeErr(t, errTooShort, b)
-
- b = make([]byte, 63)
- net.nodeA.expectDecodeErr(t, errInvalidHeader, b)
-
- // TODO some more tests would be nice :)
- // - check invalid authdata sizes
- // - check invalid handshake data sizes
-}
-
-// This test checks that all test vectors can be decoded.
-func TestTestVectorsV5(t *testing.T) {
- var (
- idA = enode.PubkeyToIDV4(&testKeyA.PublicKey)
- idB = enode.PubkeyToIDV4(&testKeyB.PublicKey)
- addr = "127.0.0.1"
- session = &session{
- writeKey: hexutil.MustDecode("0x00000000000000000000000000000000"),
- readKey: hexutil.MustDecode("0x01010101010101010101010101010101"),
- }
- challenge0A, challenge1A, challenge0B Whoareyou
- )
-
- // Create challenge packets.
- c := Whoareyou{
- Nonce: Nonce{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
- IDNonce: testIDnonce,
- }
- challenge0A, challenge1A, challenge0B = c, c, c
- challenge1A.RecordSeq = 1
- net := newHandshakeTest()
- challenge0A.Node = net.nodeA.n()
- challenge0B.Node = net.nodeB.n()
- challenge1A.Node = net.nodeA.n()
- net.close()
-
- type testVectorTest struct {
- name string // test vector name
- packet Packet // the packet to be encoded
- challenge *Whoareyou // handshake challenge passed to encoder
- prep func(*handshakeTest) // called before encode/decode
- }
- tests := []testVectorTest{
- {
- name: "v5.1-whoareyou",
- packet: &challenge0B,
- },
- {
- name: "v5.1-ping-message",
- packet: &Ping{
- ReqID: []byte{0, 0, 0, 1},
- ENRSeq: 2,
- },
- prep: func(net *handshakeTest) {
- net.nodeA.c.sc.storeNewSession(idB, addr, session)
- net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped())
- },
- },
- {
- name: "v5.1-ping-handshake-enr",
- packet: &Ping{
- ReqID: []byte{0, 0, 0, 1},
- ENRSeq: 1,
- },
- challenge: &challenge0A,
- prep: func(net *handshakeTest) {
- // Update challenge.Header.AuthData.
- net.nodeA.c.Encode(idB, "", &challenge0A, nil)
- net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge0A)
- },
- },
- {
- name: "v5.1-ping-handshake",
- packet: &Ping{
- ReqID: []byte{0, 0, 0, 1},
- ENRSeq: 1,
- },
- challenge: &challenge1A,
- prep: func(net *handshakeTest) {
- // Update challenge data.
- net.nodeA.c.Encode(idB, "", &challenge1A, nil)
- net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge1A)
- },
- },
- }
-
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- net := newHandshakeTest()
- defer net.close()
-
- // Override all random inputs.
- net.nodeA.c.sc.nonceGen = func(counter uint32) (Nonce, error) {
- return Nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, nil
- }
- net.nodeA.c.sc.maskingIVGen = func(buf []byte) error {
- return nil // all zero
- }
- net.nodeA.c.sc.ephemeralKeyGen = func() (*ecdsa.PrivateKey, error) {
- return testEphKey, nil
- }
-
- // Prime the codec for encoding/decoding.
- if test.prep != nil {
- test.prep(net)
- }
-
- file := filepath.Join("testdata", test.name+".txt")
- if *writeTestVectorsFlag {
- // Encode the packet.
- d, nonce := net.nodeA.encodeWithChallenge(t, net.nodeB, test.challenge, test.packet)
- comment := testVectorComment(net, test.packet, test.challenge, nonce)
- writeTestVector(file, comment, d)
- }
- enc := hexFile(file)
- net.nodeB.expectDecode(t, test.packet.Kind(), enc)
- })
- }
-}
-
-// testVectorComment creates the commentary for discv5 test vector files.
-func testVectorComment(net *handshakeTest, p Packet, challenge *Whoareyou, nonce Nonce) string {
- o := new(strings.Builder)
- printWhoareyou := func(p *Whoareyou) {
- fmt.Fprintf(o, "whoareyou.challenge-data = %#x\n", p.ChallengeData)
- fmt.Fprintf(o, "whoareyou.request-nonce = %#x\n", p.Nonce[:])
- fmt.Fprintf(o, "whoareyou.id-nonce = %#x\n", p.IDNonce[:])
- fmt.Fprintf(o, "whoareyou.enr-seq = %d\n", p.RecordSeq)
- }
-
- fmt.Fprintf(o, "src-node-id = %#x\n", net.nodeA.id().Bytes())
- fmt.Fprintf(o, "dest-node-id = %#x\n", net.nodeB.id().Bytes())
- switch p := p.(type) {
- case *Whoareyou:
- // WHOAREYOU packet.
- printWhoareyou(p)
- case *Ping:
- fmt.Fprintf(o, "nonce = %#x\n", nonce[:])
- fmt.Fprintf(o, "read-key = %#x\n", net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()).writeKey)
- fmt.Fprintf(o, "ping.req-id = %#x\n", p.ReqID)
- fmt.Fprintf(o, "ping.enr-seq = %d\n", p.ENRSeq)
- if challenge != nil {
- // Handshake message packet.
- fmt.Fprint(o, "\nhandshake inputs:\n\n")
- printWhoareyou(challenge)
- fmt.Fprintf(o, "ephemeral-key = %#x\n", testEphKey.D.Bytes())
- fmt.Fprintf(o, "ephemeral-pubkey = %#x\n", crypto.CompressPubkey(&testEphKey.PublicKey))
- }
- default:
- panic(fmt.Errorf("unhandled packet type %T", p))
- }
- return o.String()
-}
-
-// This benchmark checks performance of handshake packet decoding.
-func BenchmarkV5_DecodeHandshakePingSecp256k1(b *testing.B) {
- net := newHandshakeTest()
- defer net.close()
-
- var (
- idA = net.nodeA.id()
- challenge = &Whoareyou{Node: net.nodeB.n()}
- message = &Ping{ReqID: []byte("reqid")}
- )
- enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), "", message, challenge)
- if err != nil {
- b.Fatal("can't encode handshake packet")
- }
- challenge.Node = nil // force ENR signature verification in decoder
- b.ResetTimer()
-
- input := make([]byte, len(enc))
- for i := 0; i < b.N; i++ {
- copy(input, enc)
- net.nodeB.c.sc.storeSentHandshake(idA, "", challenge)
- _, _, _, err := net.nodeB.c.Decode(input, "")
- if err != nil {
- b.Fatal(err)
- }
- }
-}
-
-// This benchmark checks how long it takes to decode an encrypted ping packet.
-func BenchmarkV5_DecodePing(b *testing.B) {
- net := newHandshakeTest()
- defer net.close()
-
- session := &session{
- readKey: []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17},
- writeKey: []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134},
- }
- net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session)
- net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped())
- addrB := net.nodeA.addr()
- ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5}
- enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil)
- if err != nil {
- b.Fatalf("can't encode: %v", err)
- }
- b.ResetTimer()
-
- input := make([]byte, len(enc))
- for i := 0; i < b.N; i++ {
- copy(input, enc)
- _, _, packet, _ := net.nodeB.c.Decode(input, addrB)
- if _, ok := packet.(*Ping); !ok {
- b.Fatalf("wrong packet type %T", packet)
- }
- }
-}
-
-var pp = spew.NewDefaultConfig()
-
-type handshakeTest struct {
- nodeA, nodeB handshakeTestNode
- clock mclock.Simulated
-}
-
-type handshakeTestNode struct {
- ln *enode.LocalNode
- c *Codec
-}
-
-func newHandshakeTest() *handshakeTest {
- t := new(handshakeTest)
- t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock, DefaultProtocolID)
- t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock, DefaultProtocolID)
- return t
-}
-
-func (t *handshakeTest) close() {
- t.nodeA.ln.Database().Close()
- t.nodeB.ln.Database().Close()
-}
-
-func (n *handshakeTestNode) init(key *ecdsa.PrivateKey, ip net.IP, clock mclock.Clock, protocolID [6]byte) {
- db, _ := enode.OpenDB("")
- n.ln = enode.NewLocalNode(db, key)
- n.ln.SetStaticIP(ip)
- n.c = NewCodec(n.ln, key, clock, nil)
-}
-
-func (n *handshakeTestNode) encode(t testing.TB, to handshakeTestNode, p Packet) ([]byte, Nonce) {
- t.Helper()
- return n.encodeWithChallenge(t, to, nil, p)
-}
-
-func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNode, c *Whoareyou, p Packet) ([]byte, Nonce) {
- t.Helper()
-
- // Copy challenge and add destination node. This avoids sharing 'c' among the two codecs.
- var challenge *Whoareyou
- if c != nil {
- challengeCopy := *c
- challenge = &challengeCopy
- challenge.Node = to.n()
- }
- // Encode to destination.
- enc, nonce, err := n.c.Encode(to.id(), to.addr(), p, challenge)
- if err != nil {
- t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err))
- }
- t.Logf("(%s) -> (%s) %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.Name(), hex.Dump(enc))
- return enc, nonce
-}
-
-func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) Packet {
- t.Helper()
-
- dec, err := n.decode(p)
- if err != nil {
- t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err))
- }
- t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec))
- if dec.Kind() != ptype {
- t.Fatalf("expected packet type %d, got %d", ptype, dec.Kind())
- }
- return dec
-}
-
-func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) {
- t.Helper()
- if _, err := n.decode(p); !errors.Is(err, wantErr) {
- t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr))
- }
-}
-
-func (n *handshakeTestNode) decode(input []byte) (Packet, error) {
- _, _, p, err := n.c.Decode(input, "127.0.0.1")
- return p, err
-}
-
-func (n *handshakeTestNode) n() *enode.Node {
- return n.ln.Node()
-}
-
-func (n *handshakeTestNode) addr() string {
- return n.ln.Node().IP().String()
-}
-
-func (n *handshakeTestNode) id() enode.ID {
- return n.ln.ID()
-}
-
-// hexFile reads the given file and decodes the hex data contained in it.
-// Whitespace and any lines beginning with the # character are ignored.
-func hexFile(file string) []byte {
- fileContent, err := os.ReadFile(file)
- if err != nil {
- panic(err)
- }
-
- // Gather hex data, ignore comments.
- var text []byte
- for _, line := range bytes.Split(fileContent, []byte("\n")) {
- line = bytes.TrimSpace(line)
- if len(line) > 0 && line[0] == '#' {
- continue
- }
- text = append(text, line...)
- }
-
- // Parse the hex.
- if bytes.HasPrefix(text, []byte("0x")) {
- text = text[2:]
- }
- data := make([]byte, hex.DecodedLen(len(text)))
- if _, err := hex.Decode(data, text); err != nil {
- panic("invalid hex in " + file)
- }
- return data
-}
-
-// writeTestVector writes a test vector file with the given commentary and binary data.
-func writeTestVector(file, comment string, data []byte) {
- fd, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
- if err != nil {
- panic(err)
- }
- defer fd.Close()
-
- if len(comment) > 0 {
- for _, line := range strings.Split(strings.TrimSpace(comment), "\n") {
- fmt.Fprintf(fd, "# %s\n", line)
- }
- fmt.Fprintln(fd)
- }
- for len(data) > 0 {
- var chunk []byte
- if len(data) < 32 {
- chunk = data
- } else {
- chunk = data[:32]
- }
- data = data[len(chunk):]
- fmt.Fprintf(fd, "%x\n", chunk)
- }
-}
diff --git a/p2p/discover/v5wire/msg.go b/p2p/discover/v5wire/msg.go
deleted file mode 100644
index 401db2f6c5..0000000000
--- a/p2p/discover/v5wire/msg.go
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package v5wire
-
-import (
- "fmt"
- "net"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// Packet is implemented by all message types.
-type Packet interface {
- Name() string // Name returns a string corresponding to the message type.
- Kind() byte // Kind returns the message type.
- RequestID() []byte // Returns the request ID.
- SetRequestID([]byte) // Sets the request ID.
-
- // AppendLogInfo returns its argument 'ctx' with additional fields
- // appended for logging purposes.
- AppendLogInfo(ctx []interface{}) []interface{}
-}
-
-// Message types.
-const (
- PingMsg byte = iota + 1
- PongMsg
- FindnodeMsg
- NodesMsg
- TalkRequestMsg
- TalkResponseMsg
- RequestTicketMsg
- TicketMsg
-
- UnknownPacket = byte(255) // any non-decryptable packet
- WhoareyouPacket = byte(254) // the WHOAREYOU packet
-)
-
-// Protocol messages.
-type (
- // Unknown represents any packet that can't be decrypted.
- Unknown struct {
- Nonce Nonce
- }
-
- // WHOAREYOU contains the handshake challenge.
- Whoareyou struct {
- ChallengeData []byte // Encoded challenge
- Nonce Nonce // Nonce of request packet
- IDNonce [16]byte // Identity proof data
- RecordSeq uint64 // ENR sequence number of recipient
-
- // Node is the locally known node record of recipient.
- // This must be set by the caller of Encode.
- Node *enode.Node
-
- sent mclock.AbsTime // for handshake GC.
- }
-
- // PING is sent during liveness checks.
- Ping struct {
- ReqID []byte
- ENRSeq uint64
- }
-
- // PONG is the reply to PING.
- Pong struct {
- ReqID []byte
- ENRSeq uint64
- ToIP net.IP // These fields should mirror the UDP envelope address of the ping
- ToPort uint16 // packet, which provides a way to discover the external address (after NAT).
- }
-
- // FINDNODE is a query for nodes in the given bucket.
- Findnode struct {
- ReqID []byte
- Distances []uint
-
- // OpID is for debugging purposes and is not part of the packet encoding.
- // It identifies the 'operation' on behalf of which the request was sent.
- OpID uint64 `rlp:"-"`
- }
-
- // NODES is a response to FINDNODE.
- Nodes struct {
- ReqID []byte
- RespCount uint8 // total number of responses to the request
- Nodes []*enr.Record
- }
-
- // TALKREQ is an application-level request.
- TalkRequest struct {
- ReqID []byte
- Protocol string
- Message []byte
- }
-
- // TALKRESP is the reply to TALKREQ.
- TalkResponse struct {
- ReqID []byte
- Message []byte
- }
-)
-
-// DecodeMessage decodes the message body of a packet.
-func DecodeMessage(ptype byte, body []byte) (Packet, error) {
- var dec Packet
- switch ptype {
- case PingMsg:
- dec = new(Ping)
- case PongMsg:
- dec = new(Pong)
- case FindnodeMsg:
- dec = new(Findnode)
- case NodesMsg:
- dec = new(Nodes)
- case TalkRequestMsg:
- dec = new(TalkRequest)
- case TalkResponseMsg:
- dec = new(TalkResponse)
- default:
- return nil, fmt.Errorf("unknown packet type %d", ptype)
- }
- if err := rlp.DecodeBytes(body, dec); err != nil {
- return nil, err
- }
- if dec.RequestID() != nil && len(dec.RequestID()) > 8 {
- return nil, ErrInvalidReqID
- }
- return dec, nil
-}
-
-func (*Whoareyou) Name() string { return "WHOAREYOU/v5" }
-func (*Whoareyou) Kind() byte { return WhoareyouPacket }
-func (*Whoareyou) RequestID() []byte { return nil }
-func (*Whoareyou) SetRequestID([]byte) {}
-
-func (*Whoareyou) AppendLogInfo(ctx []interface{}) []interface{} {
- return ctx
-}
-
-func (*Unknown) Name() string { return "UNKNOWN/v5" }
-func (*Unknown) Kind() byte { return UnknownPacket }
-func (*Unknown) RequestID() []byte { return nil }
-func (*Unknown) SetRequestID([]byte) {}
-
-func (*Unknown) AppendLogInfo(ctx []interface{}) []interface{} {
- return ctx
-}
-
-func (*Ping) Name() string { return "PING/v5" }
-func (*Ping) Kind() byte { return PingMsg }
-func (p *Ping) RequestID() []byte { return p.ReqID }
-func (p *Ping) SetRequestID(id []byte) { p.ReqID = id }
-
-func (p *Ping) AppendLogInfo(ctx []interface{}) []interface{} {
- return append(ctx, "req", hexutil.Bytes(p.ReqID), "enrseq", p.ENRSeq)
-}
-
-func (*Pong) Name() string { return "PONG/v5" }
-func (*Pong) Kind() byte { return PongMsg }
-func (p *Pong) RequestID() []byte { return p.ReqID }
-func (p *Pong) SetRequestID(id []byte) { p.ReqID = id }
-
-func (p *Pong) AppendLogInfo(ctx []interface{}) []interface{} {
- return append(ctx, "req", hexutil.Bytes(p.ReqID), "enrseq", p.ENRSeq)
-}
-
-func (p *Findnode) Name() string { return "FINDNODE/v5" }
-func (p *Findnode) Kind() byte { return FindnodeMsg }
-func (p *Findnode) RequestID() []byte { return p.ReqID }
-func (p *Findnode) SetRequestID(id []byte) { p.ReqID = id }
-
-func (p *Findnode) AppendLogInfo(ctx []interface{}) []interface{} {
- ctx = append(ctx, "req", hexutil.Bytes(p.ReqID))
- if p.OpID != 0 {
- ctx = append(ctx, "opid", p.OpID)
- }
- return ctx
-}
-
-func (*Nodes) Name() string { return "NODES/v5" }
-func (*Nodes) Kind() byte { return NodesMsg }
-func (p *Nodes) RequestID() []byte { return p.ReqID }
-func (p *Nodes) SetRequestID(id []byte) { p.ReqID = id }
-
-func (p *Nodes) AppendLogInfo(ctx []interface{}) []interface{} {
- return append(ctx,
- "req", hexutil.Bytes(p.ReqID),
- "tot", p.RespCount,
- "n", len(p.Nodes),
- )
-}
-
-func (*TalkRequest) Name() string { return "TALKREQ/v5" }
-func (*TalkRequest) Kind() byte { return TalkRequestMsg }
-func (p *TalkRequest) RequestID() []byte { return p.ReqID }
-func (p *TalkRequest) SetRequestID(id []byte) { p.ReqID = id }
-
-func (p *TalkRequest) AppendLogInfo(ctx []interface{}) []interface{} {
- return append(ctx, "proto", p.Protocol, "req", hexutil.Bytes(p.ReqID), "len", len(p.Message))
-}
-
-func (*TalkResponse) Name() string { return "TALKRESP/v5" }
-func (*TalkResponse) Kind() byte { return TalkResponseMsg }
-func (p *TalkResponse) RequestID() []byte { return p.ReqID }
-func (p *TalkResponse) SetRequestID(id []byte) { p.ReqID = id }
-
-func (p *TalkResponse) AppendLogInfo(ctx []interface{}) []interface{} {
- return append(ctx, "req", hexutil.Bytes(p.ReqID), "len", len(p.Message))
-}
diff --git a/p2p/discover/v5wire/session.go b/p2p/discover/v5wire/session.go
deleted file mode 100644
index 862c21fcee..0000000000
--- a/p2p/discover/v5wire/session.go
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package v5wire
-
-import (
- "crypto/ecdsa"
- crand "crypto/rand"
- "encoding/binary"
- "time"
-
- "github.com/ethereum/go-ethereum/common/lru"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-const handshakeTimeout = time.Second
-
-// The SessionCache keeps negotiated encryption keys and
-// state for in-progress handshakes in the Discovery v5 wire protocol.
-type SessionCache struct {
- sessions lru.BasicLRU[sessionID, *session]
- handshakes map[sessionID]*Whoareyou
- clock mclock.Clock
-
- // hooks for overriding randomness.
- nonceGen func(uint32) (Nonce, error)
- maskingIVGen func([]byte) error
- ephemeralKeyGen func() (*ecdsa.PrivateKey, error)
-}
-
-// sessionID identifies a session or handshake.
-type sessionID struct {
- id enode.ID
- addr string
-}
-
-// session contains session information
-type session struct {
- writeKey []byte
- readKey []byte
- nonceCounter uint32
-}
-
-// keysFlipped returns a copy of s with the read and write keys flipped.
-func (s *session) keysFlipped() *session {
- return &session{s.readKey, s.writeKey, s.nonceCounter}
-}
-
-func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
- return &SessionCache{
- sessions: lru.NewBasicLRU[sessionID, *session](maxItems),
- handshakes: make(map[sessionID]*Whoareyou),
- clock: clock,
- nonceGen: generateNonce,
- maskingIVGen: generateMaskingIV,
- ephemeralKeyGen: crypto.GenerateKey,
- }
-}
-
-func generateNonce(counter uint32) (n Nonce, err error) {
- binary.BigEndian.PutUint32(n[:4], counter)
- _, err = crand.Read(n[4:])
- return n, err
-}
-
-func generateMaskingIV(buf []byte) error {
- _, err := crand.Read(buf)
- return err
-}
-
-// nextNonce creates a nonce for encrypting a message to the given session.
-func (sc *SessionCache) nextNonce(s *session) (Nonce, error) {
- s.nonceCounter++
- return sc.nonceGen(s.nonceCounter)
-}
-
-// session returns the current session for the given node, if any.
-func (sc *SessionCache) session(id enode.ID, addr string) *session {
- item, _ := sc.sessions.Get(sessionID{id, addr})
- return item
-}
-
-// readKey returns the current read key for the given node.
-func (sc *SessionCache) readKey(id enode.ID, addr string) []byte {
- if s := sc.session(id, addr); s != nil {
- return s.readKey
- }
- return nil
-}
-
-// storeNewSession stores new encryption keys in the cache.
-func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) {
- sc.sessions.Add(sessionID{id, addr}, s)
-}
-
-// getHandshake gets the handshake challenge we previously sent to the given remote node.
-func (sc *SessionCache) getHandshake(id enode.ID, addr string) *Whoareyou {
- return sc.handshakes[sessionID{id, addr}]
-}
-
-// storeSentHandshake stores the handshake challenge sent to the given remote node.
-func (sc *SessionCache) storeSentHandshake(id enode.ID, addr string, challenge *Whoareyou) {
- challenge.sent = sc.clock.Now()
- sc.handshakes[sessionID{id, addr}] = challenge
-}
-
-// deleteHandshake deletes handshake data for the given node.
-func (sc *SessionCache) deleteHandshake(id enode.ID, addr string) {
- delete(sc.handshakes, sessionID{id, addr})
-}
-
-// handshakeGC deletes timed-out handshakes.
-func (sc *SessionCache) handshakeGC() {
- deadline := sc.clock.Now().Add(-handshakeTimeout)
- for key, challenge := range sc.handshakes {
- if challenge.sent < deadline {
- delete(sc.handshakes, key)
- }
- }
-}
diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt
deleted file mode 100644
index 477f9e15a8..0000000000
--- a/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb
-# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9
-# nonce = 0xffffffffffffffffffffffff
-# read-key = 0x53b1c075f41876423154e157470c2f48
-# ping.req-id = 0x00000001
-# ping.enr-seq = 1
-#
-# handshake inputs:
-#
-# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000
-# whoareyou.request-nonce = 0x0102030405060708090a0b0c
-# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10
-# whoareyou.enr-seq = 0
-# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6
-# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5
-
-00000000000000000000000000000000088b3d4342774649305f313964a39e55
-ea96c005ad539c8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3
-4c4f53245d08da4bb23698868350aaad22e3ab8dd034f548a1c43cd246be9856
-2fafa0a1fa86d8e7a3b95ae78cc2b988ded6a5b59eb83ad58097252188b902b2
-1481e30e5e285f19735796706adff216ab862a9186875f9494150c4ae06fa4d1
-f0396c93f215fa4ef524e0ed04c3c21e39b1868e1ca8105e585ec17315e755e6
-cfc4dd6cb7fd8e1a1f55e49b4b5eb024221482105346f3c82b15fdaae36a3bb1
-2a494683b4a3c7f2ae41306252fed84785e2bbff3b022812d0882f06978df84a
-80d443972213342d04b9048fc3b1d5fcb1df0f822152eced6da4d3f6df27e70e
-4539717307a0208cd208d65093ccab5aa596a34d7511401987662d8cf62b1394
-71
diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt
deleted file mode 100644
index b3f304766c..0000000000
--- a/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb
-# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9
-# nonce = 0xffffffffffffffffffffffff
-# read-key = 0x4f9fac6de7567d1e3b1241dffe90f662
-# ping.req-id = 0x00000001
-# ping.enr-seq = 1
-#
-# handshake inputs:
-#
-# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000001
-# whoareyou.request-nonce = 0x0102030405060708090a0b0c
-# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10
-# whoareyou.enr-seq = 1
-# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6
-# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5
-
-00000000000000000000000000000000088b3d4342774649305f313964a39e55
-ea96c005ad521d8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3
-4c4f53245d08da4bb252012b2cba3f4f374a90a75cff91f142fa9be3e0a5f3ef
-268ccb9065aeecfd67a999e7fdc137e062b2ec4a0eb92947f0d9a74bfbf44dfb
-a776b21301f8b65efd5796706adff216ab862a9186875f9494150c4ae06fa4d1
-f0396c93f215fa4ef524f1eadf5f0f4126b79336671cbcf7a885b1f8bd2a5d83
-9cf8
diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-message.txt b/p2p/discover/v5wire/testdata/v5.1-ping-message.txt
deleted file mode 100644
index f82b99c3bc..0000000000
--- a/p2p/discover/v5wire/testdata/v5.1-ping-message.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb
-# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9
-# nonce = 0xffffffffffffffffffffffff
-# read-key = 0x00000000000000000000000000000000
-# ping.req-id = 0x00000001
-# ping.enr-seq = 2
-
-00000000000000000000000000000000088b3d4342774649325f313964a39e55
-ea96c005ad52be8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3
-4c4f53245d08dab84102ed931f66d1492acb308fa1c6715b9d139b81acbdcc
diff --git a/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt b/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt
deleted file mode 100644
index 1a75f525ee..0000000000
--- a/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb
-# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9
-# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000
-# whoareyou.request-nonce = 0x0102030405060708090a0b0c
-# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10
-# whoareyou.enr-seq = 0
-
-00000000000000000000000000000000088b3d434277464933a1ccc59f5967ad
-1d6035f15e528627dde75cd68292f9e6c27d6b66c8100a873fcbaed4e16b8d
diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go
deleted file mode 100644
index 8f1c221b80..0000000000
--- a/p2p/dnsdisc/client.go
+++ /dev/null
@@ -1,389 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package dnsdisc
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "math/rand"
- "net"
- "strings"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/lru"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "golang.org/x/sync/singleflight"
- "golang.org/x/time/rate"
-)
-
-// Client discovers nodes by querying DNS servers.
-type Client struct {
- cfg Config
- clock mclock.Clock
- entries *lru.Cache[string, entry]
- ratelimit *rate.Limiter
- singleflight singleflight.Group
-}
-
-// Config holds configuration options for the client.
-type Config struct {
- Timeout time.Duration // timeout used for DNS lookups (default 5s)
- RecheckInterval time.Duration // time between tree root update checks (default 30min)
- CacheLimit int // maximum number of cached records (default 1000)
- RateLimit float64 // maximum DNS requests / second (default 3)
- ValidSchemes enr.IdentityScheme // acceptable ENR identity schemes (default enode.ValidSchemes)
- Resolver Resolver // the DNS resolver to use (defaults to system DNS)
- Logger log.Logger // destination of client log messages (defaults to root logger)
-}
-
-// Resolver is a DNS resolver that can query TXT records.
-type Resolver interface {
- LookupTXT(ctx context.Context, domain string) ([]string, error)
-}
-
-func (cfg Config) withDefaults() Config {
- const (
- defaultTimeout = 5 * time.Second
- defaultRecheck = 30 * time.Minute
- defaultRateLimit = 3
- defaultCache = 1000
- )
- if cfg.Timeout == 0 {
- cfg.Timeout = defaultTimeout
- }
- if cfg.RecheckInterval == 0 {
- cfg.RecheckInterval = defaultRecheck
- }
- if cfg.CacheLimit == 0 {
- cfg.CacheLimit = defaultCache
- }
- if cfg.RateLimit == 0 {
- cfg.RateLimit = defaultRateLimit
- }
- if cfg.ValidSchemes == nil {
- cfg.ValidSchemes = enode.ValidSchemes
- }
- if cfg.Resolver == nil {
- cfg.Resolver = new(net.Resolver)
- }
- if cfg.Logger == nil {
- cfg.Logger = log.Root()
- }
- return cfg
-}
-
-// NewClient creates a client.
-func NewClient(cfg Config) *Client {
- cfg = cfg.withDefaults()
- rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10)
- return &Client{
- cfg: cfg,
- entries: lru.NewCache[string, entry](cfg.CacheLimit),
- clock: mclock.System{},
- ratelimit: rlimit,
- }
-}
-
-// SyncTree downloads the entire node tree at the given URL.
-func (c *Client) SyncTree(url string) (*Tree, error) {
- le, err := parseLink(url)
- if err != nil {
- return nil, fmt.Errorf("invalid enrtree URL: %v", err)
- }
- ct := newClientTree(c, new(linkCache), le)
- t := &Tree{entries: make(map[string]entry)}
- if err := ct.syncAll(t.entries); err != nil {
- return nil, err
- }
- t.root = ct.root
- return t, nil
-}
-
-// NewIterator creates an iterator that visits all nodes at the
-// given tree URLs.
-func (c *Client) NewIterator(urls ...string) (enode.Iterator, error) {
- it := c.newRandomIterator()
- for _, url := range urls {
- if err := it.addTree(url); err != nil {
- return nil, err
- }
- }
- return it, nil
-}
-
-// resolveRoot retrieves a root entry via DNS.
-func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, error) {
- e, err, _ := c.singleflight.Do(loc.str, func() (interface{}, error) {
- txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain)
- c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err)
- if err != nil {
- return rootEntry{}, err
- }
- for _, txt := range txts {
- if strings.HasPrefix(txt, rootPrefix) {
- return parseAndVerifyRoot(txt, loc)
- }
- }
- return rootEntry{}, nameError{loc.domain, errNoRoot}
- })
- return e.(rootEntry), err
-}
-
-func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) {
- e, err := parseRoot(txt)
- if err != nil {
- return e, err
- }
- if !e.verifySignature(loc.pubkey) {
- return e, entryError{typ: "root", err: errInvalidSig}
- }
- return e, nil
-}
-
-// resolveEntry retrieves an entry from the cache or fetches it from the network
-// if it isn't cached.
-func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) {
- // The rate limit always applies, even when the result might be cached. This is
- // important because it avoids hot-spinning in consumers of node iterators created on
- // this client.
- if err := c.ratelimit.Wait(ctx); err != nil {
- return nil, err
- }
- cacheKey := truncateHash(hash)
- if e, ok := c.entries.Get(cacheKey); ok {
- return e, nil
- }
-
- ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) {
- e, err := c.doResolveEntry(ctx, domain, hash)
- if err != nil {
- return nil, err
- }
- c.entries.Add(cacheKey, e)
- return e, nil
- })
- e, _ := ei.(entry)
- return e, err
-}
-
-// doResolveEntry fetches an entry via DNS.
-func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry, error) {
- wantHash, err := b32format.DecodeString(hash)
- if err != nil {
- return nil, fmt.Errorf("invalid base32 hash")
- }
- name := hash + "." + domain
- txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain)
- c.cfg.Logger.Trace("DNS discovery lookup", "name", name, "err", err)
- if err != nil {
- return nil, err
- }
- for _, txt := range txts {
- e, err := parseEntry(txt, c.cfg.ValidSchemes)
- if errors.Is(err, errUnknownEntry) {
- continue
- }
- if !bytes.HasPrefix(crypto.Keccak256([]byte(txt)), wantHash) {
- err = nameError{name, errHashMismatch}
- } else if err != nil {
- err = nameError{name, err}
- }
- return e, err
- }
- return nil, nameError{name, errNoEntry}
-}
-
-// randomIterator traverses a set of trees and returns nodes found in them.
-type randomIterator struct {
- cur *enode.Node
- ctx context.Context
- cancelFn context.CancelFunc
- c *Client
-
- mu sync.Mutex
- lc linkCache // tracks tree dependencies
- trees map[string]*clientTree // all trees
- // buffers for syncableTrees
- syncableList []*clientTree
- disabledList []*clientTree
-}
-
-func (c *Client) newRandomIterator() *randomIterator {
- ctx, cancel := context.WithCancel(context.Background())
- return &randomIterator{
- c: c,
- ctx: ctx,
- cancelFn: cancel,
- trees: make(map[string]*clientTree),
- }
-}
-
-// Node returns the current node.
-func (it *randomIterator) Node() *enode.Node {
- return it.cur
-}
-
-// Close closes the iterator.
-func (it *randomIterator) Close() {
- it.cancelFn()
-
- it.mu.Lock()
- defer it.mu.Unlock()
- it.trees = nil
-}
-
-// Next moves the iterator to the next node.
-func (it *randomIterator) Next() bool {
- it.cur = it.nextNode()
- return it.cur != nil
-}
-
-// addTree adds an enrtree:// URL to the iterator.
-func (it *randomIterator) addTree(url string) error {
- le, err := parseLink(url)
- if err != nil {
- return fmt.Errorf("invalid enrtree URL: %v", err)
- }
- it.lc.addLink("", le.str)
- return nil
-}
-
-// nextNode syncs random tree entries until it finds a node.
-func (it *randomIterator) nextNode() *enode.Node {
- for {
- ct := it.pickTree()
- if ct == nil {
- return nil
- }
- n, err := ct.syncRandom(it.ctx)
- if err != nil {
- if errors.Is(err, it.ctx.Err()) {
- return nil // context canceled.
- }
- it.c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err)
- continue
- }
- if n != nil {
- return n
- }
- }
-}
-
-// pickTree returns a random tree to sync from.
-func (it *randomIterator) pickTree() *clientTree {
- it.mu.Lock()
- defer it.mu.Unlock()
-
- // First check if iterator was closed.
- // Need to do this here to avoid nil map access in rebuildTrees.
- if it.trees == nil {
- return nil
- }
-
- // Rebuild the trees map if any links have changed.
- if it.lc.changed {
- it.rebuildTrees()
- it.lc.changed = false
- }
-
- for {
- canSync, trees := it.syncableTrees()
- switch {
- case canSync:
- // Pick a random tree.
- return trees[rand.Intn(len(trees))]
- case len(trees) > 0:
- // No sync action can be performed on any tree right now. The only meaningful
- // thing to do is waiting for any root record to get updated.
- if !it.waitForRootUpdates(trees) {
- // Iterator was closed while waiting.
- return nil
- }
- default:
- // There are no trees left, the iterator was closed.
- return nil
- }
- }
-}
-
-// syncableTrees finds trees on which any meaningful sync action can be performed.
-func (it *randomIterator) syncableTrees() (canSync bool, trees []*clientTree) {
- // Resize tree lists.
- it.syncableList = it.syncableList[:0]
- it.disabledList = it.disabledList[:0]
-
- // Partition them into the two lists.
- for _, ct := range it.trees {
- if ct.canSyncRandom() {
- it.syncableList = append(it.syncableList, ct)
- } else {
- it.disabledList = append(it.disabledList, ct)
- }
- }
- if len(it.syncableList) > 0 {
- return true, it.syncableList
- }
- return false, it.disabledList
-}
-
-// waitForRootUpdates waits for the closest scheduled root check time on the given trees.
-func (it *randomIterator) waitForRootUpdates(trees []*clientTree) bool {
- var minTree *clientTree
- var nextCheck mclock.AbsTime
- for _, ct := range trees {
- check := ct.nextScheduledRootCheck()
- if minTree == nil || check < nextCheck {
- minTree = ct
- nextCheck = check
- }
- }
-
- sleep := nextCheck.Sub(it.c.clock.Now())
- it.c.cfg.Logger.Debug("DNS iterator waiting for root updates", "sleep", sleep, "tree", minTree.loc.domain)
- timeout := it.c.clock.NewTimer(sleep)
- defer timeout.Stop()
- select {
- case <-timeout.C():
- return true
- case <-it.ctx.Done():
- return false // Iterator was closed.
- }
-}
-
-// rebuildTrees rebuilds the 'trees' map.
-func (it *randomIterator) rebuildTrees() {
- // Delete removed trees.
- for loc := range it.trees {
- if !it.lc.isReferenced(loc) {
- delete(it.trees, loc)
- }
- }
- // Add new trees.
- for loc := range it.lc.backrefs {
- if it.trees[loc] == nil {
- link, _ := parseLink(linkPrefix + loc)
- it.trees[loc] = newClientTree(it.c, &it.lc, link)
- }
- }
-}
diff --git a/p2p/dnsdisc/client_test.go b/p2p/dnsdisc/client_test.go
deleted file mode 100644
index abc35ddbd3..0000000000
--- a/p2p/dnsdisc/client_test.go
+++ /dev/null
@@ -1,478 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package dnsdisc
-
-import (
- "context"
- "crypto/ecdsa"
- "errors"
- "reflect"
- "testing"
- "time"
-
- "github.com/davecgh/go-spew/spew"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/internal/testlog"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
-)
-
-var signingKeyForTesting, _ = crypto.ToECDSA(hexutil.MustDecode("0xdc599867fc513f8f5e2c2c9c489cde5e71362d1d9ec6e693e0de063236ed1240"))
-
-func TestClientSyncTree(t *testing.T) {
- nodes := []string{
- "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA",
- "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI",
- "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o",
- }
-
- r := mapResolver{
- "n": "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA",
- "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
- "JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24",
- "2XS2367YHAXJFGLZHVAWLQD4ZY.n": nodes[0],
- "H4FHT4B454P6UXFD7JCYQ5PWDY.n": nodes[1],
- "MHTDO6TMUBRIA2XWG5LUDACK24.n": nodes[2],
- }
- var (
- wantNodes = sortByID(parseNodes(nodes))
- wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"}
- wantSeq = uint(1)
- )
-
- c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
- stree, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
- if err != nil {
- t.Fatal("sync error:", err)
- }
- if !reflect.DeepEqual(sortByID(stree.Nodes()), wantNodes) {
- t.Errorf("wrong nodes in synced tree:\nhave %v\nwant %v", spew.Sdump(stree.Nodes()), spew.Sdump(wantNodes))
- }
- if !reflect.DeepEqual(stree.Links(), wantLinks) {
- t.Errorf("wrong links in synced tree: %v", stree.Links())
- }
- if stree.Seq() != wantSeq {
- t.Errorf("synced tree has wrong seq: %d", stree.Seq())
- }
-}
-
-// In this test, syncing the tree fails because it contains an invalid ENR entry.
-func TestClientSyncTreeBadNode(t *testing.T) {
- // var b strings.Builder
- // b.WriteString(enrPrefix)
- // b.WriteString("-----")
- // badHash := subdomain(&b)
- // tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"})
- // tree.entries[badHash] = &b
- // tree.root.eroot = badHash
- // url, _ := tree.Sign(signingKeyForTesting, "n")
- // fmt.Println(url)
- // fmt.Printf("%#v\n", tree.ToTXT("n"))
-
- r := mapResolver{
- "n": "enrtree-root:v1 e=INDMVBZEEQ4ESVYAKGIYU74EAA l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=3 sig=Vl3AmunLur0JZ3sIyJPSH6A3Vvdp4F40jWQeCmkIhmcgwE4VC5U9wpK8C_uL_CMY29fd6FAhspRvq2z_VysTLAA",
- "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
- "INDMVBZEEQ4ESVYAKGIYU74EAA.n": "enr:-----",
- }
- c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
- _, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
- wantErr := nameError{name: "INDMVBZEEQ4ESVYAKGIYU74EAA.n", err: entryError{typ: "enr", err: errInvalidENR}}
- if err != wantErr {
- t.Fatalf("expected sync error %q, got %q", wantErr, err)
- }
-}
-
-// This test checks that randomIterator finds all entries.
-func TestIterator(t *testing.T) {
- var (
- keys = testKeys(30)
- nodes = testNodes(keys)
- tree, url = makeTestTree("n", nodes, nil)
- r = mapResolver(tree.ToTXT("n"))
- )
-
- c := NewClient(Config{
- Resolver: r,
- Logger: testlog.Logger(t, log.LvlTrace),
- RateLimit: 500,
- })
- it, err := c.NewIterator(url)
- if err != nil {
- t.Fatal(err)
- }
-
- checkIterator(t, it, nodes)
-}
-
-func TestIteratorCloseWithoutNext(t *testing.T) {
- tree1, url1 := makeTestTree("t1", nil, nil)
- c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))})
- it, err := c.NewIterator(url1)
- if err != nil {
- t.Fatal(err)
- }
-
- it.Close()
- ok := it.Next()
- if ok {
- t.Fatal("Next returned true after Close")
- }
-}
-
-// This test checks if closing randomIterator races.
-func TestIteratorClose(t *testing.T) {
- var (
- keys = testKeys(500)
- nodes = testNodes(keys)
- tree1, url1 = makeTestTree("t1", nodes, nil)
- )
-
- c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))})
- it, err := c.NewIterator(url1)
- if err != nil {
- t.Fatal(err)
- }
-
- done := make(chan struct{})
- go func() {
- for it.Next() {
- _ = it.Node()
- }
- close(done)
- }()
-
- time.Sleep(50 * time.Millisecond)
- it.Close()
- <-done
-}
-
-// This test checks that randomIterator traverses linked trees as well as explicitly added trees.
-func TestIteratorLinks(t *testing.T) {
- var (
- keys = testKeys(40)
- nodes = testNodes(keys)
- tree1, url1 = makeTestTree("t1", nodes[:10], nil)
- tree2, url2 = makeTestTree("t2", nodes[10:], []string{url1})
- )
-
- c := NewClient(Config{
- Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")),
- Logger: testlog.Logger(t, log.LvlTrace),
- RateLimit: 500,
- })
- it, err := c.NewIterator(url2)
- if err != nil {
- t.Fatal(err)
- }
-
- checkIterator(t, it, nodes)
-}
-
-// This test verifies that randomIterator re-checks the root of the tree to catch
-// updates to nodes.
-func TestIteratorNodeUpdates(t *testing.T) {
- var (
- clock = new(mclock.Simulated)
- keys = testKeys(30)
- nodes = testNodes(keys)
- resolver = newMapResolver()
- c = NewClient(Config{
- Resolver: resolver,
- Logger: testlog.Logger(t, log.LvlTrace),
- RecheckInterval: 20 * time.Minute,
- RateLimit: 500,
- })
- )
- c.clock = clock
- tree1, url := makeTestTree("n", nodes[:25], nil)
- it, err := c.NewIterator(url)
- if err != nil {
- t.Fatal(err)
- }
-
- // Sync the original tree.
- resolver.add(tree1.ToTXT("n"))
- checkIterator(t, it, nodes[:25])
-
- // Ensure RandomNode returns the new nodes after the tree is updated.
- updateSomeNodes(keys, nodes)
- tree2, _ := makeTestTree("n", nodes, nil)
- resolver.clear()
- resolver.add(tree2.ToTXT("n"))
- t.Log("tree updated")
-
- clock.Run(c.cfg.RecheckInterval + 1*time.Second)
- checkIterator(t, it, nodes)
-}
-
-// This test checks that the tree root is rechecked when a couple of leaf
-// requests have failed. The test is just like TestIteratorNodeUpdates, but
-// without advancing the clock by recheckInterval after the tree update.
-func TestIteratorRootRecheckOnFail(t *testing.T) {
- var (
- clock = new(mclock.Simulated)
- keys = testKeys(30)
- nodes = testNodes(keys)
- resolver = newMapResolver()
- c = NewClient(Config{
- Resolver: resolver,
- Logger: testlog.Logger(t, log.LvlTrace),
- RecheckInterval: 20 * time.Minute,
- RateLimit: 500,
- // Disabling the cache is required for this test because the client doesn't
- // notice leaf failures if all records are cached.
- CacheLimit: 1,
- })
- )
- c.clock = clock
- tree1, url := makeTestTree("n", nodes[:25], nil)
- it, err := c.NewIterator(url)
- if err != nil {
- t.Fatal(err)
- }
-
- // Sync the original tree.
- resolver.add(tree1.ToTXT("n"))
- checkIterator(t, it, nodes[:25])
-
- // Ensure RandomNode returns the new nodes after the tree is updated.
- updateSomeNodes(keys, nodes)
- tree2, _ := makeTestTree("n", nodes, nil)
- resolver.clear()
- resolver.add(tree2.ToTXT("n"))
- t.Log("tree updated")
-
- checkIterator(t, it, nodes)
-}
-
-// This test checks that the iterator works correctly when the tree is initially empty.
-func TestIteratorEmptyTree(t *testing.T) {
- var (
- clock = new(mclock.Simulated)
- keys = testKeys(1)
- nodes = testNodes(keys)
- resolver = newMapResolver()
- c = NewClient(Config{
- Resolver: resolver,
- Logger: testlog.Logger(t, log.LvlTrace),
- RecheckInterval: 20 * time.Minute,
- RateLimit: 500,
- })
- )
- c.clock = clock
- tree1, url := makeTestTree("n", nil, nil)
- tree2, _ := makeTestTree("n", nodes, nil)
- resolver.add(tree1.ToTXT("n"))
-
- // Start the iterator.
- node := make(chan *enode.Node, 1)
- it, err := c.NewIterator(url)
- if err != nil {
- t.Fatal(err)
- }
- go func() {
- it.Next()
- node <- it.Node()
- }()
-
- // Wait for the client to get stuck in waitForRootUpdates.
- clock.WaitForTimers(1)
-
- // Now update the root.
- resolver.add(tree2.ToTXT("n"))
-
- // Wait for it to pick up the root change.
- clock.Run(c.cfg.RecheckInterval)
- select {
- case n := <-node:
- if n.ID() != nodes[0].ID() {
- t.Fatalf("wrong node returned")
- }
- case <-time.After(5 * time.Second):
- t.Fatal("it.Next() did not unblock within 5s of real time")
- }
-}
-
-// updateSomeNodes applies ENR updates to some of the given nodes.
-func updateSomeNodes(keys []*ecdsa.PrivateKey, nodes []*enode.Node) {
- for i, n := range nodes[:len(nodes)/2] {
- r := n.Record()
- r.Set(enr.IP{127, 0, 0, 1})
- r.SetSeq(55)
- enode.SignV4(r, keys[i])
- n2, _ := enode.New(enode.ValidSchemes, r)
- nodes[i] = n2
- }
-}
-
-// This test verifies that randomIterator re-checks the root of the tree to catch
-// updates to links.
-func TestIteratorLinkUpdates(t *testing.T) {
- var (
- clock = new(mclock.Simulated)
- keys = testKeys(30)
- nodes = testNodes(keys)
- resolver = newMapResolver()
- c = NewClient(Config{
- Resolver: resolver,
- Logger: testlog.Logger(t, log.LvlTrace),
- RecheckInterval: 20 * time.Minute,
- RateLimit: 500,
- })
- )
- c.clock = clock
- tree3, url3 := makeTestTree("t3", nodes[20:30], nil)
- tree2, url2 := makeTestTree("t2", nodes[10:20], nil)
- tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2})
- resolver.add(tree1.ToTXT("t1"))
- resolver.add(tree2.ToTXT("t2"))
- resolver.add(tree3.ToTXT("t3"))
-
- it, err := c.NewIterator(url1)
- if err != nil {
- t.Fatal(err)
- }
-
- // Sync tree1 using RandomNode.
- checkIterator(t, it, nodes[:20])
-
- // Add link to tree3, remove link to tree2.
- tree1, _ = makeTestTree("t1", nodes[:10], []string{url3})
- resolver.add(tree1.ToTXT("t1"))
- t.Log("tree1 updated")
-
- clock.Run(c.cfg.RecheckInterval + 1*time.Second)
-
- var wantNodes []*enode.Node
- wantNodes = append(wantNodes, tree1.Nodes()...)
- wantNodes = append(wantNodes, tree3.Nodes()...)
- checkIterator(t, it, wantNodes)
-
- // Check that linked trees are GCed when they're no longer referenced.
- knownTrees := it.(*randomIterator).trees
- if len(knownTrees) != 2 {
- t.Errorf("client knows %d trees, want 2", len(knownTrees))
- }
-}
-
-func checkIterator(t *testing.T, it enode.Iterator, wantNodes []*enode.Node) {
- t.Helper()
-
- var (
- want = make(map[enode.ID]*enode.Node)
- maxCalls = len(wantNodes) * 3
- calls = 0
- )
- for _, n := range wantNodes {
- want[n.ID()] = n
- }
- for ; len(want) > 0 && calls < maxCalls; calls++ {
- if !it.Next() {
- t.Fatalf("Next returned false (call %d)", calls)
- }
- n := it.Node()
- delete(want, n.ID())
- }
- t.Logf("checkIterator called Next %d times to find %d nodes", calls, len(wantNodes))
- for _, n := range want {
- t.Errorf("iterator didn't discover node %v", n.ID())
- }
-}
-
-func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) {
- tree, err := MakeTree(1, nodes, links)
- if err != nil {
- panic(err)
- }
- url, err := tree.Sign(signingKeyForTesting, domain)
- if err != nil {
- panic(err)
- }
- return tree, url
-}
-
-// testKeys creates deterministic private keys for testing.
-func testKeys(n int) []*ecdsa.PrivateKey {
- keys := make([]*ecdsa.PrivateKey, n)
- for i := 0; i < n; i++ {
- key, err := crypto.GenerateKey()
- if err != nil {
- panic("can't generate key: " + err.Error())
- }
- keys[i] = key
- }
- return keys
-}
-
-func testNodes(keys []*ecdsa.PrivateKey) []*enode.Node {
- nodes := make([]*enode.Node, len(keys))
- for i, key := range keys {
- record := new(enr.Record)
- record.SetSeq(uint64(i))
- enode.SignV4(record, key)
- n, err := enode.New(enode.ValidSchemes, record)
- if err != nil {
- panic(err)
- }
- nodes[i] = n
- }
- return nodes
-}
-
-type mapResolver map[string]string
-
-func newMapResolver(maps ...map[string]string) mapResolver {
- mr := make(mapResolver, len(maps))
- for _, m := range maps {
- mr.add(m)
- }
- return mr
-}
-
-func (mr mapResolver) clear() {
- for k := range mr {
- delete(mr, k)
- }
-}
-
-func (mr mapResolver) add(m map[string]string) {
- for k, v := range m {
- mr[k] = v
- }
-}
-
-func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
- if record, ok := mr[name]; ok {
- return []string{record}, nil
- }
- return nil, errors.New("not found")
-}
-
-func parseNodes(rec []string) []*enode.Node {
- var ns []*enode.Node
- for _, r := range rec {
- var n enode.Node
- if err := n.UnmarshalText([]byte(r)); err != nil {
- panic(err)
- }
- ns = append(ns, &n)
- }
- return ns
-}
diff --git a/p2p/dnsdisc/doc.go b/p2p/dnsdisc/doc.go
deleted file mode 100644
index cfd81cc187..0000000000
--- a/p2p/dnsdisc/doc.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package dnsdisc implements node discovery via DNS (EIP-1459).
-package dnsdisc
diff --git a/p2p/dnsdisc/error.go b/p2p/dnsdisc/error.go
deleted file mode 100644
index 39955cabff..0000000000
--- a/p2p/dnsdisc/error.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package dnsdisc
-
-import (
- "errors"
- "fmt"
-)
-
-// Entry parse errors.
-var (
- errUnknownEntry = errors.New("unknown entry type")
- errNoPubkey = errors.New("missing public key")
- errBadPubkey = errors.New("invalid public key")
- errInvalidENR = errors.New("invalid node record")
- errInvalidChild = errors.New("invalid child hash")
- errInvalidSig = errors.New("invalid base64 signature")
- errSyntax = errors.New("invalid syntax")
-)
-
-// Resolver/sync errors
-var (
- errNoRoot = errors.New("no valid root found")
- errNoEntry = errors.New("no valid tree entry found")
- errHashMismatch = errors.New("hash mismatch")
- errENRInLinkTree = errors.New("enr entry in link tree")
- errLinkInENRTree = errors.New("link entry in ENR tree")
-)
-
-type nameError struct {
- name string
- err error
-}
-
-func (err nameError) Error() string {
- if ee, ok := err.err.(entryError); ok {
- return fmt.Sprintf("invalid %s entry at %s: %v", ee.typ, err.name, ee.err)
- }
- return err.name + ": " + err.err.Error()
-}
-
-type entryError struct {
- typ string
- err error
-}
-
-func (err entryError) Error() string {
- return fmt.Sprintf("invalid %s entry: %v", err.typ, err.err)
-}
diff --git a/p2p/dnsdisc/sync.go b/p2p/dnsdisc/sync.go
deleted file mode 100644
index 073547c90d..0000000000
--- a/p2p/dnsdisc/sync.go
+++ /dev/null
@@ -1,329 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package dnsdisc
-
-import (
- "context"
- "math/rand"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// This is the number of consecutive leaf requests that may fail before
-// we consider re-resolving the tree root.
-const rootRecheckFailCount = 5
-
-// clientTree is a full tree being synced.
-type clientTree struct {
- c *Client
- loc *linkEntry // link to this tree
-
- lastRootCheck mclock.AbsTime // last revalidation of root
- leafFailCount int
- rootFailCount int
-
- root *rootEntry
- enrs *subtreeSync
- links *subtreeSync
-
- lc *linkCache // tracks all links between all trees
- curLinks map[string]struct{} // links contained in this tree
- linkGCRoot string // root on which last link GC has run
-}
-
-func newClientTree(c *Client, lc *linkCache, loc *linkEntry) *clientTree {
- return &clientTree{c: c, lc: lc, loc: loc}
-}
-
-// syncAll retrieves all entries of the tree.
-func (ct *clientTree) syncAll(dest map[string]entry) error {
- if err := ct.updateRoot(context.Background()); err != nil {
- return err
- }
- if err := ct.links.resolveAll(dest); err != nil {
- return err
- }
- if err := ct.enrs.resolveAll(dest); err != nil {
- return err
- }
- return nil
-}
-
-// syncRandom retrieves a single entry of the tree. The Node return value
-// is non-nil if the entry was a node.
-func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error) {
- if ct.rootUpdateDue() {
- if err := ct.updateRoot(ctx); err != nil {
- return nil, err
- }
- }
-
- // Update fail counter for leaf request errors.
- defer func() {
- if err != nil {
- ct.leafFailCount++
- }
- }()
-
- // Link tree sync has priority, run it to completion before syncing ENRs.
- if !ct.links.done() {
- err := ct.syncNextLink(ctx)
- return nil, err
- }
- ct.gcLinks()
-
- // Sync next random entry in ENR tree. Once every node has been visited, we simply
- // start over. This is fine because entries are cached internally by the client LRU
- // also by DNS resolvers.
- if ct.enrs.done() {
- ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false)
- }
- return ct.syncNextRandomENR(ctx)
-}
-
-// canSyncRandom checks if any meaningful action can be performed by syncRandom.
-func (ct *clientTree) canSyncRandom() bool {
- // Note: the check for non-zero leaf count is very important here.
- // If we're done syncing all nodes, and no leaves were found, the tree
- // is empty and we can't use it for sync.
- return ct.rootUpdateDue() || !ct.links.done() || !ct.enrs.done() || ct.enrs.leaves != 0
-}
-
-// gcLinks removes outdated links from the global link cache. GC runs once
-// when the link sync finishes.
-func (ct *clientTree) gcLinks() {
- if !ct.links.done() || ct.root.lroot == ct.linkGCRoot {
- return
- }
- ct.lc.resetLinks(ct.loc.str, ct.curLinks)
- ct.linkGCRoot = ct.root.lroot
-}
-
-func (ct *clientTree) syncNextLink(ctx context.Context) error {
- hash := ct.links.missing[0]
- e, err := ct.links.resolveNext(ctx, hash)
- if err != nil {
- return err
- }
- ct.links.missing = ct.links.missing[1:]
-
- if dest, ok := e.(*linkEntry); ok {
- ct.lc.addLink(ct.loc.str, dest.str)
- ct.curLinks[dest.str] = struct{}{}
- }
- return nil
-}
-
-func (ct *clientTree) syncNextRandomENR(ctx context.Context) (*enode.Node, error) {
- index := rand.Intn(len(ct.enrs.missing))
- hash := ct.enrs.missing[index]
- e, err := ct.enrs.resolveNext(ctx, hash)
- if err != nil {
- return nil, err
- }
- ct.enrs.missing = removeHash(ct.enrs.missing, index)
- if ee, ok := e.(*enrEntry); ok {
- return ee.node, nil
- }
- return nil, nil
-}
-
-func (ct *clientTree) String() string {
- return ct.loc.String()
-}
-
-// removeHash removes the element at index from h.
-func removeHash(h []string, index int) []string {
- if len(h) == 1 {
- return nil
- }
- last := len(h) - 1
- if index < last {
- h[index] = h[last]
- h[last] = ""
- }
- return h[:last]
-}
-
-// updateRoot ensures that the given tree has an up-to-date root.
-func (ct *clientTree) updateRoot(ctx context.Context) error {
- if !ct.slowdownRootUpdate(ctx) {
- return ctx.Err()
- }
-
- ct.lastRootCheck = ct.c.clock.Now()
- ctx, cancel := context.WithTimeout(ctx, ct.c.cfg.Timeout)
- defer cancel()
- root, err := ct.c.resolveRoot(ctx, ct.loc)
- if err != nil {
- ct.rootFailCount++
- return err
- }
- ct.root = &root
- ct.rootFailCount = 0
- ct.leafFailCount = 0
-
- // Invalidate subtrees if changed.
- if ct.links == nil || root.lroot != ct.links.root {
- ct.links = newSubtreeSync(ct.c, ct.loc, root.lroot, true)
- ct.curLinks = make(map[string]struct{})
- }
- if ct.enrs == nil || root.eroot != ct.enrs.root {
- ct.enrs = newSubtreeSync(ct.c, ct.loc, root.eroot, false)
- }
- return nil
-}
-
-// rootUpdateDue returns true when a root update is needed.
-func (ct *clientTree) rootUpdateDue() bool {
- tooManyFailures := ct.leafFailCount > rootRecheckFailCount
- scheduledCheck := ct.c.clock.Now() >= ct.nextScheduledRootCheck()
- return ct.root == nil || tooManyFailures || scheduledCheck
-}
-
-func (ct *clientTree) nextScheduledRootCheck() mclock.AbsTime {
- return ct.lastRootCheck.Add(ct.c.cfg.RecheckInterval)
-}
-
-// slowdownRootUpdate applies a delay to root resolution if is tried
-// too frequently. This avoids busy polling when the client is offline.
-// Returns true if the timeout passed, false if sync was canceled.
-func (ct *clientTree) slowdownRootUpdate(ctx context.Context) bool {
- var delay time.Duration
- switch {
- case ct.rootFailCount > 20:
- delay = 10 * time.Second
- case ct.rootFailCount > 5:
- delay = 5 * time.Second
- default:
- return true
- }
- timeout := ct.c.clock.NewTimer(delay)
- defer timeout.Stop()
- select {
- case <-timeout.C():
- return true
- case <-ctx.Done():
- return false
- }
-}
-
-// subtreeSync is the sync of an ENR or link subtree.
-type subtreeSync struct {
- c *Client
- loc *linkEntry
- root string
- missing []string // missing tree node hashes
- link bool // true if this sync is for the link tree
- leaves int // counter of synced leaves
-}
-
-func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync {
- return &subtreeSync{c, loc, root, []string{root}, link, 0}
-}
-
-func (ts *subtreeSync) done() bool {
- return len(ts.missing) == 0
-}
-
-func (ts *subtreeSync) resolveAll(dest map[string]entry) error {
- for !ts.done() {
- hash := ts.missing[0]
- ctx, cancel := context.WithTimeout(context.Background(), ts.c.cfg.Timeout)
- e, err := ts.resolveNext(ctx, hash)
- cancel()
- if err != nil {
- return err
- }
- dest[hash] = e
- ts.missing = ts.missing[1:]
- }
- return nil
-}
-
-func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, error) {
- e, err := ts.c.resolveEntry(ctx, ts.loc.domain, hash)
- if err != nil {
- return nil, err
- }
- switch e := e.(type) {
- case *enrEntry:
- if ts.link {
- return nil, errENRInLinkTree
- }
- ts.leaves++
- case *linkEntry:
- if !ts.link {
- return nil, errLinkInENRTree
- }
- ts.leaves++
- case *branchEntry:
- ts.missing = append(ts.missing, e.children...)
- }
- return e, nil
-}
-
-// linkCache tracks links between trees.
-type linkCache struct {
- backrefs map[string]map[string]struct{}
- changed bool
-}
-
-func (lc *linkCache) isReferenced(r string) bool {
- return len(lc.backrefs[r]) != 0
-}
-
-func (lc *linkCache) addLink(from, to string) {
- if _, ok := lc.backrefs[to][from]; ok {
- return
- }
-
- if lc.backrefs == nil {
- lc.backrefs = make(map[string]map[string]struct{})
- }
- if _, ok := lc.backrefs[to]; !ok {
- lc.backrefs[to] = make(map[string]struct{})
- }
- lc.backrefs[to][from] = struct{}{}
- lc.changed = true
-}
-
-// resetLinks clears all links of the given tree.
-func (lc *linkCache) resetLinks(from string, keep map[string]struct{}) {
- stk := []string{from}
- for len(stk) > 0 {
- item := stk[len(stk)-1]
- stk = stk[:len(stk)-1]
-
- for r, refs := range lc.backrefs {
- if _, ok := keep[r]; ok {
- continue
- }
- if _, ok := refs[item]; !ok {
- continue
- }
- lc.changed = true
- delete(refs, item)
- if len(refs) == 0 {
- delete(lc.backrefs, r)
- stk = append(stk, r)
- }
- }
- }
-}
diff --git a/p2p/dnsdisc/sync_test.go b/p2p/dnsdisc/sync_test.go
deleted file mode 100644
index ce1cb90bae..0000000000
--- a/p2p/dnsdisc/sync_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package dnsdisc
-
-import (
- "math/rand"
- "strconv"
- "testing"
-)
-
-func TestLinkCache(t *testing.T) {
- var lc linkCache
-
- // Check adding links.
- lc.addLink("1", "2")
- if !lc.changed {
- t.Error("changed flag not set")
- }
- lc.changed = false
- lc.addLink("1", "2")
- if lc.changed {
- t.Error("changed flag set after adding link that's already present")
- }
- lc.addLink("2", "3")
- lc.addLink("3", "1")
- lc.addLink("2", "4")
- lc.changed = false
-
- if !lc.isReferenced("3") {
- t.Error("3 not referenced")
- }
- if lc.isReferenced("6") {
- t.Error("6 is referenced")
- }
-
- lc.resetLinks("1", nil)
- if !lc.changed {
- t.Error("changed flag not set")
- }
- if len(lc.backrefs) != 0 {
- t.Logf("%+v", lc)
- t.Error("reference maps should be empty")
- }
-}
-
-func TestLinkCacheRandom(t *testing.T) {
- tags := make([]string, 1000)
- for i := range tags {
- tags[i] = strconv.Itoa(i)
- }
-
- // Create random links.
- var lc linkCache
- var remove []string
- for i := 0; i < 100; i++ {
- a, b := tags[rand.Intn(len(tags))], tags[rand.Intn(len(tags))]
- lc.addLink(a, b)
- remove = append(remove, a)
- }
-
- // Remove all the links.
- for _, s := range remove {
- lc.resetLinks(s, nil)
- }
- if len(lc.backrefs) != 0 {
- t.Logf("%+v", lc)
- t.Error("reference maps should be empty")
- }
-}
diff --git a/p2p/dnsdisc/tree.go b/p2p/dnsdisc/tree.go
deleted file mode 100644
index 06b7681f18..0000000000
--- a/p2p/dnsdisc/tree.go
+++ /dev/null
@@ -1,423 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package dnsdisc
-
-import (
- "bytes"
- "crypto/ecdsa"
- "encoding/base32"
- "encoding/base64"
- "fmt"
- "io"
- "strings"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
- "golang.org/x/crypto/sha3"
- "golang.org/x/exp/slices"
-)
-
-// Tree is a merkle tree of node records.
-type Tree struct {
- root *rootEntry
- entries map[string]entry
-}
-
-// Sign signs the tree with the given private key and sets the sequence number.
-func (t *Tree) Sign(key *ecdsa.PrivateKey, domain string) (url string, err error) {
- root := *t.root
- sig, err := crypto.Sign(root.sigHash(), key)
- if err != nil {
- return "", err
- }
- root.sig = sig
- t.root = &root
- link := newLinkEntry(domain, &key.PublicKey)
- return link.String(), nil
-}
-
-// SetSignature verifies the given signature and assigns it as the tree's current
-// signature if valid.
-func (t *Tree) SetSignature(pubkey *ecdsa.PublicKey, signature string) error {
- sig, err := b64format.DecodeString(signature)
- if err != nil || len(sig) != crypto.SignatureLength {
- return errInvalidSig
- }
- root := *t.root
- root.sig = sig
- if !root.verifySignature(pubkey) {
- return errInvalidSig
- }
- t.root = &root
- return nil
-}
-
-// Seq returns the sequence number of the tree.
-func (t *Tree) Seq() uint {
- return t.root.seq
-}
-
-// Signature returns the signature of the tree.
-func (t *Tree) Signature() string {
- return b64format.EncodeToString(t.root.sig)
-}
-
-// ToTXT returns all DNS TXT records required for the tree.
-func (t *Tree) ToTXT(domain string) map[string]string {
- records := map[string]string{domain: t.root.String()}
- for _, e := range t.entries {
- sd := subdomain(e)
- if domain != "" {
- sd = sd + "." + domain
- }
- records[sd] = e.String()
- }
- return records
-}
-
-// Links returns all links contained in the tree.
-func (t *Tree) Links() []string {
- var links []string
- for _, e := range t.entries {
- if le, ok := e.(*linkEntry); ok {
- links = append(links, le.String())
- }
- }
- return links
-}
-
-// Nodes returns all nodes contained in the tree.
-func (t *Tree) Nodes() []*enode.Node {
- var nodes []*enode.Node
- for _, e := range t.entries {
- if ee, ok := e.(*enrEntry); ok {
- nodes = append(nodes, ee.node)
- }
- }
- return nodes
-}
-
-/*
-We want to keep the UDP size below 512 bytes. The UDP size is roughly:
-UDP length = 8 + UDP payload length ( 229 )
-UPD Payload length:
- - dns.id 2
- - dns.flags 2
- - dns.count.queries 2
- - dns.count.answers 2
- - dns.count.auth_rr 2
- - dns.count.add_rr 2
- - queries (query-size + 6)
- - answers :
- - dns.resp.name 2
- - dns.resp.type 2
- - dns.resp.class 2
- - dns.resp.ttl 4
- - dns.resp.len 2
- - dns.txt.length 1
- - dns.txt resp_data_size
-
-So the total size is roughly a fixed overhead of `39`, and the size of the query (domain
-name) and response. The query size is, for example,
-FVY6INQ6LZ33WLCHO3BPR3FH6Y.snap.mainnet.ethdisco.net (52)
-
-We also have some static data in the response, such as `enrtree-branch:`, and potentially
-splitting the response up with `" "`, leaving us with a size of roughly `400` that we need
-to stay below.
-
-The number `370` is used to have some margin for extra overhead (for example, the dns
-query may be larger - more subdomains).
-*/
-const (
- hashAbbrevSize = 1 + 16*13/8 // Size of an encoded hash (plus comma)
- maxChildren = 370 / hashAbbrevSize // 13 children
- minHashLength = 12
-)
-
-// MakeTree creates a tree containing the given nodes and links.
-func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) {
- // Sort records by ID and ensure all nodes have a valid record.
- records := make([]*enode.Node, len(nodes))
-
- copy(records, nodes)
- sortByID(records)
- for _, n := range records {
- if len(n.Record().Signature()) == 0 {
- return nil, fmt.Errorf("can't add node %v: unsigned node record", n.ID())
- }
- }
-
- // Create the leaf list.
- enrEntries := make([]entry, len(records))
- for i, r := range records {
- enrEntries[i] = &enrEntry{r}
- }
- linkEntries := make([]entry, len(links))
- for i, l := range links {
- le, err := parseLink(l)
- if err != nil {
- return nil, err
- }
- linkEntries[i] = le
- }
-
- // Create intermediate nodes.
- t := &Tree{entries: make(map[string]entry)}
- eroot := t.build(enrEntries)
- t.entries[subdomain(eroot)] = eroot
- lroot := t.build(linkEntries)
- t.entries[subdomain(lroot)] = lroot
- t.root = &rootEntry{seq: seq, eroot: subdomain(eroot), lroot: subdomain(lroot)}
- return t, nil
-}
-
-func (t *Tree) build(entries []entry) entry {
- if len(entries) == 1 {
- return entries[0]
- }
- if len(entries) <= maxChildren {
- hashes := make([]string, len(entries))
- for i, e := range entries {
- hashes[i] = subdomain(e)
- t.entries[hashes[i]] = e
- }
- return &branchEntry{hashes}
- }
- var subtrees []entry
- for len(entries) > 0 {
- n := maxChildren
- if len(entries) < n {
- n = len(entries)
- }
- sub := t.build(entries[:n])
- entries = entries[n:]
- subtrees = append(subtrees, sub)
- t.entries[subdomain(sub)] = sub
- }
- return t.build(subtrees)
-}
-
-func sortByID(nodes []*enode.Node) []*enode.Node {
- slices.SortFunc(nodes, func(a, b *enode.Node) int {
- return bytes.Compare(a.ID().Bytes(), b.ID().Bytes())
- })
- return nodes
-}
-
-// Entry Types
-
-type entry interface {
- fmt.Stringer
-}
-
-type (
- rootEntry struct {
- eroot string
- lroot string
- seq uint
- sig []byte
- }
- branchEntry struct {
- children []string
- }
- enrEntry struct {
- node *enode.Node
- }
- linkEntry struct {
- str string
- domain string
- pubkey *ecdsa.PublicKey
- }
-)
-
-// Entry Encoding
-
-var (
- b32format = base32.StdEncoding.WithPadding(base32.NoPadding)
- b64format = base64.RawURLEncoding
-)
-
-const (
- rootPrefix = "enrtree-root:v1"
- linkPrefix = "enrtree://"
- branchPrefix = "enrtree-branch:"
- enrPrefix = "enr:"
-)
-
-func subdomain(e entry) string {
- h := sha3.NewLegacyKeccak256()
- io.WriteString(h, e.String())
- return b32format.EncodeToString(h.Sum(nil)[:16])
-}
-
-func (e *rootEntry) String() string {
- return fmt.Sprintf(rootPrefix+" e=%s l=%s seq=%d sig=%s", e.eroot, e.lroot, e.seq, b64format.EncodeToString(e.sig))
-}
-
-func (e *rootEntry) sigHash() []byte {
- h := sha3.NewLegacyKeccak256()
- fmt.Fprintf(h, rootPrefix+" e=%s l=%s seq=%d", e.eroot, e.lroot, e.seq)
- return h.Sum(nil)
-}
-
-func (e *rootEntry) verifySignature(pubkey *ecdsa.PublicKey) bool {
- sig := e.sig[:crypto.RecoveryIDOffset] // remove recovery id
- enckey := crypto.FromECDSAPub(pubkey)
- return crypto.VerifySignature(enckey, e.sigHash(), sig)
-}
-
-func (e *branchEntry) String() string {
- return branchPrefix + strings.Join(e.children, ",")
-}
-
-func (e *enrEntry) String() string {
- return e.node.String()
-}
-
-func (e *linkEntry) String() string {
- return linkPrefix + e.str
-}
-
-func newLinkEntry(domain string, pubkey *ecdsa.PublicKey) *linkEntry {
- key := b32format.EncodeToString(crypto.CompressPubkey(pubkey))
- str := key + "@" + domain
- return &linkEntry{str, domain, pubkey}
-}
-
-// Entry Parsing
-
-func parseEntry(e string, validSchemes enr.IdentityScheme) (entry, error) {
- switch {
- case strings.HasPrefix(e, linkPrefix):
- return parseLinkEntry(e)
- case strings.HasPrefix(e, branchPrefix):
- return parseBranch(e)
- case strings.HasPrefix(e, enrPrefix):
- return parseENR(e, validSchemes)
- default:
- return nil, errUnknownEntry
- }
-}
-
-func parseRoot(e string) (rootEntry, error) {
- var eroot, lroot, sig string
- var seq uint
- if _, err := fmt.Sscanf(e, rootPrefix+" e=%s l=%s seq=%d sig=%s", &eroot, &lroot, &seq, &sig); err != nil {
- return rootEntry{}, entryError{"root", errSyntax}
- }
- if !isValidHash(eroot) || !isValidHash(lroot) {
- return rootEntry{}, entryError{"root", errInvalidChild}
- }
- sigb, err := b64format.DecodeString(sig)
- if err != nil || len(sigb) != crypto.SignatureLength {
- return rootEntry{}, entryError{"root", errInvalidSig}
- }
- return rootEntry{eroot, lroot, seq, sigb}, nil
-}
-
-func parseLinkEntry(e string) (entry, error) {
- le, err := parseLink(e)
- if err != nil {
- return nil, err
- }
- return le, nil
-}
-
-func parseLink(e string) (*linkEntry, error) {
- if !strings.HasPrefix(e, linkPrefix) {
- return nil, fmt.Errorf("wrong/missing scheme 'enrtree' in URL")
- }
- e = e[len(linkPrefix):]
- pos := strings.IndexByte(e, '@')
- if pos == -1 {
- return nil, entryError{"link", errNoPubkey}
- }
- keystring, domain := e[:pos], e[pos+1:]
- keybytes, err := b32format.DecodeString(keystring)
- if err != nil {
- return nil, entryError{"link", errBadPubkey}
- }
- key, err := crypto.DecompressPubkey(keybytes)
- if err != nil {
- return nil, entryError{"link", errBadPubkey}
- }
- return &linkEntry{e, domain, key}, nil
-}
-
-func parseBranch(e string) (entry, error) {
- e = e[len(branchPrefix):]
- if e == "" {
- return &branchEntry{}, nil // empty entry is OK
- }
- hashes := make([]string, 0, strings.Count(e, ","))
- for _, c := range strings.Split(e, ",") {
- if !isValidHash(c) {
- return nil, entryError{"branch", errInvalidChild}
- }
- hashes = append(hashes, c)
- }
- return &branchEntry{hashes}, nil
-}
-
-func parseENR(e string, validSchemes enr.IdentityScheme) (entry, error) {
- e = e[len(enrPrefix):]
- enc, err := b64format.DecodeString(e)
- if err != nil {
- return nil, entryError{"enr", errInvalidENR}
- }
- var rec enr.Record
- if err := rlp.DecodeBytes(enc, &rec); err != nil {
- return nil, entryError{"enr", err}
- }
- n, err := enode.New(validSchemes, &rec)
- if err != nil {
- return nil, entryError{"enr", err}
- }
- return &enrEntry{n}, nil
-}
-
-func isValidHash(s string) bool {
- dlen := b32format.DecodedLen(len(s))
- if dlen < minHashLength || dlen > 32 || strings.ContainsAny(s, "\n\r") {
- return false
- }
- buf := make([]byte, 32)
- _, err := b32format.Decode(buf, []byte(s))
- return err == nil
-}
-
-// truncateHash truncates the given base32 hash string to the minimum acceptable length.
-func truncateHash(hash string) string {
- maxLen := b32format.EncodedLen(minHashLength)
- if len(hash) < maxLen {
- panic(fmt.Errorf("dnsdisc: hash %q is too short", hash))
- }
- return hash[:maxLen]
-}
-
-// URL encoding
-
-// ParseURL parses an enrtree:// URL and returns its components.
-func ParseURL(url string) (domain string, pubkey *ecdsa.PublicKey, err error) {
- le, err := parseLink(url)
- if err != nil {
- return "", nil, err
- }
- return le.domain, le.pubkey, nil
-}
diff --git a/p2p/dnsdisc/tree_test.go b/p2p/dnsdisc/tree_test.go
deleted file mode 100644
index 9ed17aa4b3..0000000000
--- a/p2p/dnsdisc/tree_test.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package dnsdisc
-
-import (
- "reflect"
- "testing"
-
- "github.com/davecgh/go-spew/spew"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-func TestParseRoot(t *testing.T) {
- tests := []struct {
- input string
- e rootEntry
- err error
- }{
- {
- input: "enrtree-root:v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtw",
- err: entryError{"root", errSyntax},
- },
- {
- input: "enrtree-root:v1 e=TO4Q75OQ2N7DX4EOOR7X66A6OM l=TO4Q75OQ2N7DX4EOOR7X66A6OM seq=3 sig=N-YY6UB9xD0hFx1Gmnt7v0RfSxch5tKyry2SRDoLx7B4GfPXagwLxQqyf7gAMvApFn_ORwZQekMWa_pXrcGCtw",
- err: entryError{"root", errInvalidSig},
- },
- {
- input: "enrtree-root:v1 e=QFT4PBCRX4XQCV3VUYJ6BTCEPU l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=3FmXuVwpa8Y7OstZTx9PIb1mt8FrW7VpDOFv4AaGCsZ2EIHmhraWhe4NxYhQDlw5MjeFXYMbJjsPeKlHzmJREQE",
- e: rootEntry{
- eroot: "QFT4PBCRX4XQCV3VUYJ6BTCEPU",
- lroot: "JGUFMSAGI7KZYB3P7IZW4S5Y3A",
- seq: 3,
- sig: hexutil.MustDecode("0xdc5997b95c296bc63b3acb594f1f4f21bd66b7c16b5bb5690ce16fe006860ac6761081e686b69685ee0dc588500e5c393237855d831b263b0f78a947ce62511101"),
- },
- },
- }
- for i, test := range tests {
- e, err := parseRoot(test.input)
- if !reflect.DeepEqual(e, test.e) {
- t.Errorf("test %d: wrong entry %s, want %s", i, spew.Sdump(e), spew.Sdump(test.e))
- }
- if err != test.err {
- t.Errorf("test %d: wrong error %q, want %q", i, err, test.err)
- }
- }
-}
-
-func TestParseEntry(t *testing.T) {
- testENRs := []string{"enr:-HW4QES8QIeXTYlDzbfr1WEzE-XKY4f8gJFJzjJL-9D7TC9lJb4Z3JPRRz1lP4pL_N_QpT6rGQjAU9Apnc-C1iMP36OAgmlkgnY0iXNlY3AyNTZrMaED5IdwfMxdmR8W37HqSFdQLjDkIwBd4Q_MjxgZifgKSdM"}
- testNodes := parseNodes(testENRs)
-
- tests := []struct {
- input string
- e entry
- err error
- }{
- // Subtrees:
- {
- input: "enrtree-branch:1,2",
- err: entryError{"branch", errInvalidChild},
- },
- {
- input: "enrtree-branch:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
- err: entryError{"branch", errInvalidChild},
- },
- {
- input: "enrtree-branch:",
- e: &branchEntry{},
- },
- {
- input: "enrtree-branch:AAAAAAAAAAAAAAAAAAAA",
- e: &branchEntry{[]string{"AAAAAAAAAAAAAAAAAAAA"}},
- },
- {
- input: "enrtree-branch:AAAAAAAAAAAAAAAAAAAA,BBBBBBBBBBBBBBBBBBBB",
- e: &branchEntry{[]string{"AAAAAAAAAAAAAAAAAAAA", "BBBBBBBBBBBBBBBBBBBB"}},
- },
- // Links
- {
- input: "enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@nodes.example.org",
- e: &linkEntry{
- str: "AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@nodes.example.org",
- domain: "nodes.example.org",
- pubkey: &signingKeyForTesting.PublicKey,
- },
- },
- {
- input: "enrtree://nodes.example.org",
- err: entryError{"link", errNoPubkey},
- },
- {
- input: "enrtree://AP62DT7WOTEQZGQZOU474PP3KMEGVTTE7A7NPRXKX3DUD57@nodes.example.org",
- err: entryError{"link", errBadPubkey},
- },
- {
- input: "enrtree://AP62DT7WONEQZGQZOU474PP3KMEGVTTE7A7NPRXKX3DUD57TQHGIA@nodes.example.org",
- err: entryError{"link", errBadPubkey},
- },
- // ENRs
- {
- input: testENRs[0],
- e: &enrEntry{node: testNodes[0]},
- },
- {
- input: "enr:-HW4QLZHjM4vZXkbp-5xJoHsKSbE7W39FPC8283X-y8oHcHPTnDDlIlzL5ArvDUlHZVDPgmFASrh7cWgLOLxj4wprRkHgmlkgnY0iXNlY3AyNTZrMaEC3t2jLMhDpCDX5mbSEwDn4L3iUfyXzoO8G28XvjGRkrAg=",
- err: entryError{"enr", errInvalidENR},
- },
- // Invalid:
- {input: "", err: errUnknownEntry},
- {input: "foo", err: errUnknownEntry},
- {input: "enrtree", err: errUnknownEntry},
- {input: "enrtree-x=", err: errUnknownEntry},
- }
- for i, test := range tests {
- e, err := parseEntry(test.input, enode.ValidSchemes)
- if !reflect.DeepEqual(e, test.e) {
- t.Errorf("test %d: wrong entry %s, want %s", i, spew.Sdump(e), spew.Sdump(test.e))
- }
- if err != test.err {
- t.Errorf("test %d: wrong error %q, want %q", i, err, test.err)
- }
- }
-}
-
-func TestMakeTree(t *testing.T) {
- keys := testKeys(50)
- nodes := testNodes(keys)
- tree, err := MakeTree(2, nodes, nil)
- if err != nil {
- t.Fatal(err)
- }
- txt := tree.ToTXT("")
- if len(txt) < len(nodes)+1 {
- t.Fatal("too few TXT records in output")
- }
-}
diff --git a/p2p/enode/idscheme.go b/p2p/enode/idscheme.go
deleted file mode 100644
index fd5d868b76..0000000000
--- a/p2p/enode/idscheme.go
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "crypto/ecdsa"
- "fmt"
- "io"
-
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
- "golang.org/x/crypto/sha3"
-)
-
-// ValidSchemes is a List of known secure identity schemes.
-var ValidSchemes = enr.SchemeMap{
- "v4": V4ID{},
-}
-
-// ValidSchemesForTesting is a List of identity schemes for testing.
-var ValidSchemesForTesting = enr.SchemeMap{
- "v4": V4ID{},
- "null": NullID{},
-}
-
-// V4ID is the "v4" identity scheme.
-type V4ID struct{}
-
-// SignV4 signs a record using the v4 scheme.
-func SignV4(r *enr.Record, privkey *ecdsa.PrivateKey) error {
- // Copy r to avoid modifying it if signing fails.
- cpy := *r
- cpy.Set(enr.ID("v4"))
- cpy.Set(Secp256k1(privkey.PublicKey))
-
- h := sha3.NewLegacyKeccak256()
- rlp.Encode(h, cpy.AppendElements(nil))
- sig, err := crypto.Sign(h.Sum(nil), privkey)
- if err != nil {
- return err
- }
- sig = sig[:len(sig)-1] // remove v
- if err = cpy.SetSig(V4ID{}, sig); err == nil {
- *r = cpy
- }
- return err
-}
-
-func (V4ID) Verify(r *enr.Record, sig []byte) error {
- var entry s256raw
- if err := r.Load(&entry); err != nil {
- return err
- } else if len(entry) != 33 {
- return fmt.Errorf("invalid public key")
- }
-
- h := sha3.NewLegacyKeccak256()
- rlp.Encode(h, r.AppendElements(nil))
- if !crypto.VerifySignature(entry, h.Sum(nil), sig) {
- return enr.ErrInvalidSig
- }
- return nil
-}
-
-func (V4ID) NodeAddr(r *enr.Record) []byte {
- var pubkey Secp256k1
- err := r.Load(&pubkey)
- if err != nil {
- return nil
- }
- buf := make([]byte, 64)
- math.ReadBits(pubkey.X, buf[:32])
- math.ReadBits(pubkey.Y, buf[32:])
- return crypto.Keccak256(buf)
-}
-
-// Secp256k1 is the "secp256k1" key, which holds a public key.
-type Secp256k1 ecdsa.PublicKey
-
-func (v Secp256k1) ENRKey() string { return "secp256k1" }
-
-// EncodeRLP implements rlp.Encoder.
-func (v Secp256k1) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, crypto.CompressPubkey((*ecdsa.PublicKey)(&v)))
-}
-
-// DecodeRLP implements rlp.Decoder.
-func (v *Secp256k1) DecodeRLP(s *rlp.Stream) error {
- buf, err := s.Bytes()
- if err != nil {
- return err
- }
- pk, err := crypto.DecompressPubkey(buf)
- if err != nil {
- return err
- }
- *v = (Secp256k1)(*pk)
- return nil
-}
-
-// s256raw is an unparsed secp256k1 public key entry.
-type s256raw []byte
-
-func (s256raw) ENRKey() string { return "secp256k1" }
-
-// v4CompatID is a weaker and insecure version of the "v4" scheme which only checks for the
-// presence of a secp256k1 public key, but doesn't verify the signature.
-type v4CompatID struct {
- V4ID
-}
-
-func (v4CompatID) Verify(r *enr.Record, sig []byte) error {
- var pubkey Secp256k1
- return r.Load(&pubkey)
-}
-
-func signV4Compat(r *enr.Record, pubkey *ecdsa.PublicKey) {
- r.Set((*Secp256k1)(pubkey))
- if err := r.SetSig(v4CompatID{}, []byte{}); err != nil {
- panic(err)
- }
-}
-
-// NullID is the "null" ENR identity scheme. This scheme stores the node
-// ID in the record without any signature.
-type NullID struct{}
-
-func (NullID) Verify(r *enr.Record, sig []byte) error {
- return nil
-}
-
-func (NullID) NodeAddr(r *enr.Record) []byte {
- var id ID
- r.Load(enr.WithEntry("nulladdr", &id))
- return id[:]
-}
-
-func SignNull(r *enr.Record, id ID) *Node {
- r.Set(enr.ID("null"))
- r.Set(enr.WithEntry("nulladdr", id))
- if err := r.SetSig(NullID{}, []byte{}); err != nil {
- panic(err)
- }
- return &Node{r: *r, id: id}
-}
diff --git a/p2p/enode/idscheme_test.go b/p2p/enode/idscheme_test.go
deleted file mode 100644
index 0910e6e83f..0000000000
--- a/p2p/enode/idscheme_test.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "bytes"
- "crypto/ecdsa"
- "encoding/hex"
- "math/big"
- "testing"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-var (
- privkey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
- pubkey = &privkey.PublicKey
-)
-
-func TestEmptyNodeID(t *testing.T) {
- var r enr.Record
- if addr := ValidSchemes.NodeAddr(&r); addr != nil {
- t.Errorf("wrong address on empty record: got %v, want %v", addr, nil)
- }
-
- require.NoError(t, SignV4(&r, privkey))
- expected := "a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"
- assert.Equal(t, expected, hex.EncodeToString(ValidSchemes.NodeAddr(&r)))
-}
-
-// Checks that failure to sign leaves the record unmodified.
-func TestSignError(t *testing.T) {
- invalidKey := &ecdsa.PrivateKey{D: new(big.Int), PublicKey: *pubkey}
-
- var r enr.Record
- emptyEnc, _ := rlp.EncodeToBytes(&r)
- if err := SignV4(&r, invalidKey); err == nil {
- t.Fatal("expected error from SignV4")
- }
- newEnc, _ := rlp.EncodeToBytes(&r)
- if !bytes.Equal(newEnc, emptyEnc) {
- t.Fatal("record modified even though signing failed")
- }
-}
-
-// TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the Secp256k1 key.
-func TestGetSetSecp256k1(t *testing.T) {
- var r enr.Record
- if err := SignV4(&r, privkey); err != nil {
- t.Fatal(err)
- }
-
- var pk Secp256k1
- require.NoError(t, r.Load(&pk))
- assert.EqualValues(t, pubkey, &pk)
-}
diff --git a/p2p/enode/iter.go b/p2p/enode/iter.go
deleted file mode 100644
index b8ab4a758a..0000000000
--- a/p2p/enode/iter.go
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "sync"
- "time"
-)
-
-// Iterator represents a sequence of nodes. The Next method moves to the next node in the
-// sequence. It returns false when the sequence has ended or the iterator is closed. Close
-// may be called concurrently with Next and Node, and interrupts Next if it is blocked.
-type Iterator interface {
- Next() bool // moves to next node
- Node() *Node // returns current node
- Close() // ends the iterator
-}
-
-// ReadNodes reads at most n nodes from the given iterator. The return value contains no
-// duplicates and no nil values. To prevent looping indefinitely for small repeating node
-// sequences, this function calls Next at most n times.
-func ReadNodes(it Iterator, n int) []*Node {
- seen := make(map[ID]*Node, n)
- for i := 0; i < n && it.Next(); i++ {
- // Remove duplicates, keeping the node with higher seq.
- node := it.Node()
- prevNode, ok := seen[node.ID()]
- if ok && prevNode.Seq() > node.Seq() {
- continue
- }
- seen[node.ID()] = node
- }
- result := make([]*Node, 0, len(seen))
- for _, node := range seen {
- result = append(result, node)
- }
- return result
-}
-
-// IterNodes makes an iterator which runs through the given nodes once.
-func IterNodes(nodes []*Node) Iterator {
- return &sliceIter{nodes: nodes, index: -1}
-}
-
-// CycleNodes makes an iterator which cycles through the given nodes indefinitely.
-func CycleNodes(nodes []*Node) Iterator {
- return &sliceIter{nodes: nodes, index: -1, cycle: true}
-}
-
-type sliceIter struct {
- mu sync.Mutex
- nodes []*Node
- index int
- cycle bool
-}
-
-func (it *sliceIter) Next() bool {
- it.mu.Lock()
- defer it.mu.Unlock()
-
- if len(it.nodes) == 0 {
- return false
- }
- it.index++
- if it.index == len(it.nodes) {
- if it.cycle {
- it.index = 0
- } else {
- it.nodes = nil
- return false
- }
- }
- return true
-}
-
-func (it *sliceIter) Node() *Node {
- it.mu.Lock()
- defer it.mu.Unlock()
- if len(it.nodes) == 0 {
- return nil
- }
- return it.nodes[it.index]
-}
-
-func (it *sliceIter) Close() {
- it.mu.Lock()
- defer it.mu.Unlock()
-
- it.nodes = nil
-}
-
-// Filter wraps an iterator such that Next only returns nodes for which
-// the 'check' function returns true.
-func Filter(it Iterator, check func(*Node) bool) Iterator {
- return &filterIter{it, check}
-}
-
-type filterIter struct {
- Iterator
- check func(*Node) bool
-}
-
-func (f *filterIter) Next() bool {
- for f.Iterator.Next() {
- if f.check(f.Node()) {
- return true
- }
- }
- return false
-}
-
-// FairMix aggregates multiple node iterators. The mixer itself is an iterator which ends
-// only when Close is called. Source iterators added via AddSource are removed from the
-// mix when they end.
-//
-// The distribution of nodes returned by Next is approximately fair, i.e. FairMix
-// attempts to draw from all sources equally often. However, if a certain source is slow
-// and doesn't return a node within the configured timeout, a node from any other source
-// will be returned.
-//
-// It's safe to call AddSource and Close concurrently with Next.
-type FairMix struct {
- wg sync.WaitGroup
- fromAny chan *Node
- timeout time.Duration
- cur *Node
-
- mu sync.Mutex
- closed chan struct{}
- sources []*mixSource
- last int
-}
-
-type mixSource struct {
- it Iterator
- next chan *Node
- timeout time.Duration
-}
-
-// NewFairMix creates a mixer.
-//
-// The timeout specifies how long the mixer will wait for the next fairly-chosen source
-// before giving up and taking a node from any other source. A good way to set the timeout
-// is deciding how long you'd want to wait for a node on average. Passing a negative
-// timeout makes the mixer completely fair.
-func NewFairMix(timeout time.Duration) *FairMix {
- m := &FairMix{
- fromAny: make(chan *Node),
- closed: make(chan struct{}),
- timeout: timeout,
- }
- return m
-}
-
-// AddSource adds a source of nodes.
-func (m *FairMix) AddSource(it Iterator) {
- m.mu.Lock()
- defer m.mu.Unlock()
-
- if m.closed == nil {
- return
- }
- m.wg.Add(1)
- source := &mixSource{it, make(chan *Node), m.timeout}
- m.sources = append(m.sources, source)
- go m.runSource(m.closed, source)
-}
-
-// Close shuts down the mixer and all current sources.
-// Calling this is required to release resources associated with the mixer.
-func (m *FairMix) Close() {
- m.mu.Lock()
- defer m.mu.Unlock()
-
- if m.closed == nil {
- return
- }
- for _, s := range m.sources {
- s.it.Close()
- }
- close(m.closed)
- m.wg.Wait()
- close(m.fromAny)
- m.sources = nil
- m.closed = nil
-}
-
-// Next returns a node from a random source.
-func (m *FairMix) Next() bool {
- m.cur = nil
-
- for {
- source := m.pickSource()
- if source == nil {
- return m.nextFromAny()
- }
-
- var timeout <-chan time.Time
- if source.timeout >= 0 {
- timer := time.NewTimer(source.timeout)
- timeout = timer.C
- defer timer.Stop()
- }
-
- select {
- case n, ok := <-source.next:
- if ok {
- // Here, the timeout is reset to the configured value
- // because the source delivered a node.
- source.timeout = m.timeout
- m.cur = n
- return true
- }
- // This source has ended.
- m.deleteSource(source)
- case <-timeout:
- // The selected source did not deliver a node within the timeout, so the
- // timeout duration is halved for next time. This is supposed to improve
- // latency with stuck sources.
- source.timeout /= 2
- return m.nextFromAny()
- }
- }
-}
-
-// Node returns the current node.
-func (m *FairMix) Node() *Node {
- return m.cur
-}
-
-// nextFromAny is used when there are no sources or when the 'fair' choice
-// doesn't turn up a node quickly enough.
-func (m *FairMix) nextFromAny() bool {
- n, ok := <-m.fromAny
- if ok {
- m.cur = n
- }
- return ok
-}
-
-// pickSource chooses the next source to read from, cycling through them in order.
-func (m *FairMix) pickSource() *mixSource {
- m.mu.Lock()
- defer m.mu.Unlock()
-
- if len(m.sources) == 0 {
- return nil
- }
- m.last = (m.last + 1) % len(m.sources)
- return m.sources[m.last]
-}
-
-// deleteSource deletes a source.
-func (m *FairMix) deleteSource(s *mixSource) {
- m.mu.Lock()
- defer m.mu.Unlock()
-
- for i := range m.sources {
- if m.sources[i] == s {
- copy(m.sources[i:], m.sources[i+1:])
- m.sources[len(m.sources)-1] = nil
- m.sources = m.sources[:len(m.sources)-1]
- break
- }
- }
-}
-
-// runSource reads a single source in a loop.
-func (m *FairMix) runSource(closed chan struct{}, s *mixSource) {
- defer m.wg.Done()
- defer close(s.next)
- for s.it.Next() {
- n := s.it.Node()
- select {
- case s.next <- n:
- case m.fromAny <- n:
- case <-closed:
- return
- }
- }
-}
diff --git a/p2p/enode/iter_test.go b/p2p/enode/iter_test.go
deleted file mode 100644
index b736ed450a..0000000000
--- a/p2p/enode/iter_test.go
+++ /dev/null
@@ -1,291 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "encoding/binary"
- "runtime"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/p2p/enr"
-)
-
-func TestReadNodes(t *testing.T) {
- nodes := ReadNodes(new(genIter), 10)
- checkNodes(t, nodes, 10)
-}
-
-// This test checks that ReadNodes terminates when reading N nodes from an iterator
-// which returns less than N nodes in an endless cycle.
-func TestReadNodesCycle(t *testing.T) {
- iter := &callCountIter{
- Iterator: CycleNodes([]*Node{
- testNode(0, 0),
- testNode(1, 0),
- testNode(2, 0),
- }),
- }
- nodes := ReadNodes(iter, 10)
- checkNodes(t, nodes, 3)
- if iter.count != 10 {
- t.Fatalf("%d calls to Next, want %d", iter.count, 100)
- }
-}
-
-func TestFilterNodes(t *testing.T) {
- nodes := make([]*Node, 100)
- for i := range nodes {
- nodes[i] = testNode(uint64(i), uint64(i))
- }
-
- it := Filter(IterNodes(nodes), func(n *Node) bool {
- return n.Seq() >= 50
- })
- for i := 50; i < len(nodes); i++ {
- if !it.Next() {
- t.Fatal("Next returned false")
- }
- if it.Node() != nodes[i] {
- t.Fatalf("iterator returned wrong node %v\nwant %v", it.Node(), nodes[i])
- }
- }
- if it.Next() {
- t.Fatal("Next returned true after underlying iterator has ended")
- }
-}
-
-func checkNodes(t *testing.T, nodes []*Node, wantLen int) {
- if len(nodes) != wantLen {
- t.Errorf("slice has %d nodes, want %d", len(nodes), wantLen)
- return
- }
- seen := make(map[ID]bool, len(nodes))
- for i, e := range nodes {
- if e == nil {
- t.Errorf("nil node at index %d", i)
- return
- }
- if seen[e.ID()] {
- t.Errorf("slice has duplicate node %v", e.ID())
- return
- }
- seen[e.ID()] = true
- }
-}
-
-// This test checks fairness of FairMix in the happy case where all sources return nodes
-// within the context's deadline.
-func TestFairMix(t *testing.T) {
- for i := 0; i < 500; i++ {
- testMixerFairness(t)
- }
-}
-
-func testMixerFairness(t *testing.T) {
- mix := NewFairMix(1 * time.Second)
- mix.AddSource(&genIter{index: 1})
- mix.AddSource(&genIter{index: 2})
- mix.AddSource(&genIter{index: 3})
- defer mix.Close()
-
- nodes := ReadNodes(mix, 500)
- checkNodes(t, nodes, 500)
-
- // Verify that the nodes slice contains an approximately equal number of nodes
- // from each source.
- d := idPrefixDistribution(nodes)
- for _, count := range d {
- if approxEqual(count, len(nodes)/3, 30) {
- t.Fatalf("ID distribution is unfair: %v", d)
- }
- }
-}
-
-// This test checks that FairMix falls back to an alternative source when
-// the 'fair' choice doesn't return a node within the timeout.
-func TestFairMixNextFromAll(t *testing.T) {
- mix := NewFairMix(1 * time.Millisecond)
- mix.AddSource(&genIter{index: 1})
- mix.AddSource(CycleNodes(nil))
- defer mix.Close()
-
- nodes := ReadNodes(mix, 500)
- checkNodes(t, nodes, 500)
-
- d := idPrefixDistribution(nodes)
- if len(d) > 1 || d[1] != len(nodes) {
- t.Fatalf("wrong ID distribution: %v", d)
- }
-}
-
-// This test ensures FairMix works for Next with no sources.
-func TestFairMixEmpty(t *testing.T) {
- var (
- mix = NewFairMix(1 * time.Second)
- testN = testNode(1, 1)
- ch = make(chan *Node)
- )
- defer mix.Close()
-
- go func() {
- mix.Next()
- ch <- mix.Node()
- }()
-
- mix.AddSource(CycleNodes([]*Node{testN}))
- if n := <-ch; n != testN {
- t.Errorf("got wrong node: %v", n)
- }
-}
-
-// This test checks closing a source while Next runs.
-func TestFairMixRemoveSource(t *testing.T) {
- mix := NewFairMix(1 * time.Second)
- source := make(blockingIter)
- mix.AddSource(source)
-
- sig := make(chan *Node)
- go func() {
- <-sig
- mix.Next()
- sig <- mix.Node()
- }()
-
- sig <- nil
- runtime.Gosched()
- source.Close()
-
- wantNode := testNode(0, 0)
- mix.AddSource(CycleNodes([]*Node{wantNode}))
- n := <-sig
-
- if len(mix.sources) != 1 {
- t.Fatalf("have %d sources, want one", len(mix.sources))
- }
- if n != wantNode {
- t.Fatalf("mixer returned wrong node")
- }
-}
-
-type blockingIter chan struct{}
-
-func (it blockingIter) Next() bool {
- <-it
- return false
-}
-
-func (it blockingIter) Node() *Node {
- return nil
-}
-
-func (it blockingIter) Close() {
- close(it)
-}
-
-func TestFairMixClose(t *testing.T) {
- for i := 0; i < 20 && !t.Failed(); i++ {
- testMixerClose(t)
- }
-}
-
-func testMixerClose(t *testing.T) {
- mix := NewFairMix(-1)
- mix.AddSource(CycleNodes(nil))
- mix.AddSource(CycleNodes(nil))
-
- done := make(chan struct{})
- go func() {
- defer close(done)
- if mix.Next() {
- t.Error("Next returned true")
- }
- }()
- // This call is supposed to make it more likely that NextNode is
- // actually executing by the time we call Close.
- runtime.Gosched()
-
- mix.Close()
- select {
- case <-done:
- case <-time.After(3 * time.Second):
- t.Fatal("Next didn't unblock on Close")
- }
-
- mix.Close() // shouldn't crash
-}
-
-func idPrefixDistribution(nodes []*Node) map[uint32]int {
- d := make(map[uint32]int, len(nodes))
- for _, node := range nodes {
- id := node.ID()
- d[binary.BigEndian.Uint32(id[:4])]++
- }
- return d
-}
-
-func approxEqual(x, y, ε int) bool {
- if y > x {
- x, y = y, x
- }
- return x-y > ε
-}
-
-// genIter creates fake nodes with numbered IDs based on 'index' and 'gen'
-type genIter struct {
- node *Node
- index, gen uint32
-}
-
-func (s *genIter) Next() bool {
- index := atomic.LoadUint32(&s.index)
- if index == ^uint32(0) {
- s.node = nil
- return false
- }
- s.node = testNode(uint64(index)<<32|uint64(s.gen), 0)
- s.gen++
- return true
-}
-
-func (s *genIter) Node() *Node {
- return s.node
-}
-
-func (s *genIter) Close() {
- atomic.StoreUint32(&s.index, ^uint32(0))
-}
-
-func testNode(id, seq uint64) *Node {
- var nodeID ID
- binary.BigEndian.PutUint64(nodeID[:], id)
- r := new(enr.Record)
- r.SetSeq(seq)
- return SignNull(r, nodeID)
-}
-
-// callCountIter counts calls to NextNode.
-type callCountIter struct {
- Iterator
- count int
-}
-
-func (it *callCountIter) Next() bool {
- it.count++
- return it.Iterator.Next()
-}
diff --git a/p2p/enode/localnode.go b/p2p/enode/localnode.go
deleted file mode 100644
index a18204e752..0000000000
--- a/p2p/enode/localnode.go
+++ /dev/null
@@ -1,332 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "crypto/ecdsa"
- "fmt"
- "net"
- "reflect"
- "strconv"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/netutil"
-)
-
-const (
- // IP tracker configuration
- iptrackMinStatements = 10
- iptrackWindow = 5 * time.Minute
- iptrackContactWindow = 10 * time.Minute
-
- // time needed to wait between two updates to the local ENR
- recordUpdateThrottle = time.Millisecond
-)
-
-// LocalNode produces the signed node record of a local node, i.e. a node run in the
-// current process. Setting ENR entries via the Set method updates the record. A new version
-// of the record is signed on demand when the Node method is called.
-type LocalNode struct {
- cur atomic.Value // holds a non-nil node pointer while the record is up-to-date
-
- id ID
- key *ecdsa.PrivateKey
- db *DB
-
- // everything below is protected by a lock
- mu sync.RWMutex
- seq uint64
- update time.Time // timestamp when the record was last updated
- entries map[string]enr.Entry
- endpoint4 lnEndpoint
- endpoint6 lnEndpoint
-}
-
-type lnEndpoint struct {
- track *netutil.IPTracker
- staticIP, fallbackIP net.IP
- fallbackUDP uint16 // port
-}
-
-// NewLocalNode creates a local node.
-func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode {
- ln := &LocalNode{
- id: PubkeyToIDV4(&key.PublicKey),
- db: db,
- key: key,
- entries: make(map[string]enr.Entry),
- endpoint4: lnEndpoint{
- track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
- },
- endpoint6: lnEndpoint{
- track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
- },
- }
- ln.seq = db.localSeq(ln.id)
- ln.update = time.Now()
- ln.cur.Store((*Node)(nil))
- return ln
-}
-
-// Database returns the node database associated with the local node.
-func (ln *LocalNode) Database() *DB {
- return ln.db
-}
-
-// Node returns the current version of the local node record.
-func (ln *LocalNode) Node() *Node {
- // If we have a valid record, return that
- n := ln.cur.Load().(*Node)
- if n != nil {
- return n
- }
-
- // Record was invalidated, sign a new copy.
- ln.mu.Lock()
- defer ln.mu.Unlock()
-
- // Double check the current record, since multiple goroutines might be waiting
- // on the write mutex.
- if n = ln.cur.Load().(*Node); n != nil {
- return n
- }
-
- // The initial sequence number is the current timestamp in milliseconds. To ensure
- // that the initial sequence number will always be higher than any previous sequence
- // number (assuming the clock is correct), we want to avoid updating the record faster
- // than once per ms. So we need to sleep here until the next possible update time has
- // arrived.
- lastChange := time.Since(ln.update)
- if lastChange < recordUpdateThrottle {
- time.Sleep(recordUpdateThrottle - lastChange)
- }
-
- ln.sign()
- ln.update = time.Now()
- return ln.cur.Load().(*Node)
-}
-
-// Seq returns the current sequence number of the local node record.
-func (ln *LocalNode) Seq() uint64 {
- ln.mu.Lock()
- defer ln.mu.Unlock()
-
- return ln.seq
-}
-
-// ID returns the local node ID.
-func (ln *LocalNode) ID() ID {
- return ln.id
-}
-
-// Set puts the given entry into the local record, overwriting any existing value.
-// Use Set*IP and SetFallbackUDP to set IP addresses and UDP port, otherwise they'll
-// be overwritten by the endpoint predictor.
-//
-// Since node record updates are throttled to one per second, Set is asynchronous.
-// Any update will be queued up and published when at least one second passes from
-// the last change.
-func (ln *LocalNode) Set(e enr.Entry) {
- ln.mu.Lock()
- defer ln.mu.Unlock()
-
- ln.set(e)
-}
-
-func (ln *LocalNode) set(e enr.Entry) {
- val, exists := ln.entries[e.ENRKey()]
- if !exists || !reflect.DeepEqual(val, e) {
- ln.entries[e.ENRKey()] = e
- ln.invalidate()
- }
-}
-
-// Delete removes the given entry from the local record.
-func (ln *LocalNode) Delete(e enr.Entry) {
- ln.mu.Lock()
- defer ln.mu.Unlock()
-
- ln.delete(e)
-}
-
-func (ln *LocalNode) delete(e enr.Entry) {
- _, exists := ln.entries[e.ENRKey()]
- if exists {
- delete(ln.entries, e.ENRKey())
- ln.invalidate()
- }
-}
-
-func (ln *LocalNode) endpointForIP(ip net.IP) *lnEndpoint {
- if ip.To4() != nil {
- return &ln.endpoint4
- }
- return &ln.endpoint6
-}
-
-// SetStaticIP sets the local IP to the given one unconditionally.
-// This disables endpoint prediction.
-func (ln *LocalNode) SetStaticIP(ip net.IP) {
- ln.mu.Lock()
- defer ln.mu.Unlock()
-
- ln.endpointForIP(ip).staticIP = ip
- ln.updateEndpoints()
-}
-
-// SetFallbackIP sets the last-resort IP address. This address is used
-// if no endpoint prediction can be made and no static IP is set.
-func (ln *LocalNode) SetFallbackIP(ip net.IP) {
- ln.mu.Lock()
- defer ln.mu.Unlock()
-
- ln.endpointForIP(ip).fallbackIP = ip
- ln.updateEndpoints()
-}
-
-// SetFallbackUDP sets the last-resort UDP-on-IPv4 port. This port is used
-// if no endpoint prediction can be made.
-func (ln *LocalNode) SetFallbackUDP(port int) {
- ln.mu.Lock()
- defer ln.mu.Unlock()
-
- ln.endpoint4.fallbackUDP = uint16(port)
- ln.endpoint6.fallbackUDP = uint16(port)
- ln.updateEndpoints()
-}
-
-// UDPEndpointStatement should be called whenever a statement about the local node's
-// UDP endpoint is received. It feeds the local endpoint predictor.
-func (ln *LocalNode) UDPEndpointStatement(fromaddr, endpoint *net.UDPAddr) {
- ln.mu.Lock()
- defer ln.mu.Unlock()
-
- ln.endpointForIP(endpoint.IP).track.AddStatement(fromaddr.String(), endpoint.String())
- ln.updateEndpoints()
-}
-
-// UDPContact should be called whenever the local node has announced itself to another node
-// via UDP. It feeds the local endpoint predictor.
-func (ln *LocalNode) UDPContact(toaddr *net.UDPAddr) {
- ln.mu.Lock()
- defer ln.mu.Unlock()
-
- ln.endpointForIP(toaddr.IP).track.AddContact(toaddr.String())
- ln.updateEndpoints()
-}
-
-// updateEndpoints updates the record with predicted endpoints.
-func (ln *LocalNode) updateEndpoints() {
- ip4, udp4 := ln.endpoint4.get()
- ip6, udp6 := ln.endpoint6.get()
-
- if ip4 != nil && !ip4.IsUnspecified() {
- ln.set(enr.IPv4(ip4))
- } else {
- ln.delete(enr.IPv4{})
- }
- if ip6 != nil && !ip6.IsUnspecified() {
- ln.set(enr.IPv6(ip6))
- } else {
- ln.delete(enr.IPv6{})
- }
- if udp4 != 0 {
- ln.set(enr.UDP(udp4))
- } else {
- ln.delete(enr.UDP(0))
- }
- if udp6 != 0 && udp6 != udp4 {
- ln.set(enr.UDP6(udp6))
- } else {
- ln.delete(enr.UDP6(0))
- }
-}
-
-// get returns the endpoint with highest precedence.
-func (e *lnEndpoint) get() (newIP net.IP, newPort uint16) {
- newPort = e.fallbackUDP
- if e.fallbackIP != nil {
- newIP = e.fallbackIP
- }
- if e.staticIP != nil {
- newIP = e.staticIP
- } else if ip, port := predictAddr(e.track); ip != nil {
- newIP = ip
- newPort = port
- }
- return newIP, newPort
-}
-
-// predictAddr wraps IPTracker.PredictEndpoint, converting from its string-based
-// endpoint representation to IP and port types.
-func predictAddr(t *netutil.IPTracker) (net.IP, uint16) {
- ep := t.PredictEndpoint()
- if ep == "" {
- return nil, 0
- }
- ipString, portString, _ := net.SplitHostPort(ep)
- ip := net.ParseIP(ipString)
- port, err := strconv.ParseUint(portString, 10, 16)
- if err != nil {
- return nil, 0
- }
- return ip, uint16(port)
-}
-
-func (ln *LocalNode) invalidate() {
- ln.cur.Store((*Node)(nil))
-}
-
-func (ln *LocalNode) sign() {
- if n := ln.cur.Load().(*Node); n != nil {
- return // no changes
- }
-
- var r enr.Record
- for _, e := range ln.entries {
- r.Set(e)
- }
- ln.bumpSeq()
- r.SetSeq(ln.seq)
- if err := SignV4(&r, ln.key); err != nil {
- panic(fmt.Errorf("enode: can't sign record: %v", err))
- }
- n, err := New(ValidSchemes, &r)
- if err != nil {
- panic(fmt.Errorf("enode: can't verify local record: %v", err))
- }
- ln.cur.Store(n)
- log.Info("New local node record", "seq", ln.seq, "id", n.ID(), "ip", n.IP(), "udp", n.UDP(), "tcp", n.TCP())
-}
-
-func (ln *LocalNode) bumpSeq() {
- ln.seq++
- ln.db.storeLocalSeq(ln.id, ln.seq)
-}
-
-// nowMilliseconds gives the current timestamp at millisecond precision.
-func nowMilliseconds() uint64 {
- ns := time.Now().UnixNano()
- if ns < 0 {
- return 0
- }
- return uint64(ns / 1000 / 1000)
-}
diff --git a/p2p/enode/localnode_test.go b/p2p/enode/localnode_test.go
deleted file mode 100644
index 7f97ad392f..0000000000
--- a/p2p/enode/localnode_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "crypto/rand"
- "net"
- "testing"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/stretchr/testify/assert"
-)
-
-func newLocalNodeForTesting() (*LocalNode, *DB) {
- db, _ := OpenDB("")
- key, _ := crypto.GenerateKey()
- return NewLocalNode(db, key), db
-}
-
-func TestLocalNode(t *testing.T) {
- ln, db := newLocalNodeForTesting()
- defer db.Close()
-
- if ln.Node().ID() != ln.ID() {
- t.Fatal("inconsistent ID")
- }
-
- ln.Set(enr.WithEntry("x", uint(3)))
- var x uint
- if err := ln.Node().Load(enr.WithEntry("x", &x)); err != nil {
- t.Fatal("can't load entry 'x':", err)
- } else if x != 3 {
- t.Fatal("wrong value for entry 'x':", x)
- }
-}
-
-// This test checks that the sequence number is persisted between restarts.
-func TestLocalNodeSeqPersist(t *testing.T) {
- timestamp := nowMilliseconds()
-
- ln, db := newLocalNodeForTesting()
- defer db.Close()
-
- initialSeq := ln.Node().Seq()
- if initialSeq < timestamp {
- t.Fatalf("wrong initial seq %d, want at least %d", initialSeq, timestamp)
- }
-
- ln.Set(enr.WithEntry("x", uint(1)))
- if s := ln.Node().Seq(); s != initialSeq+1 {
- t.Fatalf("wrong seq %d after set, want %d", s, initialSeq+1)
- }
-
- // Create a new instance, it should reload the sequence number.
- // The number increases just after that because a new record is
- // created without the "x" entry.
- ln2 := NewLocalNode(db, ln.key)
- if s := ln2.Node().Seq(); s != initialSeq+2 {
- t.Fatalf("wrong seq %d on new instance, want %d", s, initialSeq+2)
- }
-
- finalSeq := ln2.Node().Seq()
-
- // Create a new instance with a different node key on the same database.
- // This should reset the sequence number.
- key, _ := crypto.GenerateKey()
- ln3 := NewLocalNode(db, key)
- if s := ln3.Node().Seq(); s < finalSeq {
- t.Fatalf("wrong seq %d on instance with changed key, want >= %d", s, finalSeq)
- }
-}
-
-// This test checks behavior of the endpoint predictor.
-func TestLocalNodeEndpoint(t *testing.T) {
- var (
- fallback = &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 80}
- predicted = &net.UDPAddr{IP: net.IP{127, 0, 1, 2}, Port: 81}
- staticIP = net.IP{127, 0, 1, 2}
- )
- ln, db := newLocalNodeForTesting()
- defer db.Close()
-
- // Nothing is set initially.
- assert.Equal(t, net.IP(nil), ln.Node().IP())
- assert.Equal(t, 0, ln.Node().UDP())
- initialSeq := ln.Node().Seq()
-
- // Set up fallback address.
- ln.SetFallbackIP(fallback.IP)
- ln.SetFallbackUDP(fallback.Port)
- assert.Equal(t, fallback.IP, ln.Node().IP())
- assert.Equal(t, fallback.Port, ln.Node().UDP())
- assert.Equal(t, initialSeq+1, ln.Node().Seq())
-
- // Add endpoint statements from random hosts.
- for i := 0; i < iptrackMinStatements; i++ {
- assert.Equal(t, fallback.IP, ln.Node().IP())
- assert.Equal(t, fallback.Port, ln.Node().UDP())
- assert.Equal(t, initialSeq+1, ln.Node().Seq())
-
- from := &net.UDPAddr{IP: make(net.IP, 4), Port: 90}
- rand.Read(from.IP)
- ln.UDPEndpointStatement(from, predicted)
- }
- assert.Equal(t, predicted.IP, ln.Node().IP())
- assert.Equal(t, predicted.Port, ln.Node().UDP())
- assert.Equal(t, initialSeq+2, ln.Node().Seq())
-
- // Static IP overrides prediction.
- ln.SetStaticIP(staticIP)
- assert.Equal(t, staticIP, ln.Node().IP())
- assert.Equal(t, fallback.Port, ln.Node().UDP())
- assert.Equal(t, initialSeq+3, ln.Node().Seq())
-}
diff --git a/p2p/enode/node.go b/p2p/enode/node.go
deleted file mode 100644
index d7a1a9a156..0000000000
--- a/p2p/enode/node.go
+++ /dev/null
@@ -1,279 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "crypto/ecdsa"
- "encoding/base64"
- "encoding/hex"
- "errors"
- "fmt"
- "math/bits"
- "net"
- "strings"
-
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-var errMissingPrefix = errors.New("missing 'enr:' prefix for base64-encoded record")
-
-// Node represents a host on the network.
-type Node struct {
- r enr.Record
- id ID
-}
-
-// New wraps a node record. The record must be valid according to the given
-// identity scheme.
-func New(validSchemes enr.IdentityScheme, r *enr.Record) (*Node, error) {
- if err := r.VerifySignature(validSchemes); err != nil {
- return nil, err
- }
- node := &Node{r: *r}
- if n := copy(node.id[:], validSchemes.NodeAddr(&node.r)); n != len(ID{}) {
- return nil, fmt.Errorf("invalid node ID length %d, need %d", n, len(ID{}))
- }
- return node, nil
-}
-
-// MustParse parses a node record or enode:// URL. It panics if the input is invalid.
-func MustParse(rawurl string) *Node {
- n, err := Parse(ValidSchemes, rawurl)
- if err != nil {
- panic("invalid node: " + err.Error())
- }
- return n
-}
-
-// Parse decodes and verifies a base64-encoded node record.
-func Parse(validSchemes enr.IdentityScheme, input string) (*Node, error) {
- if strings.HasPrefix(input, "enode://") {
- return ParseV4(input)
- }
- if !strings.HasPrefix(input, "enr:") {
- return nil, errMissingPrefix
- }
- bin, err := base64.RawURLEncoding.DecodeString(input[4:])
- if err != nil {
- return nil, err
- }
- var r enr.Record
- if err := rlp.DecodeBytes(bin, &r); err != nil {
- return nil, err
- }
- return New(validSchemes, &r)
-}
-
-// ID returns the node identifier.
-func (n *Node) ID() ID {
- return n.id
-}
-
-// Seq returns the sequence number of the underlying record.
-func (n *Node) Seq() uint64 {
- return n.r.Seq()
-}
-
-// Incomplete returns true for nodes with no IP address.
-func (n *Node) Incomplete() bool {
- return n.IP() == nil
-}
-
-// Load retrieves an entry from the underlying record.
-func (n *Node) Load(k enr.Entry) error {
- return n.r.Load(k)
-}
-
-// IP returns the IP address of the node. This prefers IPv4 addresses.
-func (n *Node) IP() net.IP {
- var (
- ip4 enr.IPv4
- ip6 enr.IPv6
- )
- if n.Load(&ip4) == nil {
- return net.IP(ip4)
- }
- if n.Load(&ip6) == nil {
- return net.IP(ip6)
- }
- return nil
-}
-
-// UDP returns the UDP port of the node.
-func (n *Node) UDP() int {
- var port enr.UDP
- n.Load(&port)
- return int(port)
-}
-
-// TCP returns the TCP port of the node.
-func (n *Node) TCP() int {
- var port enr.TCP
- n.Load(&port)
- return int(port)
-}
-
-// Pubkey returns the secp256k1 public key of the node, if present.
-func (n *Node) Pubkey() *ecdsa.PublicKey {
- var key ecdsa.PublicKey
- if n.Load((*Secp256k1)(&key)) != nil {
- return nil
- }
- return &key
-}
-
-// Record returns the node's record. The return value is a copy and may
-// be modified by the caller.
-func (n *Node) Record() *enr.Record {
- cpy := n.r
- return &cpy
-}
-
-// ValidateComplete checks whether n has a valid IP and UDP port.
-// Deprecated: don't use this method.
-func (n *Node) ValidateComplete() error {
- if n.Incomplete() {
- return errors.New("missing IP address")
- }
- if n.UDP() == 0 {
- return errors.New("missing UDP port")
- }
- ip := n.IP()
- if ip.IsMulticast() || ip.IsUnspecified() {
- return errors.New("invalid IP (multicast/unspecified)")
- }
- // Validate the node key (on curve, etc.).
- var key Secp256k1
- return n.Load(&key)
-}
-
-// String returns the text representation of the record.
-func (n *Node) String() string {
- if isNewV4(n) {
- return n.URLv4() // backwards-compatibility glue for NewV4 nodes
- }
- enc, _ := rlp.EncodeToBytes(&n.r) // always succeeds because record is valid
- b64 := base64.RawURLEncoding.EncodeToString(enc)
- return "enr:" + b64
-}
-
-// MarshalText implements encoding.TextMarshaler.
-func (n *Node) MarshalText() ([]byte, error) {
- return []byte(n.String()), nil
-}
-
-// UnmarshalText implements encoding.TextUnmarshaler.
-func (n *Node) UnmarshalText(text []byte) error {
- dec, err := Parse(ValidSchemes, string(text))
- if err == nil {
- *n = *dec
- }
- return err
-}
-
-// ID is a unique identifier for each node.
-type ID [32]byte
-
-// Bytes returns a byte slice representation of the ID
-func (n ID) Bytes() []byte {
- return n[:]
-}
-
-// ID prints as a long hexadecimal number.
-func (n ID) String() string {
- return fmt.Sprintf("%x", n[:])
-}
-
-// GoString returns the Go syntax representation of a ID is a call to HexID.
-func (n ID) GoString() string {
- return fmt.Sprintf("enode.HexID(\"%x\")", n[:])
-}
-
-// TerminalString returns a shortened hex string for terminal logging.
-func (n ID) TerminalString() string {
- return hex.EncodeToString(n[:8])
-}
-
-// MarshalText implements the encoding.TextMarshaler interface.
-func (n ID) MarshalText() ([]byte, error) {
- return []byte(hex.EncodeToString(n[:])), nil
-}
-
-// UnmarshalText implements the encoding.TextUnmarshaler interface.
-func (n *ID) UnmarshalText(text []byte) error {
- id, err := ParseID(string(text))
- if err != nil {
- return err
- }
- *n = id
- return nil
-}
-
-// HexID converts a hex string to an ID.
-// The string may be prefixed with 0x.
-// It panics if the string is not a valid ID.
-func HexID(in string) ID {
- id, err := ParseID(in)
- if err != nil {
- panic(err)
- }
- return id
-}
-
-func ParseID(in string) (ID, error) {
- var id ID
- b, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
- if err != nil {
- return id, err
- } else if len(b) != len(id) {
- return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2)
- }
- copy(id[:], b)
- return id, nil
-}
-
-// DistCmp compares the distances a->target and b->target.
-// Returns -1 if a is closer to target, 1 if b is closer to target
-// and 0 if they are equal.
-func DistCmp(target, a, b ID) int {
- for i := range target {
- da := a[i] ^ target[i]
- db := b[i] ^ target[i]
- if da > db {
- return 1
- } else if da < db {
- return -1
- }
- }
- return 0
-}
-
-// LogDist returns the logarithmic distance between a and b, log2(a ^ b).
-func LogDist(a, b ID) int {
- lz := 0
- for i := range a {
- x := a[i] ^ b[i]
- if x == 0 {
- lz += 8
- } else {
- lz += bits.LeadingZeros8(x)
- break
- }
- }
- return len(a)*8 - lz
-}
diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go
deleted file mode 100644
index d15859c477..0000000000
--- a/p2p/enode/node_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "bytes"
- "encoding/hex"
- "fmt"
- "math/big"
- "testing"
- "testing/quick"
-
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/stretchr/testify/assert"
-)
-
-var pyRecord, _ = hex.DecodeString("f884b8407098ad865b00a582051940cb9cf36836572411a47278783077011599ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1145ccb9c01826964827634826970847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31388375647082765f")
-
-// TestPythonInterop checks that we can decode and verify a record produced by the Python
-// implementation.
-func TestPythonInterop(t *testing.T) {
- var r enr.Record
- if err := rlp.DecodeBytes(pyRecord, &r); err != nil {
- t.Fatalf("can't decode: %v", err)
- }
- n, err := New(ValidSchemes, &r)
- if err != nil {
- t.Fatalf("can't verify record: %v", err)
- }
-
- var (
- wantID = HexID("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
- wantSeq = uint64(1)
- wantIP = enr.IPv4{127, 0, 0, 1}
- wantUDP = enr.UDP(30303)
- )
- if n.Seq() != wantSeq {
- t.Errorf("wrong seq: got %d, want %d", n.Seq(), wantSeq)
- }
- if n.ID() != wantID {
- t.Errorf("wrong id: got %x, want %x", n.ID(), wantID)
- }
- want := map[enr.Entry]interface{}{new(enr.IPv4): &wantIP, new(enr.UDP): &wantUDP}
- for k, v := range want {
- desc := fmt.Sprintf("loading key %q", k.ENRKey())
- if assert.NoError(t, n.Load(k), desc) {
- assert.Equal(t, k, v, desc)
- }
- }
-}
-
-func TestHexID(t *testing.T) {
- ref := ID{0, 0, 0, 0, 0, 0, 0, 128, 106, 217, 182, 31, 165, 174, 1, 67, 7, 235, 220, 150, 66, 83, 173, 205, 159, 44, 10, 57, 42, 161, 26, 188}
- id1 := HexID("0x00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
- id2 := HexID("00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
-
- if id1 != ref {
- t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:])
- }
- if id2 != ref {
- t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:])
- }
-}
-
-func TestID_textEncoding(t *testing.T) {
- ref := ID{
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
- 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20,
- 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30,
- 0x31, 0x32,
- }
- hex := "0102030405060708091011121314151617181920212223242526272829303132"
-
- text, err := ref.MarshalText()
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(text, []byte(hex)) {
- t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text)
- }
-
- id := new(ID)
- if err := id.UnmarshalText(text); err != nil {
- t.Fatal(err)
- }
- if *id != ref {
- t.Fatalf("text decoding did not match\nexpected: %s\ngot: %s", ref, id)
- }
-}
-
-func TestID_distcmp(t *testing.T) {
- distcmpBig := func(target, a, b ID) int {
- tbig := new(big.Int).SetBytes(target[:])
- abig := new(big.Int).SetBytes(a[:])
- bbig := new(big.Int).SetBytes(b[:])
- return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig))
- }
- if err := quick.CheckEqual(DistCmp, distcmpBig, nil); err != nil {
- t.Error(err)
- }
-}
-
-// The random tests is likely to miss the case where a and b are equal,
-// this test checks it explicitly.
-func TestID_distcmpEqual(t *testing.T) {
- base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
- x := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
- if DistCmp(base, x, x) != 0 {
- t.Errorf("DistCmp(base, x, x) != 0")
- }
-}
-
-func TestID_logdist(t *testing.T) {
- logdistBig := func(a, b ID) int {
- abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:])
- return new(big.Int).Xor(abig, bbig).BitLen()
- }
- if err := quick.CheckEqual(LogDist, logdistBig, nil); err != nil {
- t.Error(err)
- }
-}
-
-// The random tests is likely to miss the case where a and b are equal,
-// this test checks it explicitly.
-func TestID_logdistEqual(t *testing.T) {
- x := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
- if LogDist(x, x) != 0 {
- t.Errorf("LogDist(x, x) != 0")
- }
-}
diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go
deleted file mode 100644
index 7e7fb69b29..0000000000
--- a/p2p/enode/nodedb.go
+++ /dev/null
@@ -1,501 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "bytes"
- "crypto/rand"
- "encoding/binary"
- "fmt"
- "net"
- "os"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/syndtr/goleveldb/leveldb"
- "github.com/syndtr/goleveldb/leveldb/errors"
- "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/opt"
- "github.com/syndtr/goleveldb/leveldb/storage"
- "github.com/syndtr/goleveldb/leveldb/util"
-)
-
-// Keys in the node database.
-const (
- dbVersionKey = "version" // Version of the database to flush if changes
- dbNodePrefix = "n:" // Identifier to prefix node entries with
- dbLocalPrefix = "local:"
- dbDiscoverRoot = "v4"
- dbDiscv5Root = "v5"
-
- // These fields are stored per ID and IP, the full key is "n::v4::findfail".
- // Use nodeItemKey to create those keys.
- dbNodeFindFails = "findfail"
- dbNodePing = "lastping"
- dbNodePong = "lastpong"
- dbNodeSeq = "seq"
-
- // Local information is keyed by ID only, the full key is "local::seq".
- // Use localItemKey to create those keys.
- dbLocalSeq = "seq"
-)
-
-const (
- dbNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
- dbCleanupCycle = time.Hour // Time period for running the expiration task.
- dbVersion = 9
-)
-
-var (
- errInvalidIP = errors.New("invalid IP")
-)
-
-var zeroIP = make(net.IP, 16)
-
-// DB is the node database, storing previously seen nodes and any collected metadata about
-// them for QoS purposes.
-type DB struct {
- lvl *leveldb.DB // Interface to the database itself
- runner sync.Once // Ensures we can start at most one expirer
- quit chan struct{} // Channel to signal the expiring thread to stop
-}
-
-// OpenDB opens a node database for storing and retrieving infos about known peers in the
-// network. If no path is given an in-memory, temporary database is constructed.
-func OpenDB(path string) (*DB, error) {
- if path == "" {
- return newMemoryDB()
- }
- return newPersistentDB(path)
-}
-
-// newMemoryNodeDB creates a new in-memory node database without a persistent backend.
-func newMemoryDB() (*DB, error) {
- db, err := leveldb.Open(storage.NewMemStorage(), nil)
- if err != nil {
- return nil, err
- }
- return &DB{lvl: db, quit: make(chan struct{})}, nil
-}
-
-// newPersistentNodeDB creates/opens a leveldb backed persistent node database,
-// also flushing its contents in case of a version mismatch.
-func newPersistentDB(path string) (*DB, error) {
- opts := &opt.Options{OpenFilesCacheCapacity: 5}
- db, err := leveldb.OpenFile(path, opts)
- if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
- db, err = leveldb.RecoverFile(path, nil)
- }
- if err != nil {
- return nil, err
- }
- // The nodes contained in the cache correspond to a certain protocol version.
- // Flush all nodes if the version doesn't match.
- currentVer := make([]byte, binary.MaxVarintLen64)
- currentVer = currentVer[:binary.PutVarint(currentVer, int64(dbVersion))]
-
- blob, err := db.Get([]byte(dbVersionKey), nil)
- switch err {
- case leveldb.ErrNotFound:
- // Version not found (i.e. empty cache), insert it
- if err := db.Put([]byte(dbVersionKey), currentVer, nil); err != nil {
- db.Close()
- return nil, err
- }
-
- case nil:
- // Version present, flush if different
- if !bytes.Equal(blob, currentVer) {
- db.Close()
- if err = os.RemoveAll(path); err != nil {
- return nil, err
- }
- return newPersistentDB(path)
- }
- }
- return &DB{lvl: db, quit: make(chan struct{})}, nil
-}
-
-// nodeKey returns the database key for a node record.
-func nodeKey(id ID) []byte {
- key := append([]byte(dbNodePrefix), id[:]...)
- key = append(key, ':')
- key = append(key, dbDiscoverRoot...)
- return key
-}
-
-// splitNodeKey returns the node ID of a key created by nodeKey.
-func splitNodeKey(key []byte) (id ID, rest []byte) {
- if !bytes.HasPrefix(key, []byte(dbNodePrefix)) {
- return ID{}, nil
- }
- item := key[len(dbNodePrefix):]
- copy(id[:], item[:len(id)])
- return id, item[len(id)+1:]
-}
-
-// nodeItemKey returns the database key for a node metadata field.
-func nodeItemKey(id ID, ip net.IP, field string) []byte {
- ip16 := ip.To16()
- if ip16 == nil {
- panic(fmt.Errorf("invalid IP (length %d)", len(ip)))
- }
- return bytes.Join([][]byte{nodeKey(id), ip16, []byte(field)}, []byte{':'})
-}
-
-// splitNodeItemKey returns the components of a key created by nodeItemKey.
-func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) {
- id, key = splitNodeKey(key)
- // Skip discover root.
- if string(key) == dbDiscoverRoot {
- return id, nil, ""
- }
- key = key[len(dbDiscoverRoot)+1:]
- // Split out the IP.
- ip = key[:16]
- if ip4 := ip.To4(); ip4 != nil {
- ip = ip4
- }
- key = key[16+1:]
- // Field is the remainder of key.
- field = string(key)
- return id, ip, field
-}
-
-func v5Key(id ID, ip net.IP, field string) []byte {
- return bytes.Join([][]byte{
- []byte(dbNodePrefix),
- id[:],
- []byte(dbDiscv5Root),
- ip.To16(),
- []byte(field),
- }, []byte{':'})
-}
-
-// localItemKey returns the key of a local node item.
-func localItemKey(id ID, field string) []byte {
- key := append([]byte(dbLocalPrefix), id[:]...)
- key = append(key, ':')
- key = append(key, field...)
- return key
-}
-
-// fetchInt64 retrieves an integer associated with a particular key.
-func (db *DB) fetchInt64(key []byte) int64 {
- blob, err := db.lvl.Get(key, nil)
- if err != nil {
- return 0
- }
- val, read := binary.Varint(blob)
- if read <= 0 {
- return 0
- }
- return val
-}
-
-// storeInt64 stores an integer in the given key.
-func (db *DB) storeInt64(key []byte, n int64) error {
- blob := make([]byte, binary.MaxVarintLen64)
- blob = blob[:binary.PutVarint(blob, n)]
- return db.lvl.Put(key, blob, nil)
-}
-
-// fetchUint64 retrieves an integer associated with a particular key.
-func (db *DB) fetchUint64(key []byte) uint64 {
- blob, err := db.lvl.Get(key, nil)
- if err != nil {
- return 0
- }
- val, _ := binary.Uvarint(blob)
- return val
-}
-
-// storeUint64 stores an integer in the given key.
-func (db *DB) storeUint64(key []byte, n uint64) error {
- blob := make([]byte, binary.MaxVarintLen64)
- blob = blob[:binary.PutUvarint(blob, n)]
- return db.lvl.Put(key, blob, nil)
-}
-
-// Node retrieves a node with a given id from the database.
-func (db *DB) Node(id ID) *Node {
- blob, err := db.lvl.Get(nodeKey(id), nil)
- if err != nil {
- return nil
- }
- return mustDecodeNode(id[:], blob)
-}
-
-func mustDecodeNode(id, data []byte) *Node {
- node := new(Node)
- if err := rlp.DecodeBytes(data, &node.r); err != nil {
- panic(fmt.Errorf("p2p/enode: can't decode node %x in DB: %v", id, err))
- }
- // Restore node id cache.
- copy(node.id[:], id)
- return node
-}
-
-// UpdateNode inserts - potentially overwriting - a node into the peer database.
-func (db *DB) UpdateNode(node *Node) error {
- if node.Seq() < db.NodeSeq(node.ID()) {
- return nil
- }
- blob, err := rlp.EncodeToBytes(&node.r)
- if err != nil {
- return err
- }
- if err := db.lvl.Put(nodeKey(node.ID()), blob, nil); err != nil {
- return err
- }
- return db.storeUint64(nodeItemKey(node.ID(), zeroIP, dbNodeSeq), node.Seq())
-}
-
-// NodeSeq returns the stored record sequence number of the given node.
-func (db *DB) NodeSeq(id ID) uint64 {
- return db.fetchUint64(nodeItemKey(id, zeroIP, dbNodeSeq))
-}
-
-// Resolve returns the stored record of the node if it has a larger sequence
-// number than n.
-func (db *DB) Resolve(n *Node) *Node {
- if n.Seq() > db.NodeSeq(n.ID()) {
- return n
- }
- return db.Node(n.ID())
-}
-
-// DeleteNode deletes all information associated with a node.
-func (db *DB) DeleteNode(id ID) {
- deleteRange(db.lvl, nodeKey(id))
-}
-
-func deleteRange(db *leveldb.DB, prefix []byte) {
- it := db.NewIterator(util.BytesPrefix(prefix), nil)
- defer it.Release()
- for it.Next() {
- db.Delete(it.Key(), nil)
- }
-}
-
-// ensureExpirer is a small helper method ensuring that the data expiration
-// mechanism is running. If the expiration goroutine is already running, this
-// method simply returns.
-//
-// The goal is to start the data evacuation only after the network successfully
-// bootstrapped itself (to prevent dumping potentially useful seed nodes). Since
-// it would require significant overhead to exactly trace the first successful
-// convergence, it's simpler to "ensure" the correct state when an appropriate
-// condition occurs (i.e. a successful bonding), and discard further events.
-func (db *DB) ensureExpirer() {
- db.runner.Do(func() { go db.expirer() })
-}
-
-// expirer should be started in a go routine, and is responsible for looping ad
-// infinitum and dropping stale data from the database.
-func (db *DB) expirer() {
- tick := time.NewTicker(dbCleanupCycle)
- defer tick.Stop()
- for {
- select {
- case <-tick.C:
- db.expireNodes()
- case <-db.quit:
- return
- }
- }
-}
-
-// expireNodes iterates over the database and deletes all nodes that have not
-// been seen (i.e. received a pong from) for some time.
-func (db *DB) expireNodes() {
- it := db.lvl.NewIterator(util.BytesPrefix([]byte(dbNodePrefix)), nil)
- defer it.Release()
- if !it.Next() {
- return
- }
-
- var (
- threshold = time.Now().Add(-dbNodeExpiration).Unix()
- youngestPong int64
- atEnd = false
- )
- for !atEnd {
- id, ip, field := splitNodeItemKey(it.Key())
- if field == dbNodePong {
- time, _ := binary.Varint(it.Value())
- if time > youngestPong {
- youngestPong = time
- }
- if time < threshold {
- // Last pong from this IP older than threshold, remove fields belonging to it.
- deleteRange(db.lvl, nodeItemKey(id, ip, ""))
- }
- }
- atEnd = !it.Next()
- nextID, _ := splitNodeKey(it.Key())
- if atEnd || nextID != id {
- // We've moved beyond the last entry of the current ID.
- // Remove everything if there was no recent enough pong.
- if youngestPong > 0 && youngestPong < threshold {
- deleteRange(db.lvl, nodeKey(id))
- }
- youngestPong = 0
- }
- }
-}
-
-// LastPingReceived retrieves the time of the last ping packet received from
-// a remote node.
-func (db *DB) LastPingReceived(id ID, ip net.IP) time.Time {
- if ip = ip.To16(); ip == nil {
- return time.Time{}
- }
- return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePing)), 0)
-}
-
-// UpdateLastPingReceived updates the last time we tried contacting a remote node.
-func (db *DB) UpdateLastPingReceived(id ID, ip net.IP, instance time.Time) error {
- if ip = ip.To16(); ip == nil {
- return errInvalidIP
- }
- return db.storeInt64(nodeItemKey(id, ip, dbNodePing), instance.Unix())
-}
-
-// LastPongReceived retrieves the time of the last successful pong from remote node.
-func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time {
- if ip = ip.To16(); ip == nil {
- return time.Time{}
- }
- // Launch expirer
- db.ensureExpirer()
- return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePong)), 0)
-}
-
-// UpdateLastPongReceived updates the last pong time of a node.
-func (db *DB) UpdateLastPongReceived(id ID, ip net.IP, instance time.Time) error {
- if ip = ip.To16(); ip == nil {
- return errInvalidIP
- }
- return db.storeInt64(nodeItemKey(id, ip, dbNodePong), instance.Unix())
-}
-
-// FindFails retrieves the number of findnode failures since bonding.
-func (db *DB) FindFails(id ID, ip net.IP) int {
- if ip = ip.To16(); ip == nil {
- return 0
- }
- return int(db.fetchInt64(nodeItemKey(id, ip, dbNodeFindFails)))
-}
-
-// UpdateFindFails updates the number of findnode failures since bonding.
-func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error {
- if ip = ip.To16(); ip == nil {
- return errInvalidIP
- }
- return db.storeInt64(nodeItemKey(id, ip, dbNodeFindFails), int64(fails))
-}
-
-// FindFailsV5 retrieves the discv5 findnode failure counter.
-func (db *DB) FindFailsV5(id ID, ip net.IP) int {
- if ip = ip.To16(); ip == nil {
- return 0
- }
- return int(db.fetchInt64(v5Key(id, ip, dbNodeFindFails)))
-}
-
-// UpdateFindFailsV5 stores the discv5 findnode failure counter.
-func (db *DB) UpdateFindFailsV5(id ID, ip net.IP, fails int) error {
- if ip = ip.To16(); ip == nil {
- return errInvalidIP
- }
- return db.storeInt64(v5Key(id, ip, dbNodeFindFails), int64(fails))
-}
-
-// localSeq retrieves the local record sequence counter, defaulting to the current
-// timestamp if no previous exists. This ensures that wiping all data associated
-// with a node (apart from its key) will not generate already used sequence nums.
-func (db *DB) localSeq(id ID) uint64 {
- if seq := db.fetchUint64(localItemKey(id, dbLocalSeq)); seq > 0 {
- return seq
- }
- return nowMilliseconds()
-}
-
-// storeLocalSeq stores the local record sequence counter.
-func (db *DB) storeLocalSeq(id ID, n uint64) {
- db.storeUint64(localItemKey(id, dbLocalSeq), n)
-}
-
-// QuerySeeds retrieves random nodes to be used as potential seed nodes
-// for bootstrapping.
-func (db *DB) QuerySeeds(n int, maxAge time.Duration) []*Node {
- var (
- now = time.Now()
- nodes = make([]*Node, 0, n)
- it = db.lvl.NewIterator(nil, nil)
- id ID
- )
- defer it.Release()
-
-seek:
- for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ {
- // Seek to a random entry. The first byte is incremented by a
- // random amount each time in order to increase the likelihood
- // of hitting all existing nodes in very small databases.
- ctr := id[0]
- rand.Read(id[:])
- id[0] = ctr + id[0]%16
- it.Seek(nodeKey(id))
-
- n := nextNode(it)
- if n == nil {
- id[0] = 0
- continue seek // iterator exhausted
- }
- if now.Sub(db.LastPongReceived(n.ID(), n.IP())) > maxAge {
- continue seek
- }
- for i := range nodes {
- if nodes[i].ID() == n.ID() {
- continue seek // duplicate
- }
- }
- nodes = append(nodes, n)
- }
- return nodes
-}
-
-// reads the next node record from the iterator, skipping over other
-// database entries.
-func nextNode(it iterator.Iterator) *Node {
- for end := false; !end; end = !it.Next() {
- id, rest := splitNodeKey(it.Key())
- if string(rest) != dbDiscoverRoot {
- continue
- }
- return mustDecodeNode(id[:], it.Value())
- }
- return nil
-}
-
-// Close flushes and closes the database files.
-func (db *DB) Close() {
- close(db.quit)
- db.lvl.Close()
-}
diff --git a/p2p/enode/nodedb_test.go b/p2p/enode/nodedb_test.go
deleted file mode 100644
index 38764f31b1..0000000000
--- a/p2p/enode/nodedb_test.go
+++ /dev/null
@@ -1,469 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "bytes"
- "fmt"
- "net"
- "path/filepath"
- "reflect"
- "testing"
- "time"
-)
-
-var keytestID = HexID("51232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439")
-
-func TestDBNodeKey(t *testing.T) {
- enc := nodeKey(keytestID)
- want := []byte{
- 'n', ':',
- 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id
- 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, //
- 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, //
- 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, //
- ':', 'v', '4',
- }
- if !bytes.Equal(enc, want) {
- t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want)
- }
- id, _ := splitNodeKey(enc)
- if id != keytestID {
- t.Errorf("wrong ID from splitNodeKey")
- }
-}
-
-func TestDBNodeItemKey(t *testing.T) {
- wantIP := net.IP{127, 0, 0, 3}
- wantField := "foobar"
- enc := nodeItemKey(keytestID, wantIP, wantField)
- want := []byte{
- 'n', ':',
- 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id
- 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, //
- 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, //
- 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, //
- ':', 'v', '4', ':',
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IP
- 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x03, //
- ':', 'f', 'o', 'o', 'b', 'a', 'r',
- }
- if !bytes.Equal(enc, want) {
- t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want)
- }
- id, ip, field := splitNodeItemKey(enc)
- if id != keytestID {
- t.Errorf("splitNodeItemKey returned wrong ID: %v", id)
- }
- if !ip.Equal(wantIP) {
- t.Errorf("splitNodeItemKey returned wrong IP: %v", ip)
- }
- if field != wantField {
- t.Errorf("splitNodeItemKey returned wrong field: %q", field)
- }
-}
-
-var nodeDBInt64Tests = []struct {
- key []byte
- value int64
-}{
- {key: []byte{0x01}, value: 1},
- {key: []byte{0x02}, value: 2},
- {key: []byte{0x03}, value: 3},
-}
-
-func TestDBInt64(t *testing.T) {
- db, _ := OpenDB("")
- defer db.Close()
-
- tests := nodeDBInt64Tests
- for i := 0; i < len(tests); i++ {
- // Insert the next value
- if err := db.storeInt64(tests[i].key, tests[i].value); err != nil {
- t.Errorf("test %d: failed to store value: %v", i, err)
- }
- // Check all existing and non existing values
- for j := 0; j < len(tests); j++ {
- num := db.fetchInt64(tests[j].key)
- switch {
- case j <= i && num != tests[j].value:
- t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, tests[j].value)
- case j > i && num != 0:
- t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, 0)
- }
- }
- }
-}
-
-func TestDBFetchStore(t *testing.T) {
- node := NewV4(
- hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
- net.IP{192, 168, 0, 1},
- 30303,
- 30303,
- )
- inst := time.Now()
- num := 314
-
- db, _ := OpenDB("")
- defer db.Close()
-
- // Check fetch/store operations on a node ping object
- if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != 0 {
- t.Errorf("ping: non-existing object: %v", stored)
- }
- if err := db.UpdateLastPingReceived(node.ID(), node.IP(), inst); err != nil {
- t.Errorf("ping: failed to update: %v", err)
- }
- if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() {
- t.Errorf("ping: value mismatch: have %v, want %v", stored, inst)
- }
- // Check fetch/store operations on a node pong object
- if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != 0 {
- t.Errorf("pong: non-existing object: %v", stored)
- }
- if err := db.UpdateLastPongReceived(node.ID(), node.IP(), inst); err != nil {
- t.Errorf("pong: failed to update: %v", err)
- }
- if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() {
- t.Errorf("pong: value mismatch: have %v, want %v", stored, inst)
- }
- // Check fetch/store operations on a node findnode-failure object
- if stored := db.FindFails(node.ID(), node.IP()); stored != 0 {
- t.Errorf("find-node fails: non-existing object: %v", stored)
- }
- if err := db.UpdateFindFails(node.ID(), node.IP(), num); err != nil {
- t.Errorf("find-node fails: failed to update: %v", err)
- }
- if stored := db.FindFails(node.ID(), node.IP()); stored != num {
- t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num)
- }
- // Check fetch/store operations on an actual node object
- if stored := db.Node(node.ID()); stored != nil {
- t.Errorf("node: non-existing object: %v", stored)
- }
- if err := db.UpdateNode(node); err != nil {
- t.Errorf("node: failed to update: %v", err)
- }
- if stored := db.Node(node.ID()); stored == nil {
- t.Errorf("node: not found")
- } else if !reflect.DeepEqual(stored, node) {
- t.Errorf("node: data mismatch: have %v, want %v", stored, node)
- }
-}
-
-var nodeDBSeedQueryNodes = []struct {
- node *Node
- pong time.Time
-}{
- // This one should not be in the result set because its last
- // pong time is too far in the past.
- {
- node: NewV4(
- hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
- net.IP{127, 0, 0, 3},
- 30303,
- 30303,
- ),
- pong: time.Now().Add(-3 * time.Hour),
- },
- // This one shouldn't be in the result set because its
- // nodeID is the local node's ID.
- {
- node: NewV4(
- hexPubkey("ff93ff820abacd4351b0f14e47b324bc82ff014c226f3f66a53535734a3c150e7e38ca03ef0964ba55acddc768f5e99cd59dea95ddd4defbab1339c92fa319b2"),
- net.IP{127, 0, 0, 3},
- 30303,
- 30303,
- ),
- pong: time.Now().Add(-4 * time.Second),
- },
-
- // These should be in the result set.
- {
- node: NewV4(
- hexPubkey("c2b5eb3f5dde05f815b63777809ee3e7e0cbb20035a6b00ce327191e6eaa8f26a8d461c9112b7ab94698e7361fa19fd647e603e73239002946d76085b6f928d6"),
- net.IP{127, 0, 0, 1},
- 30303,
- 30303,
- ),
- pong: time.Now().Add(-2 * time.Second),
- },
- {
- node: NewV4(
- hexPubkey("6ca1d400c8ddf8acc94bcb0dd254911ad71a57bed5e0ae5aa205beed59b28c2339908e97990c493499613cff8ecf6c3dc7112a8ead220cdcd00d8847ca3db755"),
- net.IP{127, 0, 0, 2},
- 30303,
- 30303,
- ),
- pong: time.Now().Add(-3 * time.Second),
- },
- {
- node: NewV4(
- hexPubkey("234dc63fe4d131212b38236c4c3411288d7bec61cbf7b120ff12c43dc60c96182882f4291d209db66f8a38e986c9c010ff59231a67f9515c7d1668b86b221a47"),
- net.IP{127, 0, 0, 3},
- 30303,
- 30303,
- ),
- pong: time.Now().Add(-1 * time.Second),
- },
- {
- node: NewV4(
- hexPubkey("c013a50b4d1ebce5c377d8af8cb7114fd933ffc9627f96ad56d90fef5b7253ec736fd07ef9a81dc2955a997e54b7bf50afd0aa9f110595e2bec5bb7ce1657004"),
- net.IP{127, 0, 0, 3},
- 30303,
- 30303,
- ),
- pong: time.Now().Add(-2 * time.Second),
- },
- {
- node: NewV4(
- hexPubkey("f141087e3e08af1aeec261ff75f48b5b1637f594ea9ad670e50051646b0416daa3b134c28788cbe98af26992a47652889cd8577ccc108ac02c6a664db2dc1283"),
- net.IP{127, 0, 0, 3},
- 30303,
- 30303,
- ),
- pong: time.Now().Add(-2 * time.Second),
- },
-}
-
-func TestDBSeedQuery(t *testing.T) {
- // Querying seeds uses seeks an might not find all nodes
- // every time when the database is small. Run the test multiple
- // times to avoid flakes.
- const attempts = 15
- var err error
- for i := 0; i < attempts; i++ {
- if err = testSeedQuery(); err == nil {
- return
- }
- }
- if err != nil {
- t.Errorf("no successful run in %d attempts: %v", attempts, err)
- }
-}
-
-func testSeedQuery() error {
- db, _ := OpenDB("")
- defer db.Close()
-
- // Insert a batch of nodes for querying
- for i, seed := range nodeDBSeedQueryNodes {
- if err := db.UpdateNode(seed.node); err != nil {
- return fmt.Errorf("node %d: failed to insert: %v", i, err)
- }
- if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil {
- return fmt.Errorf("node %d: failed to insert bondTime: %v", i, err)
- }
- }
-
- // Retrieve the entire batch and check for duplicates
- seeds := db.QuerySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour)
- have := make(map[ID]struct{}, len(seeds))
- for _, seed := range seeds {
- have[seed.ID()] = struct{}{}
- }
- want := make(map[ID]struct{}, len(nodeDBSeedQueryNodes[1:]))
- for _, seed := range nodeDBSeedQueryNodes[1:] {
- want[seed.node.ID()] = struct{}{}
- }
- if len(seeds) != len(want) {
- return fmt.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want))
- }
- for id := range have {
- if _, ok := want[id]; !ok {
- return fmt.Errorf("extra seed: %v", id)
- }
- }
- for id := range want {
- if _, ok := have[id]; !ok {
- return fmt.Errorf("missing seed: %v", id)
- }
- }
- return nil
-}
-
-func TestDBPersistency(t *testing.T) {
- root := t.TempDir()
-
- var (
- testKey = []byte("somekey")
- testInt = int64(314)
- )
-
- // Create a persistent database and store some values
- db, err := OpenDB(filepath.Join(root, "database"))
- if err != nil {
- t.Fatalf("failed to create persistent database: %v", err)
- }
- if err := db.storeInt64(testKey, testInt); err != nil {
- t.Fatalf("failed to store value: %v.", err)
- }
- db.Close()
-
- // Reopen the database and check the value
- db, err = OpenDB(filepath.Join(root, "database"))
- if err != nil {
- t.Fatalf("failed to open persistent database: %v", err)
- }
- if val := db.fetchInt64(testKey); val != testInt {
- t.Fatalf("value mismatch: have %v, want %v", val, testInt)
- }
- db.Close()
-}
-
-var nodeDBExpirationNodes = []struct {
- node *Node
- pong time.Time
- storeNode bool
- exp bool
-}{
- // Node has new enough pong time and isn't expired:
- {
- node: NewV4(
- hexPubkey("8d110e2ed4b446d9b5fb50f117e5f37fb7597af455e1dab0e6f045a6eeaa786a6781141659020d38bdc5e698ed3d4d2bafa8b5061810dfa63e8ac038db2e9b67"),
- net.IP{127, 0, 0, 1},
- 30303,
- 30303,
- ),
- storeNode: true,
- pong: time.Now().Add(-dbNodeExpiration + time.Minute),
- exp: false,
- },
- // Node with pong time before expiration is removed:
- {
- node: NewV4(
- hexPubkey("913a205579c32425b220dfba999d215066e5bdbf900226b11da1907eae5e93eb40616d47412cf819664e9eacbdfcca6b0c6e07e09847a38472d4be46ab0c3672"),
- net.IP{127, 0, 0, 2},
- 30303,
- 30303,
- ),
- storeNode: true,
- pong: time.Now().Add(-dbNodeExpiration - time.Minute),
- exp: true,
- },
- // Just pong time, no node stored:
- {
- node: NewV4(
- hexPubkey("b56670e0b6bad2c5dab9f9fe6f061a16cf78d68b6ae2cfda3144262d08d97ce5f46fd8799b6d1f709b1abe718f2863e224488bd7518e5e3b43809ac9bd1138ca"),
- net.IP{127, 0, 0, 3},
- 30303,
- 30303,
- ),
- storeNode: false,
- pong: time.Now().Add(-dbNodeExpiration - time.Minute),
- exp: true,
- },
- // Node with multiple pong times, all older than expiration.
- {
- node: NewV4(
- hexPubkey("29f619cebfd32c9eab34aec797ed5e3fe15b9b45be95b4df3f5fe6a9ae892f433eb08d7698b2ef3621568b0fb70d57b515ab30d4e72583b798298e0f0a66b9d1"),
- net.IP{127, 0, 0, 4},
- 30303,
- 30303,
- ),
- storeNode: true,
- pong: time.Now().Add(-dbNodeExpiration - time.Minute),
- exp: true,
- },
- {
- node: NewV4(
- hexPubkey("29f619cebfd32c9eab34aec797ed5e3fe15b9b45be95b4df3f5fe6a9ae892f433eb08d7698b2ef3621568b0fb70d57b515ab30d4e72583b798298e0f0a66b9d1"),
- net.IP{127, 0, 0, 5},
- 30303,
- 30303,
- ),
- storeNode: false,
- pong: time.Now().Add(-dbNodeExpiration - 2*time.Minute),
- exp: true,
- },
- // Node with multiple pong times, one newer, one older than expiration.
- {
- node: NewV4(
- hexPubkey("3b73a9e5f4af6c4701c57c73cc8cfa0f4802840b24c11eba92aac3aef65644a3728b4b2aec8199f6d72bd66be2c65861c773129039bd47daa091ca90a6d4c857"),
- net.IP{127, 0, 0, 6},
- 30303,
- 30303,
- ),
- storeNode: true,
- pong: time.Now().Add(-dbNodeExpiration + time.Minute),
- exp: false,
- },
- {
- node: NewV4(
- hexPubkey("3b73a9e5f4af6c4701c57c73cc8cfa0f4802840b24c11eba92aac3aef65644a3728b4b2aec8199f6d72bd66be2c65861c773129039bd47daa091ca90a6d4c857"),
- net.IP{127, 0, 0, 7},
- 30303,
- 30303,
- ),
- storeNode: false,
- pong: time.Now().Add(-dbNodeExpiration - time.Minute),
- exp: true,
- },
-}
-
-func TestDBExpiration(t *testing.T) {
- db, _ := OpenDB("")
- defer db.Close()
-
- // Add all the test nodes and set their last pong time.
- for i, seed := range nodeDBExpirationNodes {
- if seed.storeNode {
- if err := db.UpdateNode(seed.node); err != nil {
- t.Fatalf("node %d: failed to insert: %v", i, err)
- }
- }
- if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil {
- t.Fatalf("node %d: failed to update bondTime: %v", i, err)
- }
- }
-
- db.expireNodes()
-
- // Check that expired entries have been removed.
- unixZeroTime := time.Unix(0, 0)
- for i, seed := range nodeDBExpirationNodes {
- node := db.Node(seed.node.ID())
- pong := db.LastPongReceived(seed.node.ID(), seed.node.IP())
- if seed.exp {
- if seed.storeNode && node != nil {
- t.Errorf("node %d (%s) shouldn't be present after expiration", i, seed.node.ID().TerminalString())
- }
- if !pong.Equal(unixZeroTime) {
- t.Errorf("pong time %d (%s %v) shouldn't be present after expiration", i, seed.node.ID().TerminalString(), seed.node.IP())
- }
- } else {
- if seed.storeNode && node == nil {
- t.Errorf("node %d (%s) should be present after expiration", i, seed.node.ID().TerminalString())
- }
- if !pong.Equal(seed.pong.Truncate(1 * time.Second)) {
- t.Errorf("pong time %d (%s) should be %v after expiration, but is %v", i, seed.node.ID().TerminalString(), seed.pong, pong)
- }
- }
- }
-}
-
-// This test checks that expiration works when discovery v5 data is present
-// in the database.
-func TestDBExpireV5(t *testing.T) {
- db, _ := OpenDB("")
- defer db.Close()
-
- ip := net.IP{127, 0, 0, 1}
- db.UpdateFindFailsV5(ID{}, ip, 4)
- db.expireNodes()
-}
diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go
deleted file mode 100644
index 0272eee987..0000000000
--- a/p2p/enode/urlv4.go
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "crypto/ecdsa"
- "encoding/hex"
- "errors"
- "fmt"
- "net"
- "net/url"
- "regexp"
- "strconv"
-
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enr"
-)
-
-var (
- incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
- lookupIPFunc = net.LookupIP
-)
-
-// MustParseV4 parses a node URL. It panics if the URL is not valid.
-func MustParseV4(rawurl string) *Node {
- n, err := ParseV4(rawurl)
- if err != nil {
- panic("invalid node URL: " + err.Error())
- }
- return n
-}
-
-// ParseV4 parses a node URL.
-//
-// There are two basic forms of node URLs:
-//
-// - incomplete nodes, which only have the public key (node ID)
-// - complete nodes, which contain the public key and IP/Port information
-//
-// For incomplete nodes, the designator must look like one of these
-//
-// enode://
-//
-//
-// For complete nodes, the node ID is encoded in the username portion
-// of the URL, separated from the host by an @ sign. The hostname can
-// only be given as an IP address or using DNS domain name.
-// The port in the host name section is the TCP listening port. If the
-// TCP and UDP (discovery) ports differ, the UDP port is specified as
-// query parameter "discport".
-//
-// In the following example, the node URL describes
-// a node with IP address 10.3.58.6, TCP listening port 30303
-// and UDP discovery port 30301.
-//
-// enode://@10.3.58.6:30303?discport=30301
-func ParseV4(rawurl string) (*Node, error) {
- if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
- id, err := parsePubkey(m[1])
- if err != nil {
- return nil, fmt.Errorf("invalid public key (%v)", err)
- }
- return NewV4(id, nil, 0, 0), nil
- }
- return parseComplete(rawurl)
-}
-
-// NewV4 creates a node from discovery v4 node information. The record
-// contained in the node has a zero-length signature.
-func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node {
- var r enr.Record
- if len(ip) > 0 {
- r.Set(enr.IP(ip))
- }
- if udp != 0 {
- r.Set(enr.UDP(udp))
- }
- if tcp != 0 {
- r.Set(enr.TCP(tcp))
- }
- signV4Compat(&r, pubkey)
- n, err := New(v4CompatID{}, &r)
- if err != nil {
- panic(err)
- }
- return n
-}
-
-// isNewV4 returns true for nodes created by NewV4.
-func isNewV4(n *Node) bool {
- var k s256raw
- return n.r.IdentityScheme() == "" && n.r.Load(&k) == nil && len(n.r.Signature()) == 0
-}
-
-func parseComplete(rawurl string) (*Node, error) {
- var (
- id *ecdsa.PublicKey
- tcpPort, udpPort uint64
- )
- u, err := url.Parse(rawurl)
- if err != nil {
- return nil, err
- }
- if u.Scheme != "enode" {
- return nil, errors.New("invalid URL scheme, want \"enode\"")
- }
- // Parse the Node ID from the user portion.
- if u.User == nil {
- return nil, errors.New("does not contain node ID")
- }
- if id, err = parsePubkey(u.User.String()); err != nil {
- return nil, fmt.Errorf("invalid public key (%v)", err)
- }
- // Parse the IP address.
- ip := net.ParseIP(u.Hostname())
- if ip == nil {
- ips, err := lookupIPFunc(u.Hostname())
- if err != nil {
- return nil, err
- }
- ip = ips[0]
- }
- // Ensure the IP is 4 bytes long for IPv4 addresses.
- if ipv4 := ip.To4(); ipv4 != nil {
- ip = ipv4
- }
- // Parse the port numbers.
- if tcpPort, err = strconv.ParseUint(u.Port(), 10, 16); err != nil {
- return nil, errors.New("invalid port")
- }
- udpPort = tcpPort
- qv := u.Query()
- if qv.Get("discport") != "" {
- udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16)
- if err != nil {
- return nil, errors.New("invalid discport in query")
- }
- }
- return NewV4(id, ip, int(tcpPort), int(udpPort)), nil
-}
-
-// parsePubkey parses a hex-encoded secp256k1 public key.
-func parsePubkey(in string) (*ecdsa.PublicKey, error) {
- b, err := hex.DecodeString(in)
- if err != nil {
- return nil, err
- } else if len(b) != 64 {
- return nil, fmt.Errorf("wrong length, want %d hex chars", 128)
- }
- b = append([]byte{0x4}, b...)
- return crypto.UnmarshalPubkey(b)
-}
-
-func (n *Node) URLv4() string {
- var (
- scheme enr.ID
- nodeid string
- key ecdsa.PublicKey
- )
- n.Load(&scheme)
- n.Load((*Secp256k1)(&key))
- switch {
- case scheme == "v4" || key != ecdsa.PublicKey{}:
- nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:])
- default:
- nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:])
- }
- u := url.URL{Scheme: "enode"}
- if n.Incomplete() {
- u.Host = nodeid
- } else {
- addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()}
- u.User = url.User(nodeid)
- u.Host = addr.String()
- if n.UDP() != n.TCP() {
- u.RawQuery = "discport=" + strconv.Itoa(n.UDP())
- }
- }
- return u.String()
-}
-
-// PubkeyToIDV4 derives the v4 node address from the given public key.
-func PubkeyToIDV4(key *ecdsa.PublicKey) ID {
- e := make([]byte, 64)
- math.ReadBits(key.X, e[:len(e)/2])
- math.ReadBits(key.Y, e[len(e)/2:])
- return ID(crypto.Keccak256Hash(e))
-}
diff --git a/p2p/enode/urlv4_test.go b/p2p/enode/urlv4_test.go
deleted file mode 100644
index 33de96cc57..0000000000
--- a/p2p/enode/urlv4_test.go
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enode
-
-import (
- "crypto/ecdsa"
- "errors"
- "net"
- "reflect"
- "strings"
- "testing"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/enr"
-)
-
-func init() {
- lookupIPFunc = func(name string) ([]net.IP, error) {
- if name == "node.example.org" {
- return []net.IP{{33, 44, 55, 66}}, nil
- }
- return nil, errors.New("no such host")
- }
-}
-
-var parseNodeTests = []struct {
- input string
- wantError string
- wantResult *Node
-}{
- // Records
- {
- input: "enr:-IS4QGrdq0ugARp5T2BZ41TrZOqLc_oKvZoPuZP5--anqWE_J-Tucc1xgkOL7qXl0puJgT7qc2KSvcupc4NCb0nr4tdjgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQM6UUF2Rm-oFe1IH_rQkRCi00T2ybeMHRSvw1HDpRvjPYN1ZHCCdl8",
- wantResult: func() *Node {
- testKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
- var r enr.Record
- r.Set(enr.IP{127, 0, 0, 1})
- r.Set(enr.UDP(30303))
- r.SetSeq(99)
- SignV4(&r, testKey)
- n, _ := New(ValidSchemes, &r)
- return n
- }(),
- },
- // Invalid Records
- {
- input: "enr:",
- wantError: "EOF", // could be nicer
- },
- {
- input: "enr:x",
- wantError: "illegal base64 data at input byte 0",
- },
- {
- input: "enr:-EmGZm9vYmFyY4JpZIJ2NIJpcIR_AAABiXNlY3AyNTZrMaEDOlFBdkZvqBXtSB_60JEQotNE9sm3jB0Ur8NRw6Ub4z2DdWRwgnZf",
- wantError: enr.ErrInvalidSig.Error(),
- },
- // Complete node URLs with IP address and ports
- {
- input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@invalid.:3",
- wantError: `no such host`,
- },
- {
- input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo",
- wantError: `invalid port`,
- },
- {
- input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo",
- wantError: `invalid discport in query`,
- },
- {
- input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
- wantResult: NewV4(
- hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
- net.IP{127, 0, 0, 1},
- 52150,
- 52150,
- ),
- },
- {
- input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
- wantResult: NewV4(
- hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
- net.ParseIP("::"),
- 52150,
- 52150,
- ),
- },
- {
- input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150",
- wantResult: NewV4(
- hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
- net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
- 52150,
- 52150,
- ),
- },
- {
- input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334",
- wantResult: NewV4(
- hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
- net.IP{0x7f, 0x0, 0x0, 0x1},
- 52150,
- 22334,
- ),
- },
- // Incomplete node URLs with no address
- {
- input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
- wantResult: NewV4(
- hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
- nil, 0, 0,
- ),
- },
- // Invalid URLs
- {
- input: "",
- wantError: errMissingPrefix.Error(),
- },
- {
- input: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
- wantError: errMissingPrefix.Error(),
- },
- {
- input: "01010101",
- wantError: errMissingPrefix.Error(),
- },
- {
- input: "enode://01010101@123.124.125.126:3",
- wantError: `invalid public key (wrong length, want 128 hex chars)`,
- },
- {
- input: "enode://01010101",
- wantError: `invalid public key (wrong length, want 128 hex chars)`,
- },
- {
- input: "http://foobar",
- wantError: errMissingPrefix.Error(),
- },
- {
- input: "://foo",
- wantError: errMissingPrefix.Error(),
- },
-}
-
-func hexPubkey(h string) *ecdsa.PublicKey {
- k, err := parsePubkey(h)
- if err != nil {
- panic(err)
- }
- return k
-}
-
-func TestParseNode(t *testing.T) {
- for _, test := range parseNodeTests {
- n, err := Parse(ValidSchemes, test.input)
- if test.wantError != "" {
- if err == nil {
- t.Errorf("test %q:\n got nil error, expected %#q", test.input, test.wantError)
- continue
- } else if !strings.Contains(err.Error(), test.wantError) {
- t.Errorf("test %q:\n got error %#q, expected %#q", test.input, err.Error(), test.wantError)
- continue
- }
- } else {
- if err != nil {
- t.Errorf("test %q:\n unexpected error: %v", test.input, err)
- continue
- }
- if !reflect.DeepEqual(n, test.wantResult) {
- t.Errorf("test %q:\n result mismatch:\ngot: %#v\nwant: %#v", test.input, n, test.wantResult)
- }
- }
- }
-}
-
-func TestNodeString(t *testing.T) {
- for i, test := range parseNodeTests {
- if test.wantError == "" && strings.HasPrefix(test.input, "enode://") {
- str := test.wantResult.String()
- if str != test.input {
- t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.input)
- }
- }
- }
-}
diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go
deleted file mode 100644
index 2b093b2f1a..0000000000
--- a/p2p/enr/enr.go
+++ /dev/null
@@ -1,335 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package enr implements Ethereum Node Records as defined in EIP-778. A node record holds
-// arbitrary information about a node on the peer-to-peer network. Node information is
-// stored in key/value pairs. To store and retrieve key/values in a record, use the Entry
-// interface.
-//
-// # Signature Handling
-//
-// Records must be signed before transmitting them to another node.
-//
-// Decoding a record doesn't check its signature. Code working with records from an
-// untrusted source must always verify two things: that the record uses an identity scheme
-// deemed secure, and that the signature is valid according to the declared scheme.
-//
-// When creating a record, set the entries you want and use a signing function provided by
-// the identity scheme to add the signature. Modifying a record invalidates the signature.
-//
-// Package enr supports the "secp256k1-keccak" identity scheme.
-package enr
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "sort"
-
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-const SizeLimit = 300 // maximum encoded size of a node record in bytes
-
-var (
- ErrInvalidSig = errors.New("invalid signature on node record")
- errNotSorted = errors.New("record key/value pairs are not sorted by key")
- errDuplicateKey = errors.New("record contains duplicate key")
- errIncompletePair = errors.New("record contains incomplete k/v pair")
- errIncompleteList = errors.New("record contains less than two list elements")
- errTooBig = fmt.Errorf("record bigger than %d bytes", SizeLimit)
- errEncodeUnsigned = errors.New("can't encode unsigned record")
- errNotFound = errors.New("no such key in record")
-)
-
-// An IdentityScheme is capable of verifying record signatures and
-// deriving node addresses.
-type IdentityScheme interface {
- Verify(r *Record, sig []byte) error
- NodeAddr(r *Record) []byte
-}
-
-// SchemeMap is a registry of named identity schemes.
-type SchemeMap map[string]IdentityScheme
-
-func (m SchemeMap) Verify(r *Record, sig []byte) error {
- s := m[r.IdentityScheme()]
- if s == nil {
- return ErrInvalidSig
- }
- return s.Verify(r, sig)
-}
-
-func (m SchemeMap) NodeAddr(r *Record) []byte {
- s := m[r.IdentityScheme()]
- if s == nil {
- return nil
- }
- return s.NodeAddr(r)
-}
-
-// Record represents a node record. The zero value is an empty record.
-type Record struct {
- seq uint64 // sequence number
- signature []byte // the signature
- raw []byte // RLP encoded record
- pairs []pair // sorted list of all key/value pairs
-}
-
-// pair is a key/value pair in a record.
-type pair struct {
- k string
- v rlp.RawValue
-}
-
-// Size returns the encoded size of the record.
-func (r *Record) Size() uint64 {
- if r.raw != nil {
- return uint64(len(r.raw))
- }
- return computeSize(r)
-}
-
-func computeSize(r *Record) uint64 {
- size := uint64(rlp.IntSize(r.seq))
- size += rlp.BytesSize(r.signature)
- for _, p := range r.pairs {
- size += rlp.StringSize(p.k)
- size += uint64(len(p.v))
- }
- return rlp.ListSize(size)
-}
-
-// Seq returns the sequence number.
-func (r *Record) Seq() uint64 {
- return r.seq
-}
-
-// SetSeq updates the record sequence number. This invalidates any signature on the record.
-// Calling SetSeq is usually not required because setting any key in a signed record
-// increments the sequence number.
-func (r *Record) SetSeq(s uint64) {
- r.signature = nil
- r.raw = nil
- r.seq = s
-}
-
-// Load retrieves the value of a key/value pair. The given Entry must be a pointer and will
-// be set to the value of the entry in the record.
-//
-// Errors returned by Load are wrapped in KeyError. You can distinguish decoding errors
-// from missing keys using the IsNotFound function.
-func (r *Record) Load(e Entry) error {
- i := sort.Search(len(r.pairs), func(i int) bool { return r.pairs[i].k >= e.ENRKey() })
- if i < len(r.pairs) && r.pairs[i].k == e.ENRKey() {
- if err := rlp.DecodeBytes(r.pairs[i].v, e); err != nil {
- return &KeyError{Key: e.ENRKey(), Err: err}
- }
- return nil
- }
- return &KeyError{Key: e.ENRKey(), Err: errNotFound}
-}
-
-// Set adds or updates the given entry in the record. It panics if the value can't be
-// encoded. If the record is signed, Set increments the sequence number and invalidates
-// the sequence number.
-func (r *Record) Set(e Entry) {
- blob, err := rlp.EncodeToBytes(e)
- if err != nil {
- panic(fmt.Errorf("enr: can't encode %s: %v", e.ENRKey(), err))
- }
- r.invalidate()
-
- pairs := make([]pair, len(r.pairs))
- copy(pairs, r.pairs)
- i := sort.Search(len(pairs), func(i int) bool { return pairs[i].k >= e.ENRKey() })
- switch {
- case i < len(pairs) && pairs[i].k == e.ENRKey():
- // element is present at r.pairs[i]
- pairs[i].v = blob
- case i < len(r.pairs):
- // insert pair before i-th elem
- el := pair{e.ENRKey(), blob}
- pairs = append(pairs, pair{})
- copy(pairs[i+1:], pairs[i:])
- pairs[i] = el
- default:
- // element should be placed at the end of r.pairs
- pairs = append(pairs, pair{e.ENRKey(), blob})
- }
- r.pairs = pairs
-}
-
-func (r *Record) invalidate() {
- if r.signature != nil {
- r.seq++
- }
- r.signature = nil
- r.raw = nil
-}
-
-// Signature returns the signature of the record.
-func (r *Record) Signature() []byte {
- if r.signature == nil {
- return nil
- }
- cpy := make([]byte, len(r.signature))
- copy(cpy, r.signature)
- return cpy
-}
-
-// EncodeRLP implements rlp.Encoder. Encoding fails if
-// the record is unsigned.
-func (r Record) EncodeRLP(w io.Writer) error {
- if r.signature == nil {
- return errEncodeUnsigned
- }
- _, err := w.Write(r.raw)
- return err
-}
-
-// DecodeRLP implements rlp.Decoder. Decoding doesn't verify the signature.
-func (r *Record) DecodeRLP(s *rlp.Stream) error {
- dec, raw, err := decodeRecord(s)
- if err != nil {
- return err
- }
- *r = dec
- r.raw = raw
- return nil
-}
-
-func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) {
- raw, err = s.Raw()
- if err != nil {
- return dec, raw, err
- }
- if len(raw) > SizeLimit {
- return dec, raw, errTooBig
- }
-
- // Decode the RLP container.
- s = rlp.NewStream(bytes.NewReader(raw), 0)
- if _, err := s.List(); err != nil {
- return dec, raw, err
- }
- if err = s.Decode(&dec.signature); err != nil {
- if err == rlp.EOL {
- err = errIncompleteList
- }
- return dec, raw, err
- }
- if err = s.Decode(&dec.seq); err != nil {
- if err == rlp.EOL {
- err = errIncompleteList
- }
- return dec, raw, err
- }
- // The rest of the record contains sorted k/v pairs.
- var prevkey string
- for i := 0; ; i++ {
- var kv pair
- if err := s.Decode(&kv.k); err != nil {
- if err == rlp.EOL {
- break
- }
- return dec, raw, err
- }
- if err := s.Decode(&kv.v); err != nil {
- if err == rlp.EOL {
- return dec, raw, errIncompletePair
- }
- return dec, raw, err
- }
- if i > 0 {
- if kv.k == prevkey {
- return dec, raw, errDuplicateKey
- }
- if kv.k < prevkey {
- return dec, raw, errNotSorted
- }
- }
- dec.pairs = append(dec.pairs, kv)
- prevkey = kv.k
- }
- return dec, raw, s.ListEnd()
-}
-
-// IdentityScheme returns the name of the identity scheme in the record.
-func (r *Record) IdentityScheme() string {
- var id ID
- r.Load(&id)
- return string(id)
-}
-
-// VerifySignature checks whether the record is signed using the given identity scheme.
-func (r *Record) VerifySignature(s IdentityScheme) error {
- return s.Verify(r, r.signature)
-}
-
-// SetSig sets the record signature. It returns an error if the encoded record is larger
-// than the size limit or if the signature is invalid according to the passed scheme.
-//
-// You can also use SetSig to remove the signature explicitly by passing a nil scheme
-// and signature.
-//
-// SetSig panics when either the scheme or the signature (but not both) are nil.
-func (r *Record) SetSig(s IdentityScheme, sig []byte) error {
- switch {
- // Prevent storing invalid data.
- case s == nil && sig != nil:
- panic("enr: invalid call to SetSig with non-nil signature but nil scheme")
- case s != nil && sig == nil:
- panic("enr: invalid call to SetSig with nil signature but non-nil scheme")
- // Verify if we have a scheme.
- case s != nil:
- if err := s.Verify(r, sig); err != nil {
- return err
- }
- raw, err := r.encode(sig)
- if err != nil {
- return err
- }
- r.signature, r.raw = sig, raw
- // Reset otherwise.
- default:
- r.signature, r.raw = nil, nil
- }
- return nil
-}
-
-// AppendElements appends the sequence number and entries to the given slice.
-func (r *Record) AppendElements(list []interface{}) []interface{} {
- list = append(list, r.seq)
- for _, p := range r.pairs {
- list = append(list, p.k, p.v)
- }
- return list
-}
-
-func (r *Record) encode(sig []byte) (raw []byte, err error) {
- list := make([]interface{}, 1, 2*len(r.pairs)+2)
- list[0] = sig
- list = r.AppendElements(list)
- if raw, err = rlp.EncodeToBytes(list); err != nil {
- return nil, err
- }
- if len(raw) > SizeLimit {
- return nil, errTooBig
- }
- return raw, nil
-}
diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go
deleted file mode 100644
index b85ee209d5..0000000000
--- a/p2p/enr/enr_test.go
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enr
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "math/rand"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
-
-func randomString(strlen int) string {
- b := make([]byte, strlen)
- rnd.Read(b)
- return string(b)
-}
-
-// TestGetSetID tests encoding/decoding and setting/getting of the ID key.
-func TestGetSetID(t *testing.T) {
- id := ID("someid")
- var r Record
- r.Set(id)
-
- var id2 ID
- require.NoError(t, r.Load(&id2))
- assert.Equal(t, id, id2)
-}
-
-// TestGetSetIP4 tests encoding/decoding and setting/getting of the IP key.
-func TestGetSetIPv4(t *testing.T) {
- ip := IPv4{192, 168, 0, 3}
- var r Record
- r.Set(ip)
-
- var ip2 IPv4
- require.NoError(t, r.Load(&ip2))
- assert.Equal(t, ip, ip2)
-}
-
-// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP6 key.
-func TestGetSetIPv6(t *testing.T) {
- ip := IPv6{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}
- var r Record
- r.Set(ip)
-
- var ip2 IPv6
- require.NoError(t, r.Load(&ip2))
- assert.Equal(t, ip, ip2)
-}
-
-// TestGetSetUDP tests encoding/decoding and setting/getting of the UDP key.
-func TestGetSetUDP(t *testing.T) {
- port := UDP(30309)
- var r Record
- r.Set(port)
-
- var port2 UDP
- require.NoError(t, r.Load(&port2))
- assert.Equal(t, port, port2)
-}
-
-func TestLoadErrors(t *testing.T) {
- var r Record
- ip4 := IPv4{127, 0, 0, 1}
- r.Set(ip4)
-
- // Check error for missing keys.
- var udp UDP
- err := r.Load(&udp)
- if !IsNotFound(err) {
- t.Error("IsNotFound should return true for missing key")
- }
- assert.Equal(t, &KeyError{Key: udp.ENRKey(), Err: errNotFound}, err)
-
- // Check error for invalid keys.
- var list []uint
- err = r.Load(WithEntry(ip4.ENRKey(), &list))
- kerr, ok := err.(*KeyError)
- if !ok {
- t.Fatalf("expected KeyError, got %T", err)
- }
- assert.Equal(t, kerr.Key, ip4.ENRKey())
- assert.Error(t, kerr.Err)
- if IsNotFound(err) {
- t.Error("IsNotFound should return false for decoding errors")
- }
-}
-
-// TestSortedGetAndSet tests that Set produced a sorted pairs slice.
-func TestSortedGetAndSet(t *testing.T) {
- type pair struct {
- k string
- v uint32
- }
-
- for _, tt := range []struct {
- input []pair
- want []pair
- }{
- {
- input: []pair{{"a", 1}, {"c", 2}, {"b", 3}},
- want: []pair{{"a", 1}, {"b", 3}, {"c", 2}},
- },
- {
- input: []pair{{"a", 1}, {"c", 2}, {"b", 3}, {"d", 4}, {"a", 5}, {"bb", 6}},
- want: []pair{{"a", 5}, {"b", 3}, {"bb", 6}, {"c", 2}, {"d", 4}},
- },
- {
- input: []pair{{"c", 2}, {"b", 3}, {"d", 4}, {"a", 5}, {"bb", 6}},
- want: []pair{{"a", 5}, {"b", 3}, {"bb", 6}, {"c", 2}, {"d", 4}},
- },
- } {
- var r Record
- for _, i := range tt.input {
- r.Set(WithEntry(i.k, &i.v))
- }
- for i, w := range tt.want {
- // set got's key from r.pair[i], so that we preserve order of pairs
- got := pair{k: r.pairs[i].k}
- assert.NoError(t, r.Load(WithEntry(w.k, &got.v)))
- assert.Equal(t, w, got)
- }
- }
-}
-
-// TestDirty tests record signature removal on setting of new key/value pair in record.
-func TestDirty(t *testing.T) {
- var r Record
-
- if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned {
- t.Errorf("expected errEncodeUnsigned, got %#v", err)
- }
-
- require.NoError(t, signTest([]byte{5}, &r))
- if len(r.signature) == 0 {
- t.Error("record is not signed")
- }
- _, err := rlp.EncodeToBytes(r)
- assert.NoError(t, err)
-
- r.SetSeq(3)
- if len(r.signature) != 0 {
- t.Error("signature still set after modification")
- }
- if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned {
- t.Errorf("expected errEncodeUnsigned, got %#v", err)
- }
-}
-
-func TestSize(t *testing.T) {
- var r Record
-
- // Empty record size is 3 bytes.
- // Unsigned records cannot be encoded, but they could, the encoding
- // would be [ 0, 0 ] -> 0xC28080.
- assert.Equal(t, uint64(3), r.Size())
-
- // Add one attribute. The size increases to 5, the encoding
- // would be [ 0, 0, "k", "v" ] -> 0xC58080C26B76.
- r.Set(WithEntry("k", "v"))
- assert.Equal(t, uint64(5), r.Size())
-
- // Now add a signature.
- nodeid := []byte{1, 2, 3, 4, 5, 6, 7, 8}
- signTest(nodeid, &r)
- assert.Equal(t, uint64(45), r.Size())
- enc, _ := rlp.EncodeToBytes(&r)
- if r.Size() != uint64(len(enc)) {
- t.Error("Size() not equal encoded length", len(enc))
- }
- if r.Size() != computeSize(&r) {
- t.Error("Size() not equal computed size", computeSize(&r))
- }
-}
-
-func TestSeq(t *testing.T) {
- var r Record
-
- assert.Equal(t, uint64(0), r.Seq())
- r.Set(UDP(1))
- assert.Equal(t, uint64(0), r.Seq())
- signTest([]byte{5}, &r)
- assert.Equal(t, uint64(0), r.Seq())
- r.Set(UDP(2))
- assert.Equal(t, uint64(1), r.Seq())
-}
-
-// TestGetSetOverwrite tests value overwrite when setting a new value with an existing key in record.
-func TestGetSetOverwrite(t *testing.T) {
- var r Record
-
- ip := IPv4{192, 168, 0, 3}
- r.Set(ip)
-
- ip2 := IPv4{192, 168, 0, 4}
- r.Set(ip2)
-
- var ip3 IPv4
- require.NoError(t, r.Load(&ip3))
- assert.Equal(t, ip2, ip3)
-}
-
-// TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record.
-func TestSignEncodeAndDecode(t *testing.T) {
- var r Record
- r.Set(UDP(30303))
- r.Set(IPv4{127, 0, 0, 1})
- require.NoError(t, signTest([]byte{5}, &r))
-
- blob, err := rlp.EncodeToBytes(r)
- require.NoError(t, err)
-
- var r2 Record
- require.NoError(t, rlp.DecodeBytes(blob, &r2))
- assert.Equal(t, r, r2)
-
- blob2, err := rlp.EncodeToBytes(r2)
- require.NoError(t, err)
- assert.Equal(t, blob, blob2)
-}
-
-// TestRecordTooBig tests that records bigger than SizeLimit bytes cannot be signed.
-func TestRecordTooBig(t *testing.T) {
- var r Record
- key := randomString(10)
-
- // set a big value for random key, expect error
- r.Set(WithEntry(key, randomString(SizeLimit)))
- if err := signTest([]byte{5}, &r); err != errTooBig {
- t.Fatalf("expected to get errTooBig, got %#v", err)
- }
-
- // set an acceptable value for random key, expect no error
- r.Set(WithEntry(key, randomString(100)))
- require.NoError(t, signTest([]byte{5}, &r))
-}
-
-// This checks that incomplete RLP inputs are handled correctly.
-func TestDecodeIncomplete(t *testing.T) {
- type decTest struct {
- input []byte
- err error
- }
- tests := []decTest{
- {[]byte{0xC0}, errIncompleteList},
- {[]byte{0xC1, 0x1}, errIncompleteList},
- {[]byte{0xC2, 0x1, 0x2}, nil},
- {[]byte{0xC3, 0x1, 0x2, 0x3}, errIncompletePair},
- {[]byte{0xC4, 0x1, 0x2, 0x3, 0x4}, nil},
- {[]byte{0xC5, 0x1, 0x2, 0x3, 0x4, 0x5}, errIncompletePair},
- }
- for _, test := range tests {
- var r Record
- err := rlp.DecodeBytes(test.input, &r)
- if err != test.err {
- t.Errorf("wrong error for %X: %v", test.input, err)
- }
- }
-}
-
-// TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs.
-func TestSignEncodeAndDecodeRandom(t *testing.T) {
- var r Record
-
- // random key/value pairs for testing
- pairs := map[string]uint32{}
- for i := 0; i < 10; i++ {
- key := randomString(7)
- value := rnd.Uint32()
- pairs[key] = value
- r.Set(WithEntry(key, &value))
- }
-
- require.NoError(t, signTest([]byte{5}, &r))
-
- enc, err := rlp.EncodeToBytes(r)
- require.NoError(t, err)
- require.Equal(t, uint64(len(enc)), r.Size())
- require.Equal(t, uint64(len(enc)), computeSize(&r))
-
- for k, v := range pairs {
- desc := fmt.Sprintf("key %q", k)
- var got uint32
- buf := WithEntry(k, &got)
- require.NoError(t, r.Load(buf), desc)
- require.Equal(t, v, got, desc)
- }
-}
-
-type testSig struct{}
-
-type testID []byte
-
-func (id testID) ENRKey() string { return "testid" }
-
-func signTest(id []byte, r *Record) error {
- r.Set(ID("test"))
- r.Set(testID(id))
- return r.SetSig(testSig{}, makeTestSig(id, r.Seq()))
-}
-
-func makeTestSig(id []byte, seq uint64) []byte {
- sig := make([]byte, 8, len(id)+8)
- binary.BigEndian.PutUint64(sig[:8], seq)
- sig = append(sig, id...)
- return sig
-}
-
-func (testSig) Verify(r *Record, sig []byte) error {
- var id []byte
- if err := r.Load((*testID)(&id)); err != nil {
- return err
- }
- if !bytes.Equal(sig, makeTestSig(id, r.Seq())) {
- return ErrInvalidSig
- }
- return nil
-}
-
-func (testSig) NodeAddr(r *Record) []byte {
- var id []byte
- if err := r.Load((*testID)(&id)); err != nil {
- return nil
- }
- return id
-}
diff --git a/p2p/enr/entries.go b/p2p/enr/entries.go
deleted file mode 100644
index 9945a436c9..0000000000
--- a/p2p/enr/entries.go
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package enr
-
-import (
- "errors"
- "fmt"
- "io"
- "net"
-
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// Entry is implemented by known node record entry types.
-//
-// To define a new entry that is to be included in a node record,
-// create a Go type that satisfies this interface. The type should
-// also implement rlp.Decoder if additional checks are needed on the value.
-type Entry interface {
- ENRKey() string
-}
-
-type generic struct {
- key string
- value interface{}
-}
-
-func (g generic) ENRKey() string { return g.key }
-
-func (g generic) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, g.value)
-}
-
-func (g *generic) DecodeRLP(s *rlp.Stream) error {
- return s.Decode(g.value)
-}
-
-// WithEntry wraps any value with a key name. It can be used to set and load arbitrary values
-// in a record. The value v must be supported by rlp. To use WithEntry with Load, the value
-// must be a pointer.
-func WithEntry(k string, v interface{}) Entry {
- return &generic{key: k, value: v}
-}
-
-// TCP is the "tcp" key, which holds the TCP port of the node.
-type TCP uint16
-
-func (v TCP) ENRKey() string { return "tcp" }
-
-// TCP6 is the "tcp6" key, which holds the IPv6-specific tcp6 port of the node.
-type TCP6 uint16
-
-func (v TCP6) ENRKey() string { return "tcp6" }
-
-// UDP is the "udp" key, which holds the UDP port of the node.
-type UDP uint16
-
-func (v UDP) ENRKey() string { return "udp" }
-
-// UDP6 is the "udp6" key, which holds the IPv6-specific UDP port of the node.
-type UDP6 uint16
-
-func (v UDP6) ENRKey() string { return "udp6" }
-
-// ID is the "id" key, which holds the name of the identity scheme.
-type ID string
-
-const IDv4 = ID("v4") // the default identity scheme
-
-func (v ID) ENRKey() string { return "id" }
-
-// IP is either the "ip" or "ip6" key, depending on the value.
-// Use this value to encode IP addresses that can be either v4 or v6.
-// To load an address from a record use the IPv4 or IPv6 types.
-type IP net.IP
-
-func (v IP) ENRKey() string {
- if net.IP(v).To4() == nil {
- return "ip6"
- }
- return "ip"
-}
-
-// EncodeRLP implements rlp.Encoder.
-func (v IP) EncodeRLP(w io.Writer) error {
- if ip4 := net.IP(v).To4(); ip4 != nil {
- return rlp.Encode(w, ip4)
- }
- if ip6 := net.IP(v).To16(); ip6 != nil {
- return rlp.Encode(w, ip6)
- }
- return fmt.Errorf("invalid IP address: %v", net.IP(v))
-}
-
-// DecodeRLP implements rlp.Decoder.
-func (v *IP) DecodeRLP(s *rlp.Stream) error {
- if err := s.Decode((*net.IP)(v)); err != nil {
- return err
- }
- if len(*v) != 4 && len(*v) != 16 {
- return fmt.Errorf("invalid IP address, want 4 or 16 bytes: %v", *v)
- }
- return nil
-}
-
-// IPv4 is the "ip" key, which holds the IP address of the node.
-type IPv4 net.IP
-
-func (v IPv4) ENRKey() string { return "ip" }
-
-// EncodeRLP implements rlp.Encoder.
-func (v IPv4) EncodeRLP(w io.Writer) error {
- ip4 := net.IP(v).To4()
- if ip4 == nil {
- return fmt.Errorf("invalid IPv4 address: %v", net.IP(v))
- }
- return rlp.Encode(w, ip4)
-}
-
-// DecodeRLP implements rlp.Decoder.
-func (v *IPv4) DecodeRLP(s *rlp.Stream) error {
- if err := s.Decode((*net.IP)(v)); err != nil {
- return err
- }
- if len(*v) != 4 {
- return fmt.Errorf("invalid IPv4 address, want 4 bytes: %v", *v)
- }
- return nil
-}
-
-// IPv6 is the "ip6" key, which holds the IP address of the node.
-type IPv6 net.IP
-
-func (v IPv6) ENRKey() string { return "ip6" }
-
-// EncodeRLP implements rlp.Encoder.
-func (v IPv6) EncodeRLP(w io.Writer) error {
- ip6 := net.IP(v).To16()
- if ip6 == nil {
- return fmt.Errorf("invalid IPv6 address: %v", net.IP(v))
- }
- return rlp.Encode(w, ip6)
-}
-
-// DecodeRLP implements rlp.Decoder.
-func (v *IPv6) DecodeRLP(s *rlp.Stream) error {
- if err := s.Decode((*net.IP)(v)); err != nil {
- return err
- }
- if len(*v) != 16 {
- return fmt.Errorf("invalid IPv6 address, want 16 bytes: %v", *v)
- }
- return nil
-}
-
-// KeyError is an error related to a key.
-type KeyError struct {
- Key string
- Err error
-}
-
-// Error implements error.
-func (err *KeyError) Error() string {
- if err.Err == errNotFound {
- return fmt.Sprintf("missing ENR key %q", err.Key)
- }
- return fmt.Sprintf("ENR key %q: %v", err.Key, err.Err)
-}
-
-func (err *KeyError) Unwrap() error {
- return err.Err
-}
-
-// IsNotFound reports whether the given error means that a key/value pair is
-// missing from a record.
-func IsNotFound(err error) bool {
- var ke *KeyError
- if errors.As(err, &ke) {
- return ke.Err == errNotFound
- }
- return false
-}
diff --git a/p2p/message.go b/p2p/message.go
deleted file mode 100644
index 3ab56ee350..0000000000
--- a/p2p/message.go
+++ /dev/null
@@ -1,325 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// Msg defines the structure of a p2p message.
-//
-// Note that a Msg can only be sent once since the Payload reader is
-// consumed during sending. It is not possible to create a Msg and
-// send it any number of times. If you want to reuse an encoded
-// structure, encode the payload into a byte array and create a
-// separate Msg with a bytes.Reader as Payload for each send.
-type Msg struct {
- Code uint64
- Size uint32 // Size of the raw payload
- Payload io.Reader
- ReceivedAt time.Time
-
- meterCap Cap // Protocol name and version for egress metering
- meterCode uint64 // Message within protocol for egress metering
- meterSize uint32 // Compressed message size for ingress metering
-}
-
-// Decode parses the RLP content of a message into
-// the given value, which must be a pointer.
-//
-// For the decoding rules, please see package rlp.
-func (msg Msg) Decode(val interface{}) error {
- s := rlp.NewStream(msg.Payload, uint64(msg.Size))
- if err := s.Decode(val); err != nil {
- return newPeerError(errInvalidMsg, "(code %x) (size %d) %v", msg.Code, msg.Size, err)
- }
- return nil
-}
-
-func (msg Msg) String() string {
- return fmt.Sprintf("msg #%v (%v bytes)", msg.Code, msg.Size)
-}
-
-// Discard reads any remaining payload data into a black hole.
-func (msg Msg) Discard() error {
- _, err := io.Copy(io.Discard, msg.Payload)
- return err
-}
-
-func (msg Msg) Time() time.Time {
- return msg.ReceivedAt
-}
-
-type MsgReader interface {
- ReadMsg() (Msg, error)
-}
-
-type MsgWriter interface {
- // WriteMsg sends a message. It will block until the message's
- // Payload has been consumed by the other end.
- //
- // Note that messages can be sent only once because their
- // payload reader is drained.
- WriteMsg(Msg) error
-}
-
-// MsgReadWriter provides reading and writing of encoded messages.
-// Implementations should ensure that ReadMsg and WriteMsg can be
-// called simultaneously from multiple goroutines.
-type MsgReadWriter interface {
- MsgReader
- MsgWriter
-}
-
-// Send writes an RLP-encoded message with the given code.
-// data should encode as an RLP list.
-func Send(w MsgWriter, msgcode uint64, data interface{}) error {
- size, r, err := rlp.EncodeToReader(data)
- if err != nil {
- return err
- }
- return w.WriteMsg(Msg{Code: msgcode, Size: uint32(size), Payload: r})
-}
-
-// SendItems writes an RLP with the given code and data elements.
-// For a call such as:
-//
-// SendItems(w, code, e1, e2, e3)
-//
-// the message payload will be an RLP list containing the items:
-//
-// [e1, e2, e3]
-func SendItems(w MsgWriter, msgcode uint64, elems ...interface{}) error {
- return Send(w, msgcode, elems)
-}
-
-// eofSignal wraps a reader with eof signaling. the eof channel is
-// closed when the wrapped reader returns an error or when count bytes
-// have been read.
-type eofSignal struct {
- wrapped io.Reader
- count uint32 // number of bytes left
- eof chan<- struct{}
-}
-
-// note: when using eofSignal to detect whether a message payload
-// has been read, Read might not be called for zero sized messages.
-func (r *eofSignal) Read(buf []byte) (int, error) {
- if r.count == 0 {
- if r.eof != nil {
- r.eof <- struct{}{}
- r.eof = nil
- }
- return 0, io.EOF
- }
-
- max := len(buf)
- if int(r.count) < len(buf) {
- max = int(r.count)
- }
- n, err := r.wrapped.Read(buf[:max])
- r.count -= uint32(n)
- if (err != nil || r.count == 0) && r.eof != nil {
- r.eof <- struct{}{} // tell Peer that msg has been consumed
- r.eof = nil
- }
- return n, err
-}
-
-// MsgPipe creates a message pipe. Reads on one end are matched
-// with writes on the other. The pipe is full-duplex, both ends
-// implement MsgReadWriter.
-func MsgPipe() (*MsgPipeRW, *MsgPipeRW) {
- var (
- c1, c2 = make(chan Msg), make(chan Msg)
- closing = make(chan struct{})
- closed = new(atomic.Bool)
- rw1 = &MsgPipeRW{c1, c2, closing, closed}
- rw2 = &MsgPipeRW{c2, c1, closing, closed}
- )
- return rw1, rw2
-}
-
-// ErrPipeClosed is returned from pipe operations after the
-// pipe has been closed.
-var ErrPipeClosed = errors.New("p2p: read or write on closed message pipe")
-
-// MsgPipeRW is an endpoint of a MsgReadWriter pipe.
-type MsgPipeRW struct {
- w chan<- Msg
- r <-chan Msg
- closing chan struct{}
- closed *atomic.Bool
-}
-
-// WriteMsg sends a message on the pipe.
-// It blocks until the receiver has consumed the message payload.
-func (p *MsgPipeRW) WriteMsg(msg Msg) error {
- if !p.closed.Load() {
- consumed := make(chan struct{}, 1)
- msg.Payload = &eofSignal{msg.Payload, msg.Size, consumed}
- select {
- case p.w <- msg:
- if msg.Size > 0 {
- // wait for payload read or discard
- select {
- case <-consumed:
- case <-p.closing:
- }
- }
- return nil
- case <-p.closing:
- }
- }
- return ErrPipeClosed
-}
-
-// ReadMsg returns a message sent on the other end of the pipe.
-func (p *MsgPipeRW) ReadMsg() (Msg, error) {
- if !p.closed.Load() {
- select {
- case msg := <-p.r:
- return msg, nil
- case <-p.closing:
- }
- }
- return Msg{}, ErrPipeClosed
-}
-
-// Close unblocks any pending ReadMsg and WriteMsg calls on both ends
-// of the pipe. They will return ErrPipeClosed. Close also
-// interrupts any reads from a message payload.
-func (p *MsgPipeRW) Close() error {
- if p.closed.Swap(true) {
- // someone else is already closing
- return nil
- }
- close(p.closing)
- return nil
-}
-
-// ExpectMsg reads a message from r and verifies that its
-// code and encoded RLP content match the provided values.
-// If content is nil, the payload is discarded and not verified.
-func ExpectMsg(r MsgReader, code uint64, content interface{}) error {
- msg, err := r.ReadMsg()
- if err != nil {
- return err
- }
- if msg.Code != code {
- return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, code)
- }
- if content == nil {
- return msg.Discard()
- }
- contentEnc, err := rlp.EncodeToBytes(content)
- if err != nil {
- panic("content encode error: " + err.Error())
- }
- if int(msg.Size) != len(contentEnc) {
- return fmt.Errorf("message size mismatch: got %d, want %d", msg.Size, len(contentEnc))
- }
- actualContent, err := io.ReadAll(msg.Payload)
- if err != nil {
- return err
- }
- if !bytes.Equal(actualContent, contentEnc) {
- return fmt.Errorf("message payload mismatch:\ngot: %x\nwant: %x", actualContent, contentEnc)
- }
- return nil
-}
-
-// msgEventer wraps a MsgReadWriter and sends events whenever a message is sent
-// or received
-type msgEventer struct {
- MsgReadWriter
-
- feed *event.Feed
- peerID enode.ID
- Protocol string
- localAddress string
- remoteAddress string
-}
-
-// newMsgEventer returns a msgEventer which sends message events to the given
-// feed
-func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID enode.ID, proto, remote, local string) *msgEventer {
- return &msgEventer{
- MsgReadWriter: rw,
- feed: feed,
- peerID: peerID,
- Protocol: proto,
- remoteAddress: remote,
- localAddress: local,
- }
-}
-
-// ReadMsg reads a message from the underlying MsgReadWriter and emits a
-// "message received" event
-func (ev *msgEventer) ReadMsg() (Msg, error) {
- msg, err := ev.MsgReadWriter.ReadMsg()
- if err != nil {
- return msg, err
- }
- ev.feed.Send(&PeerEvent{
- Type: PeerEventTypeMsgRecv,
- Peer: ev.peerID,
- Protocol: ev.Protocol,
- MsgCode: &msg.Code,
- MsgSize: &msg.Size,
- LocalAddress: ev.localAddress,
- RemoteAddress: ev.remoteAddress,
- })
- return msg, nil
-}
-
-// WriteMsg writes a message to the underlying MsgReadWriter and emits a
-// "message sent" event
-func (ev *msgEventer) WriteMsg(msg Msg) error {
- err := ev.MsgReadWriter.WriteMsg(msg)
- if err != nil {
- return err
- }
- ev.feed.Send(&PeerEvent{
- Type: PeerEventTypeMsgSend,
- Peer: ev.peerID,
- Protocol: ev.Protocol,
- MsgCode: &msg.Code,
- MsgSize: &msg.Size,
- LocalAddress: ev.localAddress,
- RemoteAddress: ev.remoteAddress,
- })
- return nil
-}
-
-// Close closes the underlying MsgReadWriter if it implements the io.Closer
-// interface
-func (ev *msgEventer) Close() error {
- if v, ok := ev.MsgReadWriter.(io.Closer); ok {
- return v.Close()
- }
- return nil
-}
diff --git a/p2p/message_test.go b/p2p/message_test.go
deleted file mode 100644
index e575c5d96e..0000000000
--- a/p2p/message_test.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "bytes"
- "fmt"
- "io"
- "runtime"
- "testing"
- "time"
-)
-
-func ExampleMsgPipe() {
- rw1, rw2 := MsgPipe()
- go func() {
- Send(rw1, 8, [][]byte{{0, 0}})
- Send(rw1, 5, [][]byte{{1, 1}})
- rw1.Close()
- }()
-
- for {
- msg, err := rw2.ReadMsg()
- if err != nil {
- break
- }
- var data [][]byte
- msg.Decode(&data)
- fmt.Printf("msg: %d, %x\n", msg.Code, data[0])
- }
- // Output:
- // msg: 8, 0000
- // msg: 5, 0101
-}
-
-func TestMsgPipeUnblockWrite(t *testing.T) {
-loop:
- for i := 0; i < 100; i++ {
- rw1, rw2 := MsgPipe()
- done := make(chan struct{})
- go func() {
- if err := SendItems(rw1, 1); err == nil {
- t.Error("EncodeMsg returned nil error")
- } else if err != ErrPipeClosed {
- t.Errorf("EncodeMsg returned wrong error: got %v, want %v", err, ErrPipeClosed)
- }
- close(done)
- }()
-
- // this call should ensure that EncodeMsg is waiting to
- // deliver sometimes. if this isn't done, Close is likely to
- // be executed before EncodeMsg starts and then we won't test
- // all the cases.
- runtime.Gosched()
-
- rw2.Close()
- select {
- case <-done:
- case <-time.After(200 * time.Millisecond):
- t.Errorf("write didn't unblock")
- break loop
- }
- }
-}
-
-// This test should panic if concurrent close isn't implemented correctly.
-func TestMsgPipeConcurrentClose(t *testing.T) {
- rw1, _ := MsgPipe()
- for i := 0; i < 10; i++ {
- go rw1.Close()
- }
-}
-
-func TestEOFSignal(t *testing.T) {
- rb := make([]byte, 10)
-
- // empty reader
- eof := make(chan struct{}, 1)
- sig := &eofSignal{new(bytes.Buffer), 0, eof}
- if n, err := sig.Read(rb); n != 0 || err != io.EOF {
- t.Errorf("Read returned unexpected values: (%v, %v)", n, err)
- }
- select {
- case <-eof:
- default:
- t.Error("EOF chan not signaled")
- }
-
- // count before error
- eof = make(chan struct{}, 1)
- sig = &eofSignal{bytes.NewBufferString("aaaaaaaa"), 4, eof}
- if n, err := sig.Read(rb); n != 4 || err != nil {
- t.Errorf("Read returned unexpected values: (%v, %v)", n, err)
- }
- select {
- case <-eof:
- default:
- t.Error("EOF chan not signaled")
- }
-
- // error before count
- eof = make(chan struct{}, 1)
- sig = &eofSignal{bytes.NewBufferString("aaaa"), 999, eof}
- if n, err := sig.Read(rb); n != 4 || err != nil {
- t.Errorf("Read returned unexpected values: (%v, %v)", n, err)
- }
- if n, err := sig.Read(rb); n != 0 || err != io.EOF {
- t.Errorf("Read returned unexpected values: (%v, %v)", n, err)
- }
- select {
- case <-eof:
- default:
- t.Error("EOF chan not signaled")
- }
-
- // no signal if neither occurs
- eof = make(chan struct{}, 1)
- sig = &eofSignal{bytes.NewBufferString("aaaaaaaaaaaaaaaaaaaaa"), 999, eof}
- if n, err := sig.Read(rb); n != 10 || err != nil {
- t.Errorf("Read returned unexpected values: (%v, %v)", n, err)
- }
- select {
- case <-eof:
- t.Error("unexpected EOF signal")
- default:
- }
-}
diff --git a/p2p/metrics.go b/p2p/metrics.go
deleted file mode 100644
index a6e36b91a8..0000000000
--- a/p2p/metrics.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Contains the meters and timers used by the networking layer.
-
-package p2p
-
-import (
- "errors"
- "net"
-
- "github.com/ethereum/go-ethereum/metrics"
-)
-
-const (
- // HandleHistName is the prefix of the per-packet serving time histograms.
- HandleHistName = "p2p/handle"
-
- // ingressMeterName is the prefix of the per-packet inbound metrics.
- ingressMeterName = "p2p/ingress"
-
- // egressMeterName is the prefix of the per-packet outbound metrics.
- egressMeterName = "p2p/egress"
-)
-
-var (
- activePeerGauge metrics.Gauge = metrics.NilGauge{}
-
- ingressTrafficMeter = metrics.NewRegisteredMeter("p2p/ingress", nil)
- egressTrafficMeter = metrics.NewRegisteredMeter("p2p/egress", nil)
-
- // general ingress/egress connection meters
- serveMeter metrics.Meter = metrics.NilMeter{}
- serveSuccessMeter metrics.Meter = metrics.NilMeter{}
- dialMeter metrics.Meter = metrics.NilMeter{}
- dialSuccessMeter metrics.Meter = metrics.NilMeter{}
- dialConnectionError metrics.Meter = metrics.NilMeter{}
-
- // handshake error meters
- dialTooManyPeers = metrics.NewRegisteredMeter("p2p/dials/error/saturated", nil)
- dialAlreadyConnected = metrics.NewRegisteredMeter("p2p/dials/error/known", nil)
- dialSelf = metrics.NewRegisteredMeter("p2p/dials/error/self", nil)
- dialUselessPeer = metrics.NewRegisteredMeter("p2p/dials/error/useless", nil)
- dialUnexpectedIdentity = metrics.NewRegisteredMeter("p2p/dials/error/id/unexpected", nil)
- dialEncHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/enc", nil)
- dialProtoHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/proto", nil)
-)
-
-func init() {
- if !metrics.Enabled {
- return
- }
-
- activePeerGauge = metrics.NewRegisteredGauge("p2p/peers", nil)
- serveMeter = metrics.NewRegisteredMeter("p2p/serves", nil)
- serveSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success", nil)
- dialMeter = metrics.NewRegisteredMeter("p2p/dials", nil)
- dialSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success", nil)
- dialConnectionError = metrics.NewRegisteredMeter("p2p/dials/error/connection", nil)
-}
-
-// markDialError matches errors that occur while setting up a dial connection
-// to the corresponding meter.
-func markDialError(err error) {
- if !metrics.Enabled {
- return
- }
- if err2 := errors.Unwrap(err); err2 != nil {
- err = err2
- }
- switch err {
- case DiscTooManyPeers:
- dialTooManyPeers.Mark(1)
- case DiscAlreadyConnected:
- dialAlreadyConnected.Mark(1)
- case DiscSelf:
- dialSelf.Mark(1)
- case DiscUselessPeer:
- dialUselessPeer.Mark(1)
- case DiscUnexpectedIdentity:
- dialUnexpectedIdentity.Mark(1)
- case errEncHandshakeError:
- dialEncHandshakeError.Mark(1)
- case errProtoHandshakeError:
- dialProtoHandshakeError.Mark(1)
- }
-}
-
-// meteredConn is a wrapper around a net.Conn that meters both the
-// inbound and outbound network traffic.
-type meteredConn struct {
- net.Conn
-}
-
-// newMeteredConn creates a new metered connection, bumps the ingress or egress
-// connection meter and also increases the metered peer count. If the metrics
-// system is disabled, function returns the original connection.
-func newMeteredConn(conn net.Conn) net.Conn {
- if !metrics.Enabled {
- return conn
- }
- return &meteredConn{Conn: conn}
-}
-
-// Read delegates a network read to the underlying connection, bumping the common
-// and the peer ingress traffic meters along the way.
-func (c *meteredConn) Read(b []byte) (n int, err error) {
- n, err = c.Conn.Read(b)
- ingressTrafficMeter.Mark(int64(n))
- return n, err
-}
-
-// Write delegates a network write to the underlying connection, bumping the common
-// and the peer egress traffic meters along the way.
-func (c *meteredConn) Write(b []byte) (n int, err error) {
- n, err = c.Conn.Write(b)
- egressTrafficMeter.Mark(int64(n))
- return n, err
-}
diff --git a/p2p/msgrate/msgrate.go b/p2p/msgrate/msgrate.go
deleted file mode 100644
index de1a3177db..0000000000
--- a/p2p/msgrate/msgrate.go
+++ /dev/null
@@ -1,465 +0,0 @@
-// Copyright 2021 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package msgrate allows estimating the throughput of peers for more balanced syncs.
-package msgrate
-
-import (
- "context"
- "errors"
- "math"
- "sort"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// measurementImpact is the impact a single measurement has on a peer's final
-// capacity value. A value closer to 0 reacts slower to sudden network changes,
-// but it is also more stable against temporary hiccups. 0.1 worked well for
-// most of Ethereum's existence, so might as well go with it.
-const measurementImpact = 0.1
-
-// capacityOverestimation is the ratio of items to over-estimate when retrieving
-// a peer's capacity to avoid locking into a lower value due to never attempting
-// to fetch more than some local stable value.
-const capacityOverestimation = 1.01
-
-// rttMinEstimate is the minimal round trip time to target requests for. Since
-// every request entails a 2 way latency + bandwidth + serving database lookups,
-// it should be generous enough to permit meaningful work to be done on top of
-// the transmission costs.
-const rttMinEstimate = 2 * time.Second
-
-// rttMaxEstimate is the maximal round trip time to target requests for. Although
-// the expectation is that a well connected node will never reach this, certain
-// special connectivity ones might experience significant delays (e.g. satellite
-// uplink with 3s RTT). This value should be low enough to forbid stalling the
-// pipeline too long, but large enough to cover the worst of the worst links.
-const rttMaxEstimate = 20 * time.Second
-
-// rttPushdownFactor is a multiplier to attempt forcing quicker requests than
-// what the message rate tracker estimates. The reason is that message rate
-// tracking adapts queries to the RTT, but multiple RTT values can be perfectly
-// valid, they just result in higher packet sizes. Since smaller packets almost
-// always result in stabler download streams, this factor hones in on the lowest
-// RTT from all the functional ones.
-const rttPushdownFactor = 0.9
-
-// rttMinConfidence is the minimum value the roundtrip confidence factor may drop
-// to. Since the target timeouts are based on how confident the tracker is in the
-// true roundtrip, it's important to not allow too huge fluctuations.
-const rttMinConfidence = 0.1
-
-// ttlScaling is the multiplier that converts the estimated roundtrip time to a
-// timeout cap for network requests. The expectation is that peers' response time
-// will fluctuate around the estimated roundtrip, but depending in their load at
-// request time, it might be higher than anticipated. This scaling factor ensures
-// that we allow remote connections some slack but at the same time do enforce a
-// behavior similar to our median peers.
-const ttlScaling = 3
-
-// ttlLimit is the maximum timeout allowance to prevent reaching crazy numbers
-// if some unforeseen network events happen. As much as we try to hone in on
-// the most optimal values, it doesn't make any sense to go above a threshold,
-// even if everything is slow and screwy.
-const ttlLimit = time.Minute
-
-// tuningConfidenceCap is the number of active peers above which to stop detuning
-// the confidence number. The idea here is that once we hone in on the capacity
-// of a meaningful number of peers, adding one more should ot have a significant
-// impact on things, so just ron with the originals.
-const tuningConfidenceCap = 10
-
-// tuningImpact is the influence that a new tuning target has on the previously
-// cached value. This number is mostly just an out-of-the-blue heuristic that
-// prevents the estimates from jumping around. There's no particular reason for
-// the current value.
-const tuningImpact = 0.25
-
-// Tracker estimates the throughput capacity of a peer with regard to each data
-// type it can deliver. The goal is to dynamically adjust request sizes to max
-// out network throughput without overloading either the peer or the local node.
-//
-// By tracking in real time the latencies and bandwidths peers exhibit for each
-// packet type, it's possible to prevent overloading by detecting a slowdown on
-// one type when another type is pushed too hard.
-//
-// Similarly, real time measurements also help avoid overloading the local net
-// connection if our peers would otherwise be capable to deliver more, but the
-// local link is saturated. In that case, the live measurements will force us
-// to reduce request sizes until the throughput gets stable.
-//
-// Lastly, message rate measurements allows us to detect if a peer is unusually
-// slow compared to other peers, in which case we can decide to keep it around
-// or free up the slot so someone closer.
-//
-// Since throughput tracking and estimation adapts dynamically to live network
-// conditions, it's fine to have multiple trackers locally track the same peer
-// in different subsystem. The throughput will simply be distributed across the
-// two trackers if both are highly active.
-type Tracker struct {
- // capacity is the number of items retrievable per second of a given type.
- // It is analogous to bandwidth, but we deliberately avoided using bytes
- // as the unit, since serving nodes also spend a lot of time loading data
- // from disk, which is linear in the number of items, but mostly constant
- // in their sizes.
- //
- // Callers of course are free to use the item counter as a byte counter if
- // or when their protocol of choice if capped by bytes instead of items.
- // (eg. eth.getHeaders vs snap.getAccountRange).
- capacity map[uint64]float64
-
- // roundtrip is the latency a peer in general responds to data requests.
- // This number is not used inside the tracker, but is exposed to compare
- // peers to each other and filter out slow ones. Note however, it only
- // makes sense to compare RTTs if the caller caters request sizes for
- // each peer to target the same RTT. There's no need to make this number
- // the real networking RTT, we just need a number to compare peers with.
- roundtrip time.Duration
-
- lock sync.RWMutex
-}
-
-// NewTracker creates a new message rate tracker for a specific peer. An initial
-// RTT is needed to avoid a peer getting marked as an outlier compared to others
-// right after joining. It's suggested to use the median rtt across all peers to
-// init a new peer tracker.
-func NewTracker(caps map[uint64]float64, rtt time.Duration) *Tracker {
- if caps == nil {
- caps = make(map[uint64]float64)
- }
- return &Tracker{
- capacity: caps,
- roundtrip: rtt,
- }
-}
-
-// Capacity calculates the number of items the peer is estimated to be able to
-// retrieve within the allotted time slot. The method will round up any division
-// errors and will add an additional overestimation ratio on top. The reason for
-// overshooting the capacity is because certain message types might not increase
-// the load proportionally to the requested items, so fetching a bit more might
-// still take the same RTT. By forcefully overshooting by a small amount, we can
-// avoid locking into a lower-that-real capacity.
-func (t *Tracker) Capacity(kind uint64, targetRTT time.Duration) int {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- // Calculate the actual measured throughput
- throughput := t.capacity[kind] * float64(targetRTT) / float64(time.Second)
-
- // Return an overestimation to force the peer out of a stuck minima, adding
- // +1 in case the item count is too low for the overestimator to dent
- return roundCapacity(1 + capacityOverestimation*throughput)
-}
-
-// roundCapacity gives the integer value of a capacity.
-// The result fits int32, and is guaranteed to be positive.
-func roundCapacity(cap float64) int {
- const maxInt32 = float64(1<<31 - 1)
- return int(math.Min(maxInt32, math.Max(1, math.Ceil(cap))))
-}
-
-// Update modifies the peer's capacity values for a specific data type with a new
-// measurement. If the delivery is zero, the peer is assumed to have either timed
-// out or to not have the requested data, resulting in a slash to 0 capacity. This
-// avoids assigning the peer retrievals that it won't be able to honour.
-func (t *Tracker) Update(kind uint64, elapsed time.Duration, items int) {
- t.lock.Lock()
- defer t.lock.Unlock()
-
- // If nothing was delivered (timeout / unavailable data), reduce throughput
- // to minimum
- if items == 0 {
- t.capacity[kind] = 0
- return
- }
- // Otherwise update the throughput with a new measurement
- if elapsed <= 0 {
- elapsed = 1 // +1 (ns) to ensure non-zero divisor
- }
- measured := float64(items) / (float64(elapsed) / float64(time.Second))
-
- t.capacity[kind] = (1-measurementImpact)*(t.capacity[kind]) + measurementImpact*measured
- t.roundtrip = time.Duration((1-measurementImpact)*float64(t.roundtrip) + measurementImpact*float64(elapsed))
-}
-
-// Trackers is a set of message rate trackers across a number of peers with the
-// goal of aggregating certain measurements across the entire set for outlier
-// filtering and newly joining initialization.
-type Trackers struct {
- trackers map[string]*Tracker
-
- // roundtrip is the current best guess as to what is a stable round trip time
- // across the entire collection of connected peers. This is derived from the
- // various trackers added, but is used as a cache to avoid recomputing on each
- // network request. The value is updated once every RTT to avoid fluctuations
- // caused by hiccups or peer events.
- roundtrip time.Duration
-
- // confidence represents the probability that the estimated roundtrip value
- // is the real one across all our peers. The confidence value is used as an
- // impact factor of new measurements on old estimates. As our connectivity
- // stabilizes, this value gravitates towards 1, new measurements having
- // almost no impact. If there's a large peer churn and few peers, then new
- // measurements will impact it more. The confidence is increased with every
- // packet and dropped with every new connection.
- confidence float64
-
- // tuned is the time instance the tracker recalculated its cached roundtrip
- // value and confidence values. A cleaner way would be to have a heartbeat
- // goroutine do it regularly, but that requires a lot of maintenance to just
- // run every now and again.
- tuned time.Time
-
- // The fields below can be used to override certain default values. Their
- // purpose is to allow quicker tests. Don't use them in production.
- OverrideTTLLimit time.Duration
-
- log log.Logger
- lock sync.RWMutex
-}
-
-// NewTrackers creates an empty set of trackers to be filled with peers.
-func NewTrackers(log log.Logger) *Trackers {
- return &Trackers{
- trackers: make(map[string]*Tracker),
- roundtrip: rttMaxEstimate,
- confidence: 1,
- tuned: time.Now(),
- OverrideTTLLimit: ttlLimit,
- log: log,
- }
-}
-
-// Track inserts a new tracker into the set.
-func (t *Trackers) Track(id string, tracker *Tracker) error {
- t.lock.Lock()
- defer t.lock.Unlock()
-
- if _, ok := t.trackers[id]; ok {
- return errors.New("already tracking")
- }
- t.trackers[id] = tracker
- t.detune()
-
- return nil
-}
-
-// Untrack stops tracking a previously added peer.
-func (t *Trackers) Untrack(id string) error {
- t.lock.Lock()
- defer t.lock.Unlock()
-
- if _, ok := t.trackers[id]; !ok {
- return errors.New("not tracking")
- }
- delete(t.trackers, id)
- return nil
-}
-
-// MedianRoundTrip returns the median RTT across all known trackers. The purpose
-// of the median RTT is to initialize a new peer with sane statistics that it will
-// hopefully outperform. If it seriously underperforms, there's a risk of dropping
-// the peer, but that is ok as we're aiming for a strong median.
-func (t *Trackers) MedianRoundTrip() time.Duration {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return t.medianRoundTrip()
-}
-
-// medianRoundTrip is the internal lockless version of MedianRoundTrip to be used
-// by the QoS tuner.
-func (t *Trackers) medianRoundTrip() time.Duration {
- // Gather all the currently measured round trip times
- rtts := make([]float64, 0, len(t.trackers))
- for _, tt := range t.trackers {
- tt.lock.RLock()
- rtts = append(rtts, float64(tt.roundtrip))
- tt.lock.RUnlock()
- }
- sort.Float64s(rtts)
-
- var median time.Duration
- switch len(rtts) {
- case 0:
- median = rttMaxEstimate
- case 1:
- median = time.Duration(rtts[0])
- default:
- idx := int(math.Sqrt(float64(len(rtts))))
- median = time.Duration(rtts[idx])
- }
- // Restrict the RTT into some QoS defaults, irrelevant of true RTT
- if median < rttMinEstimate {
- median = rttMinEstimate
- }
- if median > rttMaxEstimate {
- median = rttMaxEstimate
- }
- return median
-}
-
-// MeanCapacities returns the capacities averaged across all the added trackers.
-// The purpose of the mean capacities are to initialize a new peer with some sane
-// starting values that it will hopefully outperform. If the mean overshoots, the
-// peer will be cut back to minimal capacity and given another chance.
-func (t *Trackers) MeanCapacities() map[uint64]float64 {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return t.meanCapacities()
-}
-
-// meanCapacities is the internal lockless version of MeanCapacities used for
-// debug logging.
-func (t *Trackers) meanCapacities() map[uint64]float64 {
- capacities := make(map[uint64]float64, len(t.trackers))
- for _, tt := range t.trackers {
- tt.lock.RLock()
- for key, val := range tt.capacity {
- capacities[key] += val
- }
- tt.lock.RUnlock()
- }
- for key, val := range capacities {
- capacities[key] = val / float64(len(t.trackers))
- }
- return capacities
-}
-
-// TargetRoundTrip returns the current target round trip time for a request to
-// complete in.The returned RTT is slightly under the estimated RTT. The reason
-// is that message rate estimation is a 2 dimensional problem which is solvable
-// for any RTT. The goal is to gravitate towards smaller RTTs instead of large
-// messages, to result in a stabler download stream.
-func (t *Trackers) TargetRoundTrip() time.Duration {
- // Recalculate the internal caches if it's been a while
- t.tune()
-
- // Caches surely recent, return target roundtrip
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return time.Duration(float64(t.roundtrip) * rttPushdownFactor)
-}
-
-// TargetTimeout returns the timeout allowance for a single request to finish
-// under. The timeout is proportional to the roundtrip, but also takes into
-// consideration the tracker's confidence in said roundtrip and scales it
-// accordingly. The final value is capped to avoid runaway requests.
-func (t *Trackers) TargetTimeout() time.Duration {
- // Recalculate the internal caches if it's been a while
- t.tune()
-
- // Caches surely recent, return target timeout
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return t.targetTimeout()
-}
-
-// targetTimeout is the internal lockless version of TargetTimeout to be used
-// during QoS tuning.
-func (t *Trackers) targetTimeout() time.Duration {
- timeout := time.Duration(ttlScaling * float64(t.roundtrip) / t.confidence)
- if timeout > t.OverrideTTLLimit {
- timeout = t.OverrideTTLLimit
- }
- return timeout
-}
-
-// tune gathers the individual tracker statistics and updates the estimated
-// request round trip time.
-func (t *Trackers) tune() {
- // Tune may be called concurrently all over the place, but we only want to
- // periodically update and even then only once. First check if it was updated
- // recently and abort if so.
- t.lock.RLock()
- dirty := time.Since(t.tuned) > t.roundtrip
- t.lock.RUnlock()
- if !dirty {
- return
- }
- // If an update is needed, obtain a write lock but make sure we don't update
- // it on all concurrent threads one by one.
- t.lock.Lock()
- defer t.lock.Unlock()
-
- if dirty := time.Since(t.tuned) > t.roundtrip; !dirty {
- return // A concurrent request beat us to the tuning
- }
- // First thread reaching the tuning point, update the estimates and return
- t.roundtrip = time.Duration((1-tuningImpact)*float64(t.roundtrip) + tuningImpact*float64(t.medianRoundTrip()))
- t.confidence = t.confidence + (1-t.confidence)/2
-
- t.tuned = time.Now()
- t.log.Debug("Recalculated msgrate QoS values", "rtt", t.roundtrip, "confidence", t.confidence, "ttl", t.targetTimeout(), "next", t.tuned.Add(t.roundtrip))
- if t.log.Enabled(context.Background(), log.LevelTrace) {
- t.log.Trace("Debug dump of mean capacities", "caps", t.meanCapacities())
- }
-}
-
-// detune reduces the tracker's confidence in order to make fresh measurements
-// have a larger impact on the estimates. It is meant to be used during new peer
-// connections so they can have a proper impact on the estimates.
-func (t *Trackers) detune() {
- // If we have a single peer, confidence is always 1
- if len(t.trackers) == 1 {
- t.confidence = 1
- return
- }
- // If we have a ton of peers, don't drop the confidence since there's enough
- // remaining to retain the same throughput
- if len(t.trackers) >= tuningConfidenceCap {
- return
- }
- // Otherwise drop the confidence factor
- peers := float64(len(t.trackers))
-
- t.confidence = t.confidence * (peers - 1) / peers
- if t.confidence < rttMinConfidence {
- t.confidence = rttMinConfidence
- }
- t.log.Debug("Relaxed msgrate QoS values", "rtt", t.roundtrip, "confidence", t.confidence, "ttl", t.targetTimeout())
-}
-
-// Capacity is a helper function to access a specific tracker without having to
-// track it explicitly outside.
-func (t *Trackers) Capacity(id string, kind uint64, targetRTT time.Duration) int {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- tracker := t.trackers[id]
- if tracker == nil {
- return 1 // Unregister race, don't return 0, it's a dangerous number
- }
- return tracker.Capacity(kind, targetRTT)
-}
-
-// Update is a helper function to access a specific tracker without having to
-// track it explicitly outside.
-func (t *Trackers) Update(id string, kind uint64, elapsed time.Duration, items int) {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- if tracker := t.trackers[id]; tracker != nil {
- tracker.Update(kind, elapsed, items)
- }
-}
diff --git a/p2p/msgrate/msgrate_test.go b/p2p/msgrate/msgrate_test.go
deleted file mode 100644
index a5c8dd0518..0000000000
--- a/p2p/msgrate/msgrate_test.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package msgrate
-
-import "testing"
-
-func TestCapacityOverflow(t *testing.T) {
- tracker := NewTracker(nil, 1)
- tracker.Update(1, 1, 100000)
- cap := tracker.Capacity(1, 10000000)
- if int32(cap) < 0 {
- t.Fatalf("Negative: %v", int32(cap))
- }
-}
diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go
deleted file mode 100644
index 2aa1f85585..0000000000
--- a/p2p/nat/nat.go
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package nat provides access to common network port mapping protocols.
-package nat
-
-import (
- "errors"
- "fmt"
- "net"
- "strings"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- natpmp "github.com/jackpal/go-nat-pmp"
-)
-
-// Interface An implementation of nat.Interface can map local ports to ports
-// accessible from the Internet.
-type Interface interface {
- // These methods manage a mapping between a port on the local
- // machine to a port that can be connected to from the internet.
- //
- // protocol is "UDP" or "TCP". Some implementations allow setting
- // a display name for the mapping. The mapping may be removed by
- // the gateway when its lifetime ends.
- AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error)
- DeleteMapping(protocol string, extport, intport int) error
-
- // ExternalIP should return the external (Internet-facing)
- // address of the gateway device.
- ExternalIP() (net.IP, error)
-
- // String should return name of the method. This is used for logging.
- String() string
-}
-
-// Parse parses a NAT interface description.
-// The following formats are currently accepted.
-// Note that mechanism names are not case-sensitive.
-//
-// "" or "none" return nil
-// "extip:77.12.33.4" will assume the local machine is reachable on the given IP
-// "any" uses the first auto-detected mechanism
-// "upnp" uses the Universal Plug and Play protocol
-// "pmp" uses NAT-PMP with an auto-detected gateway address
-// "pmp:192.168.0.1" uses NAT-PMP with the given gateway address
-func Parse(spec string) (Interface, error) {
- var (
- before, after, found = strings.Cut(spec, ":")
- mech = strings.ToLower(before)
- ip net.IP
- )
- if found {
- ip = net.ParseIP(after)
- if ip == nil {
- return nil, errors.New("invalid IP address")
- }
- }
- switch mech {
- case "", "none", "off":
- return nil, nil
- case "any", "auto", "on":
- return Any(), nil
- case "extip", "ip":
- if ip == nil {
- return nil, errors.New("missing IP address")
- }
- return ExtIP(ip), nil
- case "upnp":
- return UPnP(), nil
- case "pmp", "natpmp", "nat-pmp":
- return PMP(ip), nil
- default:
- return nil, fmt.Errorf("unknown mechanism %q", before)
- }
-}
-
-const (
- DefaultMapTimeout = 10 * time.Minute
-)
-
-// Map adds a port mapping on m and keeps it alive until c is closed.
-// This function is typically invoked in its own goroutine.
-//
-// Note that Map does not handle the situation where the NAT interface assigns a different
-// external port than the requested one.
-func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) {
- log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m)
- refresh := time.NewTimer(DefaultMapTimeout)
- defer func() {
- refresh.Stop()
- log.Debug("Deleting port mapping")
- m.DeleteMapping(protocol, extport, intport)
- }()
- if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil {
- log.Debug("Couldn't add port mapping", "err", err)
- } else {
- log.Info("Mapped network port")
- }
- for {
- select {
- case _, ok := <-c:
- if !ok {
- return
- }
- case <-refresh.C:
- log.Trace("Refreshing port mapping")
- if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil {
- log.Debug("Couldn't add port mapping", "err", err)
- }
- refresh.Reset(DefaultMapTimeout)
- }
- }
-}
-
-// ExtIP assumes that the local machine is reachable on the given
-// external IP address, and that any required ports were mapped manually.
-// Mapping operations will not return an error but won't actually do anything.
-type ExtIP net.IP
-
-func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
-func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
-
-// These do nothing.
-
-func (ExtIP) AddMapping(string, int, int, string, time.Duration) (uint16, error) { return 0, nil }
-func (ExtIP) DeleteMapping(string, int, int) error { return nil }
-
-// Any returns a port mapper that tries to discover any supported
-// mechanism on the local network.
-func Any() Interface {
- // TODO: attempt to discover whether the local machine has an
- // Internet-class address. Return ExtIP in this case.
- return startautodisc("UPnP or NAT-PMP", func() Interface {
- found := make(chan Interface, 2)
- go func() { found <- discoverUPnP() }()
- go func() { found <- discoverPMP() }()
- for i := 0; i < cap(found); i++ {
- if c := <-found; c != nil {
- return c
- }
- }
- return nil
- })
-}
-
-// UPnP returns a port mapper that uses UPnP. It will attempt to
-// discover the address of your router using UDP broadcasts.
-func UPnP() Interface {
- return startautodisc("UPnP", discoverUPnP)
-}
-
-// PMP returns a port mapper that uses NAT-PMP. The provided gateway
-// address should be the IP of your router. If the given gateway
-// address is nil, PMP will attempt to auto-discover the router.
-func PMP(gateway net.IP) Interface {
- if gateway != nil {
- return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
- }
- return startautodisc("NAT-PMP", discoverPMP)
-}
-
-// autodisc represents a port mapping mechanism that is still being
-// auto-discovered. Calls to the Interface methods on this type will
-// wait until the discovery is done and then call the method on the
-// discovered mechanism.
-//
-// This type is useful because discovery can take a while but we
-// want return an Interface value from UPnP, PMP and Auto immediately.
-type autodisc struct {
- what string // type of interface being autodiscovered
- once sync.Once
- doit func() Interface
-
- mu sync.Mutex
- found Interface
-}
-
-func startautodisc(what string, doit func() Interface) Interface {
- // TODO: monitor network configuration and rerun doit when it changes.
- return &autodisc{what: what, doit: doit}
-}
-
-func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
- if err := n.wait(); err != nil {
- return 0, err
- }
- return n.found.AddMapping(protocol, extport, intport, name, lifetime)
-}
-
-func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error {
- if err := n.wait(); err != nil {
- return err
- }
- return n.found.DeleteMapping(protocol, extport, intport)
-}
-
-func (n *autodisc) ExternalIP() (net.IP, error) {
- if err := n.wait(); err != nil {
- return nil, err
- }
- return n.found.ExternalIP()
-}
-
-func (n *autodisc) String() string {
- n.mu.Lock()
- defer n.mu.Unlock()
- if n.found == nil {
- return n.what
- }
- return n.found.String()
-}
-
-// wait blocks until auto-discovery has been performed.
-func (n *autodisc) wait() error {
- n.once.Do(func() {
- n.mu.Lock()
- n.found = n.doit()
- n.mu.Unlock()
- })
- if n.found == nil {
- return fmt.Errorf("no %s router discovered", n.what)
- }
- return nil
-}
diff --git a/p2p/nat/nat_test.go b/p2p/nat/nat_test.go
deleted file mode 100644
index 814e6d9e14..0000000000
--- a/p2p/nat/nat_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package nat
-
-import (
- "net"
- "testing"
- "time"
-)
-
-// This test checks that autodisc doesn't hang and returns
-// consistent results when multiple goroutines call its methods
-// concurrently.
-func TestAutoDiscRace(t *testing.T) {
- ad := startautodisc("thing", func() Interface {
- time.Sleep(500 * time.Millisecond)
- return ExtIP{33, 44, 55, 66}
- })
-
- // Spawn a few concurrent calls to ad.ExternalIP.
- type rval struct {
- ip net.IP
- err error
- }
- results := make(chan rval, 50)
- for i := 0; i < cap(results); i++ {
- go func() {
- ip, err := ad.ExternalIP()
- results <- rval{ip, err}
- }()
- }
-
- // Check that they all return the correct result within the deadline.
- deadline := time.After(2 * time.Second)
- for i := 0; i < cap(results); i++ {
- select {
- case <-deadline:
- t.Fatal("deadline exceeded")
- case rval := <-results:
- if rval.err != nil {
- t.Errorf("result %d: unexpected error: %v", i, rval.err)
- }
- wantIP := net.IP{33, 44, 55, 66}
- if !rval.ip.Equal(wantIP) {
- t.Errorf("result %d: got IP %v, want %v", i, rval.ip, wantIP)
- }
- }
- }
-}
diff --git a/p2p/nat/natpmp.go b/p2p/nat/natpmp.go
deleted file mode 100644
index 97601c99dc..0000000000
--- a/p2p/nat/natpmp.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package nat
-
-import (
- "fmt"
- "net"
- "strings"
- "time"
-
- natpmp "github.com/jackpal/go-nat-pmp"
-)
-
-// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
-// the common interface.
-type pmp struct {
- gw net.IP
- c *natpmp.Client
-}
-
-func (n *pmp) String() string {
- return fmt.Sprintf("NAT-PMP(%v)", n.gw)
-}
-
-func (n *pmp) ExternalIP() (net.IP, error) {
- response, err := n.c.GetExternalAddress()
- if err != nil {
- return nil, err
- }
- return response.ExternalIPAddress[:], nil
-}
-
-func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
- if lifetime <= 0 {
- return 0, fmt.Errorf("lifetime must not be <= 0")
- }
- // Note order of port arguments is switched between our
- // AddMapping and the client's AddPortMapping.
- res, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second))
- if err != nil {
- return 0, err
- }
-
- // NAT-PMP maps an alternative available port number if the requested port
- // is already mapped to another address and returns success. Handling of
- // alternate port numbers is done by the caller.
- return res.MappedExternalPort, nil
-}
-
-func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) {
- // To destroy a mapping, send an add-port with an internalPort of
- // the internal port to destroy, an external port of zero and a
- // time of zero.
- _, err = n.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0)
- return err
-}
-
-func discoverPMP() Interface {
- // run external address lookups on all potential gateways
- gws := potentialGateways()
- found := make(chan *pmp, len(gws))
- for i := range gws {
- gw := gws[i]
- go func() {
- c := natpmp.NewClient(gw)
- if _, err := c.GetExternalAddress(); err != nil {
- found <- nil
- } else {
- found <- &pmp{gw, c}
- }
- }()
- }
- // return the one that responds first.
- // discovery needs to be quick, so we stop caring about
- // any responses after a very short timeout.
- timeout := time.NewTimer(1 * time.Second)
- defer timeout.Stop()
- for range gws {
- select {
- case c := <-found:
- if c != nil {
- return c
- }
- case <-timeout.C:
- return nil
- }
- }
- return nil
-}
-
-// TODO: improve this. We currently assume that (on most networks)
-// the router is X.X.X.1 in a local LAN range.
-func potentialGateways() (gws []net.IP) {
- ifaces, err := net.Interfaces()
- if err != nil {
- return nil
- }
- for _, iface := range ifaces {
- ifaddrs, err := iface.Addrs()
- if err != nil {
- return gws
- }
- for _, addr := range ifaddrs {
- if x, ok := addr.(*net.IPNet); ok {
- if x.IP.IsPrivate() {
- ip := x.IP.Mask(x.Mask).To4()
- if ip != nil {
- ip[3] = ip[3] | 0x01
- gws = append(gws, ip)
- }
- }
- }
- }
- }
- return gws
-}
diff --git a/p2p/nat/natupnp.go b/p2p/nat/natupnp.go
deleted file mode 100644
index c90c4f3de8..0000000000
--- a/p2p/nat/natupnp.go
+++ /dev/null
@@ -1,250 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package nat
-
-import (
- "errors"
- "fmt"
- "math"
- "math/rand"
- "net"
- "strings"
- "sync"
- "time"
-
- "github.com/huin/goupnp"
- "github.com/huin/goupnp/dcps/internetgateway1"
- "github.com/huin/goupnp/dcps/internetgateway2"
-)
-
-const (
- soapRequestTimeout = 3 * time.Second
- rateLimit = 200 * time.Millisecond
-)
-
-type upnp struct {
- dev *goupnp.RootDevice
- service string
- client upnpClient
- mu sync.Mutex
- lastReqTime time.Time
- rand *rand.Rand
-}
-
-type upnpClient interface {
- GetExternalIPAddress() (string, error)
- AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
- DeletePortMapping(string, uint16, string) error
- GetNATRSIPStatus() (sip bool, nat bool, err error)
-}
-
-func (n *upnp) natEnabled() bool {
- var ok bool
- var err error
- n.withRateLimit(func() error {
- _, ok, err = n.client.GetNATRSIPStatus()
- return err
- })
- return err == nil && ok
-}
-
-func (n *upnp) ExternalIP() (addr net.IP, err error) {
- var ipString string
- n.withRateLimit(func() error {
- ipString, err = n.client.GetExternalIPAddress()
- return err
- })
-
- if err != nil {
- return nil, err
- }
- ip := net.ParseIP(ipString)
- if ip == nil {
- return nil, errors.New("bad IP in response")
- }
- return ip, nil
-}
-
-func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) {
- ip, err := n.internalAddress()
- if err != nil {
- return 0, nil // TODO: Shouldn't we return the error?
- }
- protocol = strings.ToUpper(protocol)
- lifetimeS := uint32(lifetime / time.Second)
- n.DeleteMapping(protocol, extport, intport)
-
- err = n.withRateLimit(func() error {
- return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
- })
- if err == nil {
- return uint16(extport), nil
- }
-
- return uint16(extport), n.withRateLimit(func() error {
- p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
- if err == nil {
- extport = int(p)
- }
- return err
- })
-}
-
-func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
- if client, ok := n.client.(*internetgateway2.WANIPConnection2); ok {
- return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
- }
- // It will retry with a random port number if the client does
- // not support AddAnyPortMapping.
- extport = n.randomPort()
- err := n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
- if err != nil {
- return 0, err
- }
- return uint16(extport), nil
-}
-
-func (n *upnp) randomPort() int {
- if n.rand == nil {
- n.rand = rand.New(rand.NewSource(time.Now().UnixNano()))
- }
- return n.rand.Intn(math.MaxUint16-10000) + 10000
-}
-
-func (n *upnp) internalAddress() (net.IP, error) {
- devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host)
- if err != nil {
- return nil, err
- }
- ifaces, err := net.Interfaces()
- if err != nil {
- return nil, err
- }
- for _, iface := range ifaces {
- addrs, err := iface.Addrs()
- if err != nil {
- return nil, err
- }
- for _, addr := range addrs {
- if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) {
- return x.IP, nil
- }
- }
- }
- return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
-}
-
-func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
- return n.withRateLimit(func() error {
- return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
- })
-}
-
-func (n *upnp) String() string {
- return "UPNP " + n.service
-}
-
-func (n *upnp) withRateLimit(fn func() error) error {
- n.mu.Lock()
- defer n.mu.Unlock()
-
- lastreq := time.Since(n.lastReqTime)
- if lastreq < rateLimit {
- time.Sleep(rateLimit - lastreq)
- }
- err := fn()
- n.lastReqTime = time.Now()
- return err
-}
-
-// discoverUPnP searches for Internet Gateway Devices
-// and returns the first one it can find on the local network.
-func discoverUPnP() Interface {
- found := make(chan *upnp, 2)
- // IGDv1
- go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp {
- switch sc.Service.ServiceType {
- case internetgateway1.URN_WANIPConnection_1:
- return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}}
- case internetgateway1.URN_WANPPPConnection_1:
- return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
- }
- return nil
- })
- // IGDv2
- go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp {
- switch sc.Service.ServiceType {
- case internetgateway2.URN_WANIPConnection_1:
- return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}}
- case internetgateway2.URN_WANIPConnection_2:
- return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}}
- case internetgateway2.URN_WANPPPConnection_1:
- return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
- }
- return nil
- })
- for i := 0; i < cap(found); i++ {
- if c := <-found; c != nil {
- return c
- }
- }
- return nil
-}
-
-// finds devices matching the given target and calls matcher for all
-// advertised services of each device. The first non-nil service found
-// is sent into out. If no service matched, nil is sent.
-func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) {
- devs, err := goupnp.DiscoverDevices(target)
- if err != nil {
- out <- nil
- return
- }
- found := false
- for i := 0; i < len(devs) && !found; i++ {
- if devs[i].Root == nil {
- continue
- }
- devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
- if found {
- return
- }
- // check for a matching IGD service
- sc := goupnp.ServiceClient{
- SOAPClient: service.NewSOAPClient(),
- RootDevice: devs[i].Root,
- Location: devs[i].Location,
- Service: service,
- }
- sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
- upnp := matcher(sc)
- if upnp == nil {
- return
- }
- upnp.dev = devs[i].Root
-
- // check whether port mapping is enabled
- if upnp.natEnabled() {
- out <- upnp
- found = true
- }
- })
- }
- if !found {
- out <- nil
- }
-}
diff --git a/p2p/nat/natupnp_test.go b/p2p/nat/natupnp_test.go
deleted file mode 100644
index 9072451d50..0000000000
--- a/p2p/nat/natupnp_test.go
+++ /dev/null
@@ -1,249 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package nat
-
-import (
- "fmt"
- "io"
- "net"
- "net/http"
- "os"
- "runtime"
- "strings"
- "testing"
-
- "github.com/huin/goupnp/httpu"
-)
-
-func TestUPNP_DDWRT(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skipf("disabled to avoid firewall prompt")
- }
-
- dev := &fakeIGD{
- t: t,
- ssdpResp: "HTTP/1.1 200 OK\r\n" +
- "Cache-Control: max-age=300\r\n" +
- "Date: Sun, 10 May 2015 10:05:33 GMT\r\n" +
- "Ext: \r\n" +
- "Location: http://{{listenAddr}}/InternetGatewayDevice.xml\r\n" +
- "Server: POSIX UPnP/1.0 DD-WRT Linux/V24\r\n" +
- "ST: urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" +
- "USN: uuid:CB2471CC-CF2E-9795-8D9C-E87B34C16800::urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" +
- "\r\n",
- httpResps: map[string]string{
- "GET /InternetGatewayDevice.xml": `
-
-
-
- 1
- 0
-
-
- urn:schemas-upnp-org:device:InternetGatewayDevice:1
- DD-WRT
- http://www.dd-wrt.com
- Gateway
- Asus RT-N16:DD-WRT
- Asus RT-N16
- V24
- 0000001
- http://www.dd-wrt.com
- uuid:A13AB4C3-3A14-E386-DE6A-EFEA923A06FE
-
-
- urn:schemas-upnp-org:service:Layer3Forwarding:1
- urn:upnp-org:serviceId:L3Forwarding1
- /x_layer3forwarding.xml
- /control?Layer3Forwarding
- /event?Layer3Forwarding
-
-
-
-
- urn:schemas-upnp-org:device:WANDevice:1
- WANDevice
- DD-WRT
- http://www.dd-wrt.com
- Gateway
- router
- http://www.dd-wrt.com
- uuid:48FD569B-F9A9-96AE-4EE6-EB403D3DB91A
-
-
- urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1
- urn:upnp-org:serviceId:WANCommonIFC1
- /x_wancommoninterfaceconfig.xml
- /control?WANCommonInterfaceConfig
- /event?WANCommonInterfaceConfig
-
-
-
-
- urn:schemas-upnp-org:device:WANConnectionDevice:1
- WAN Connection Device
- DD-WRT
- http://www.dd-wrt.com
- Gateway
- router
- http://www.dd-wrt.com
- uuid:CB2471CC-CF2E-9795-8D9C-E87B34C16800
-
-
- urn:schemas-upnp-org:service:WANIPConnection:1
- urn:upnp-org:serviceId:WANIPConn1
- /x_wanipconnection.xml
- /control?WANIPConnection
- /event?WANIPConnection
-
-
-
-
-
-
- urn:schemas-upnp-org:device:LANDevice:1
- LANDevice
- DD-WRT
- http://www.dd-wrt.com
- Gateway
- router
- http://www.dd-wrt.com
- uuid:04021998-3B35-2BDB-7B3C-99DA4435DA09
-
-
- urn:schemas-upnp-org:service:LANHostConfigManagement:1
- urn:upnp-org:serviceId:LANHostCfg1
- /x_lanhostconfigmanagement.xml
- /control?LANHostConfigManagement
- /event?LANHostConfigManagement
-
-
-
-
- http://{{listenAddr}}
-
-
- `,
- // The response to our GetNATRSIPStatus call. This
- // particular implementation has a bug where the elements
- // inside u:GetNATRSIPStatusResponse are not properly
- // namespaced.
- "POST /control?WANIPConnection": `
-
-
-
- 0
- 1
-
-
-
- `,
- },
- }
- if err := dev.listen(); err != nil {
- t.Skipf("cannot listen: %v", err)
- }
- dev.serve()
- defer dev.close()
-
- // Attempt to discover the fake device.
- discovered := discoverUPnP()
- if discovered == nil {
- if os.Getenv("CI") != "" {
- t.Fatalf("not discovered")
- } else {
- t.Skipf("UPnP not discovered (known issue, see https://github.com/ethereum/go-ethereum/issues/21476)")
- }
- }
- upnp, _ := discovered.(*upnp)
- if upnp.service != "IGDv1-IP1" {
- t.Errorf("upnp.service mismatch: got %q, want %q", upnp.service, "IGDv1-IP1")
- }
- wantURL := "http://" + dev.listener.Addr().String() + "/InternetGatewayDevice.xml"
- if upnp.dev.URLBaseStr != wantURL {
- t.Errorf("upnp.dev.URLBaseStr mismatch: got %q, want %q", upnp.dev.URLBaseStr, wantURL)
- }
-}
-
-// fakeIGD presents itself as a discoverable UPnP device which sends
-// canned responses to HTTPU and HTTP requests.
-type fakeIGD struct {
- t *testing.T // for logging
-
- listener net.Listener
- mcastListener *net.UDPConn
-
- // This should be a complete HTTP response (including headers).
- // It is sent as the response to any sspd packet. Any occurrence
- // of "{{listenAddr}}" is replaced with the actual TCP listen
- // address of the HTTP server.
- ssdpResp string
- // This one should contain XML payloads for all requests
- // performed. The keys contain method and path, e.g. "GET /foo/bar".
- // As with ssdpResp, "{{listenAddr}}" is replaced with the TCP
- // listen address.
- httpResps map[string]string
-}
-
-// httpu.Handler
-func (dev *fakeIGD) ServeMessage(r *http.Request) {
- dev.t.Logf(`HTTPU request %s %s`, r.Method, r.RequestURI)
- conn, err := net.Dial("udp4", r.RemoteAddr)
- if err != nil {
- fmt.Printf("reply Dial error: %v", err)
- return
- }
- defer conn.Close()
- io.WriteString(conn, dev.replaceListenAddr(dev.ssdpResp))
-}
-
-// http.Handler
-func (dev *fakeIGD) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if resp, ok := dev.httpResps[r.Method+" "+r.RequestURI]; ok {
- dev.t.Logf(`HTTP request "%s %s" --> %d`, r.Method, r.RequestURI, 200)
- io.WriteString(w, dev.replaceListenAddr(resp))
- } else {
- dev.t.Logf(`HTTP request "%s %s" --> %d`, r.Method, r.RequestURI, 404)
- w.WriteHeader(http.StatusNotFound)
- }
-}
-
-func (dev *fakeIGD) replaceListenAddr(resp string) string {
- return strings.ReplaceAll(resp, "{{listenAddr}}", dev.listener.Addr().String())
-}
-
-func (dev *fakeIGD) listen() (err error) {
- if dev.listener, err = net.Listen("tcp", "127.0.0.1:0"); err != nil {
- return err
- }
- laddr := &net.UDPAddr{IP: net.ParseIP("239.255.255.250"), Port: 1900}
- if dev.mcastListener, err = net.ListenMulticastUDP("udp", nil, laddr); err != nil {
- dev.listener.Close()
- return err
- }
- return nil
-}
-
-func (dev *fakeIGD) serve() {
- go httpu.Serve(dev.mcastListener, dev)
- go http.Serve(dev.listener, dev)
-}
-
-func (dev *fakeIGD) close() {
- dev.mcastListener.Close()
- dev.listener.Close()
-}
diff --git a/p2p/netutil/addrutil.go b/p2p/netutil/addrutil.go
deleted file mode 100644
index fb6d8d2731..0000000000
--- a/p2p/netutil/addrutil.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package netutil
-
-import "net"
-
-// AddrIP gets the IP address contained in addr. It returns nil if no address is present.
-func AddrIP(addr net.Addr) net.IP {
- switch a := addr.(type) {
- case *net.IPAddr:
- return a.IP
- case *net.TCPAddr:
- return a.IP
- case *net.UDPAddr:
- return a.IP
- default:
- return nil
- }
-}
diff --git a/p2p/netutil/error.go b/p2p/netutil/error.go
deleted file mode 100644
index 5d3d9bfd65..0000000000
--- a/p2p/netutil/error.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package netutil
-
-// IsTemporaryError checks whether the given error should be considered temporary.
-func IsTemporaryError(err error) bool {
- tempErr, ok := err.(interface {
- Temporary() bool
- })
- return ok && tempErr.Temporary() || isPacketTooBig(err)
-}
-
-// IsTimeout checks whether the given error is a timeout.
-func IsTimeout(err error) bool {
- timeoutErr, ok := err.(interface {
- Timeout() bool
- })
- return ok && timeoutErr.Timeout()
-}
diff --git a/p2p/netutil/error_test.go b/p2p/netutil/error_test.go
deleted file mode 100644
index 84d5c2c206..0000000000
--- a/p2p/netutil/error_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package netutil
-
-import (
- "net"
- "testing"
- "time"
-)
-
-// This test checks that isPacketTooBig correctly identifies
-// errors that result from receiving a UDP packet larger
-// than the supplied receive buffer.
-func TestIsPacketTooBig(t *testing.T) {
- listener, err := net.ListenPacket("udp", "127.0.0.1:0")
- if err != nil {
- t.Fatal(err)
- }
- defer listener.Close()
- sender, err := net.Dial("udp", listener.LocalAddr().String())
- if err != nil {
- t.Fatal(err)
- }
- defer sender.Close()
-
- sendN := 1800
- recvN := 300
- for i := 0; i < 20; i++ {
- go func() {
- buf := make([]byte, sendN)
- for i := range buf {
- buf[i] = byte(i)
- }
- sender.Write(buf)
- }()
-
- buf := make([]byte, recvN)
- listener.SetDeadline(time.Now().Add(1 * time.Second))
- n, _, err := listener.ReadFrom(buf)
- if err != nil {
- if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
- continue
- }
- if !isPacketTooBig(err) {
- t.Fatalf("unexpected read error: %v", err)
- }
- continue
- }
- if n != recvN {
- t.Fatalf("short read: %d, want %d", n, recvN)
- }
- for i := range buf {
- if buf[i] != byte(i) {
- t.Fatalf("error in pattern")
- }
- }
- }
-}
diff --git a/p2p/netutil/iptrack.go b/p2p/netutil/iptrack.go
deleted file mode 100644
index a070499e19..0000000000
--- a/p2p/netutil/iptrack.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package netutil
-
-import (
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-// IPTracker predicts the external endpoint, i.e. IP address and port, of the local host
-// based on statements made by other hosts.
-type IPTracker struct {
- window time.Duration
- contactWindow time.Duration
- minStatements int
- clock mclock.Clock
- statements map[string]ipStatement
- contact map[string]mclock.AbsTime
- lastStatementGC mclock.AbsTime
- lastContactGC mclock.AbsTime
-}
-
-type ipStatement struct {
- endpoint string
- time mclock.AbsTime
-}
-
-// NewIPTracker creates an IP tracker.
-//
-// The window parameters configure the amount of past network events which are kept. The
-// minStatements parameter enforces a minimum number of statements which must be recorded
-// before any prediction is made. Higher values for these parameters decrease 'flapping' of
-// predictions as network conditions change. Window duration values should typically be in
-// the range of minutes.
-func NewIPTracker(window, contactWindow time.Duration, minStatements int) *IPTracker {
- return &IPTracker{
- window: window,
- contactWindow: contactWindow,
- statements: make(map[string]ipStatement),
- minStatements: minStatements,
- contact: make(map[string]mclock.AbsTime),
- clock: mclock.System{},
- }
-}
-
-// PredictFullConeNAT checks whether the local host is behind full cone NAT. It predicts by
-// checking whether any statement has been received from a node we didn't contact before
-// the statement was made.
-func (it *IPTracker) PredictFullConeNAT() bool {
- now := it.clock.Now()
- it.gcContact(now)
- it.gcStatements(now)
- for host, st := range it.statements {
- if c, ok := it.contact[host]; !ok || c > st.time {
- return true
- }
- }
- return false
-}
-
-// PredictEndpoint returns the current prediction of the external endpoint.
-func (it *IPTracker) PredictEndpoint() string {
- it.gcStatements(it.clock.Now())
-
- // The current strategy is simple: find the endpoint with most statements.
- counts := make(map[string]int, len(it.statements))
- maxcount, max := 0, ""
- for _, s := range it.statements {
- c := counts[s.endpoint] + 1
- counts[s.endpoint] = c
- if c > maxcount && c >= it.minStatements {
- maxcount, max = c, s.endpoint
- }
- }
- return max
-}
-
-// AddStatement records that a certain host thinks our external endpoint is the one given.
-func (it *IPTracker) AddStatement(host, endpoint string) {
- now := it.clock.Now()
- it.statements[host] = ipStatement{endpoint, now}
- if time.Duration(now-it.lastStatementGC) >= it.window {
- it.gcStatements(now)
- }
-}
-
-// AddContact records that a packet containing our endpoint information has been sent to a
-// certain host.
-func (it *IPTracker) AddContact(host string) {
- now := it.clock.Now()
- it.contact[host] = now
- if time.Duration(now-it.lastContactGC) >= it.contactWindow {
- it.gcContact(now)
- }
-}
-
-func (it *IPTracker) gcStatements(now mclock.AbsTime) {
- it.lastStatementGC = now
- cutoff := now.Add(-it.window)
- for host, s := range it.statements {
- if s.time < cutoff {
- delete(it.statements, host)
- }
- }
-}
-
-func (it *IPTracker) gcContact(now mclock.AbsTime) {
- it.lastContactGC = now
- cutoff := now.Add(-it.contactWindow)
- for host, ct := range it.contact {
- if ct < cutoff {
- delete(it.contact, host)
- }
- }
-}
diff --git a/p2p/netutil/iptrack_test.go b/p2p/netutil/iptrack_test.go
deleted file mode 100644
index ee3bba861e..0000000000
--- a/p2p/netutil/iptrack_test.go
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package netutil
-
-import (
- crand "crypto/rand"
- "fmt"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-const (
- opStatement = iota
- opContact
- opPredict
- opCheckFullCone
-)
-
-type iptrackTestEvent struct {
- op int
- time int // absolute, in milliseconds
- ip, from string
-}
-
-func TestIPTracker(t *testing.T) {
- tests := map[string][]iptrackTestEvent{
- "minStatements": {
- {opPredict, 0, "", ""},
- {opStatement, 0, "127.0.0.1", "127.0.0.2"},
- {opPredict, 1000, "", ""},
- {opStatement, 1000, "127.0.0.1", "127.0.0.3"},
- {opPredict, 1000, "", ""},
- {opStatement, 1000, "127.0.0.1", "127.0.0.4"},
- {opPredict, 1000, "127.0.0.1", ""},
- },
- "window": {
- {opStatement, 0, "127.0.0.1", "127.0.0.2"},
- {opStatement, 2000, "127.0.0.1", "127.0.0.3"},
- {opStatement, 3000, "127.0.0.1", "127.0.0.4"},
- {opPredict, 10000, "127.0.0.1", ""},
- {opPredict, 10001, "", ""}, // first statement expired
- {opStatement, 10100, "127.0.0.1", "127.0.0.2"},
- {opPredict, 10200, "127.0.0.1", ""},
- },
- "fullcone": {
- {opContact, 0, "", "127.0.0.2"},
- {opStatement, 10, "127.0.0.1", "127.0.0.2"},
- {opContact, 2000, "", "127.0.0.3"},
- {opStatement, 2010, "127.0.0.1", "127.0.0.3"},
- {opContact, 3000, "", "127.0.0.4"},
- {opStatement, 3010, "127.0.0.1", "127.0.0.4"},
- {opCheckFullCone, 3500, "false", ""},
- },
- "fullcone_2": {
- {opContact, 0, "", "127.0.0.2"},
- {opStatement, 10, "127.0.0.1", "127.0.0.2"},
- {opContact, 2000, "", "127.0.0.3"},
- {opStatement, 2010, "127.0.0.1", "127.0.0.3"},
- {opStatement, 3000, "127.0.0.1", "127.0.0.4"},
- {opContact, 3010, "", "127.0.0.4"},
- {opCheckFullCone, 3500, "true", ""},
- },
- }
- for name, test := range tests {
- t.Run(name, func(t *testing.T) { runIPTrackerTest(t, test) })
- }
-}
-
-func runIPTrackerTest(t *testing.T, evs []iptrackTestEvent) {
- var (
- clock mclock.Simulated
- it = NewIPTracker(10*time.Second, 10*time.Second, 3)
- )
- it.clock = &clock
- for i, ev := range evs {
- evtime := time.Duration(ev.time) * time.Millisecond
- clock.Run(evtime - time.Duration(clock.Now()))
- switch ev.op {
- case opStatement:
- it.AddStatement(ev.from, ev.ip)
- case opContact:
- it.AddContact(ev.from)
- case opPredict:
- if pred := it.PredictEndpoint(); pred != ev.ip {
- t.Errorf("op %d: wrong prediction %q, want %q", i, pred, ev.ip)
- }
- case opCheckFullCone:
- pred := fmt.Sprintf("%t", it.PredictFullConeNAT())
- if pred != ev.ip {
- t.Errorf("op %d: wrong prediction %s, want %s", i, pred, ev.ip)
- }
- }
- }
-}
-
-// This checks that old statements and contacts are GCed even if Predict* isn't called.
-func TestIPTrackerForceGC(t *testing.T) {
- var (
- clock mclock.Simulated
- window = 10 * time.Second
- rate = 50 * time.Millisecond
- max = int(window/rate) + 1
- it = NewIPTracker(window, window, 3)
- )
- it.clock = &clock
-
- for i := 0; i < 5*max; i++ {
- e1 := make([]byte, 4)
- e2 := make([]byte, 4)
- crand.Read(e1)
- crand.Read(e2)
- it.AddStatement(string(e1), string(e2))
- it.AddContact(string(e1))
- clock.Run(rate)
- }
- if len(it.contact) > 2*max {
- t.Errorf("contacts not GCed, have %d", len(it.contact))
- }
- if len(it.statements) > 2*max {
- t.Errorf("statements not GCed, have %d", len(it.statements))
- }
-}
diff --git a/p2p/netutil/net.go b/p2p/netutil/net.go
deleted file mode 100644
index d5da3c694f..0000000000
--- a/p2p/netutil/net.go
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package netutil contains extensions to the net package.
-package netutil
-
-import (
- "bytes"
- "errors"
- "fmt"
- "net"
- "sort"
- "strings"
-)
-
-var lan4, lan6, special4, special6 Netlist
-
-func init() {
- // Lists from RFC 5735, RFC 5156,
- // https://www.iana.org/assignments/iana-ipv4-special-registry/
- lan4.Add("0.0.0.0/8") // "This" network
- lan4.Add("10.0.0.0/8") // Private Use
- lan4.Add("172.16.0.0/12") // Private Use
- lan4.Add("192.168.0.0/16") // Private Use
- lan6.Add("fe80::/10") // Link-Local
- lan6.Add("fc00::/7") // Unique-Local
- special4.Add("192.0.0.0/29") // IPv4 Service Continuity
- special4.Add("192.0.0.9/32") // PCP Anycast
- special4.Add("192.0.0.170/32") // NAT64/DNS64 Discovery
- special4.Add("192.0.0.171/32") // NAT64/DNS64 Discovery
- special4.Add("192.0.2.0/24") // TEST-NET-1
- special4.Add("192.31.196.0/24") // AS112
- special4.Add("192.52.193.0/24") // AMT
- special4.Add("192.88.99.0/24") // 6to4 Relay Anycast
- special4.Add("192.175.48.0/24") // AS112
- special4.Add("198.18.0.0/15") // Device Benchmark Testing
- special4.Add("198.51.100.0/24") // TEST-NET-2
- special4.Add("203.0.113.0/24") // TEST-NET-3
- special4.Add("255.255.255.255/32") // Limited Broadcast
-
- // http://www.iana.org/assignments/iana-ipv6-special-registry/
- special6.Add("100::/64")
- special6.Add("2001::/32")
- special6.Add("2001:1::1/128")
- special6.Add("2001:2::/48")
- special6.Add("2001:3::/32")
- special6.Add("2001:4:112::/48")
- special6.Add("2001:5::/32")
- special6.Add("2001:10::/28")
- special6.Add("2001:20::/28")
- special6.Add("2001:db8::/32")
- special6.Add("2002::/16")
-}
-
-// Netlist is a list of IP networks.
-type Netlist []net.IPNet
-
-// ParseNetlist parses a comma-separated list of CIDR masks.
-// Whitespace and extra commas are ignored.
-func ParseNetlist(s string) (*Netlist, error) {
- ws := strings.NewReplacer(" ", "", "\n", "", "\t", "")
- masks := strings.Split(ws.Replace(s), ",")
- l := make(Netlist, 0)
- for _, mask := range masks {
- if mask == "" {
- continue
- }
- _, n, err := net.ParseCIDR(mask)
- if err != nil {
- return nil, err
- }
- l = append(l, *n)
- }
- return &l, nil
-}
-
-// MarshalTOML implements toml.MarshalerRec.
-func (l Netlist) MarshalTOML() interface{} {
- list := make([]string, 0, len(l))
- for _, net := range l {
- list = append(list, net.String())
- }
- return list
-}
-
-// UnmarshalTOML implements toml.UnmarshalerRec.
-func (l *Netlist) UnmarshalTOML(fn func(interface{}) error) error {
- var masks []string
- if err := fn(&masks); err != nil {
- return err
- }
- for _, mask := range masks {
- _, n, err := net.ParseCIDR(mask)
- if err != nil {
- return err
- }
- *l = append(*l, *n)
- }
- return nil
-}
-
-// Add parses a CIDR mask and appends it to the list. It panics for invalid masks and is
-// intended to be used for setting up static lists.
-func (l *Netlist) Add(cidr string) {
- _, n, err := net.ParseCIDR(cidr)
- if err != nil {
- panic(err)
- }
- *l = append(*l, *n)
-}
-
-// Contains reports whether the given IP is contained in the list.
-func (l *Netlist) Contains(ip net.IP) bool {
- if l == nil {
- return false
- }
- for _, net := range *l {
- if net.Contains(ip) {
- return true
- }
- }
- return false
-}
-
-// IsLAN reports whether an IP is a local network address.
-func IsLAN(ip net.IP) bool {
- if ip.IsLoopback() {
- return true
- }
- if v4 := ip.To4(); v4 != nil {
- return lan4.Contains(v4)
- }
- return lan6.Contains(ip)
-}
-
-// IsSpecialNetwork reports whether an IP is located in a special-use network range
-// This includes broadcast, multicast and documentation addresses.
-func IsSpecialNetwork(ip net.IP) bool {
- if ip.IsMulticast() {
- return true
- }
- if v4 := ip.To4(); v4 != nil {
- return special4.Contains(v4)
- }
- return special6.Contains(ip)
-}
-
-var (
- errInvalid = errors.New("invalid IP")
- errUnspecified = errors.New("zero address")
- errSpecial = errors.New("special network")
- errLoopback = errors.New("loopback address from non-loopback host")
- errLAN = errors.New("LAN address from WAN host")
-)
-
-// CheckRelayIP reports whether an IP relayed from the given sender IP
-// is a valid connection target.
-//
-// There are four rules:
-// - Special network addresses are never valid.
-// - Loopback addresses are OK if relayed by a loopback host.
-// - LAN addresses are OK if relayed by a LAN host.
-// - All other addresses are always acceptable.
-func CheckRelayIP(sender, addr net.IP) error {
- if len(addr) != net.IPv4len && len(addr) != net.IPv6len {
- return errInvalid
- }
- if addr.IsUnspecified() {
- return errUnspecified
- }
- if IsSpecialNetwork(addr) {
- return errSpecial
- }
- if addr.IsLoopback() && !sender.IsLoopback() {
- return errLoopback
- }
- if IsLAN(addr) && !IsLAN(sender) {
- return errLAN
- }
- return nil
-}
-
-// SameNet reports whether two IP addresses have an equal prefix of the given bit length.
-func SameNet(bits uint, ip, other net.IP) bool {
- ip4, other4 := ip.To4(), other.To4()
- switch {
- case (ip4 == nil) != (other4 == nil):
- return false
- case ip4 != nil:
- return sameNet(bits, ip4, other4)
- default:
- return sameNet(bits, ip.To16(), other.To16())
- }
-}
-
-func sameNet(bits uint, ip, other net.IP) bool {
- nb := int(bits / 8)
- mask := ^byte(0xFF >> (bits % 8))
- if mask != 0 && nb < len(ip) && ip[nb]&mask != other[nb]&mask {
- return false
- }
- return nb <= len(ip) && ip[:nb].Equal(other[:nb])
-}
-
-// DistinctNetSet tracks IPs, ensuring that at most N of them
-// fall into the same network range.
-type DistinctNetSet struct {
- Subnet uint // number of common prefix bits
- Limit uint // maximum number of IPs in each subnet
-
- members map[string]uint
- buf net.IP
-}
-
-// Add adds an IP address to the set. It returns false (and doesn't add the IP) if the
-// number of existing IPs in the defined range exceeds the limit.
-func (s *DistinctNetSet) Add(ip net.IP) bool {
- key := s.key(ip)
- n := s.members[string(key)]
- if n < s.Limit {
- s.members[string(key)] = n + 1
- return true
- }
- return false
-}
-
-// Remove removes an IP from the set.
-func (s *DistinctNetSet) Remove(ip net.IP) {
- key := s.key(ip)
- if n, ok := s.members[string(key)]; ok {
- if n == 1 {
- delete(s.members, string(key))
- } else {
- s.members[string(key)] = n - 1
- }
- }
-}
-
-// Contains whether the given IP is contained in the set.
-func (s DistinctNetSet) Contains(ip net.IP) bool {
- key := s.key(ip)
- _, ok := s.members[string(key)]
- return ok
-}
-
-// Len returns the number of tracked IPs.
-func (s DistinctNetSet) Len() int {
- n := uint(0)
- for _, i := range s.members {
- n += i
- }
- return int(n)
-}
-
-// key encodes the map key for an address into a temporary buffer.
-//
-// The first byte of key is '4' or '6' to distinguish IPv4/IPv6 address types.
-// The remainder of the key is the IP, truncated to the number of bits.
-func (s *DistinctNetSet) key(ip net.IP) net.IP {
- // Lazily initialize storage.
- if s.members == nil {
- s.members = make(map[string]uint)
- s.buf = make(net.IP, 17)
- }
- // Canonicalize ip and bits.
- typ := byte('6')
- if ip4 := ip.To4(); ip4 != nil {
- typ, ip = '4', ip4
- }
- bits := s.Subnet
- if bits > uint(len(ip)*8) {
- bits = uint(len(ip) * 8)
- }
- // Encode the prefix into s.buf.
- nb := int(bits / 8)
- mask := ^byte(0xFF >> (bits % 8))
- s.buf[0] = typ
- buf := append(s.buf[:1], ip[:nb]...)
- if nb < len(ip) && mask != 0 {
- buf = append(buf, ip[nb]&mask)
- }
- return buf
-}
-
-// String implements fmt.Stringer
-func (s DistinctNetSet) String() string {
- var buf bytes.Buffer
- buf.WriteString("{")
- keys := make([]string, 0, len(s.members))
- for k := range s.members {
- keys = append(keys, k)
- }
- sort.Strings(keys)
- for i, k := range keys {
- var ip net.IP
- if k[0] == '4' {
- ip = make(net.IP, 4)
- } else {
- ip = make(net.IP, 16)
- }
- copy(ip, k[1:])
- fmt.Fprintf(&buf, "%v×%d", ip, s.members[k])
- if i != len(keys)-1 {
- buf.WriteString(" ")
- }
- }
- buf.WriteString("}")
- return buf.String()
-}
diff --git a/p2p/netutil/net_test.go b/p2p/netutil/net_test.go
deleted file mode 100644
index 3a6aa081f2..0000000000
--- a/p2p/netutil/net_test.go
+++ /dev/null
@@ -1,262 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package netutil
-
-import (
- "fmt"
- "net"
- "reflect"
- "testing"
- "testing/quick"
-
- "github.com/davecgh/go-spew/spew"
-)
-
-func TestParseNetlist(t *testing.T) {
- var tests = []struct {
- input string
- wantErr error
- wantList *Netlist
- }{
- {
- input: "",
- wantList: &Netlist{},
- },
- {
- input: "127.0.0.0/8",
- wantErr: nil,
- wantList: &Netlist{{IP: net.IP{127, 0, 0, 0}, Mask: net.CIDRMask(8, 32)}},
- },
- {
- input: "127.0.0.0/44",
- wantErr: &net.ParseError{Type: "CIDR address", Text: "127.0.0.0/44"},
- },
- {
- input: "127.0.0.0/16, 23.23.23.23/24,",
- wantList: &Netlist{
- {IP: net.IP{127, 0, 0, 0}, Mask: net.CIDRMask(16, 32)},
- {IP: net.IP{23, 23, 23, 0}, Mask: net.CIDRMask(24, 32)},
- },
- },
- }
-
- for _, test := range tests {
- l, err := ParseNetlist(test.input)
- if !reflect.DeepEqual(err, test.wantErr) {
- t.Errorf("%q: got error %q, want %q", test.input, err, test.wantErr)
- continue
- }
- if !reflect.DeepEqual(l, test.wantList) {
- spew.Dump(l)
- spew.Dump(test.wantList)
- t.Errorf("%q: got %v, want %v", test.input, l, test.wantList)
- }
- }
-}
-
-func TestNilNetListContains(t *testing.T) {
- var list *Netlist
- checkContains(t, list.Contains, nil, []string{"1.2.3.4"})
-}
-
-func TestIsLAN(t *testing.T) {
- checkContains(t, IsLAN,
- []string{ // included
- "0.0.0.0",
- "0.2.0.8",
- "127.0.0.1",
- "10.0.1.1",
- "10.22.0.3",
- "172.31.252.251",
- "192.168.1.4",
- "fe80::f4a1:8eff:fec5:9d9d",
- "febf::ab32:2233",
- "fc00::4",
- },
- []string{ // excluded
- "192.0.2.1",
- "1.0.0.0",
- "172.32.0.1",
- "fec0::2233",
- },
- )
-}
-
-func TestIsSpecialNetwork(t *testing.T) {
- checkContains(t, IsSpecialNetwork,
- []string{ // included
- "192.0.2.1",
- "192.0.2.44",
- "2001:db8:85a3:8d3:1319:8a2e:370:7348",
- "255.255.255.255",
- "224.0.0.22", // IPv4 multicast
- "ff05::1:3", // IPv6 multicast
- },
- []string{ // excluded
- "192.0.3.1",
- "1.0.0.0",
- "172.32.0.1",
- "fec0::2233",
- },
- )
-}
-
-func checkContains(t *testing.T, fn func(net.IP) bool, inc, exc []string) {
- for _, s := range inc {
- if !fn(parseIP(s)) {
- t.Error("returned false for included address", s)
- }
- }
- for _, s := range exc {
- if fn(parseIP(s)) {
- t.Error("returned true for excluded address", s)
- }
- }
-}
-
-func parseIP(s string) net.IP {
- ip := net.ParseIP(s)
- if ip == nil {
- panic("invalid " + s)
- }
- return ip
-}
-
-func TestCheckRelayIP(t *testing.T) {
- tests := []struct {
- sender, addr string
- want error
- }{
- {"127.0.0.1", "0.0.0.0", errUnspecified},
- {"192.168.0.1", "0.0.0.0", errUnspecified},
- {"23.55.1.242", "0.0.0.0", errUnspecified},
- {"127.0.0.1", "255.255.255.255", errSpecial},
- {"192.168.0.1", "255.255.255.255", errSpecial},
- {"23.55.1.242", "255.255.255.255", errSpecial},
- {"192.168.0.1", "127.0.2.19", errLoopback},
- {"23.55.1.242", "192.168.0.1", errLAN},
-
- {"127.0.0.1", "127.0.2.19", nil},
- {"127.0.0.1", "192.168.0.1", nil},
- {"127.0.0.1", "23.55.1.242", nil},
- {"192.168.0.1", "192.168.0.1", nil},
- {"192.168.0.1", "23.55.1.242", nil},
- {"23.55.1.242", "23.55.1.242", nil},
- }
-
- for _, test := range tests {
- err := CheckRelayIP(parseIP(test.sender), parseIP(test.addr))
- if err != test.want {
- t.Errorf("%s from %s: got %q, want %q", test.addr, test.sender, err, test.want)
- }
- }
-}
-
-func BenchmarkCheckRelayIP(b *testing.B) {
- sender := parseIP("23.55.1.242")
- addr := parseIP("23.55.1.2")
- for i := 0; i < b.N; i++ {
- CheckRelayIP(sender, addr)
- }
-}
-
-func TestSameNet(t *testing.T) {
- tests := []struct {
- ip, other string
- bits uint
- want bool
- }{
- {"0.0.0.0", "0.0.0.0", 32, true},
- {"0.0.0.0", "0.0.0.1", 0, true},
- {"0.0.0.0", "0.0.0.1", 31, true},
- {"0.0.0.0", "0.0.0.1", 32, false},
- {"0.33.0.1", "0.34.0.2", 8, true},
- {"0.33.0.1", "0.34.0.2", 13, true},
- {"0.33.0.1", "0.34.0.2", 15, false},
- }
-
- for _, test := range tests {
- if ok := SameNet(test.bits, parseIP(test.ip), parseIP(test.other)); ok != test.want {
- t.Errorf("SameNet(%d, %s, %s) == %t, want %t", test.bits, test.ip, test.other, ok, test.want)
- }
- }
-}
-
-func ExampleSameNet() {
- // This returns true because the IPs are in the same /24 network:
- fmt.Println(SameNet(24, net.IP{127, 0, 0, 1}, net.IP{127, 0, 0, 3}))
- // This call returns false:
- fmt.Println(SameNet(24, net.IP{127, 3, 0, 1}, net.IP{127, 5, 0, 3}))
- // Output:
- // true
- // false
-}
-
-func TestDistinctNetSet(t *testing.T) {
- ops := []struct {
- add, remove string
- fails bool
- }{
- {add: "127.0.0.1"},
- {add: "127.0.0.2"},
- {add: "127.0.0.3", fails: true},
- {add: "127.32.0.1"},
- {add: "127.32.0.2"},
- {add: "127.32.0.3", fails: true},
- {add: "127.33.0.1", fails: true},
- {add: "127.34.0.1"},
- {add: "127.34.0.2"},
- {add: "127.34.0.3", fails: true},
- // Make room for an address, then add again.
- {remove: "127.0.0.1"},
- {add: "127.0.0.3"},
- {add: "127.0.0.3", fails: true},
- }
-
- set := DistinctNetSet{Subnet: 15, Limit: 2}
- for _, op := range ops {
- var desc string
- if op.add != "" {
- desc = fmt.Sprintf("Add(%s)", op.add)
- if ok := set.Add(parseIP(op.add)); ok != !op.fails {
- t.Errorf("%s == %t, want %t", desc, ok, !op.fails)
- }
- } else {
- desc = fmt.Sprintf("Remove(%s)", op.remove)
- set.Remove(parseIP(op.remove))
- }
- t.Logf("%s: %v", desc, set)
- }
-}
-
-func TestDistinctNetSetAddRemove(t *testing.T) {
- cfg := &quick.Config{}
- fn := func(ips []net.IP) bool {
- s := DistinctNetSet{Limit: 3, Subnet: 2}
- for _, ip := range ips {
- s.Add(ip)
- }
- for _, ip := range ips {
- s.Remove(ip)
- }
- return s.Len() == 0
- }
-
- if err := quick.Check(fn, cfg); err != nil {
- t.Fatal(err)
- }
-}
diff --git a/p2p/netutil/toobig_notwindows.go b/p2p/netutil/toobig_notwindows.go
deleted file mode 100644
index f9f936ae56..0000000000
--- a/p2p/netutil/toobig_notwindows.go
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-//go:build !windows
-// +build !windows
-
-package netutil
-
-// isPacketTooBig reports whether err indicates that a UDP packet didn't
-// fit the receive buffer. There is no such error on
-// non-Windows platforms.
-func isPacketTooBig(err error) bool {
- return false
-}
diff --git a/p2p/netutil/toobig_windows.go b/p2p/netutil/toobig_windows.go
deleted file mode 100644
index 652903e83c..0000000000
--- a/p2p/netutil/toobig_windows.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-//go:build windows
-// +build windows
-
-package netutil
-
-import (
- "net"
- "os"
- "syscall"
-)
-
-const _WSAEMSGSIZE = syscall.Errno(10040)
-
-// isPacketTooBig reports whether err indicates that a UDP packet didn't
-// fit the receive buffer. On Windows, WSARecvFrom returns
-// code WSAEMSGSIZE and no data if this happens.
-func isPacketTooBig(err error) bool {
- if opErr, ok := err.(*net.OpError); ok {
- if scErr, ok := opErr.Err.(*os.SyscallError); ok {
- return scErr.Err == _WSAEMSGSIZE
- }
- return opErr.Err == _WSAEMSGSIZE
- }
- return false
-}
diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go
deleted file mode 100644
index 1e1757559c..0000000000
--- a/p2p/nodestate/nodestate.go
+++ /dev/null
@@ -1,1023 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package nodestate
-
-import (
- "errors"
- "reflect"
- "sync"
- "time"
- "unsafe"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-var (
- ErrInvalidField = errors.New("invalid field type")
- ErrClosed = errors.New("already closed")
-)
-
-type (
- // NodeStateMachine implements a network node-related event subscription system.
- // It can assign binary state flags and fields of arbitrary type to each node and allows
- // subscriptions to flag/field changes which can also modify further flags and fields,
- // potentially triggering further subscriptions. An operation includes an initial change
- // and all resulting subsequent changes and always ends in a consistent global state.
- // It is initiated by a "top level" SetState/SetField call that blocks (also blocking other
- // top-level functions) until the operation is finished. Callbacks making further changes
- // should use the non-blocking SetStateSub/SetFieldSub functions. The tree of events
- // resulting from the initial changes is traversed in a breadth-first order, ensuring for
- // each subscription callback that all other callbacks caused by the same change triggering
- // the current callback are processed before anything is triggered by the changes made in the
- // current callback. In practice this logic ensures that all subscriptions "see" events in
- // the logical order, callbacks are never called concurrently and "back and forth" effects
- // are also possible. The state machine design should ensure that infinite event cycles
- // cannot happen.
- // The caller can also add timeouts assigned to a certain node and a subset of state flags.
- // If the timeout elapses, the flags are reset. If all relevant flags are reset then the timer
- // is dropped. State flags with no timeout are persisted in the database if the flag
- // descriptor enables saving. If a node has no state flags set at any moment then it is discarded.
- // Note: in order to avoid mutex deadlocks the callbacks should never lock a mutex that
- // might be locked when the top level SetState/SetField functions are called. If a function
- // potentially performs state/field changes then it is recommended to mention this fact in the
- // function description, along with whether it should run inside an operation callback.
- NodeStateMachine struct {
- started, closed bool
- lock sync.Mutex
- clock mclock.Clock
- db ethdb.KeyValueStore
- dbNodeKey []byte
- nodes map[enode.ID]*nodeInfo
- offlineCallbackList []offlineCallback
- opFlag bool // an operation has started
- opWait *sync.Cond // signaled when the operation ends
- opPending []func() // pending callback list of the current operation
-
- // Registered state flags or fields. Modifications are allowed
- // only when the node state machine has not been started.
- setup *Setup
- fields []*fieldInfo
- saveFlags bitMask
-
- // Installed callbacks. Modifications are allowed only when the
- // node state machine has not been started.
- stateSubs []stateSub
-
- // Testing hooks, only for testing purposes.
- saveNodeHook func(*nodeInfo)
- }
-
- // Flags represents a set of flags from a certain setup
- Flags struct {
- mask bitMask
- setup *Setup
- }
-
- // Field represents a field from a certain setup
- Field struct {
- index int
- setup *Setup
- }
-
- // flagDefinition describes a node state flag. Each registered instance is automatically
- // mapped to a bit of the 64 bit node states.
- // If persistent is true then the node is saved when state machine is shutdown.
- flagDefinition struct {
- name string
- persistent bool
- }
-
- // fieldDefinition describes an optional node field of the given type. The contents
- // of the field are only retained for each node as long as at least one of the
- // state flags is set.
- fieldDefinition struct {
- name string
- ftype reflect.Type
- encode func(interface{}) ([]byte, error)
- decode func([]byte) (interface{}, error)
- }
-
- // Setup contains the list of flags and fields used by the application
- Setup struct {
- Version uint
- flags []flagDefinition
- fields []fieldDefinition
- }
-
- // bitMask describes a node state or state mask. It represents a subset
- // of node flags with each bit assigned to a flag index (LSB represents flag 0).
- bitMask uint64
-
- // StateCallback is a subscription callback which is called when one of the
- // state flags that is included in the subscription state mask is changed.
- // Note: oldState and newState are also masked with the subscription mask so only
- // the relevant bits are included.
- StateCallback func(n *enode.Node, oldState, newState Flags)
-
- // FieldCallback is a subscription callback which is called when the value of
- // a specific field is changed.
- FieldCallback func(n *enode.Node, state Flags, oldValue, newValue interface{})
-
- // nodeInfo contains node state, fields and state timeouts
- nodeInfo struct {
- node *enode.Node
- state bitMask
- timeouts []*nodeStateTimeout
- fields []interface{}
- fieldCount int
- db, dirty bool
- }
-
- nodeInfoEnc struct {
- Enr enr.Record
- Version uint
- State bitMask
- Fields [][]byte
- }
-
- stateSub struct {
- mask bitMask
- callback StateCallback
- }
-
- nodeStateTimeout struct {
- mask bitMask
- timer mclock.Timer
- }
-
- fieldInfo struct {
- fieldDefinition
- subs []FieldCallback
- }
-
- offlineCallback struct {
- node *nodeInfo
- state bitMask
- fields []interface{}
- }
-)
-
-// offlineState is a special state that is assumed to be set before a node is loaded from
-// the database and after it is shut down.
-const offlineState = bitMask(1)
-
-// NewFlag creates a new node state flag
-func (s *Setup) NewFlag(name string) Flags {
- if s.flags == nil {
- s.flags = []flagDefinition{{name: "offline"}}
- }
- f := Flags{mask: bitMask(1) << uint(len(s.flags)), setup: s}
- s.flags = append(s.flags, flagDefinition{name: name})
- return f
-}
-
-// NewPersistentFlag creates a new persistent node state flag
-func (s *Setup) NewPersistentFlag(name string) Flags {
- if s.flags == nil {
- s.flags = []flagDefinition{{name: "offline"}}
- }
- f := Flags{mask: bitMask(1) << uint(len(s.flags)), setup: s}
- s.flags = append(s.flags, flagDefinition{name: name, persistent: true})
- return f
-}
-
-// OfflineFlag returns the system-defined offline flag belonging to the given setup
-func (s *Setup) OfflineFlag() Flags {
- return Flags{mask: offlineState, setup: s}
-}
-
-// NewField creates a new node state field
-func (s *Setup) NewField(name string, ftype reflect.Type) Field {
- f := Field{index: len(s.fields), setup: s}
- s.fields = append(s.fields, fieldDefinition{
- name: name,
- ftype: ftype,
- })
- return f
-}
-
-// NewPersistentField creates a new persistent node field
-func (s *Setup) NewPersistentField(name string, ftype reflect.Type, encode func(interface{}) ([]byte, error), decode func([]byte) (interface{}, error)) Field {
- f := Field{index: len(s.fields), setup: s}
- s.fields = append(s.fields, fieldDefinition{
- name: name,
- ftype: ftype,
- encode: encode,
- decode: decode,
- })
- return f
-}
-
-// flagOp implements binary flag operations and also checks whether the operands belong to the same setup
-func flagOp(a, b Flags, trueIfA, trueIfB, trueIfBoth bool) Flags {
- if a.setup == nil {
- if a.mask != 0 {
- panic("Node state flags have no setup reference")
- }
- a.setup = b.setup
- }
- if b.setup == nil {
- if b.mask != 0 {
- panic("Node state flags have no setup reference")
- }
- b.setup = a.setup
- }
- if a.setup != b.setup {
- panic("Node state flags belong to a different setup")
- }
- res := Flags{setup: a.setup}
- if trueIfA {
- res.mask |= a.mask & ^b.mask
- }
- if trueIfB {
- res.mask |= b.mask & ^a.mask
- }
- if trueIfBoth {
- res.mask |= a.mask & b.mask
- }
- return res
-}
-
-// And returns the set of flags present in both a and b
-func (a Flags) And(b Flags) Flags { return flagOp(a, b, false, false, true) }
-
-// AndNot returns the set of flags present in a but not in b
-func (a Flags) AndNot(b Flags) Flags { return flagOp(a, b, true, false, false) }
-
-// Or returns the set of flags present in either a or b
-func (a Flags) Or(b Flags) Flags { return flagOp(a, b, true, true, true) }
-
-// Xor returns the set of flags present in either a or b but not both
-func (a Flags) Xor(b Flags) Flags { return flagOp(a, b, true, true, false) }
-
-// HasAll returns true if b is a subset of a
-func (a Flags) HasAll(b Flags) bool { return flagOp(a, b, false, true, false).mask == 0 }
-
-// HasNone returns true if a and b have no shared flags
-func (a Flags) HasNone(b Flags) bool { return flagOp(a, b, false, false, true).mask == 0 }
-
-// Equals returns true if a and b have the same flags set
-func (a Flags) Equals(b Flags) bool { return flagOp(a, b, true, true, false).mask == 0 }
-
-// IsEmpty returns true if a has no flags set
-func (a Flags) IsEmpty() bool { return a.mask == 0 }
-
-// MergeFlags merges multiple sets of state flags
-func MergeFlags(list ...Flags) Flags {
- if len(list) == 0 {
- return Flags{}
- }
- res := list[0]
- for i := 1; i < len(list); i++ {
- res = res.Or(list[i])
- }
- return res
-}
-
-// String returns a list of the names of the flags specified in the bit mask
-func (f Flags) String() string {
- if f.mask == 0 {
- return "[]"
- }
- s := "["
- comma := false
- for index, flag := range f.setup.flags {
- if f.mask&(bitMask(1)< 8*int(unsafe.Sizeof(bitMask(0))) {
- panic("Too many node state flags")
- }
- ns := &NodeStateMachine{
- db: db,
- dbNodeKey: dbKey,
- clock: clock,
- setup: setup,
- nodes: make(map[enode.ID]*nodeInfo),
- fields: make([]*fieldInfo, len(setup.fields)),
- }
- ns.opWait = sync.NewCond(&ns.lock)
- stateNameMap := make(map[string]int, len(setup.flags))
- for index, flag := range setup.flags {
- if _, ok := stateNameMap[flag.name]; ok {
- panic("Node state flag name collision: " + flag.name)
- }
- stateNameMap[flag.name] = index
- if flag.persistent {
- ns.saveFlags |= bitMask(1) << uint(index)
- }
- }
- fieldNameMap := make(map[string]int, len(setup.fields))
- for index, field := range setup.fields {
- if _, ok := fieldNameMap[field.name]; ok {
- panic("Node field name collision: " + field.name)
- }
- ns.fields[index] = &fieldInfo{fieldDefinition: field}
- fieldNameMap[field.name] = index
- }
- return ns
-}
-
-// stateMask checks whether the set of flags belongs to the same setup and returns its internal bit mask
-func (ns *NodeStateMachine) stateMask(flags Flags) bitMask {
- if flags.setup != ns.setup && flags.mask != 0 {
- panic("Node state flags belong to a different setup")
- }
- return flags.mask
-}
-
-// fieldIndex checks whether the field belongs to the same setup and returns its internal index
-func (ns *NodeStateMachine) fieldIndex(field Field) int {
- if field.setup != ns.setup {
- panic("Node field belongs to a different setup")
- }
- return field.index
-}
-
-// SubscribeState adds a node state subscription. The callback is called while the state
-// machine mutex is not held and it is allowed to make further state updates using the
-// non-blocking SetStateSub/SetFieldSub functions. All callbacks of an operation are running
-// from the thread/goroutine of the initial caller and parallel operations are not permitted.
-// Therefore the callback is never called concurrently. It is the responsibility of the
-// implemented state logic to avoid deadlocks and to reach a stable state in a finite amount
-// of steps.
-// State subscriptions should be installed before loading the node database or making the
-// first state update.
-func (ns *NodeStateMachine) SubscribeState(flags Flags, callback StateCallback) {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- if ns.started {
- panic("state machine already started")
- }
- ns.stateSubs = append(ns.stateSubs, stateSub{ns.stateMask(flags), callback})
-}
-
-// SubscribeField adds a node field subscription. Same rules apply as for SubscribeState.
-func (ns *NodeStateMachine) SubscribeField(field Field, callback FieldCallback) {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- if ns.started {
- panic("state machine already started")
- }
- f := ns.fields[ns.fieldIndex(field)]
- f.subs = append(f.subs, callback)
-}
-
-// newNode creates a new nodeInfo
-func (ns *NodeStateMachine) newNode(n *enode.Node) *nodeInfo {
- return &nodeInfo{node: n, fields: make([]interface{}, len(ns.fields))}
-}
-
-// checkStarted checks whether the state machine has already been started and panics otherwise.
-func (ns *NodeStateMachine) checkStarted() {
- if !ns.started {
- panic("state machine not started yet")
- }
-}
-
-// Start starts the state machine, enabling state and field operations and disabling
-// further subscriptions.
-func (ns *NodeStateMachine) Start() {
- ns.lock.Lock()
- if ns.started {
- panic("state machine already started")
- }
- ns.started = true
- if ns.db != nil {
- ns.loadFromDb()
- }
-
- ns.opStart()
- ns.offlineCallbacks(true)
- ns.opFinish()
- ns.lock.Unlock()
-}
-
-// Stop stops the state machine and saves its state if a database was supplied
-func (ns *NodeStateMachine) Stop() {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- ns.checkStarted()
- if !ns.opStart() {
- panic("already closed")
- }
- for _, node := range ns.nodes {
- fields := make([]interface{}, len(node.fields))
- copy(fields, node.fields)
- ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node, node.state, fields})
- }
- if ns.db != nil {
- ns.saveToDb()
- }
- ns.offlineCallbacks(false)
- ns.closed = true
- ns.opFinish()
-}
-
-// loadFromDb loads persisted node states from the database
-func (ns *NodeStateMachine) loadFromDb() {
- it := ns.db.NewIterator(ns.dbNodeKey, nil)
- for it.Next() {
- var id enode.ID
- if len(it.Key()) != len(ns.dbNodeKey)+len(id) {
- log.Error("Node state db entry with invalid length", "found", len(it.Key()), "expected", len(ns.dbNodeKey)+len(id))
- continue
- }
- copy(id[:], it.Key()[len(ns.dbNodeKey):])
- ns.decodeNode(id, it.Value())
- }
-}
-
-type dummyIdentity enode.ID
-
-func (id dummyIdentity) Verify(r *enr.Record, sig []byte) error { return nil }
-func (id dummyIdentity) NodeAddr(r *enr.Record) []byte { return id[:] }
-
-// decodeNode decodes a node database entry and adds it to the node set if successful
-func (ns *NodeStateMachine) decodeNode(id enode.ID, data []byte) {
- var enc nodeInfoEnc
- if err := rlp.DecodeBytes(data, &enc); err != nil {
- log.Error("Failed to decode node info", "id", id, "error", err)
- return
- }
- n, _ := enode.New(dummyIdentity(id), &enc.Enr)
- node := ns.newNode(n)
- node.db = true
-
- if enc.Version != ns.setup.Version {
- log.Debug("Removing stored node with unknown version", "current", ns.setup.Version, "stored", enc.Version)
- ns.deleteNode(id)
- return
- }
- if len(enc.Fields) > len(ns.setup.fields) {
- log.Error("Invalid node field count", "id", id, "stored", len(enc.Fields))
- return
- }
- // Resolve persisted node fields
- for i, encField := range enc.Fields {
- if len(encField) == 0 {
- continue
- }
- if decode := ns.fields[i].decode; decode != nil {
- if field, err := decode(encField); err == nil {
- node.fields[i] = field
- node.fieldCount++
- } else {
- log.Error("Failed to decode node field", "id", id, "field name", ns.fields[i].name, "error", err)
- return
- }
- } else {
- log.Error("Cannot decode node field", "id", id, "field name", ns.fields[i].name)
- return
- }
- }
- // It's a compatible node record, add it to set.
- ns.nodes[id] = node
- node.state = enc.State
- fields := make([]interface{}, len(node.fields))
- copy(fields, node.fields)
- ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node, node.state, fields})
- log.Debug("Loaded node state", "id", id, "state", Flags{mask: enc.State, setup: ns.setup})
-}
-
-// saveNode saves the given node info to the database
-func (ns *NodeStateMachine) saveNode(id enode.ID, node *nodeInfo) error {
- if ns.db == nil {
- return nil
- }
-
- storedState := node.state & ns.saveFlags
- for _, t := range node.timeouts {
- storedState &= ^t.mask
- }
- enc := nodeInfoEnc{
- Enr: *node.node.Record(),
- Version: ns.setup.Version,
- State: storedState,
- Fields: make([][]byte, len(ns.fields)),
- }
- log.Debug("Saved node state", "id", id, "state", Flags{mask: enc.State, setup: ns.setup})
- lastIndex := -1
- for i, f := range node.fields {
- if f == nil {
- continue
- }
- encode := ns.fields[i].encode
- if encode == nil {
- continue
- }
- blob, err := encode(f)
- if err != nil {
- return err
- }
- enc.Fields[i] = blob
- lastIndex = i
- }
- if storedState == 0 && lastIndex == -1 {
- if node.db {
- node.db = false
- ns.deleteNode(id)
- }
- node.dirty = false
- return nil
- }
- enc.Fields = enc.Fields[:lastIndex+1]
- data, err := rlp.EncodeToBytes(&enc)
- if err != nil {
- return err
- }
- if err := ns.db.Put(append(ns.dbNodeKey, id[:]...), data); err != nil {
- return err
- }
- node.dirty, node.db = false, true
-
- if ns.saveNodeHook != nil {
- ns.saveNodeHook(node)
- }
- return nil
-}
-
-// deleteNode removes a node info from the database
-func (ns *NodeStateMachine) deleteNode(id enode.ID) {
- ns.db.Delete(append(ns.dbNodeKey, id[:]...))
-}
-
-// saveToDb saves the persistent flags and fields of all nodes that have been changed
-func (ns *NodeStateMachine) saveToDb() {
- for id, node := range ns.nodes {
- if node.dirty {
- err := ns.saveNode(id, node)
- if err != nil {
- log.Error("Failed to save node", "id", id, "error", err)
- }
- }
- }
-}
-
-// updateEnode updates the enode entry belonging to the given node if it already exists
-func (ns *NodeStateMachine) updateEnode(n *enode.Node) (enode.ID, *nodeInfo) {
- id := n.ID()
- node := ns.nodes[id]
- if node != nil && n.Seq() > node.node.Seq() {
- node.node = n
- node.dirty = true
- }
- return id, node
-}
-
-// Persist saves the persistent state and fields of the given node immediately
-func (ns *NodeStateMachine) Persist(n *enode.Node) error {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- ns.checkStarted()
- if id, node := ns.updateEnode(n); node != nil && node.dirty {
- err := ns.saveNode(id, node)
- if err != nil {
- log.Error("Failed to save node", "id", id, "error", err)
- }
- return err
- }
- return nil
-}
-
-// SetState updates the given node state flags and blocks until the operation is finished.
-// If a flag with a timeout is set again, the operation removes or replaces the existing timeout.
-func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) error {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- if !ns.opStart() {
- return ErrClosed
- }
- ns.setState(n, setFlags, resetFlags, timeout)
- ns.opFinish()
- return nil
-}
-
-// SetStateSub updates the given node state flags without blocking (should be called
-// from a subscription/operation callback).
-func (ns *NodeStateMachine) SetStateSub(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- ns.opCheck()
- ns.setState(n, setFlags, resetFlags, timeout)
-}
-
-func (ns *NodeStateMachine) setState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) {
- ns.checkStarted()
- set, reset := ns.stateMask(setFlags), ns.stateMask(resetFlags)
- id, node := ns.updateEnode(n)
- if node == nil {
- if set == 0 {
- return
- }
- node = ns.newNode(n)
- ns.nodes[id] = node
- }
- oldState := node.state
- newState := (node.state & (^reset)) | set
- changed := oldState ^ newState
- node.state = newState
-
- // Remove the timeout callbacks for all reset and set flags,
- // even they are not existent(it's noop).
- ns.removeTimeouts(node, set|reset)
-
- // Register the timeout callback if required
- if timeout != 0 && set != 0 {
- ns.addTimeout(n, set, timeout)
- }
- if newState == oldState {
- return
- }
- if newState == 0 && node.fieldCount == 0 {
- delete(ns.nodes, id)
- if node.db {
- ns.deleteNode(id)
- }
- } else {
- if changed&ns.saveFlags != 0 {
- node.dirty = true
- }
- }
- callback := func() {
- for _, sub := range ns.stateSubs {
- if changed&sub.mask != 0 {
- sub.callback(n, Flags{mask: oldState & sub.mask, setup: ns.setup}, Flags{mask: newState & sub.mask, setup: ns.setup})
- }
- }
- }
- ns.opPending = append(ns.opPending, callback)
-}
-
-// opCheck checks whether an operation is active
-func (ns *NodeStateMachine) opCheck() {
- if !ns.opFlag {
- panic("Operation has not started")
- }
-}
-
-// opStart waits until other operations are finished and starts a new one
-func (ns *NodeStateMachine) opStart() bool {
- for ns.opFlag {
- ns.opWait.Wait()
- }
- if ns.closed {
- return false
- }
- ns.opFlag = true
- return true
-}
-
-// opFinish finishes the current operation by running all pending callbacks.
-// Callbacks resulting from a state/field change performed in a previous callback are always
-// put at the end of the pending list and therefore processed after all callbacks resulting
-// from the previous state/field change.
-func (ns *NodeStateMachine) opFinish() {
- for len(ns.opPending) != 0 {
- list := ns.opPending
- ns.lock.Unlock()
- for _, cb := range list {
- cb()
- }
- ns.lock.Lock()
- ns.opPending = ns.opPending[len(list):]
- }
- ns.opPending = nil
- ns.opFlag = false
- ns.opWait.Broadcast()
-}
-
-// Operation calls the given function as an operation callback. This allows the caller
-// to start an operation with multiple initial changes. The same rules apply as for
-// subscription callbacks.
-func (ns *NodeStateMachine) Operation(fn func()) error {
- ns.lock.Lock()
- started := ns.opStart()
- ns.lock.Unlock()
- if !started {
- return ErrClosed
- }
- fn()
- ns.lock.Lock()
- ns.opFinish()
- ns.lock.Unlock()
- return nil
-}
-
-// offlineCallbacks calls state update callbacks at startup or shutdown
-func (ns *NodeStateMachine) offlineCallbacks(start bool) {
- for _, cb := range ns.offlineCallbackList {
- cb := cb
- callback := func() {
- for _, sub := range ns.stateSubs {
- offState := offlineState & sub.mask
- onState := cb.state & sub.mask
- if offState == onState {
- continue
- }
- if start {
- sub.callback(cb.node.node, Flags{mask: offState, setup: ns.setup}, Flags{mask: onState, setup: ns.setup})
- } else {
- sub.callback(cb.node.node, Flags{mask: onState, setup: ns.setup}, Flags{mask: offState, setup: ns.setup})
- }
- }
- for i, f := range cb.fields {
- if f == nil || ns.fields[i].subs == nil {
- continue
- }
- for _, fsub := range ns.fields[i].subs {
- if start {
- fsub(cb.node.node, Flags{mask: offlineState, setup: ns.setup}, nil, f)
- } else {
- fsub(cb.node.node, Flags{mask: offlineState, setup: ns.setup}, f, nil)
- }
- }
- }
- }
- ns.opPending = append(ns.opPending, callback)
- }
- ns.offlineCallbackList = nil
-}
-
-// AddTimeout adds a node state timeout associated to the given state flag(s).
-// After the specified time interval, the relevant states will be reset.
-func (ns *NodeStateMachine) AddTimeout(n *enode.Node, flags Flags, timeout time.Duration) error {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- ns.checkStarted()
- if ns.closed {
- return ErrClosed
- }
- ns.addTimeout(n, ns.stateMask(flags), timeout)
- return nil
-}
-
-// addTimeout adds a node state timeout associated to the given state flag(s).
-func (ns *NodeStateMachine) addTimeout(n *enode.Node, mask bitMask, timeout time.Duration) {
- _, node := ns.updateEnode(n)
- if node == nil {
- return
- }
- mask &= node.state
- if mask == 0 {
- return
- }
- ns.removeTimeouts(node, mask)
- t := &nodeStateTimeout{mask: mask}
- t.timer = ns.clock.AfterFunc(timeout, func() {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- if !ns.opStart() {
- return
- }
- ns.setState(n, Flags{}, Flags{mask: t.mask, setup: ns.setup}, 0)
- ns.opFinish()
- })
- node.timeouts = append(node.timeouts, t)
- if mask&ns.saveFlags != 0 {
- node.dirty = true
- }
-}
-
-// removeTimeout removes node state timeouts associated to the given state flag(s).
-// If a timeout was associated to multiple flags which are not all included in the
-// specified remove mask then only the included flags are de-associated and the timer
-// stays active.
-func (ns *NodeStateMachine) removeTimeouts(node *nodeInfo, mask bitMask) {
- for i := 0; i < len(node.timeouts); i++ {
- t := node.timeouts[i]
- match := t.mask & mask
- if match == 0 {
- continue
- }
- t.mask -= match
- if t.mask != 0 {
- continue
- }
- t.timer.Stop()
- node.timeouts[i] = node.timeouts[len(node.timeouts)-1]
- node.timeouts = node.timeouts[:len(node.timeouts)-1]
- i--
- if match&ns.saveFlags != 0 {
- node.dirty = true
- }
- }
-}
-
-// GetField retrieves the given field of the given node. Note that when used in a
-// subscription callback the result can be out of sync with the state change represented
-// by the callback parameters so extra safety checks might be necessary.
-func (ns *NodeStateMachine) GetField(n *enode.Node, field Field) interface{} {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- ns.checkStarted()
- if ns.closed {
- return nil
- }
- if _, node := ns.updateEnode(n); node != nil {
- return node.fields[ns.fieldIndex(field)]
- }
- return nil
-}
-
-// GetState retrieves the current state of the given node. Note that when used in a
-// subscription callback the result can be out of sync with the state change represented
-// by the callback parameters so extra safety checks might be necessary.
-func (ns *NodeStateMachine) GetState(n *enode.Node) Flags {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- ns.checkStarted()
- if ns.closed {
- return Flags{}
- }
- if _, node := ns.updateEnode(n); node != nil {
- return Flags{mask: node.state, setup: ns.setup}
- }
- return Flags{}
-}
-
-// SetField sets the given field of the given node and blocks until the operation is finished
-func (ns *NodeStateMachine) SetField(n *enode.Node, field Field, value interface{}) error {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- if !ns.opStart() {
- return ErrClosed
- }
- err := ns.setField(n, field, value)
- ns.opFinish()
- return err
-}
-
-// SetFieldSub sets the given field of the given node without blocking (should be called
-// from a subscription/operation callback).
-func (ns *NodeStateMachine) SetFieldSub(n *enode.Node, field Field, value interface{}) error {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- ns.opCheck()
- return ns.setField(n, field, value)
-}
-
-func (ns *NodeStateMachine) setField(n *enode.Node, field Field, value interface{}) error {
- ns.checkStarted()
- id, node := ns.updateEnode(n)
- if node == nil {
- if value == nil {
- return nil
- }
- node = ns.newNode(n)
- ns.nodes[id] = node
- }
- fieldIndex := ns.fieldIndex(field)
- f := ns.fields[fieldIndex]
- if value != nil && reflect.TypeOf(value) != f.ftype {
- log.Error("Invalid field type", "type", reflect.TypeOf(value), "required", f.ftype)
- return ErrInvalidField
- }
- oldValue := node.fields[fieldIndex]
- if value == oldValue {
- return nil
- }
- if oldValue != nil {
- node.fieldCount--
- }
- if value != nil {
- node.fieldCount++
- }
- node.fields[fieldIndex] = value
- if node.state == 0 && node.fieldCount == 0 {
- delete(ns.nodes, id)
- if node.db {
- ns.deleteNode(id)
- }
- } else {
- if f.encode != nil {
- node.dirty = true
- }
- }
- state := node.state
- callback := func() {
- for _, cb := range f.subs {
- cb(n, Flags{mask: state, setup: ns.setup}, oldValue, value)
- }
- }
- ns.opPending = append(ns.opPending, callback)
- return nil
-}
-
-// ForEach calls the callback for each node having all of the required and none of the
-// disabled flags set.
-// Note that this callback is not an operation callback but ForEach can be called from an
-// Operation callback or Operation can also be called from a ForEach callback if necessary.
-func (ns *NodeStateMachine) ForEach(requireFlags, disableFlags Flags, cb func(n *enode.Node, state Flags)) {
- ns.lock.Lock()
- ns.checkStarted()
- type callback struct {
- node *enode.Node
- state bitMask
- }
- require, disable := ns.stateMask(requireFlags), ns.stateMask(disableFlags)
- var callbacks []callback
- for _, node := range ns.nodes {
- if node.state&require == require && node.state&disable == 0 {
- callbacks = append(callbacks, callback{node.node, node.state & (require | disable)})
- }
- }
- ns.lock.Unlock()
- for _, c := range callbacks {
- cb(c.node, Flags{mask: c.state, setup: ns.setup})
- }
-}
-
-// GetNode returns the enode currently associated with the given ID
-func (ns *NodeStateMachine) GetNode(id enode.ID) *enode.Node {
- ns.lock.Lock()
- defer ns.lock.Unlock()
-
- ns.checkStarted()
- if node := ns.nodes[id]; node != nil {
- return node.node
- }
- return nil
-}
-
-// AddLogMetrics adds logging and/or metrics for nodes entering, exiting and currently
-// being in a given set specified by required and disabled state flags
-func (ns *NodeStateMachine) AddLogMetrics(requireFlags, disableFlags Flags, name string, inMeter, outMeter metrics.Meter, gauge metrics.Gauge) {
- var count int64
- ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState Flags) {
- oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags)
- newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags)
- if newMatch == oldMatch {
- return
- }
-
- if newMatch {
- count++
- if name != "" {
- log.Debug("Node entered", "set", name, "id", n.ID(), "count", count)
- }
- if inMeter != nil {
- inMeter.Mark(1)
- }
- } else {
- count--
- if name != "" {
- log.Debug("Node left", "set", name, "id", n.ID(), "count", count)
- }
- if outMeter != nil {
- outMeter.Mark(1)
- }
- }
- if gauge != nil {
- gauge.Update(count)
- }
- })
-}
diff --git a/p2p/nodestate/nodestate_test.go b/p2p/nodestate/nodestate_test.go
deleted file mode 100644
index d06ad755e2..0000000000
--- a/p2p/nodestate/nodestate_test.go
+++ /dev/null
@@ -1,407 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package nodestate
-
-import (
- "errors"
- "fmt"
- "reflect"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-func testSetup(flagPersist []bool, fieldType []reflect.Type) (*Setup, []Flags, []Field) {
- setup := &Setup{}
- flags := make([]Flags, len(flagPersist))
- for i, persist := range flagPersist {
- if persist {
- flags[i] = setup.NewPersistentFlag(fmt.Sprintf("flag-%d", i))
- } else {
- flags[i] = setup.NewFlag(fmt.Sprintf("flag-%d", i))
- }
- }
- fields := make([]Field, len(fieldType))
- for i, ftype := range fieldType {
- switch ftype {
- case reflect.TypeOf(uint64(0)):
- fields[i] = setup.NewPersistentField(fmt.Sprintf("field-%d", i), ftype, uint64FieldEnc, uint64FieldDec)
- case reflect.TypeOf(""):
- fields[i] = setup.NewPersistentField(fmt.Sprintf("field-%d", i), ftype, stringFieldEnc, stringFieldDec)
- default:
- fields[i] = setup.NewField(fmt.Sprintf("field-%d", i), ftype)
- }
- }
- return setup, flags, fields
-}
-
-func testNode(b byte) *enode.Node {
- r := &enr.Record{}
- r.SetSig(dummyIdentity{b}, []byte{42})
- n, _ := enode.New(dummyIdentity{b}, r)
- return n
-}
-
-func TestCallback(t *testing.T) {
- mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
-
- s, flags, _ := testSetup([]bool{false, false, false}, nil)
- ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
-
- set0 := make(chan struct{}, 1)
- set1 := make(chan struct{}, 1)
- set2 := make(chan struct{}, 1)
- ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) { set0 <- struct{}{} })
- ns.SubscribeState(flags[1], func(n *enode.Node, oldState, newState Flags) { set1 <- struct{}{} })
- ns.SubscribeState(flags[2], func(n *enode.Node, oldState, newState Flags) { set2 <- struct{}{} })
-
- ns.Start()
-
- ns.SetState(testNode(1), flags[0], Flags{}, 0)
- ns.SetState(testNode(1), flags[1], Flags{}, time.Second)
- ns.SetState(testNode(1), flags[2], Flags{}, 2*time.Second)
-
- for i := 0; i < 3; i++ {
- select {
- case <-set0:
- case <-set1:
- case <-set2:
- case <-time.After(time.Second):
- t.Fatalf("failed to invoke callback")
- }
- }
-}
-
-func TestPersistentFlags(t *testing.T) {
- mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
-
- s, flags, _ := testSetup([]bool{true, true, true, false}, nil)
- ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
-
- saveNode := make(chan *nodeInfo, 5)
- ns.saveNodeHook = func(node *nodeInfo) {
- saveNode <- node
- }
-
- ns.Start()
-
- ns.SetState(testNode(1), flags[0], Flags{}, time.Second) // state with timeout should not be saved
- ns.SetState(testNode(2), flags[1], Flags{}, 0)
- ns.SetState(testNode(3), flags[2], Flags{}, 0)
- ns.SetState(testNode(4), flags[3], Flags{}, 0)
- ns.SetState(testNode(5), flags[0], Flags{}, 0)
- ns.Persist(testNode(5))
- select {
- case <-saveNode:
- case <-time.After(time.Second):
- t.Fatalf("Timeout")
- }
- ns.Stop()
-
- for i := 0; i < 2; i++ {
- select {
- case <-saveNode:
- case <-time.After(time.Second):
- t.Fatalf("Timeout")
- }
- }
- select {
- case <-saveNode:
- t.Fatalf("Unexpected saveNode")
- case <-time.After(time.Millisecond * 100):
- }
-}
-
-func TestSetField(t *testing.T) {
- mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
-
- s, flags, fields := testSetup([]bool{true}, []reflect.Type{reflect.TypeOf("")})
- ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
-
- saveNode := make(chan *nodeInfo, 1)
- ns.saveNodeHook = func(node *nodeInfo) {
- saveNode <- node
- }
-
- ns.Start()
-
- // Set field before setting state
- ns.SetField(testNode(1), fields[0], "hello world")
- field := ns.GetField(testNode(1), fields[0])
- if field == nil {
- t.Fatalf("Field should be set before setting states")
- }
- ns.SetField(testNode(1), fields[0], nil)
- field = ns.GetField(testNode(1), fields[0])
- if field != nil {
- t.Fatalf("Field should be unset")
- }
- // Set field after setting state
- ns.SetState(testNode(1), flags[0], Flags{}, 0)
- ns.SetField(testNode(1), fields[0], "hello world")
- field = ns.GetField(testNode(1), fields[0])
- if field == nil {
- t.Fatalf("Field should be set after setting states")
- }
- if err := ns.SetField(testNode(1), fields[0], 123); err == nil {
- t.Fatalf("Invalid field should be rejected")
- }
- // Dirty node should be written back
- ns.Stop()
- select {
- case <-saveNode:
- case <-time.After(time.Second):
- t.Fatalf("Timeout")
- }
-}
-
-func TestSetState(t *testing.T) {
- mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
-
- s, flags, _ := testSetup([]bool{false, false, false}, nil)
- ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
-
- type change struct{ old, new Flags }
- set := make(chan change, 1)
- ns.SubscribeState(flags[0].Or(flags[1]), func(n *enode.Node, oldState, newState Flags) {
- set <- change{
- old: oldState,
- new: newState,
- }
- })
-
- ns.Start()
-
- check := func(expectOld, expectNew Flags, expectChange bool) {
- if expectChange {
- select {
- case c := <-set:
- if !c.old.Equals(expectOld) {
- t.Fatalf("Old state mismatch")
- }
- if !c.new.Equals(expectNew) {
- t.Fatalf("New state mismatch")
- }
- case <-time.After(time.Second):
- }
- return
- }
- select {
- case <-set:
- t.Fatalf("Unexpected change")
- case <-time.After(time.Millisecond * 100):
- return
- }
- }
- ns.SetState(testNode(1), flags[0], Flags{}, 0)
- check(Flags{}, flags[0], true)
-
- ns.SetState(testNode(1), flags[1], Flags{}, 0)
- check(flags[0], flags[0].Or(flags[1]), true)
-
- ns.SetState(testNode(1), flags[2], Flags{}, 0)
- check(Flags{}, Flags{}, false)
-
- ns.SetState(testNode(1), Flags{}, flags[0], 0)
- check(flags[0].Or(flags[1]), flags[1], true)
-
- ns.SetState(testNode(1), Flags{}, flags[1], 0)
- check(flags[1], Flags{}, true)
-
- ns.SetState(testNode(1), Flags{}, flags[2], 0)
- check(Flags{}, Flags{}, false)
-
- ns.SetState(testNode(1), flags[0].Or(flags[1]), Flags{}, time.Second)
- check(Flags{}, flags[0].Or(flags[1]), true)
- clock.Run(time.Second)
- check(flags[0].Or(flags[1]), Flags{}, true)
-}
-
-func uint64FieldEnc(field interface{}) ([]byte, error) {
- if u, ok := field.(uint64); ok {
- enc, err := rlp.EncodeToBytes(&u)
- return enc, err
- }
- return nil, errors.New("invalid field type")
-}
-
-func uint64FieldDec(enc []byte) (interface{}, error) {
- var u uint64
- err := rlp.DecodeBytes(enc, &u)
- return u, err
-}
-
-func stringFieldEnc(field interface{}) ([]byte, error) {
- if s, ok := field.(string); ok {
- return []byte(s), nil
- }
- return nil, errors.New("invalid field type")
-}
-
-func stringFieldDec(enc []byte) (interface{}, error) {
- return string(enc), nil
-}
-
-func TestPersistentFields(t *testing.T) {
- mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
-
- s, flags, fields := testSetup([]bool{true}, []reflect.Type{reflect.TypeOf(uint64(0)), reflect.TypeOf("")})
- ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
-
- ns.Start()
- ns.SetState(testNode(1), flags[0], Flags{}, 0)
- ns.SetField(testNode(1), fields[0], uint64(100))
- ns.SetField(testNode(1), fields[1], "hello world")
- ns.Stop()
-
- ns2 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
-
- ns2.Start()
- field0 := ns2.GetField(testNode(1), fields[0])
- if !reflect.DeepEqual(field0, uint64(100)) {
- t.Fatalf("Field changed")
- }
- field1 := ns2.GetField(testNode(1), fields[1])
- if !reflect.DeepEqual(field1, "hello world") {
- t.Fatalf("Field changed")
- }
-
- s.Version++
- ns3 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
- ns3.Start()
- if ns3.GetField(testNode(1), fields[0]) != nil {
- t.Fatalf("Old field version should have been discarded")
- }
-}
-
-func TestFieldSub(t *testing.T) {
- mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
-
- s, flags, fields := testSetup([]bool{true}, []reflect.Type{reflect.TypeOf(uint64(0))})
- ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
-
- var (
- lastState Flags
- lastOldValue, lastNewValue interface{}
- )
- ns.SubscribeField(fields[0], func(n *enode.Node, state Flags, oldValue, newValue interface{}) {
- lastState, lastOldValue, lastNewValue = state, oldValue, newValue
- })
- check := func(state Flags, oldValue, newValue interface{}) {
- if !lastState.Equals(state) || lastOldValue != oldValue || lastNewValue != newValue {
- t.Fatalf("Incorrect field sub callback (expected [%v %v %v], got [%v %v %v])", state, oldValue, newValue, lastState, lastOldValue, lastNewValue)
- }
- }
- ns.Start()
- ns.SetState(testNode(1), flags[0], Flags{}, 0)
- ns.SetField(testNode(1), fields[0], uint64(100))
- check(flags[0], nil, uint64(100))
- ns.Stop()
- check(s.OfflineFlag(), uint64(100), nil)
-
- ns2 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
- ns2.SubscribeField(fields[0], func(n *enode.Node, state Flags, oldValue, newValue interface{}) {
- lastState, lastOldValue, lastNewValue = state, oldValue, newValue
- })
- ns2.Start()
- check(s.OfflineFlag(), nil, uint64(100))
- ns2.SetState(testNode(1), Flags{}, flags[0], 0)
- ns2.SetField(testNode(1), fields[0], nil)
- check(Flags{}, uint64(100), nil)
- ns2.Stop()
-}
-
-func TestDuplicatedFlags(t *testing.T) {
- mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
-
- s, flags, _ := testSetup([]bool{true}, nil)
- ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
-
- type change struct{ old, new Flags }
- set := make(chan change, 1)
- ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) {
- set <- change{oldState, newState}
- })
-
- ns.Start()
- defer ns.Stop()
-
- check := func(expectOld, expectNew Flags, expectChange bool) {
- if expectChange {
- select {
- case c := <-set:
- if !c.old.Equals(expectOld) {
- t.Fatalf("Old state mismatch")
- }
- if !c.new.Equals(expectNew) {
- t.Fatalf("New state mismatch")
- }
- case <-time.After(time.Second):
- }
- return
- }
- select {
- case <-set:
- t.Fatalf("Unexpected change")
- case <-time.After(time.Millisecond * 100):
- return
- }
- }
- ns.SetState(testNode(1), flags[0], Flags{}, time.Second)
- check(Flags{}, flags[0], true)
- ns.SetState(testNode(1), flags[0], Flags{}, 2*time.Second) // extend the timeout to 2s
- check(Flags{}, flags[0], false)
-
- clock.Run(2 * time.Second)
- check(flags[0], Flags{}, true)
-}
-
-func TestCallbackOrder(t *testing.T) {
- mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
-
- s, flags, _ := testSetup([]bool{false, false, false, false}, nil)
- ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
-
- ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) {
- if newState.Equals(flags[0]) {
- ns.SetStateSub(n, flags[1], Flags{}, 0)
- ns.SetStateSub(n, flags[2], Flags{}, 0)
- }
- })
- ns.SubscribeState(flags[1], func(n *enode.Node, oldState, newState Flags) {
- if newState.Equals(flags[1]) {
- ns.SetStateSub(n, flags[3], Flags{}, 0)
- }
- })
- lastState := Flags{}
- ns.SubscribeState(MergeFlags(flags[1], flags[2], flags[3]), func(n *enode.Node, oldState, newState Flags) {
- if !oldState.Equals(lastState) {
- t.Fatalf("Wrong callback order")
- }
- lastState = newState
- })
-
- ns.Start()
- defer ns.Stop()
-
- ns.SetState(testNode(1), flags[0], Flags{}, 0)
-}
diff --git a/p2p/peer.go b/p2p/peer.go
deleted file mode 100644
index 65a7903f58..0000000000
--- a/p2p/peer.go
+++ /dev/null
@@ -1,548 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "errors"
- "fmt"
- "io"
- "net"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rlp"
- "golang.org/x/exp/slices"
-)
-
-var (
- ErrShuttingDown = errors.New("shutting down")
-)
-
-const (
- baseProtocolVersion = 5
- baseProtocolLength = uint64(16)
- baseProtocolMaxMsgSize = 2 * 1024
-
- snappyProtocolVersion = 5
-
- pingInterval = 15 * time.Second
-)
-
-const (
- // devp2p message codes
- handshakeMsg = 0x00
- discMsg = 0x01
- pingMsg = 0x02
- pongMsg = 0x03
-)
-
-// protoHandshake is the RLP structure of the protocol handshake.
-type protoHandshake struct {
- Version uint64
- Name string
- Caps []Cap
- ListenPort uint64
- ID []byte // secp256k1 public key
-
- // Ignore additional fields (for forward compatibility).
- Rest []rlp.RawValue `rlp:"tail"`
-}
-
-// PeerEventType is the type of peer events emitted by a p2p.Server
-type PeerEventType string
-
-const (
- // PeerEventTypeAdd is the type of event emitted when a peer is added
- // to a p2p.Server
- PeerEventTypeAdd PeerEventType = "add"
-
- // PeerEventTypeDrop is the type of event emitted when a peer is
- // dropped from a p2p.Server
- PeerEventTypeDrop PeerEventType = "drop"
-
- // PeerEventTypeMsgSend is the type of event emitted when a
- // message is successfully sent to a peer
- PeerEventTypeMsgSend PeerEventType = "msgsend"
-
- // PeerEventTypeMsgRecv is the type of event emitted when a
- // message is received from a peer
- PeerEventTypeMsgRecv PeerEventType = "msgrecv"
-)
-
-// PeerEvent is an event emitted when peers are either added or dropped from
-// a p2p.Server or when a message is sent or received on a peer connection
-type PeerEvent struct {
- Type PeerEventType `json:"type"`
- Peer enode.ID `json:"peer"`
- Error string `json:"error,omitempty"`
- Protocol string `json:"protocol,omitempty"`
- MsgCode *uint64 `json:"msg_code,omitempty"`
- MsgSize *uint32 `json:"msg_size,omitempty"`
- LocalAddress string `json:"local,omitempty"`
- RemoteAddress string `json:"remote,omitempty"`
-}
-
-// Peer represents a connected remote node.
-type Peer struct {
- rw *conn
- running map[string]*protoRW
- log log.Logger
- created mclock.AbsTime
-
- wg sync.WaitGroup
- protoErr chan error
- closed chan struct{}
- pingRecv chan struct{}
- disc chan DiscReason
-
- // events receives message send / receive events if set
- events *event.Feed
- testPipe *MsgPipeRW // for testing
-}
-
-// NewPeer returns a peer for testing purposes.
-func NewPeer(id enode.ID, name string, caps []Cap) *Peer {
- // Generate a fake set of local protocols to match as running caps. Almost
- // no fields needs to be meaningful here as we're only using it to cross-
- // check with the "remote" caps array.
- protos := make([]Protocol, len(caps))
- for i, cap := range caps {
- protos[i].Name = cap.Name
- protos[i].Version = cap.Version
- }
- pipe, _ := net.Pipe()
- node := enode.SignNull(new(enr.Record), id)
- conn := &conn{fd: pipe, transport: nil, node: node, caps: caps, name: name}
- peer := newPeer(log.Root(), conn, protos)
- close(peer.closed) // ensures Disconnect doesn't block
- return peer
-}
-
-// NewPeerPipe creates a peer for testing purposes.
-// The message pipe given as the last parameter is closed when
-// Disconnect is called on the peer.
-func NewPeerPipe(id enode.ID, name string, caps []Cap, pipe *MsgPipeRW) *Peer {
- p := NewPeer(id, name, caps)
- p.testPipe = pipe
- return p
-}
-
-// ID returns the node's public key.
-func (p *Peer) ID() enode.ID {
- return p.rw.node.ID()
-}
-
-// Node returns the peer's node descriptor.
-func (p *Peer) Node() *enode.Node {
- return p.rw.node
-}
-
-// Name returns an abbreviated form of the name
-func (p *Peer) Name() string {
- s := p.rw.name
- if len(s) > 20 {
- return s[:20] + "..."
- }
- return s
-}
-
-// Fullname returns the node name that the remote node advertised.
-func (p *Peer) Fullname() string {
- return p.rw.name
-}
-
-// Caps returns the capabilities (supported subprotocols) of the remote peer.
-func (p *Peer) Caps() []Cap {
- // TODO: maybe return copy
- return p.rw.caps
-}
-
-// RunningCap returns true if the peer is actively connected using any of the
-// enumerated versions of a specific protocol, meaning that at least one of the
-// versions is supported by both this node and the peer p.
-func (p *Peer) RunningCap(protocol string, versions []uint) bool {
- if proto, ok := p.running[protocol]; ok {
- for _, ver := range versions {
- if proto.Version == ver {
- return true
- }
- }
- }
- return false
-}
-
-// RemoteAddr returns the remote address of the network connection.
-func (p *Peer) RemoteAddr() net.Addr {
- return p.rw.fd.RemoteAddr()
-}
-
-// LocalAddr returns the local address of the network connection.
-func (p *Peer) LocalAddr() net.Addr {
- return p.rw.fd.LocalAddr()
-}
-
-// Disconnect terminates the peer connection with the given reason.
-// It returns immediately and does not wait until the connection is closed.
-func (p *Peer) Disconnect(reason DiscReason) {
- if p.testPipe != nil {
- p.testPipe.Close()
- }
-
- select {
- case p.disc <- reason:
- case <-p.closed:
- }
-}
-
-// String implements fmt.Stringer.
-func (p *Peer) String() string {
- id := p.ID()
- return fmt.Sprintf("Peer %x %v", id[:8], p.RemoteAddr())
-}
-
-// Inbound returns true if the peer is an inbound connection
-func (p *Peer) Inbound() bool {
- return p.rw.is(inboundConn)
-}
-
-func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer {
- protomap := matchProtocols(protocols, conn.caps, conn)
- p := &Peer{
- rw: conn,
- running: protomap,
- created: mclock.Now(),
- disc: make(chan DiscReason),
- protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop
- closed: make(chan struct{}),
- pingRecv: make(chan struct{}, 16),
- log: log.New("id", conn.node.ID(), "conn", conn.flags),
- }
- return p
-}
-
-func (p *Peer) Log() log.Logger {
- return p.log
-}
-
-func (p *Peer) run() (remoteRequested bool, err error) {
- var (
- writeStart = make(chan struct{}, 1)
- writeErr = make(chan error, 1)
- readErr = make(chan error, 1)
- reason DiscReason // sent to the peer
- )
- p.wg.Add(2)
- go p.readLoop(readErr)
- go p.pingLoop()
-
- // Start all protocol handlers.
- writeStart <- struct{}{}
- p.startProtocols(writeStart, writeErr)
-
- // Wait for an error or disconnect.
-loop:
- for {
- select {
- case err = <-writeErr:
- // A write finished. Allow the next write to start if
- // there was no error.
- if err != nil {
- reason = DiscNetworkError
- break loop
- }
- writeStart <- struct{}{}
- case err = <-readErr:
- if r, ok := err.(DiscReason); ok {
- remoteRequested = true
- reason = r
- } else {
- reason = DiscNetworkError
- }
- break loop
- case err = <-p.protoErr:
- reason = discReasonForError(err)
- break loop
- case err = <-p.disc:
- reason = discReasonForError(err)
- break loop
- }
- }
-
- close(p.closed)
- p.rw.close(reason)
- p.wg.Wait()
- return remoteRequested, err
-}
-
-func (p *Peer) pingLoop() {
- defer p.wg.Done()
-
- ping := time.NewTimer(pingInterval)
- defer ping.Stop()
-
- for {
- select {
- case <-ping.C:
- if err := SendItems(p.rw, pingMsg); err != nil {
- p.protoErr <- err
- return
- }
- ping.Reset(pingInterval)
-
- case <-p.pingRecv:
- SendItems(p.rw, pongMsg)
-
- case <-p.closed:
- return
- }
- }
-}
-
-func (p *Peer) readLoop(errc chan<- error) {
- defer p.wg.Done()
- for {
- msg, err := p.rw.ReadMsg()
- if err != nil {
- errc <- err
- return
- }
- msg.ReceivedAt = time.Now()
- if err = p.handle(msg); err != nil {
- errc <- err
- return
- }
- }
-}
-
-func (p *Peer) handle(msg Msg) error {
- switch {
- case msg.Code == pingMsg:
- msg.Discard()
- select {
- case p.pingRecv <- struct{}{}:
- case <-p.closed:
- }
- case msg.Code == discMsg:
- // This is the last message. We don't need to discard or
- // check errors because, the connection will be closed after it.
- var m struct{ R DiscReason }
- rlp.Decode(msg.Payload, &m)
- return m.R
- case msg.Code < baseProtocolLength:
- // ignore other base protocol messages
- return msg.Discard()
- default:
- // it's a subprotocol message
- proto, err := p.getProto(msg.Code)
- if err != nil {
- return fmt.Errorf("msg code out of range: %v", msg.Code)
- }
- if metrics.Enabled {
- m := fmt.Sprintf("%s/%s/%d/%#02x", ingressMeterName, proto.Name, proto.Version, msg.Code-proto.offset)
- metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize))
- metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1)
- }
- select {
- case proto.in <- msg:
- return nil
- case <-p.closed:
- return io.EOF
- }
- }
- return nil
-}
-
-func countMatchingProtocols(protocols []Protocol, caps []Cap) int {
- n := 0
- for _, cap := range caps {
- for _, proto := range protocols {
- if proto.Name == cap.Name && proto.Version == cap.Version {
- n++
- }
- }
- }
- return n
-}
-
-// matchProtocols creates structures for matching named subprotocols.
-func matchProtocols(protocols []Protocol, caps []Cap, rw MsgReadWriter) map[string]*protoRW {
- slices.SortFunc(caps, Cap.Cmp)
- offset := baseProtocolLength
- result := make(map[string]*protoRW)
-
-outer:
- for _, cap := range caps {
- for _, proto := range protocols {
- if proto.Name == cap.Name && proto.Version == cap.Version {
- // If an old protocol version matched, revert it
- if old := result[cap.Name]; old != nil {
- offset -= old.Length
- }
- // Assign the new match
- result[cap.Name] = &protoRW{Protocol: proto, offset: offset, in: make(chan Msg), w: rw}
- offset += proto.Length
-
- continue outer
- }
- }
- }
- return result
-}
-
-func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error) {
- p.wg.Add(len(p.running))
- for _, proto := range p.running {
- proto := proto
- proto.closed = p.closed
- proto.wstart = writeStart
- proto.werr = writeErr
- var rw MsgReadWriter = proto
- if p.events != nil {
- rw = newMsgEventer(rw, p.events, p.ID(), proto.Name, p.Info().Network.RemoteAddress, p.Info().Network.LocalAddress)
- }
- p.log.Trace(fmt.Sprintf("Starting protocol %s/%d", proto.Name, proto.Version))
- go func() {
- defer p.wg.Done()
- err := proto.Run(p, rw)
- if err == nil {
- p.log.Trace(fmt.Sprintf("Protocol %s/%d returned", proto.Name, proto.Version))
- err = errProtocolReturned
- } else if !errors.Is(err, io.EOF) {
- p.log.Trace(fmt.Sprintf("Protocol %s/%d failed", proto.Name, proto.Version), "err", err)
- }
- p.protoErr <- err
- }()
- }
-}
-
-// getProto finds the protocol responsible for handling
-// the given message code.
-func (p *Peer) getProto(code uint64) (*protoRW, error) {
- for _, proto := range p.running {
- if code >= proto.offset && code < proto.offset+proto.Length {
- return proto, nil
- }
- }
- return nil, newPeerError(errInvalidMsgCode, "%d", code)
-}
-
-type protoRW struct {
- Protocol
- in chan Msg // receives read messages
- closed <-chan struct{} // receives when peer is shutting down
- wstart <-chan struct{} // receives when write may start
- werr chan<- error // for write results
- offset uint64
- w MsgWriter
-}
-
-func (rw *protoRW) WriteMsg(msg Msg) (err error) {
- if msg.Code >= rw.Length {
- return newPeerError(errInvalidMsgCode, "not handled")
- }
- msg.meterCap = rw.cap()
- msg.meterCode = msg.Code
-
- msg.Code += rw.offset
-
- select {
- case <-rw.wstart:
- err = rw.w.WriteMsg(msg)
- // Report write status back to Peer.run. It will initiate
- // shutdown if the error is non-nil and unblock the next write
- // otherwise. The calling protocol code should exit for errors
- // as well but we don't want to rely on that.
- rw.werr <- err
- case <-rw.closed:
- err = ErrShuttingDown
- }
- return err
-}
-
-func (rw *protoRW) ReadMsg() (Msg, error) {
- select {
- case msg := <-rw.in:
- msg.Code -= rw.offset
- return msg, nil
- case <-rw.closed:
- return Msg{}, io.EOF
- }
-}
-
-// PeerInfo represents a short summary of the information known about a connected
-// peer. Sub-protocol independent fields are contained and initialized here, with
-// protocol specifics delegated to all connected sub-protocols.
-type PeerInfo struct {
- ENR string `json:"enr,omitempty"` // Ethereum Node Record
- Enode string `json:"enode"` // Node URL
- ID string `json:"id"` // Unique node identifier
- Name string `json:"name"` // Name of the node, including client type, version, OS, custom data
- Caps []string `json:"caps"` // Protocols advertised by this peer
- Network struct {
- LocalAddress string `json:"localAddress"` // Local endpoint of the TCP data connection
- RemoteAddress string `json:"remoteAddress"` // Remote endpoint of the TCP data connection
- Inbound bool `json:"inbound"`
- Trusted bool `json:"trusted"`
- Static bool `json:"static"`
- } `json:"network"`
- Protocols map[string]interface{} `json:"protocols"` // Sub-protocol specific metadata fields
-}
-
-// Info gathers and returns a collection of metadata known about a peer.
-func (p *Peer) Info() *PeerInfo {
- // Gather the protocol capabilities
- var caps []string
- for _, cap := range p.Caps() {
- caps = append(caps, cap.String())
- }
- // Assemble the generic peer metadata
- info := &PeerInfo{
- Enode: p.Node().URLv4(),
- ID: p.ID().String(),
- Name: p.Fullname(),
- Caps: caps,
- Protocols: make(map[string]interface{}, len(p.running)),
- }
- if p.Node().Seq() > 0 {
- info.ENR = p.Node().String()
- }
- info.Network.LocalAddress = p.LocalAddr().String()
- info.Network.RemoteAddress = p.RemoteAddr().String()
- info.Network.Inbound = p.rw.is(inboundConn)
- info.Network.Trusted = p.rw.is(trustedConn)
- info.Network.Static = p.rw.is(staticDialedConn)
-
- // Gather all the running protocol infos
- for _, proto := range p.running {
- protoInfo := interface{}("unknown")
- if query := proto.Protocol.PeerInfo; query != nil {
- if metadata := query(p.ID()); metadata != nil {
- protoInfo = metadata
- } else {
- protoInfo = "handshake"
- }
- }
- info.Protocols[proto.Name] = protoInfo
- }
- return info
-}
diff --git a/p2p/peer_error.go b/p2p/peer_error.go
deleted file mode 100644
index ebc59de251..0000000000
--- a/p2p/peer_error.go
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "errors"
- "fmt"
-)
-
-const (
- errInvalidMsgCode = iota
- errInvalidMsg
-)
-
-var errorToString = map[int]string{
- errInvalidMsgCode: "invalid message code",
- errInvalidMsg: "invalid message",
-}
-
-type peerError struct {
- code int
- message string
-}
-
-func newPeerError(code int, format string, v ...interface{}) *peerError {
- desc, ok := errorToString[code]
- if !ok {
- panic("invalid error code")
- }
- err := &peerError{code, desc}
- if format != "" {
- err.message += ": " + fmt.Sprintf(format, v...)
- }
- return err
-}
-
-func (pe *peerError) Error() string {
- return pe.message
-}
-
-var errProtocolReturned = errors.New("protocol returned")
-
-type DiscReason uint8
-
-const (
- DiscRequested DiscReason = iota
- DiscNetworkError
- DiscProtocolError
- DiscUselessPeer
- DiscTooManyPeers
- DiscAlreadyConnected
- DiscIncompatibleVersion
- DiscInvalidIdentity
- DiscQuitting
- DiscUnexpectedIdentity
- DiscSelf
- DiscReadTimeout
- DiscSubprotocolError = DiscReason(0x10)
-)
-
-var discReasonToString = [...]string{
- DiscRequested: "disconnect requested",
- DiscNetworkError: "network error",
- DiscProtocolError: "breach of protocol",
- DiscUselessPeer: "useless peer",
- DiscTooManyPeers: "too many peers",
- DiscAlreadyConnected: "already connected",
- DiscIncompatibleVersion: "incompatible p2p protocol version",
- DiscInvalidIdentity: "invalid node identity",
- DiscQuitting: "client quitting",
- DiscUnexpectedIdentity: "unexpected identity",
- DiscSelf: "connected to self",
- DiscReadTimeout: "read timeout",
- DiscSubprotocolError: "subprotocol error",
-}
-
-func (d DiscReason) String() string {
- if len(discReasonToString) <= int(d) {
- return fmt.Sprintf("unknown disconnect reason %d", d)
- }
- return discReasonToString[d]
-}
-
-func (d DiscReason) Error() string {
- return d.String()
-}
-
-func discReasonForError(err error) DiscReason {
- if reason, ok := err.(DiscReason); ok {
- return reason
- }
- if errors.Is(err, errProtocolReturned) {
- return DiscQuitting
- }
- peerError, ok := err.(*peerError)
- if ok {
- switch peerError.code {
- case errInvalidMsgCode, errInvalidMsg:
- return DiscProtocolError
- default:
- return DiscSubprotocolError
- }
- }
- return DiscSubprotocolError
-}
diff --git a/p2p/peer_test.go b/p2p/peer_test.go
deleted file mode 100644
index 4308bbd2eb..0000000000
--- a/p2p/peer_test.go
+++ /dev/null
@@ -1,362 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "encoding/binary"
- "errors"
- "fmt"
- "math/rand"
- "net"
- "reflect"
- "strconv"
- "strings"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
-)
-
-var discard = Protocol{
- Name: "discard",
- Length: 1,
- Run: func(p *Peer, rw MsgReadWriter) error {
- for {
- msg, err := rw.ReadMsg()
- if err != nil {
- return err
- }
- fmt.Printf("discarding %d\n", msg.Code)
- if err = msg.Discard(); err != nil {
- return err
- }
- }
- },
-}
-
-// uintID encodes i into a node ID.
-func uintID(i uint16) enode.ID {
- var id enode.ID
- binary.BigEndian.PutUint16(id[:], i)
- return id
-}
-
-// newNode creates a node record with the given address.
-func newNode(id enode.ID, addr string) *enode.Node {
- var r enr.Record
- if addr != "" {
- // Set the port if present.
- if strings.Contains(addr, ":") {
- hs, ps, err := net.SplitHostPort(addr)
- if err != nil {
- panic(fmt.Errorf("invalid address %q", addr))
- }
- port, err := strconv.Atoi(ps)
- if err != nil {
- panic(fmt.Errorf("invalid port in %q", addr))
- }
- r.Set(enr.TCP(port))
- r.Set(enr.UDP(port))
- addr = hs
- }
- // Set the IP.
- ip := net.ParseIP(addr)
- if ip == nil {
- panic(fmt.Errorf("invalid IP %q", addr))
- }
- r.Set(enr.IP(ip))
- }
- return enode.SignNull(&r, id)
-}
-
-func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) {
- var (
- fd1, fd2 = net.Pipe()
- key1, key2 = newkey(), newkey()
- t1 = newTestTransport(&key2.PublicKey, fd1, nil)
- t2 = newTestTransport(&key1.PublicKey, fd2, &key1.PublicKey)
- )
-
- c1 := &conn{fd: fd1, node: newNode(uintID(1), ""), transport: t1}
- c2 := &conn{fd: fd2, node: newNode(uintID(2), ""), transport: t2}
- for _, p := range protos {
- c1.caps = append(c1.caps, p.cap())
- c2.caps = append(c2.caps, p.cap())
- }
-
- peer := newPeer(log.Root(), c1, protos)
- errc := make(chan error, 1)
- go func() {
- _, err := peer.run()
- errc <- err
- }()
-
- closer := func() { c2.close(errors.New("close func called")) }
- return closer, c2, peer, errc
-}
-
-func TestPeerProtoReadMsg(t *testing.T) {
- proto := Protocol{
- Name: "a",
- Length: 5,
- Run: func(peer *Peer, rw MsgReadWriter) error {
- if err := ExpectMsg(rw, 2, []uint{1}); err != nil {
- t.Error(err)
- }
- if err := ExpectMsg(rw, 3, []uint{2}); err != nil {
- t.Error(err)
- }
- if err := ExpectMsg(rw, 4, []uint{3}); err != nil {
- t.Error(err)
- }
- return nil
- },
- }
-
- closer, rw, _, errc := testPeer([]Protocol{proto})
- defer closer()
-
- Send(rw, baseProtocolLength+2, []uint{1})
- Send(rw, baseProtocolLength+3, []uint{2})
- Send(rw, baseProtocolLength+4, []uint{3})
-
- select {
- case err := <-errc:
- if err != errProtocolReturned {
- t.Errorf("peer returned error: %v", err)
- }
- case <-time.After(2 * time.Second):
- t.Errorf("receive timeout")
- }
-}
-
-func TestPeerProtoEncodeMsg(t *testing.T) {
- proto := Protocol{
- Name: "a",
- Length: 2,
- Run: func(peer *Peer, rw MsgReadWriter) error {
- if err := SendItems(rw, 2); err == nil {
- t.Error("expected error for out-of-range msg code, got nil")
- }
- if err := SendItems(rw, 1, "foo", "bar"); err != nil {
- t.Errorf("write error: %v", err)
- }
- return nil
- },
- }
- closer, rw, _, _ := testPeer([]Protocol{proto})
- defer closer()
-
- if err := ExpectMsg(rw, 17, []string{"foo", "bar"}); err != nil {
- t.Error(err)
- }
-}
-
-func TestPeerPing(t *testing.T) {
- closer, rw, _, _ := testPeer(nil)
- defer closer()
- if err := SendItems(rw, pingMsg); err != nil {
- t.Fatal(err)
- }
- if err := ExpectMsg(rw, pongMsg, nil); err != nil {
- t.Error(err)
- }
-}
-
-// This test checks that a disconnect message sent by a peer is returned
-// as the error from Peer.run.
-func TestPeerDisconnect(t *testing.T) {
- closer, rw, _, disc := testPeer(nil)
- defer closer()
-
- if err := SendItems(rw, discMsg, DiscQuitting); err != nil {
- t.Fatal(err)
- }
- select {
- case reason := <-disc:
- if reason != DiscQuitting {
- t.Errorf("run returned wrong reason: got %v, want %v", reason, DiscQuitting)
- }
- case <-time.After(500 * time.Millisecond):
- t.Error("peer did not return")
- }
-}
-
-// This test is supposed to verify that Peer can reliably handle
-// multiple causes of disconnection occurring at the same time.
-func TestPeerDisconnectRace(t *testing.T) {
- maybe := func() bool { return rand.Intn(2) == 1 }
-
- for i := 0; i < 1000; i++ {
- protoclose := make(chan error)
- protodisc := make(chan DiscReason)
- closer, rw, p, disc := testPeer([]Protocol{
- {
- Name: "closereq",
- Run: func(p *Peer, rw MsgReadWriter) error { return <-protoclose },
- Length: 1,
- },
- {
- Name: "disconnect",
- Run: func(p *Peer, rw MsgReadWriter) error { p.Disconnect(<-protodisc); return nil },
- Length: 1,
- },
- })
-
- // Simulate incoming messages.
- go SendItems(rw, baseProtocolLength+1)
- go SendItems(rw, baseProtocolLength+2)
- // Close the network connection.
- go closer()
- // Make protocol "closereq" return.
- protoclose <- errors.New("protocol closed")
- // Make protocol "disconnect" call peer.Disconnect
- protodisc <- DiscAlreadyConnected
- // In some cases, simulate something else calling peer.Disconnect.
- if maybe() {
- go p.Disconnect(DiscInvalidIdentity)
- }
- // In some cases, simulate remote requesting a disconnect.
- if maybe() {
- go SendItems(rw, discMsg, DiscQuitting)
- }
-
- select {
- case <-disc:
- case <-time.After(2 * time.Second):
- // Peer.run should return quickly. If it doesn't the Peer
- // goroutines are probably deadlocked. Call panic in order to
- // show the stacks.
- panic("Peer.run took to long to return.")
- }
- }
-}
-
-func TestNewPeer(t *testing.T) {
- name := "nodename"
- caps := []Cap{{"foo", 2}, {"bar", 3}}
- id := randomID()
- p := NewPeer(id, name, caps)
- if p.ID() != id {
- t.Errorf("ID mismatch: got %v, expected %v", p.ID(), id)
- }
- if p.Name() != name {
- t.Errorf("Name mismatch: got %v, expected %v", p.Name(), name)
- }
- if !reflect.DeepEqual(p.Caps(), caps) {
- t.Errorf("Caps mismatch: got %v, expected %v", p.Caps(), caps)
- }
-
- p.Disconnect(DiscAlreadyConnected) // Should not hang
-}
-
-func TestMatchProtocols(t *testing.T) {
- tests := []struct {
- Remote []Cap
- Local []Protocol
- Match map[string]protoRW
- }{
- {
- // No remote capabilities
- Local: []Protocol{{Name: "a"}},
- },
- {
- // No local protocols
- Remote: []Cap{{Name: "a"}},
- },
- {
- // No mutual protocols
- Remote: []Cap{{Name: "a"}},
- Local: []Protocol{{Name: "b"}},
- },
- {
- // Some matches, some differences
- Remote: []Cap{{Name: "local"}, {Name: "match1"}, {Name: "match2"}},
- Local: []Protocol{{Name: "match1"}, {Name: "match2"}, {Name: "remote"}},
- Match: map[string]protoRW{"match1": {Protocol: Protocol{Name: "match1"}}, "match2": {Protocol: Protocol{Name: "match2"}}},
- },
- {
- // Various alphabetical ordering
- Remote: []Cap{{Name: "aa"}, {Name: "ab"}, {Name: "bb"}, {Name: "ba"}},
- Local: []Protocol{{Name: "ba"}, {Name: "bb"}, {Name: "ab"}, {Name: "aa"}},
- Match: map[string]protoRW{"aa": {Protocol: Protocol{Name: "aa"}}, "ab": {Protocol: Protocol{Name: "ab"}}, "ba": {Protocol: Protocol{Name: "ba"}}, "bb": {Protocol: Protocol{Name: "bb"}}},
- },
- {
- // No mutual versions
- Remote: []Cap{{Version: 1}},
- Local: []Protocol{{Version: 2}},
- },
- {
- // Multiple versions, single common
- Remote: []Cap{{Version: 1}, {Version: 2}},
- Local: []Protocol{{Version: 2}, {Version: 3}},
- Match: map[string]protoRW{"": {Protocol: Protocol{Version: 2}}},
- },
- {
- // Multiple versions, multiple common
- Remote: []Cap{{Version: 1}, {Version: 2}, {Version: 3}, {Version: 4}},
- Local: []Protocol{{Version: 2}, {Version: 3}},
- Match: map[string]protoRW{"": {Protocol: Protocol{Version: 3}}},
- },
- {
- // Various version orderings
- Remote: []Cap{{Version: 4}, {Version: 1}, {Version: 3}, {Version: 2}},
- Local: []Protocol{{Version: 2}, {Version: 3}, {Version: 1}},
- Match: map[string]protoRW{"": {Protocol: Protocol{Version: 3}}},
- },
- {
- // Versions overriding sub-protocol lengths
- Remote: []Cap{{Version: 1}, {Version: 2}, {Version: 3}, {Name: "a"}},
- Local: []Protocol{{Version: 1, Length: 1}, {Version: 2, Length: 2}, {Version: 3, Length: 3}, {Name: "a"}},
- Match: map[string]protoRW{"": {Protocol: Protocol{Version: 3}}, "a": {Protocol: Protocol{Name: "a"}, offset: 3}},
- },
- }
-
- for i, tt := range tests {
- result := matchProtocols(tt.Local, tt.Remote, nil)
- if len(result) != len(tt.Match) {
- t.Errorf("test %d: negotiation mismatch: have %v, want %v", i, len(result), len(tt.Match))
- continue
- }
- // Make sure all negotiated protocols are needed and correct
- for name, proto := range result {
- match, ok := tt.Match[name]
- if !ok {
- t.Errorf("test %d, proto '%s': negotiated but shouldn't have", i, name)
- continue
- }
- if proto.Name != match.Name {
- t.Errorf("test %d, proto '%s': name mismatch: have %v, want %v", i, name, proto.Name, match.Name)
- }
- if proto.Version != match.Version {
- t.Errorf("test %d, proto '%s': version mismatch: have %v, want %v", i, name, proto.Version, match.Version)
- }
- if proto.offset-baseProtocolLength != match.offset {
- t.Errorf("test %d, proto '%s': offset mismatch: have %v, want %v", i, name, proto.offset-baseProtocolLength, match.offset)
- }
- }
- // Make sure no protocols missed negotiation
- for name := range tt.Match {
- if _, ok := result[name]; !ok {
- t.Errorf("test %d, proto '%s': not negotiated, should have", i, name)
- continue
- }
- }
- }
-}
diff --git a/p2p/protocol.go b/p2p/protocol.go
deleted file mode 100644
index 9bb6785a22..0000000000
--- a/p2p/protocol.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "fmt"
- "strings"
-
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
-)
-
-// Protocol represents a P2P subprotocol implementation.
-type Protocol struct {
- // Name should contain the official protocol name,
- // often a three-letter word.
- Name string
-
- // Version should contain the version number of the protocol.
- Version uint
-
- // Length should contain the number of message codes used
- // by the protocol.
- Length uint64
-
- // Run is called in a new goroutine when the protocol has been
- // negotiated with a peer. It should read and write messages from
- // rw. The Payload for each message must be fully consumed.
- //
- // The peer connection is closed when Start returns. It should return
- // any protocol-level error (such as an I/O error) that is
- // encountered.
- Run func(peer *Peer, rw MsgReadWriter) error
-
- // NodeInfo is an optional helper method to retrieve protocol specific metadata
- // about the host node.
- NodeInfo func() interface{}
-
- // PeerInfo is an optional helper method to retrieve protocol specific metadata
- // about a certain peer in the network. If an info retrieval function is set,
- // but returns nil, it is assumed that the protocol handshake is still running.
- PeerInfo func(id enode.ID) interface{}
-
- // DialCandidates, if non-nil, is a way to tell Server about protocol-specific nodes
- // that should be dialed. The server continuously reads nodes from the iterator and
- // attempts to create connections to them.
- DialCandidates enode.Iterator
-
- // Attributes contains protocol specific information for the node record.
- Attributes []enr.Entry
-}
-
-func (p Protocol) cap() Cap {
- return Cap{p.Name, p.Version}
-}
-
-// Cap is the structure of a peer capability.
-type Cap struct {
- Name string
- Version uint
-}
-
-func (cap Cap) String() string {
- return fmt.Sprintf("%s/%d", cap.Name, cap.Version)
-}
-
-// Cmp defines the canonical sorting order of capabilities.
-func (cap Cap) Cmp(other Cap) int {
- if cap.Name == other.Name {
- if cap.Version < other.Version {
- return -1
- }
- if cap.Version > other.Version {
- return 1
- }
- return 0
- }
- return strings.Compare(cap.Name, other.Name)
-}
diff --git a/p2p/rlpx/buffer.go b/p2p/rlpx/buffer.go
deleted file mode 100644
index bb38e10577..0000000000
--- a/p2p/rlpx/buffer.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2021 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package rlpx
-
-import (
- "io"
-)
-
-// readBuffer implements buffering for network reads. This type is similar to bufio.Reader,
-// with two crucial differences: the buffer slice is exposed, and the buffer keeps all
-// read data available until reset.
-//
-// How to use this type:
-//
-// Keep a readBuffer b alongside the underlying network connection. When reading a packet
-// from the connection, first call b.reset(). This empties b.data. Now perform reads
-// through b.read() until the end of the packet is reached. The complete packet data is
-// now available in b.data.
-type readBuffer struct {
- data []byte
- end int
-}
-
-// reset removes all processed data which was read since the last call to reset.
-// After reset, len(b.data) is zero.
-func (b *readBuffer) reset() {
- unprocessed := b.end - len(b.data)
- copy(b.data[:unprocessed], b.data[len(b.data):b.end])
- b.end = unprocessed
- b.data = b.data[:0]
-}
-
-// read reads at least n bytes from r, returning the bytes.
-// The returned slice is valid until the next call to reset.
-func (b *readBuffer) read(r io.Reader, n int) ([]byte, error) {
- offset := len(b.data)
- have := b.end - len(b.data)
-
- // If n bytes are available in the buffer, there is no need to read from r at all.
- if have >= n {
- b.data = b.data[:offset+n]
- return b.data[offset : offset+n], nil
- }
-
- // Make buffer space available.
- need := n - have
- b.grow(need)
-
- // Read.
- rn, err := io.ReadAtLeast(r, b.data[b.end:cap(b.data)], need)
- if err != nil {
- return nil, err
- }
- b.end += rn
- b.data = b.data[:offset+n]
- return b.data[offset : offset+n], nil
-}
-
-// grow ensures the buffer has at least n bytes of unused space.
-func (b *readBuffer) grow(n int) {
- if cap(b.data)-b.end >= n {
- return
- }
- need := n - (cap(b.data) - b.end)
- offset := len(b.data)
- b.data = append(b.data[:cap(b.data)], make([]byte, need)...)
- b.data = b.data[:offset]
-}
-
-// writeBuffer implements buffering for network writes. This is essentially
-// a convenience wrapper around a byte slice.
-type writeBuffer struct {
- data []byte
-}
-
-func (b *writeBuffer) reset() {
- b.data = b.data[:0]
-}
-
-func (b *writeBuffer) appendZero(n int) []byte {
- offset := len(b.data)
- b.data = append(b.data, make([]byte, n)...)
- return b.data[offset : offset+n]
-}
-
-func (b *writeBuffer) Write(data []byte) (int, error) {
- b.data = append(b.data, data...)
- return len(data), nil
-}
-
-const maxUint24 = int(^uint32(0) >> 8)
-
-func readUint24(b []byte) uint32 {
- return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
-}
-
-func putUint24(v uint32, b []byte) {
- b[0] = byte(v >> 16)
- b[1] = byte(v >> 8)
- b[2] = byte(v)
-}
-
-// growslice ensures b has the wanted length by either expanding it to its capacity
-// or allocating a new slice if b has insufficient capacity.
-func growslice(b []byte, wantLength int) []byte {
- if len(b) >= wantLength {
- return b
- }
- if cap(b) >= wantLength {
- return b[:cap(b)]
- }
- return make([]byte, wantLength)
-}
diff --git a/p2p/rlpx/buffer_test.go b/p2p/rlpx/buffer_test.go
deleted file mode 100644
index 9fee4172bd..0000000000
--- a/p2p/rlpx/buffer_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package rlpx
-
-import (
- "bytes"
- "testing"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/stretchr/testify/assert"
-)
-
-func TestReadBufferReset(t *testing.T) {
- reader := bytes.NewReader(hexutil.MustDecode("0x010202030303040505"))
- var b readBuffer
-
- s1, _ := b.read(reader, 1)
- s2, _ := b.read(reader, 2)
- s3, _ := b.read(reader, 3)
-
- assert.Equal(t, []byte{1}, s1)
- assert.Equal(t, []byte{2, 2}, s2)
- assert.Equal(t, []byte{3, 3, 3}, s3)
-
- b.reset()
-
- s4, _ := b.read(reader, 1)
- s5, _ := b.read(reader, 2)
-
- assert.Equal(t, []byte{4}, s4)
- assert.Equal(t, []byte{5, 5}, s5)
-
- s6, err := b.read(reader, 2)
-
- assert.EqualError(t, err, "EOF")
- assert.Nil(t, s6)
-}
diff --git a/p2p/rlpx/rlpx.go b/p2p/rlpx/rlpx.go
deleted file mode 100644
index 8bd6f64b9b..0000000000
--- a/p2p/rlpx/rlpx.go
+++ /dev/null
@@ -1,676 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package rlpx implements the RLPx transport protocol.
-package rlpx
-
-import (
- "bytes"
- "crypto/aes"
- "crypto/cipher"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/hmac"
- "crypto/rand"
- "encoding/binary"
- "errors"
- "fmt"
- "hash"
- "io"
- mrand "math/rand"
- "net"
- "time"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/crypto/ecies"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/golang/snappy"
- "golang.org/x/crypto/sha3"
-)
-
-// Conn is an RLPx network connection. It wraps a low-level network connection. The
-// underlying connection should not be used for other activity when it is wrapped by Conn.
-//
-// Before sending messages, a handshake must be performed by calling the Handshake method.
-// This type is not generally safe for concurrent use, but reading and writing of messages
-// may happen concurrently after the handshake.
-type Conn struct {
- dialDest *ecdsa.PublicKey
- conn net.Conn
- session *sessionState
-
- // These are the buffers for snappy compression.
- // Compression is enabled if they are non-nil.
- snappyReadBuffer []byte
- snappyWriteBuffer []byte
-}
-
-// sessionState contains the session keys.
-type sessionState struct {
- enc cipher.Stream
- dec cipher.Stream
-
- egressMAC hashMAC
- ingressMAC hashMAC
- rbuf readBuffer
- wbuf writeBuffer
-}
-
-// hashMAC holds the state of the RLPx v4 MAC contraption.
-type hashMAC struct {
- cipher cipher.Block
- hash hash.Hash
- aesBuffer [16]byte
- hashBuffer [32]byte
- seedBuffer [32]byte
-}
-
-func newHashMAC(cipher cipher.Block, h hash.Hash) hashMAC {
- m := hashMAC{cipher: cipher, hash: h}
- if cipher.BlockSize() != len(m.aesBuffer) {
- panic(fmt.Errorf("invalid MAC cipher block size %d", cipher.BlockSize()))
- }
- if h.Size() != len(m.hashBuffer) {
- panic(fmt.Errorf("invalid MAC digest size %d", h.Size()))
- }
- return m
-}
-
-// NewConn wraps the given network connection. If dialDest is non-nil, the connection
-// behaves as the initiator during the handshake.
-func NewConn(conn net.Conn, dialDest *ecdsa.PublicKey) *Conn {
- return &Conn{
- dialDest: dialDest,
- conn: conn,
- }
-}
-
-// SetSnappy enables or disables snappy compression of messages. This is usually called
-// after the devp2p Hello message exchange when the negotiated version indicates that
-// compression is available on both ends of the connection.
-func (c *Conn) SetSnappy(snappy bool) {
- if snappy {
- c.snappyReadBuffer = []byte{}
- c.snappyWriteBuffer = []byte{}
- } else {
- c.snappyReadBuffer = nil
- c.snappyWriteBuffer = nil
- }
-}
-
-// SetReadDeadline sets the deadline for all future read operations.
-func (c *Conn) SetReadDeadline(time time.Time) error {
- return c.conn.SetReadDeadline(time)
-}
-
-// SetWriteDeadline sets the deadline for all future write operations.
-func (c *Conn) SetWriteDeadline(time time.Time) error {
- return c.conn.SetWriteDeadline(time)
-}
-
-// SetDeadline sets the deadline for all future read and write operations.
-func (c *Conn) SetDeadline(time time.Time) error {
- return c.conn.SetDeadline(time)
-}
-
-// Read reads a message from the connection.
-// The returned data buffer is valid until the next call to Read.
-func (c *Conn) Read() (code uint64, data []byte, wireSize int, err error) {
- if c.session == nil {
- panic("can't ReadMsg before handshake")
- }
-
- frame, err := c.session.readFrame(c.conn)
- if err != nil {
- return 0, nil, 0, err
- }
- code, data, err = rlp.SplitUint64(frame)
- if err != nil {
- return 0, nil, 0, fmt.Errorf("invalid message code: %v", err)
- }
- wireSize = len(data)
-
- // If snappy is enabled, verify and decompress message.
- if c.snappyReadBuffer != nil {
- var actualSize int
- actualSize, err = snappy.DecodedLen(data)
- if err != nil {
- return code, nil, 0, err
- }
- if actualSize > maxUint24 {
- return code, nil, 0, errPlainMessageTooLarge
- }
- c.snappyReadBuffer = growslice(c.snappyReadBuffer, actualSize)
- data, err = snappy.Decode(c.snappyReadBuffer, data)
- }
- return code, data, wireSize, err
-}
-
-func (h *sessionState) readFrame(conn io.Reader) ([]byte, error) {
- h.rbuf.reset()
-
- // Read the frame header.
- header, err := h.rbuf.read(conn, 32)
- if err != nil {
- return nil, err
- }
-
- // Verify header MAC.
- wantHeaderMAC := h.ingressMAC.computeHeader(header[:16])
- if !hmac.Equal(wantHeaderMAC, header[16:]) {
- return nil, errors.New("bad header MAC")
- }
-
- // Decrypt the frame header to get the frame size.
- h.dec.XORKeyStream(header[:16], header[:16])
- fsize := readUint24(header[:16])
- // Frame size rounded up to 16 byte boundary for padding.
- rsize := fsize
- if padding := fsize % 16; padding > 0 {
- rsize += 16 - padding
- }
-
- // Read the frame content.
- frame, err := h.rbuf.read(conn, int(rsize))
- if err != nil {
- return nil, err
- }
-
- // Validate frame MAC.
- frameMAC, err := h.rbuf.read(conn, 16)
- if err != nil {
- return nil, err
- }
- wantFrameMAC := h.ingressMAC.computeFrame(frame)
- if !hmac.Equal(wantFrameMAC, frameMAC) {
- return nil, errors.New("bad frame MAC")
- }
-
- // Decrypt the frame data.
- h.dec.XORKeyStream(frame, frame)
- return frame[:fsize], nil
-}
-
-// Write writes a message to the connection.
-//
-// Write returns the written size of the message data. This may be less than or equal to
-// len(data) depending on whether snappy compression is enabled.
-func (c *Conn) Write(code uint64, data []byte) (uint32, error) {
- if c.session == nil {
- panic("can't WriteMsg before handshake")
- }
- if len(data) > maxUint24 {
- return 0, errPlainMessageTooLarge
- }
- if c.snappyWriteBuffer != nil {
- // Ensure the buffer has sufficient size.
- // Package snappy will allocate its own buffer if the provided
- // one is smaller than MaxEncodedLen.
- c.snappyWriteBuffer = growslice(c.snappyWriteBuffer, snappy.MaxEncodedLen(len(data)))
- data = snappy.Encode(c.snappyWriteBuffer, data)
- }
-
- wireSize := uint32(len(data))
- err := c.session.writeFrame(c.conn, code, data)
- return wireSize, err
-}
-
-func (h *sessionState) writeFrame(conn io.Writer, code uint64, data []byte) error {
- h.wbuf.reset()
-
- // Write header.
- fsize := rlp.IntSize(code) + len(data)
- if fsize > maxUint24 {
- return errPlainMessageTooLarge
- }
- header := h.wbuf.appendZero(16)
- putUint24(uint32(fsize), header)
- copy(header[3:], zeroHeader)
- h.enc.XORKeyStream(header, header)
-
- // Write header MAC.
- h.wbuf.Write(h.egressMAC.computeHeader(header))
-
- // Encode and encrypt the frame data.
- offset := len(h.wbuf.data)
- h.wbuf.data = rlp.AppendUint64(h.wbuf.data, code)
- h.wbuf.Write(data)
- if padding := fsize % 16; padding > 0 {
- h.wbuf.appendZero(16 - padding)
- }
- framedata := h.wbuf.data[offset:]
- h.enc.XORKeyStream(framedata, framedata)
-
- // Write frame MAC.
- h.wbuf.Write(h.egressMAC.computeFrame(framedata))
-
- _, err := conn.Write(h.wbuf.data)
- return err
-}
-
-// computeHeader computes the MAC of a frame header.
-func (m *hashMAC) computeHeader(header []byte) []byte {
- sum1 := m.hash.Sum(m.hashBuffer[:0])
- return m.compute(sum1, header)
-}
-
-// computeFrame computes the MAC of framedata.
-func (m *hashMAC) computeFrame(framedata []byte) []byte {
- m.hash.Write(framedata)
- seed := m.hash.Sum(m.seedBuffer[:0])
- return m.compute(seed, seed[:16])
-}
-
-// compute computes the MAC of a 16-byte 'seed'.
-//
-// To do this, it encrypts the current value of the hash state, then XORs the ciphertext
-// with seed. The obtained value is written back into the hash state and hash output is
-// taken again. The first 16 bytes of the resulting sum are the MAC value.
-//
-// This MAC construction is a horrible, legacy thing.
-func (m *hashMAC) compute(sum1, seed []byte) []byte {
- if len(seed) != len(m.aesBuffer) {
- panic("invalid MAC seed")
- }
-
- m.cipher.Encrypt(m.aesBuffer[:], sum1)
- for i := range m.aesBuffer {
- m.aesBuffer[i] ^= seed[i]
- }
- m.hash.Write(m.aesBuffer[:])
- sum2 := m.hash.Sum(m.hashBuffer[:0])
- return sum2[:16]
-}
-
-// Handshake performs the handshake. This must be called before any data is written
-// or read from the connection.
-func (c *Conn) Handshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
- var (
- sec Secrets
- err error
- h handshakeState
- )
- if c.dialDest != nil {
- sec, err = h.runInitiator(c.conn, prv, c.dialDest)
- } else {
- sec, err = h.runRecipient(c.conn, prv)
- }
- if err != nil {
- return nil, err
- }
- c.InitWithSecrets(sec)
- c.session.rbuf = h.rbuf
- c.session.wbuf = h.wbuf
- return sec.remote, err
-}
-
-// InitWithSecrets injects connection secrets as if a handshake had
-// been performed. This cannot be called after the handshake.
-func (c *Conn) InitWithSecrets(sec Secrets) {
- if c.session != nil {
- panic("can't handshake twice")
- }
- macc, err := aes.NewCipher(sec.MAC)
- if err != nil {
- panic("invalid MAC secret: " + err.Error())
- }
- encc, err := aes.NewCipher(sec.AES)
- if err != nil {
- panic("invalid AES secret: " + err.Error())
- }
- // we use an all-zeroes IV for AES because the key used
- // for encryption is ephemeral.
- iv := make([]byte, encc.BlockSize())
- c.session = &sessionState{
- enc: cipher.NewCTR(encc, iv),
- dec: cipher.NewCTR(encc, iv),
- egressMAC: newHashMAC(macc, sec.EgressMAC),
- ingressMAC: newHashMAC(macc, sec.IngressMAC),
- }
-}
-
-// Close closes the underlying network connection.
-func (c *Conn) Close() error {
- return c.conn.Close()
-}
-
-// Constants for the handshake.
-const (
- sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2
- sigLen = crypto.SignatureLength // elliptic S256
- pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte
- shaLen = 32 // hash length (for nonce etc)
-
- eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */
-)
-
-var (
- // this is used in place of actual frame header data.
- // TODO: replace this when Msg contains the protocol type code.
- zeroHeader = []byte{0xC2, 0x80, 0x80}
-
- // errPlainMessageTooLarge is returned if a decompressed message length exceeds
- // the allowed 24 bits (i.e. length >= 16MB).
- errPlainMessageTooLarge = errors.New("message length >= 16MB")
-)
-
-// Secrets represents the connection secrets which are negotiated during the handshake.
-type Secrets struct {
- AES, MAC []byte
- EgressMAC, IngressMAC hash.Hash
- remote *ecdsa.PublicKey
-}
-
-// handshakeState contains the state of the encryption handshake.
-type handshakeState struct {
- initiator bool
- remote *ecies.PublicKey // remote-pubk
- initNonce, respNonce []byte // nonce
- randomPrivKey *ecies.PrivateKey // ecdhe-random
- remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk
-
- rbuf readBuffer
- wbuf writeBuffer
-}
-
-// RLPx v4 handshake auth (defined in EIP-8).
-type authMsgV4 struct {
- Signature [sigLen]byte
- InitiatorPubkey [pubLen]byte
- Nonce [shaLen]byte
- Version uint
-
- // Ignore additional fields (forward-compatibility)
- Rest []rlp.RawValue `rlp:"tail"`
-}
-
-// RLPx v4 handshake response (defined in EIP-8).
-type authRespV4 struct {
- RandomPubkey [pubLen]byte
- Nonce [shaLen]byte
- Version uint
-
- // Ignore additional fields (forward-compatibility)
- Rest []rlp.RawValue `rlp:"tail"`
-}
-
-// runRecipient negotiates a session token on conn.
-// it should be called on the listening side of the connection.
-//
-// prv is the local client's private key.
-func (h *handshakeState) runRecipient(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, err error) {
- authMsg := new(authMsgV4)
- authPacket, err := h.readMsg(authMsg, prv, conn)
- if err != nil {
- return s, err
- }
- if err := h.handleAuthMsg(authMsg, prv); err != nil {
- return s, err
- }
-
- authRespMsg, err := h.makeAuthResp()
- if err != nil {
- return s, err
- }
- authRespPacket, err := h.sealEIP8(authRespMsg)
- if err != nil {
- return s, err
- }
- if _, err = conn.Write(authRespPacket); err != nil {
- return s, err
- }
-
- return h.secrets(authPacket, authRespPacket)
-}
-
-func (h *handshakeState) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error {
- // Import the remote identity.
- rpub, err := importPublicKey(msg.InitiatorPubkey[:])
- if err != nil {
- return err
- }
- h.initNonce = msg.Nonce[:]
- h.remote = rpub
-
- // Generate random keypair for ECDH.
- // If a private key is already set, use it instead of generating one (for testing).
- if h.randomPrivKey == nil {
- h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
- if err != nil {
- return err
- }
- }
-
- // Check the signature.
- token, err := h.staticSharedSecret(prv)
- if err != nil {
- return err
- }
- signedMsg := xor(token, h.initNonce)
- remoteRandomPub, err := crypto.Ecrecover(signedMsg, msg.Signature[:])
- if err != nil {
- return err
- }
- h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
- return nil
-}
-
-// secrets is called after the handshake is completed.
-// It extracts the connection secrets from the handshake values.
-func (h *handshakeState) secrets(auth, authResp []byte) (Secrets, error) {
- ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen)
- if err != nil {
- return Secrets{}, err
- }
-
- // derive base secrets from ephemeral key agreement
- sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce))
- aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret)
- s := Secrets{
- remote: h.remote.ExportECDSA(),
- AES: aesSecret,
- MAC: crypto.Keccak256(ecdheSecret, aesSecret),
- }
-
- // setup sha3 instances for the MACs
- mac1 := sha3.NewLegacyKeccak256()
- mac1.Write(xor(s.MAC, h.respNonce))
- mac1.Write(auth)
- mac2 := sha3.NewLegacyKeccak256()
- mac2.Write(xor(s.MAC, h.initNonce))
- mac2.Write(authResp)
- if h.initiator {
- s.EgressMAC, s.IngressMAC = mac1, mac2
- } else {
- s.EgressMAC, s.IngressMAC = mac2, mac1
- }
-
- return s, nil
-}
-
-// staticSharedSecret returns the static shared secret, the result
-// of key agreement between the local and remote static node key.
-func (h *handshakeState) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) {
- return ecies.ImportECDSA(prv).GenerateShared(h.remote, sskLen, sskLen)
-}
-
-// runInitiator negotiates a session token on conn.
-// it should be called on the dialing side of the connection.
-//
-// prv is the local client's private key.
-func (h *handshakeState) runInitiator(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s Secrets, err error) {
- h.initiator = true
- h.remote = ecies.ImportECDSAPublic(remote)
-
- authMsg, err := h.makeAuthMsg(prv)
- if err != nil {
- return s, err
- }
- authPacket, err := h.sealEIP8(authMsg)
- if err != nil {
- return s, err
- }
-
- if _, err = conn.Write(authPacket); err != nil {
- return s, err
- }
-
- authRespMsg := new(authRespV4)
- authRespPacket, err := h.readMsg(authRespMsg, prv, conn)
- if err != nil {
- return s, err
- }
- if err := h.handleAuthResp(authRespMsg); err != nil {
- return s, err
- }
-
- return h.secrets(authPacket, authRespPacket)
-}
-
-// makeAuthMsg creates the initiator handshake message.
-func (h *handshakeState) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) {
- // Generate random initiator nonce.
- h.initNonce = make([]byte, shaLen)
- _, err := rand.Read(h.initNonce)
- if err != nil {
- return nil, err
- }
- // Generate random keypair to for ECDH.
- h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
- if err != nil {
- return nil, err
- }
-
- // Sign known message: static-shared-secret ^ nonce
- token, err := h.staticSharedSecret(prv)
- if err != nil {
- return nil, err
- }
- signed := xor(token, h.initNonce)
- signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA())
- if err != nil {
- return nil, err
- }
-
- msg := new(authMsgV4)
- copy(msg.Signature[:], signature)
- copy(msg.InitiatorPubkey[:], crypto.FromECDSAPub(&prv.PublicKey)[1:])
- copy(msg.Nonce[:], h.initNonce)
- msg.Version = 4
- return msg, nil
-}
-
-func (h *handshakeState) handleAuthResp(msg *authRespV4) (err error) {
- h.respNonce = msg.Nonce[:]
- h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:])
- return err
-}
-
-func (h *handshakeState) makeAuthResp() (msg *authRespV4, err error) {
- // Generate random nonce.
- h.respNonce = make([]byte, shaLen)
- if _, err = rand.Read(h.respNonce); err != nil {
- return nil, err
- }
-
- msg = new(authRespV4)
- copy(msg.Nonce[:], h.respNonce)
- copy(msg.RandomPubkey[:], exportPubkey(&h.randomPrivKey.PublicKey))
- msg.Version = 4
- return msg, nil
-}
-
-// readMsg reads an encrypted handshake message, decoding it into msg.
-func (h *handshakeState) readMsg(msg interface{}, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) {
- h.rbuf.reset()
- h.rbuf.grow(512)
-
- // Read the size prefix.
- prefix, err := h.rbuf.read(r, 2)
- if err != nil {
- return nil, err
- }
- size := binary.BigEndian.Uint16(prefix)
-
- // Read the handshake packet.
- packet, err := h.rbuf.read(r, int(size))
- if err != nil {
- return nil, err
- }
- dec, err := ecies.ImportECDSA(prv).Decrypt(packet, nil, prefix)
- if err != nil {
- return nil, err
- }
- // Can't use rlp.DecodeBytes here because it rejects
- // trailing data (forward-compatibility).
- s := rlp.NewStream(bytes.NewReader(dec), 0)
- err = s.Decode(msg)
- return h.rbuf.data[:len(prefix)+len(packet)], err
-}
-
-// sealEIP8 encrypts a handshake message.
-func (h *handshakeState) sealEIP8(msg interface{}) ([]byte, error) {
- h.wbuf.reset()
-
- // Write the message plaintext.
- if err := rlp.Encode(&h.wbuf, msg); err != nil {
- return nil, err
- }
- // Pad with random amount of data. the amount needs to be at least 100 bytes to make
- // the message distinguishable from pre-EIP-8 handshakes.
- h.wbuf.appendZero(mrand.Intn(100) + 100)
-
- prefix := make([]byte, 2)
- binary.BigEndian.PutUint16(prefix, uint16(len(h.wbuf.data)+eciesOverhead))
-
- enc, err := ecies.Encrypt(rand.Reader, h.remote, h.wbuf.data, nil, prefix)
- return append(prefix, enc...), err
-}
-
-// importPublicKey unmarshals 512 bit public keys.
-func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) {
- var pubKey65 []byte
- switch len(pubKey) {
- case 64:
- // add 'uncompressed key' flag
- pubKey65 = append([]byte{0x04}, pubKey...)
- case 65:
- pubKey65 = pubKey
- default:
- return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey))
- }
- // TODO: fewer pointless conversions
- pub, err := crypto.UnmarshalPubkey(pubKey65)
- if err != nil {
- return nil, err
- }
- return ecies.ImportECDSAPublic(pub), nil
-}
-
-func exportPubkey(pub *ecies.PublicKey) []byte {
- if pub == nil {
- panic("nil pubkey")
- }
- return elliptic.Marshal(pub.Curve, pub.X, pub.Y)[1:]
-}
-
-func xor(one, other []byte) (xor []byte) {
- xor = make([]byte, len(one))
- for i := 0; i < len(one); i++ {
- xor[i] = one[i] ^ other[i]
- }
- return xor
-}
diff --git a/p2p/rlpx/rlpx_test.go b/p2p/rlpx/rlpx_test.go
deleted file mode 100644
index 136cb1b5bf..0000000000
--- a/p2p/rlpx/rlpx_test.go
+++ /dev/null
@@ -1,453 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package rlpx
-
-import (
- "bytes"
- "crypto/ecdsa"
- "encoding/hex"
- "fmt"
- "io"
- "math/rand"
- "net"
- "reflect"
- "strings"
- "testing"
-
- "github.com/davecgh/go-spew/spew"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/crypto/ecies"
- "github.com/ethereum/go-ethereum/p2p/simulations/pipes"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/stretchr/testify/assert"
-)
-
-type message struct {
- code uint64
- data []byte
- err error
-}
-
-func TestHandshake(t *testing.T) {
- p1, p2 := createPeers(t)
- p1.Close()
- p2.Close()
-}
-
-// This test checks that messages can be sent and received through WriteMsg/ReadMsg.
-func TestReadWriteMsg(t *testing.T) {
- peer1, peer2 := createPeers(t)
- defer peer1.Close()
- defer peer2.Close()
-
- testCode := uint64(23)
- testData := []byte("test")
- checkMsgReadWrite(t, peer1, peer2, testCode, testData)
-
- t.Log("enabling snappy")
- peer1.SetSnappy(true)
- peer2.SetSnappy(true)
- checkMsgReadWrite(t, peer1, peer2, testCode, testData)
-}
-
-func checkMsgReadWrite(t *testing.T, p1, p2 *Conn, msgCode uint64, msgData []byte) {
- // Set up the reader.
- ch := make(chan message, 1)
- go func() {
- var msg message
- msg.code, msg.data, _, msg.err = p1.Read()
- ch <- msg
- }()
-
- // Write the message.
- _, err := p2.Write(msgCode, msgData)
- if err != nil {
- t.Fatal(err)
- }
-
- // Check it was received correctly.
- msg := <-ch
- assert.Equal(t, msgCode, msg.code, "wrong message code returned from ReadMsg")
- assert.Equal(t, msgData, msg.data, "wrong message data returned from ReadMsg")
-}
-
-func createPeers(t *testing.T) (peer1, peer2 *Conn) {
- conn1, conn2 := net.Pipe()
- key1, key2 := newkey(), newkey()
- peer1 = NewConn(conn1, &key2.PublicKey) // dialer
- peer2 = NewConn(conn2, nil) // listener
- doHandshake(t, peer1, peer2, key1, key2)
- return peer1, peer2
-}
-
-func doHandshake(t *testing.T, peer1, peer2 *Conn, key1, key2 *ecdsa.PrivateKey) {
- keyChan := make(chan *ecdsa.PublicKey, 1)
- go func() {
- pubKey, err := peer2.Handshake(key2)
- if err != nil {
- t.Errorf("peer2 could not do handshake: %v", err)
- }
- keyChan <- pubKey
- }()
-
- pubKey2, err := peer1.Handshake(key1)
- if err != nil {
- t.Errorf("peer1 could not do handshake: %v", err)
- }
- pubKey1 := <-keyChan
-
- // Confirm the handshake was successful.
- if !reflect.DeepEqual(pubKey1, &key1.PublicKey) || !reflect.DeepEqual(pubKey2, &key2.PublicKey) {
- t.Fatal("unsuccessful handshake")
- }
-}
-
-// This test checks the frame data of written messages.
-func TestFrameReadWrite(t *testing.T) {
- conn := NewConn(nil, nil)
- hash := fakeHash([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})
- conn.InitWithSecrets(Secrets{
- AES: crypto.Keccak256(),
- MAC: crypto.Keccak256(),
- IngressMAC: hash,
- EgressMAC: hash,
- })
- h := conn.session
-
- golden := unhex(`
- 00828ddae471818bb0bfa6b551d1cb42
- 01010101010101010101010101010101
- ba628a4ba590cb43f7848f41c4382885
- 01010101010101010101010101010101
- `)
- msgCode := uint64(8)
- msg := []uint{1, 2, 3, 4}
- msgEnc, _ := rlp.EncodeToBytes(msg)
-
- // Check writeFrame. The frame that's written should be equal to the test vector.
- buf := new(bytes.Buffer)
- if err := h.writeFrame(buf, msgCode, msgEnc); err != nil {
- t.Fatalf("WriteMsg error: %v", err)
- }
- if !bytes.Equal(buf.Bytes(), golden) {
- t.Fatalf("output mismatch:\n got: %x\n want: %x", buf.Bytes(), golden)
- }
-
- // Check readFrame on the test vector.
- content, err := h.readFrame(bytes.NewReader(golden))
- if err != nil {
- t.Fatalf("ReadMsg error: %v", err)
- }
- wantContent := unhex("08C401020304")
- if !bytes.Equal(content, wantContent) {
- t.Errorf("frame content mismatch:\ngot %x\nwant %x", content, wantContent)
- }
-}
-
-type fakeHash []byte
-
-func (fakeHash) Write(p []byte) (int, error) { return len(p), nil }
-func (fakeHash) Reset() {}
-func (fakeHash) BlockSize() int { return 0 }
-func (h fakeHash) Size() int { return len(h) }
-func (h fakeHash) Sum(b []byte) []byte { return append(b, h...) }
-
-type handshakeAuthTest struct {
- input string
- wantVersion uint
- wantRest []rlp.RawValue
-}
-
-var eip8HandshakeAuthTests = []handshakeAuthTest{
- // (Authâ‚‚) EIP-8 encoding
- {
- input: `
- 01b304ab7578555167be8154d5cc456f567d5ba302662433674222360f08d5f1534499d3678b513b
- 0fca474f3a514b18e75683032eb63fccb16c156dc6eb2c0b1593f0d84ac74f6e475f1b8d56116b84
- 9634a8c458705bf83a626ea0384d4d7341aae591fae42ce6bd5c850bfe0b999a694a49bbbaf3ef6c
- da61110601d3b4c02ab6c30437257a6e0117792631a4b47c1d52fc0f8f89caadeb7d02770bf999cc
- 147d2df3b62e1ffb2c9d8c125a3984865356266bca11ce7d3a688663a51d82defaa8aad69da39ab6
- d5470e81ec5f2a7a47fb865ff7cca21516f9299a07b1bc63ba56c7a1a892112841ca44b6e0034dee
- 70c9adabc15d76a54f443593fafdc3b27af8059703f88928e199cb122362a4b35f62386da7caad09
- c001edaeb5f8a06d2b26fb6cb93c52a9fca51853b68193916982358fe1e5369e249875bb8d0d0ec3
- 6f917bc5e1eafd5896d46bd61ff23f1a863a8a8dcd54c7b109b771c8e61ec9c8908c733c0263440e
- 2aa067241aaa433f0bb053c7b31a838504b148f570c0ad62837129e547678c5190341e4f1693956c
- 3bf7678318e2d5b5340c9e488eefea198576344afbdf66db5f51204a6961a63ce072c8926c
- `,
- wantVersion: 4,
- wantRest: []rlp.RawValue{},
- },
- // (Auth₃) RLPx v4 EIP-8 encoding with version 56, additional list elements
- {
- input: `
- 01b8044c6c312173685d1edd268aa95e1d495474c6959bcdd10067ba4c9013df9e40ff45f5bfd6f7
- 2471f93a91b493f8e00abc4b80f682973de715d77ba3a005a242eb859f9a211d93a347fa64b597bf
- 280a6b88e26299cf263b01b8dfdb712278464fd1c25840b995e84d367d743f66c0e54a586725b7bb
- f12acca27170ae3283c1073adda4b6d79f27656993aefccf16e0d0409fe07db2dc398a1b7e8ee93b
- cd181485fd332f381d6a050fba4c7641a5112ac1b0b61168d20f01b479e19adf7fdbfa0905f63352
- bfc7e23cf3357657455119d879c78d3cf8c8c06375f3f7d4861aa02a122467e069acaf513025ff19
- 6641f6d2810ce493f51bee9c966b15c5043505350392b57645385a18c78f14669cc4d960446c1757
- 1b7c5d725021babbcd786957f3d17089c084907bda22c2b2675b4378b114c601d858802a55345a15
- 116bc61da4193996187ed70d16730e9ae6b3bb8787ebcaea1871d850997ddc08b4f4ea668fbf3740
- 7ac044b55be0908ecb94d4ed172ece66fd31bfdadf2b97a8bc690163ee11f5b575a4b44e36e2bfb2
- f0fce91676fd64c7773bac6a003f481fddd0bae0a1f31aa27504e2a533af4cef3b623f4791b2cca6
- d490
- `,
- wantVersion: 56,
- wantRest: []rlp.RawValue{{0x01}, {0x02}, {0xC2, 0x04, 0x05}},
- },
-}
-
-type handshakeAckTest struct {
- input string
- wantVersion uint
- wantRest []rlp.RawValue
-}
-
-var eip8HandshakeRespTests = []handshakeAckTest{
- // (Ackâ‚‚) EIP-8 encoding
- {
- input: `
- 01ea0451958701280a56482929d3b0757da8f7fbe5286784beead59d95089c217c9b917788989470
- b0e330cc6e4fb383c0340ed85fab836ec9fb8a49672712aeabbdfd1e837c1ff4cace34311cd7f4de
- 05d59279e3524ab26ef753a0095637ac88f2b499b9914b5f64e143eae548a1066e14cd2f4bd7f814
- c4652f11b254f8a2d0191e2f5546fae6055694aed14d906df79ad3b407d94692694e259191cde171
- ad542fc588fa2b7333313d82a9f887332f1dfc36cea03f831cb9a23fea05b33deb999e85489e645f
- 6aab1872475d488d7bd6c7c120caf28dbfc5d6833888155ed69d34dbdc39c1f299be1057810f34fb
- e754d021bfca14dc989753d61c413d261934e1a9c67ee060a25eefb54e81a4d14baff922180c395d
- 3f998d70f46f6b58306f969627ae364497e73fc27f6d17ae45a413d322cb8814276be6ddd13b885b
- 201b943213656cde498fa0e9ddc8e0b8f8a53824fbd82254f3e2c17e8eaea009c38b4aa0a3f306e8
- 797db43c25d68e86f262e564086f59a2fc60511c42abfb3057c247a8a8fe4fb3ccbadde17514b7ac
- 8000cdb6a912778426260c47f38919a91f25f4b5ffb455d6aaaf150f7e5529c100ce62d6d92826a7
- 1778d809bdf60232ae21ce8a437eca8223f45ac37f6487452ce626f549b3b5fdee26afd2072e4bc7
- 5833c2464c805246155289f4
- `,
- wantVersion: 4,
- wantRest: []rlp.RawValue{},
- },
- // (Ack₃) EIP-8 encoding with version 57, additional list elements
- {
- input: `
- 01f004076e58aae772bb101ab1a8e64e01ee96e64857ce82b1113817c6cdd52c09d26f7b90981cd7
- ae835aeac72e1573b8a0225dd56d157a010846d888dac7464baf53f2ad4e3d584531fa203658fab0
- 3a06c9fd5e35737e417bc28c1cbf5e5dfc666de7090f69c3b29754725f84f75382891c561040ea1d
- dc0d8f381ed1b9d0d4ad2a0ec021421d847820d6fa0ba66eaf58175f1b235e851c7e2124069fbc20
- 2888ddb3ac4d56bcbd1b9b7eab59e78f2e2d400905050f4a92dec1c4bdf797b3fc9b2f8e84a482f3
- d800386186712dae00d5c386ec9387a5e9c9a1aca5a573ca91082c7d68421f388e79127a5177d4f8
- 590237364fd348c9611fa39f78dcdceee3f390f07991b7b47e1daa3ebcb6ccc9607811cb17ce51f1
- c8c2c5098dbdd28fca547b3f58c01a424ac05f869f49c6a34672ea2cbbc558428aa1fe48bbfd6115
- 8b1b735a65d99f21e70dbc020bfdface9f724a0d1fb5895db971cc81aa7608baa0920abb0a565c9c
- 436e2fd13323428296c86385f2384e408a31e104670df0791d93e743a3a5194ee6b076fb6323ca59
- 3011b7348c16cf58f66b9633906ba54a2ee803187344b394f75dd2e663a57b956cb830dd7a908d4f
- 39a2336a61ef9fda549180d4ccde21514d117b6c6fd07a9102b5efe710a32af4eeacae2cb3b1dec0
- 35b9593b48b9d3ca4c13d245d5f04169b0b1
- `,
- wantVersion: 57,
- wantRest: []rlp.RawValue{{0x06}, {0xC2, 0x07, 0x08}, {0x81, 0xFA}},
- },
-}
-
-var (
- keyA, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
- keyB, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-)
-
-func TestHandshakeForwardCompatibility(t *testing.T) {
- var (
- pubA = crypto.FromECDSAPub(&keyA.PublicKey)[1:]
- pubB = crypto.FromECDSAPub(&keyB.PublicKey)[1:]
- ephA, _ = crypto.HexToECDSA("869d6ecf5211f1cc60418a13b9d870b22959d0c16f02bec714c960dd2298a32d")
- ephB, _ = crypto.HexToECDSA("e238eb8e04fee6511ab04c6dd3c89ce097b11f25d584863ac2b6d5b35b1847e4")
- ephPubA = crypto.FromECDSAPub(&ephA.PublicKey)[1:]
- ephPubB = crypto.FromECDSAPub(&ephB.PublicKey)[1:]
- nonceA = unhex("7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6")
- nonceB = unhex("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd")
- _, _, _, _ = pubA, pubB, ephPubA, ephPubB
- authSignature = unhex("299ca6acfd35e3d72d8ba3d1e2b60b5561d5af5218eb5bc182045769eb4226910a301acae3b369fffc4a4899d6b02531e89fd4fe36a2cf0d93607ba470b50f7800")
- _ = authSignature
- )
- makeAuth := func(test handshakeAuthTest) *authMsgV4 {
- msg := &authMsgV4{Version: test.wantVersion, Rest: test.wantRest}
- copy(msg.Signature[:], authSignature)
- copy(msg.InitiatorPubkey[:], pubA)
- copy(msg.Nonce[:], nonceA)
- return msg
- }
- makeAck := func(test handshakeAckTest) *authRespV4 {
- msg := &authRespV4{Version: test.wantVersion, Rest: test.wantRest}
- copy(msg.RandomPubkey[:], ephPubB)
- copy(msg.Nonce[:], nonceB)
- return msg
- }
-
- // check auth msg parsing
- for _, test := range eip8HandshakeAuthTests {
- var h handshakeState
- r := bytes.NewReader(unhex(test.input))
- msg := new(authMsgV4)
- ciphertext, err := h.readMsg(msg, keyB, r)
- if err != nil {
- t.Errorf("error for input %x:\n %v", unhex(test.input), err)
- continue
- }
- if !bytes.Equal(ciphertext, unhex(test.input)) {
- t.Errorf("wrong ciphertext for input %x:\n %x", unhex(test.input), ciphertext)
- }
- want := makeAuth(test)
- if !reflect.DeepEqual(msg, want) {
- t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", unhex(test.input), spew.Sdump(msg), spew.Sdump(want))
- }
- }
-
- // check auth resp parsing
- for _, test := range eip8HandshakeRespTests {
- var h handshakeState
- input := unhex(test.input)
- r := bytes.NewReader(input)
- msg := new(authRespV4)
- ciphertext, err := h.readMsg(msg, keyA, r)
- if err != nil {
- t.Errorf("error for input %x:\n %v", input, err)
- continue
- }
- if !bytes.Equal(ciphertext, input) {
- t.Errorf("wrong ciphertext for input %x:\n %x", input, err)
- }
- want := makeAck(test)
- if !reflect.DeepEqual(msg, want) {
- t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", input, spew.Sdump(msg), spew.Sdump(want))
- }
- }
-
- // check derivation for (Authâ‚‚, Ackâ‚‚) on recipient side
- var (
- hs = &handshakeState{
- initiator: false,
- respNonce: nonceB,
- randomPrivKey: ecies.ImportECDSA(ephB),
- }
- authCiphertext = unhex(eip8HandshakeAuthTests[0].input)
- authRespCiphertext = unhex(eip8HandshakeRespTests[0].input)
- authMsg = makeAuth(eip8HandshakeAuthTests[0])
- wantAES = unhex("80e8632c05fed6fc2a13b0f8d31a3cf645366239170ea067065aba8e28bac487")
- wantMAC = unhex("2ea74ec5dae199227dff1af715362700e989d889d7a493cb0639691efb8e5f98")
- wantFooIngressHash = unhex("0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5")
- )
- if err := hs.handleAuthMsg(authMsg, keyB); err != nil {
- t.Fatalf("handleAuthMsg: %v", err)
- }
- derived, err := hs.secrets(authCiphertext, authRespCiphertext)
- if err != nil {
- t.Fatalf("secrets: %v", err)
- }
- if !bytes.Equal(derived.AES, wantAES) {
- t.Errorf("aes-secret mismatch:\ngot %x\nwant %x", derived.AES, wantAES)
- }
- if !bytes.Equal(derived.MAC, wantMAC) {
- t.Errorf("mac-secret mismatch:\ngot %x\nwant %x", derived.MAC, wantMAC)
- }
- io.WriteString(derived.IngressMAC, "foo")
- fooIngressHash := derived.IngressMAC.Sum(nil)
- if !bytes.Equal(fooIngressHash, wantFooIngressHash) {
- t.Errorf("ingress-mac('foo') mismatch:\ngot %x\nwant %x", fooIngressHash, wantFooIngressHash)
- }
-}
-
-func BenchmarkHandshakeRead(b *testing.B) {
- var input = unhex(eip8HandshakeAuthTests[0].input)
-
- for i := 0; i < b.N; i++ {
- var (
- h handshakeState
- r = bytes.NewReader(input)
- msg = new(authMsgV4)
- )
- if _, err := h.readMsg(msg, keyB, r); err != nil {
- b.Fatal(err)
- }
- }
-}
-
-func BenchmarkThroughput(b *testing.B) {
- pipe1, pipe2, err := pipes.TCPPipe()
- if err != nil {
- b.Fatal(err)
- }
-
- var (
- conn1, conn2 = NewConn(pipe1, nil), NewConn(pipe2, &keyA.PublicKey)
- handshakeDone = make(chan error, 1)
- msgdata = make([]byte, 1024)
- rand = rand.New(rand.NewSource(1337))
- )
- rand.Read(msgdata)
-
- // Server side.
- go func() {
- defer conn1.Close()
- // Perform handshake.
- _, err := conn1.Handshake(keyA)
- handshakeDone <- err
- if err != nil {
- return
- }
- conn1.SetSnappy(true)
- // Keep sending messages until connection closed.
- for {
- if _, err := conn1.Write(0, msgdata); err != nil {
- return
- }
- }
- }()
-
- // Set up client side.
- defer conn2.Close()
- if _, err := conn2.Handshake(keyB); err != nil {
- b.Fatal("client handshake error:", err)
- }
- conn2.SetSnappy(true)
- if err := <-handshakeDone; err != nil {
- b.Fatal("server handshake error:", err)
- }
-
- // Read N messages.
- b.SetBytes(int64(len(msgdata)))
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- _, _, _, err := conn2.Read()
- if err != nil {
- b.Fatal("read error:", err)
- }
- }
-}
-
-func unhex(str string) []byte {
- r := strings.NewReplacer("\t", "", " ", "", "\n", "")
- b, err := hex.DecodeString(r.Replace(str))
- if err != nil {
- panic(fmt.Sprintf("invalid hex string: %q", str))
- }
- return b
-}
-
-func newkey() *ecdsa.PrivateKey {
- key, err := crypto.GenerateKey()
- if err != nil {
- panic("couldn't generate key: " + err.Error())
- }
- return key
-}
diff --git a/p2p/server.go b/p2p/server.go
deleted file mode 100644
index 8f42765a8c..0000000000
--- a/p2p/server.go
+++ /dev/null
@@ -1,1134 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package p2p implements the Ethereum p2p network protocols.
-package p2p
-
-import (
- "bytes"
- "crypto/ecdsa"
- "encoding/hex"
- "errors"
- "fmt"
- "net"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/event"
- "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/p2p/nat"
- "github.com/ethereum/go-ethereum/p2p/netutil"
- "golang.org/x/exp/slices"
-)
-
-const (
- defaultDialTimeout = 15 * time.Second
-
- // This is the fairness knob for the discovery mixer. When looking for peers, we'll
- // wait this long for a single source of candidates before moving on and trying other
- // sources.
- discmixTimeout = 5 * time.Second
-
- // Connectivity defaults.
- defaultMaxPendingPeers = 50
- defaultDialRatio = 3
-
- // This time limits inbound connection attempts per source IP.
- inboundThrottleTime = 30 * time.Second
-
- // Maximum time allowed for reading a complete message.
- // This is effectively the amount of time a connection can be idle.
- frameReadTimeout = 30 * time.Second
-
- // Maximum amount of time allowed for writing a complete message.
- frameWriteTimeout = 20 * time.Second
-)
-
-var (
- errServerStopped = errors.New("server stopped")
- errEncHandshakeError = errors.New("rlpx enc error")
- errProtoHandshakeError = errors.New("rlpx proto error")
-)
-
-// Config holds Server options.
-type Config struct {
- // This field must be set to a valid secp256k1 private key.
- PrivateKey *ecdsa.PrivateKey `toml:"-"`
-
- // MaxPeers is the maximum number of peers that can be
- // connected. It must be greater than zero.
- MaxPeers int
-
- // MaxPendingPeers is the maximum number of peers that can be pending in the
- // handshake phase, counted separately for inbound and outbound connections.
- // Zero defaults to preset values.
- MaxPendingPeers int `toml:",omitempty"`
-
- // DialRatio controls the ratio of inbound to dialed connections.
- // Example: a DialRatio of 2 allows 1/2 of connections to be dialed.
- // Setting DialRatio to zero defaults it to 3.
- DialRatio int `toml:",omitempty"`
-
- // NoDiscovery can be used to disable the peer discovery mechanism.
- // Disabling is useful for protocol debugging (manual topology).
- NoDiscovery bool
-
- // DiscoveryV4 specifies whether V4 discovery should be started.
- DiscoveryV4 bool `toml:",omitempty"`
-
- // DiscoveryV5 specifies whether the new topic-discovery based V5 discovery
- // protocol should be started or not.
- DiscoveryV5 bool `toml:",omitempty"`
-
- // Name sets the node name of this server.
- Name string `toml:"-"`
-
- // BootstrapNodes are used to establish connectivity
- // with the rest of the network.
- BootstrapNodes []*enode.Node
-
- // BootstrapNodesV5 are used to establish connectivity
- // with the rest of the network using the V5 discovery
- // protocol.
- BootstrapNodesV5 []*enode.Node `toml:",omitempty"`
-
- // Static nodes are used as pre-configured connections which are always
- // maintained and re-connected on disconnects.
- StaticNodes []*enode.Node
-
- // Trusted nodes are used as pre-configured connections which are always
- // allowed to connect, even above the peer limit.
- TrustedNodes []*enode.Node
-
- // Connectivity can be restricted to certain IP networks.
- // If this option is set to a non-nil value, only hosts which match one of the
- // IP networks contained in the list are considered.
- NetRestrict *netutil.Netlist `toml:",omitempty"`
-
- // NodeDatabase is the path to the database containing the previously seen
- // live nodes in the network.
- NodeDatabase string `toml:",omitempty"`
-
- // Protocols should contain the protocols supported
- // by the server. Matching protocols are launched for
- // each peer.
- Protocols []Protocol `toml:"-" json:"-"`
-
- // If ListenAddr is set to a non-nil address, the server
- // will listen for incoming connections.
- //
- // If the port is zero, the operating system will pick a port. The
- // ListenAddr field will be updated with the actual address when
- // the server is started.
- ListenAddr string
-
- // If DiscAddr is set to a non-nil value, the server will use ListenAddr
- // for TCP and DiscAddr for the UDP discovery protocol.
- DiscAddr string
-
- // If set to a non-nil value, the given NAT port mapper
- // is used to make the listening port available to the
- // Internet.
- NAT nat.Interface `toml:",omitempty"`
-
- // If Dialer is set to a non-nil value, the given Dialer
- // is used to dial outbound peer connections.
- Dialer NodeDialer `toml:"-"`
-
- // If NoDial is true, the server will not dial any peers.
- NoDial bool `toml:",omitempty"`
-
- // If EnableMsgEvents is set then the server will emit PeerEvents
- // whenever a message is sent to or received from a peer
- EnableMsgEvents bool
-
- // Logger is a custom logger to use with the p2p.Server.
- Logger log.Logger `toml:",omitempty"`
-
- clock mclock.Clock
-}
-
-// Server manages all peer connections.
-type Server struct {
- // Config fields may not be modified while the server is running.
- Config
-
- // Hooks for testing. These are useful because we can inhibit
- // the whole protocol stack.
- newTransport func(net.Conn, *ecdsa.PublicKey) transport
- newPeerHook func(*Peer)
- listenFunc func(network, addr string) (net.Listener, error)
-
- lock sync.Mutex // protects running
- running bool
-
- listener net.Listener
- ourHandshake *protoHandshake
- loopWG sync.WaitGroup // loop, listenLoop
- peerFeed event.Feed
- log log.Logger
-
- nodedb *enode.DB
- localnode *enode.LocalNode
- ntab *discover.UDPv4
- DiscV5 *discover.UDPv5
- discmix *enode.FairMix
- dialsched *dialScheduler
-
- // This is read by the NAT port mapping loop.
- portMappingRegister chan *portMapping
-
- // Channels into the run loop.
- quit chan struct{}
- addtrusted chan *enode.Node
- removetrusted chan *enode.Node
- peerOp chan peerOpFunc
- peerOpDone chan struct{}
- delpeer chan peerDrop
- checkpointPostHandshake chan *conn
- checkpointAddPeer chan *conn
-
- // State of run loop and listenLoop.
- inboundHistory expHeap
-}
-
-type peerOpFunc func(map[enode.ID]*Peer)
-
-type peerDrop struct {
- *Peer
- err error
- requested bool // true if signaled by the peer
-}
-
-type connFlag int32
-
-const (
- dynDialedConn connFlag = 1 << iota
- staticDialedConn
- inboundConn
- trustedConn
-)
-
-// conn wraps a network connection with information gathered
-// during the two handshakes.
-type conn struct {
- fd net.Conn
- transport
- node *enode.Node
- flags connFlag
- cont chan error // The run loop uses cont to signal errors to SetupConn.
- caps []Cap // valid after the protocol handshake
- name string // valid after the protocol handshake
-}
-
-type transport interface {
- // The two handshakes.
- doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error)
- doProtoHandshake(our *protoHandshake) (*protoHandshake, error)
- // The MsgReadWriter can only be used after the encryption
- // handshake has completed. The code uses conn.id to track this
- // by setting it to a non-nil value after the encryption handshake.
- MsgReadWriter
- // transports must provide Close because we use MsgPipe in some of
- // the tests. Closing the actual network connection doesn't do
- // anything in those tests because MsgPipe doesn't use it.
- close(err error)
-}
-
-func (c *conn) String() string {
- s := c.flags.String()
- if (c.node.ID() != enode.ID{}) {
- s += " " + c.node.ID().String()
- }
- s += " " + c.fd.RemoteAddr().String()
- return s
-}
-
-func (f connFlag) String() string {
- s := ""
- if f&trustedConn != 0 {
- s += "-trusted"
- }
- if f&dynDialedConn != 0 {
- s += "-dyndial"
- }
- if f&staticDialedConn != 0 {
- s += "-staticdial"
- }
- if f&inboundConn != 0 {
- s += "-inbound"
- }
- if s != "" {
- s = s[1:]
- }
- return s
-}
-
-func (c *conn) is(f connFlag) bool {
- flags := connFlag(atomic.LoadInt32((*int32)(&c.flags)))
- return flags&f != 0
-}
-
-func (c *conn) set(f connFlag, val bool) {
- for {
- oldFlags := connFlag(atomic.LoadInt32((*int32)(&c.flags)))
- flags := oldFlags
- if val {
- flags |= f
- } else {
- flags &= ^f
- }
- if atomic.CompareAndSwapInt32((*int32)(&c.flags), int32(oldFlags), int32(flags)) {
- return
- }
- }
-}
-
-// LocalNode returns the local node record.
-func (srv *Server) LocalNode() *enode.LocalNode {
- return srv.localnode
-}
-
-// Peers returns all connected peers.
-func (srv *Server) Peers() []*Peer {
- var ps []*Peer
- srv.doPeerOp(func(peers map[enode.ID]*Peer) {
- for _, p := range peers {
- ps = append(ps, p)
- }
- })
- return ps
-}
-
-// PeerCount returns the number of connected peers.
-func (srv *Server) PeerCount() int {
- var count int
- srv.doPeerOp(func(ps map[enode.ID]*Peer) {
- count = len(ps)
- })
- return count
-}
-
-// AddPeer adds the given node to the static node set. When there is room in the peer set,
-// the server will connect to the node. If the connection fails for any reason, the server
-// will attempt to reconnect the peer.
-func (srv *Server) AddPeer(node *enode.Node) {
- srv.dialsched.addStatic(node)
-}
-
-// RemovePeer removes a node from the static node set. It also disconnects from the given
-// node if it is currently connected as a peer.
-//
-// This method blocks until all protocols have exited and the peer is removed. Do not use
-// RemovePeer in protocol implementations, call Disconnect on the Peer instead.
-func (srv *Server) RemovePeer(node *enode.Node) {
- var (
- ch chan *PeerEvent
- sub event.Subscription
- )
- // Disconnect the peer on the main loop.
- srv.doPeerOp(func(peers map[enode.ID]*Peer) {
- srv.dialsched.removeStatic(node)
- if peer := peers[node.ID()]; peer != nil {
- ch = make(chan *PeerEvent, 1)
- sub = srv.peerFeed.Subscribe(ch)
- peer.Disconnect(DiscRequested)
- }
- })
- // Wait for the peer connection to end.
- if ch != nil {
- defer sub.Unsubscribe()
- for ev := range ch {
- if ev.Peer == node.ID() && ev.Type == PeerEventTypeDrop {
- return
- }
- }
- }
-}
-
-// AddTrustedPeer adds the given node to a reserved trusted list which allows the
-// node to always connect, even if the slot are full.
-func (srv *Server) AddTrustedPeer(node *enode.Node) {
- select {
- case srv.addtrusted <- node:
- case <-srv.quit:
- }
-}
-
-// RemoveTrustedPeer removes the given node from the trusted peer set.
-func (srv *Server) RemoveTrustedPeer(node *enode.Node) {
- select {
- case srv.removetrusted <- node:
- case <-srv.quit:
- }
-}
-
-// SubscribeEvents subscribes the given channel to peer events
-func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription {
- return srv.peerFeed.Subscribe(ch)
-}
-
-// Self returns the local node's endpoint information.
-func (srv *Server) Self() *enode.Node {
- srv.lock.Lock()
- ln := srv.localnode
- srv.lock.Unlock()
-
- if ln == nil {
- return enode.NewV4(&srv.PrivateKey.PublicKey, net.ParseIP("0.0.0.0"), 0, 0)
- }
- return ln.Node()
-}
-
-// Stop terminates the server and all active peer connections.
-// It blocks until all active connections have been closed.
-func (srv *Server) Stop() {
- srv.lock.Lock()
- if !srv.running {
- srv.lock.Unlock()
- return
- }
- srv.running = false
- if srv.listener != nil {
- // this unblocks listener Accept
- srv.listener.Close()
- }
- close(srv.quit)
- srv.lock.Unlock()
- 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
-}
-
-// ReadFromUDP implements discover.UDPConn
-func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
- packet, ok := <-s.unhandled
- if !ok {
- return 0, nil, 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) {
- srv.lock.Lock()
- defer srv.lock.Unlock()
- if srv.running {
- return errors.New("server already running")
- }
- srv.running = true
- srv.log = srv.Logger
- if srv.log == nil {
- srv.log = log.Root()
- }
- if srv.clock == nil {
- srv.clock = mclock.System{}
- }
- if srv.NoDial && srv.ListenAddr == "" {
- srv.log.Warn("P2P server will be useless, neither dialing nor listening")
- }
-
- // static fields
- if srv.PrivateKey == nil {
- return errors.New("Server.PrivateKey must be set to a non-nil key")
- }
- if srv.newTransport == nil {
- srv.newTransport = newRLPX
- }
- if srv.listenFunc == nil {
- srv.listenFunc = net.Listen
- }
- srv.quit = make(chan struct{})
- srv.delpeer = make(chan peerDrop)
- srv.checkpointPostHandshake = make(chan *conn)
- srv.checkpointAddPeer = make(chan *conn)
- srv.addtrusted = make(chan *enode.Node)
- srv.removetrusted = make(chan *enode.Node)
- srv.peerOp = make(chan peerOpFunc)
- srv.peerOpDone = make(chan struct{})
-
- if err := srv.setupLocalNode(); err != nil {
- return err
- }
- srv.setupPortMapping()
-
- if srv.ListenAddr != "" {
- if err := srv.setupListening(); err != nil {
- return err
- }
- }
- if err := srv.setupDiscovery(); err != nil {
- return err
- }
- srv.setupDialScheduler()
-
- srv.loopWG.Add(1)
- go srv.run()
- return nil
-}
-
-func (srv *Server) setupLocalNode() error {
- // Create the devp2p handshake.
- pubkey := crypto.FromECDSAPub(&srv.PrivateKey.PublicKey)
- srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: pubkey[1:]}
- for _, p := range srv.Protocols {
- srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap())
- }
- slices.SortFunc(srv.ourHandshake.Caps, Cap.Cmp)
-
- // Create the local node.
- db, err := enode.OpenDB(srv.NodeDatabase)
- if err != nil {
- return err
- }
- srv.nodedb = db
- srv.localnode = enode.NewLocalNode(db, srv.PrivateKey)
- srv.localnode.SetFallbackIP(net.IP{127, 0, 0, 1})
- // TODO: check conflicts
- for _, p := range srv.Protocols {
- for _, e := range p.Attributes {
- srv.localnode.Set(e)
- }
- }
- return nil
-}
-
-func (srv *Server) setupDiscovery() error {
- srv.discmix = enode.NewFairMix(discmixTimeout)
-
- // Don't listen on UDP endpoint if DHT is disabled.
- if srv.NoDiscovery {
- return nil
- }
- conn, err := srv.setupUDPListening()
- if err != nil {
- return err
- }
-
- var (
- sconn discover.UDPConn = conn
- unhandled chan discover.ReadPacket
- )
- // If both versions of discovery are running, setup a shared
- // connection, so v5 can read unhandled messages from v4.
- if srv.DiscoveryV4 && srv.DiscoveryV5 {
- unhandled = make(chan discover.ReadPacket, 100)
- sconn = &sharedUDPConn{conn, unhandled}
- }
-
- // Start discovery services.
- if srv.DiscoveryV4 {
- cfg := discover.Config{
- PrivateKey: srv.PrivateKey,
- NetRestrict: srv.NetRestrict,
- Bootnodes: srv.BootstrapNodes,
- Unhandled: unhandled,
- Log: srv.log,
- }
- ntab, err := discover.ListenV4(conn, srv.localnode, cfg)
- if err != nil {
- return err
- }
- srv.ntab = ntab
- srv.discmix.AddSource(ntab.RandomNodes())
- }
- if srv.DiscoveryV5 {
- cfg := discover.Config{
- PrivateKey: srv.PrivateKey,
- NetRestrict: srv.NetRestrict,
- Bootnodes: srv.BootstrapNodesV5,
- Log: srv.log,
- }
- srv.DiscV5, err = discover.ListenV5(sconn, srv.localnode, cfg)
- if err != nil {
- return err
- }
- }
-
- // Add protocol-specific discovery sources.
- added := make(map[string]bool)
- for _, proto := range srv.Protocols {
- if proto.DialCandidates != nil && !added[proto.Name] {
- srv.discmix.AddSource(proto.DialCandidates)
- added[proto.Name] = true
- }
- }
- return nil
-}
-
-func (srv *Server) setupDialScheduler() {
- config := dialConfig{
- self: srv.localnode.ID(),
- maxDialPeers: srv.maxDialedConns(),
- maxActiveDials: srv.MaxPendingPeers,
- log: srv.Logger,
- netRestrict: srv.NetRestrict,
- dialer: srv.Dialer,
- clock: srv.clock,
- }
- if srv.ntab != nil {
- config.resolver = srv.ntab
- }
- if config.dialer == nil {
- config.dialer = tcpDialer{&net.Dialer{Timeout: defaultDialTimeout}}
- }
- srv.dialsched = newDialScheduler(config, srv.discmix, srv.SetupConn)
- for _, n := range srv.StaticNodes {
- srv.dialsched.addStatic(n)
- }
-}
-
-func (srv *Server) maxInboundConns() int {
- return srv.MaxPeers - srv.maxDialedConns()
-}
-
-func (srv *Server) maxDialedConns() (limit int) {
- if srv.NoDial || srv.MaxPeers == 0 {
- return 0
- }
- if srv.DialRatio == 0 {
- limit = srv.MaxPeers / defaultDialRatio
- } else {
- limit = srv.MaxPeers / srv.DialRatio
- }
- if limit == 0 {
- limit = 1
- }
- return limit
-}
-
-func (srv *Server) setupListening() error {
- // Launch the listener.
- listener, err := srv.listenFunc("tcp", srv.ListenAddr)
- if err != nil {
- return err
- }
- srv.listener = listener
- srv.ListenAddr = listener.Addr().String()
-
- // Update the local node record and map the TCP listening port if NAT is configured.
- tcp, isTCP := listener.Addr().(*net.TCPAddr)
- if isTCP {
- srv.localnode.Set(enr.TCP(tcp.Port))
- if !tcp.IP.IsLoopback() && !tcp.IP.IsPrivate() {
- srv.portMappingRegister <- &portMapping{
- protocol: "TCP",
- name: "ethereum p2p",
- port: tcp.Port,
- }
- }
- }
-
- srv.loopWG.Add(1)
- go srv.listenLoop()
- return nil
-}
-
-func (srv *Server) setupUDPListening() (*net.UDPConn, error) {
- listenAddr := srv.ListenAddr
-
- // Use an alternate listening address for UDP if
- // a custom discovery address is configured.
- if srv.DiscAddr != "" {
- listenAddr = srv.DiscAddr
- }
- addr, err := net.ResolveUDPAddr("udp", listenAddr)
- if err != nil {
- return nil, err
- }
- conn, err := net.ListenUDP("udp", addr)
- if err != nil {
- return nil, err
- }
- laddr := conn.LocalAddr().(*net.UDPAddr)
- srv.localnode.SetFallbackUDP(laddr.Port)
- srv.log.Debug("UDP listener up", "addr", laddr)
- if !laddr.IP.IsLoopback() && !laddr.IP.IsPrivate() {
- srv.portMappingRegister <- &portMapping{
- protocol: "UDP",
- name: "ethereum peer discovery",
- port: laddr.Port,
- }
- }
-
- return conn, nil
-}
-
-// doPeerOp runs fn on the main loop.
-func (srv *Server) doPeerOp(fn peerOpFunc) {
- select {
- case srv.peerOp <- fn:
- <-srv.peerOpDone
- case <-srv.quit:
- }
-}
-
-// run is the main loop of the server.
-func (srv *Server) run() {
- srv.log.Info("Started P2P networking", "self", srv.localnode.Node().URLv4())
- defer srv.loopWG.Done()
- defer srv.nodedb.Close()
- defer srv.discmix.Close()
- defer srv.dialsched.stop()
-
- var (
- peers = make(map[enode.ID]*Peer)
- inboundCount = 0
- trusted = make(map[enode.ID]bool, len(srv.TrustedNodes))
- )
- // Put trusted nodes into a map to speed up checks.
- // Trusted peers are loaded on startup or added via AddTrustedPeer RPC.
- for _, n := range srv.TrustedNodes {
- trusted[n.ID()] = true
- }
-
-running:
- for {
- select {
- case <-srv.quit:
- // The server was stopped. Run the cleanup logic.
- break running
-
- case n := <-srv.addtrusted:
- // This channel is used by AddTrustedPeer to add a node
- // to the trusted node set.
- srv.log.Trace("Adding trusted node", "node", n)
- trusted[n.ID()] = true
- if p, ok := peers[n.ID()]; ok {
- p.rw.set(trustedConn, true)
- }
-
- case n := <-srv.removetrusted:
- // This channel is used by RemoveTrustedPeer to remove a node
- // from the trusted node set.
- srv.log.Trace("Removing trusted node", "node", n)
- delete(trusted, n.ID())
- if p, ok := peers[n.ID()]; ok {
- p.rw.set(trustedConn, false)
- }
-
- case op := <-srv.peerOp:
- // This channel is used by Peers and PeerCount.
- op(peers)
- srv.peerOpDone <- struct{}{}
-
- case c := <-srv.checkpointPostHandshake:
- // A connection has passed the encryption handshake so
- // the remote identity is known (but hasn't been verified yet).
- if trusted[c.node.ID()] {
- // Ensure that the trusted flag is set before checking against MaxPeers.
- c.flags |= trustedConn
- }
- // TODO: track in-progress inbound node IDs (pre-Peer) to avoid dialing them.
- c.cont <- srv.postHandshakeChecks(peers, inboundCount, c)
-
- case c := <-srv.checkpointAddPeer:
- // At this point the connection is past the protocol handshake.
- // Its capabilities are known and the remote identity is verified.
- err := srv.addPeerChecks(peers, inboundCount, c)
- if err == nil {
- // The handshakes are done and it passed all checks.
- p := srv.launchPeer(c)
- peers[c.node.ID()] = p
- srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", p.Name())
- srv.dialsched.peerAdded(c)
- if p.Inbound() {
- inboundCount++
- serveSuccessMeter.Mark(1)
- } else {
- dialSuccessMeter.Mark(1)
- }
- activePeerGauge.Inc(1)
- }
- c.cont <- err
-
- case pd := <-srv.delpeer:
- // A peer disconnected.
- d := common.PrettyDuration(mclock.Now() - pd.created)
- delete(peers, pd.ID())
- srv.log.Debug("Removing p2p peer", "peercount", len(peers), "id", pd.ID(), "duration", d, "req", pd.requested, "err", pd.err)
- srv.dialsched.peerRemoved(pd.rw)
- if pd.Inbound() {
- inboundCount--
- }
- activePeerGauge.Dec(1)
- }
- }
-
- srv.log.Trace("P2P networking is spinning down")
-
- // Terminate discovery. If there is a running lookup it will terminate soon.
- if srv.ntab != nil {
- srv.ntab.Close()
- }
- if srv.DiscV5 != nil {
- srv.DiscV5.Close()
- }
- // Disconnect all peers.
- for _, p := range peers {
- p.Disconnect(DiscQuitting)
- }
- // Wait for peers to shut down. Pending connections and tasks are
- // not handled here and will terminate soon-ish because srv.quit
- // is closed.
- for len(peers) > 0 {
- p := <-srv.delpeer
- p.log.Trace("<-delpeer (spindown)")
- delete(peers, p.ID())
- }
-}
-
-func (srv *Server) postHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error {
- switch {
- case !c.is(trustedConn) && len(peers) >= srv.MaxPeers:
- return DiscTooManyPeers
- case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns():
- return DiscTooManyPeers
- case peers[c.node.ID()] != nil:
- return DiscAlreadyConnected
- case c.node.ID() == srv.localnode.ID():
- return DiscSelf
- default:
- return nil
- }
-}
-
-func (srv *Server) addPeerChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error {
- // Drop connections with no matching protocols.
- if len(srv.Protocols) > 0 && countMatchingProtocols(srv.Protocols, c.caps) == 0 {
- return DiscUselessPeer
- }
- // Repeat the post-handshake checks because the
- // peer set might have changed since those checks were performed.
- return srv.postHandshakeChecks(peers, inboundCount, c)
-}
-
-// listenLoop runs in its own goroutine and accepts
-// inbound connections.
-func (srv *Server) listenLoop() {
- srv.log.Debug("TCP listener up", "addr", srv.listener.Addr())
-
- // The slots channel limits accepts of new connections.
- tokens := defaultMaxPendingPeers
- if srv.MaxPendingPeers > 0 {
- tokens = srv.MaxPendingPeers
- }
- slots := make(chan struct{}, tokens)
- for i := 0; i < tokens; i++ {
- slots <- struct{}{}
- }
-
- // Wait for slots to be returned on exit. This ensures all connection goroutines
- // are down before listenLoop returns.
- defer srv.loopWG.Done()
- defer func() {
- for i := 0; i < cap(slots); i++ {
- <-slots
- }
- }()
-
- for {
- // Wait for a free slot before accepting.
- <-slots
-
- var (
- fd net.Conn
- err error
- lastLog time.Time
- )
- for {
- fd, err = srv.listener.Accept()
- if netutil.IsTemporaryError(err) {
- if time.Since(lastLog) > 1*time.Second {
- srv.log.Debug("Temporary read error", "err", err)
- lastLog = time.Now()
- }
- time.Sleep(time.Millisecond * 200)
- continue
- } else if err != nil {
- srv.log.Debug("Read error", "err", err)
- slots <- struct{}{}
- return
- }
- break
- }
-
- remoteIP := netutil.AddrIP(fd.RemoteAddr())
- if err := srv.checkInboundConn(remoteIP); err != nil {
- srv.log.Debug("Rejected inbound connection", "addr", fd.RemoteAddr(), "err", err)
- fd.Close()
- slots <- struct{}{}
- continue
- }
- if remoteIP != nil {
- fd = newMeteredConn(fd)
- serveMeter.Mark(1)
- srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr())
- }
- go func() {
- srv.SetupConn(fd, inboundConn, nil)
- slots <- struct{}{}
- }()
- }
-}
-
-func (srv *Server) checkInboundConn(remoteIP net.IP) error {
- if remoteIP == nil {
- return nil
- }
- // Reject connections that do not match NetRestrict.
- if srv.NetRestrict != nil && !srv.NetRestrict.Contains(remoteIP) {
- return fmt.Errorf("not in netrestrict list")
- }
- // Reject Internet peers that try too often.
- now := srv.clock.Now()
- srv.inboundHistory.expire(now, nil)
- if !netutil.IsLAN(remoteIP) && srv.inboundHistory.contains(remoteIP.String()) {
- return fmt.Errorf("too many attempts")
- }
- srv.inboundHistory.add(remoteIP.String(), now.Add(inboundThrottleTime))
- return nil
-}
-
-// SetupConn runs the handshakes and attempts to add the connection
-// as a peer. It returns when the connection has been added as a peer
-// or the handshakes have failed.
-func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error {
- c := &conn{fd: fd, flags: flags, cont: make(chan error)}
- if dialDest == nil {
- c.transport = srv.newTransport(fd, nil)
- } else {
- c.transport = srv.newTransport(fd, dialDest.Pubkey())
- }
-
- err := srv.setupConn(c, flags, dialDest)
- if err != nil {
- if !c.is(inboundConn) {
- markDialError(err)
- }
- c.close(err)
- }
- return err
-}
-
-func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) error {
- // Prevent leftover pending conns from entering the handshake.
- srv.lock.Lock()
- running := srv.running
- srv.lock.Unlock()
- if !running {
- return errServerStopped
- }
-
- // If dialing, figure out the remote public key.
- if dialDest != nil {
- dialPubkey := new(ecdsa.PublicKey)
- if err := dialDest.Load((*enode.Secp256k1)(dialPubkey)); err != nil {
- err = fmt.Errorf("%w: dial destination doesn't have a secp256k1 public key", errEncHandshakeError)
- srv.log.Trace("Setting up connection failed", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
- return err
- }
- }
-
- // Run the RLPx handshake.
- remotePubkey, err := c.doEncHandshake(srv.PrivateKey)
- if err != nil {
- srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
- return fmt.Errorf("%w: %v", errEncHandshakeError, err)
- }
- if dialDest != nil {
- c.node = dialDest
- } else {
- c.node = nodeFromConn(remotePubkey, c.fd)
- }
- clog := srv.log.New("id", c.node.ID(), "addr", c.fd.RemoteAddr(), "conn", c.flags)
- err = srv.checkpoint(c, srv.checkpointPostHandshake)
- if err != nil {
- clog.Trace("Rejected peer", "err", err)
- return err
- }
-
- // Run the capability negotiation handshake.
- phs, err := c.doProtoHandshake(srv.ourHandshake)
- if err != nil {
- clog.Trace("Failed p2p handshake", "err", err)
- return fmt.Errorf("%w: %v", errProtoHandshakeError, err)
- }
- if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) {
- clog.Trace("Wrong devp2p handshake identity", "phsid", hex.EncodeToString(phs.ID))
- return DiscUnexpectedIdentity
- }
- c.caps, c.name = phs.Caps, phs.Name
- err = srv.checkpoint(c, srv.checkpointAddPeer)
- if err != nil {
- clog.Trace("Rejected peer", "err", err)
- return err
- }
-
- return nil
-}
-
-func nodeFromConn(pubkey *ecdsa.PublicKey, conn net.Conn) *enode.Node {
- var ip net.IP
- var port int
- if tcp, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
- ip = tcp.IP
- port = tcp.Port
- }
- return enode.NewV4(pubkey, ip, port, port)
-}
-
-// checkpoint sends the conn to run, which performs the
-// post-handshake checks for the stage (posthandshake, addpeer).
-func (srv *Server) checkpoint(c *conn, stage chan<- *conn) error {
- select {
- case stage <- c:
- case <-srv.quit:
- return errServerStopped
- }
- return <-c.cont
-}
-
-func (srv *Server) launchPeer(c *conn) *Peer {
- p := newPeer(srv.log, c, srv.Protocols)
- if srv.EnableMsgEvents {
- // If message events are enabled, pass the peerFeed
- // to the peer.
- p.events = &srv.peerFeed
- }
- go srv.runPeer(p)
- return p
-}
-
-// runPeer runs in its own goroutine for each peer.
-func (srv *Server) runPeer(p *Peer) {
- if srv.newPeerHook != nil {
- srv.newPeerHook(p)
- }
- srv.peerFeed.Send(&PeerEvent{
- Type: PeerEventTypeAdd,
- Peer: p.ID(),
- RemoteAddress: p.RemoteAddr().String(),
- LocalAddress: p.LocalAddr().String(),
- })
-
- // Run the per-peer main loop.
- remoteRequested, err := p.run()
-
- // Announce disconnect on the main loop to update the peer set.
- // The main loop waits for existing peers to be sent on srv.delpeer
- // before returning, so this send should not select on srv.quit.
- srv.delpeer <- peerDrop{p, err, remoteRequested}
-
- // Broadcast peer drop to external subscribers. This needs to be
- // after the send to delpeer so subscribers have a consistent view of
- // the peer set (i.e. Server.Peers() doesn't include the peer when the
- // event is received).
- srv.peerFeed.Send(&PeerEvent{
- Type: PeerEventTypeDrop,
- Peer: p.ID(),
- Error: err.Error(),
- RemoteAddress: p.RemoteAddr().String(),
- LocalAddress: p.LocalAddr().String(),
- })
-}
-
-// NodeInfo represents a short summary of the information known about the host.
-type NodeInfo struct {
- ID string `json:"id"` // Unique node identifier (also the encryption key)
- Name string `json:"name"` // Name of the node, including client type, version, OS, custom data
- Enode string `json:"enode"` // Enode URL for adding this peer from remote peers
- ENR string `json:"enr"` // Ethereum Node Record
- IP string `json:"ip"` // IP address of the node
- Ports struct {
- Discovery int `json:"discovery"` // UDP listening port for discovery protocol
- Listener int `json:"listener"` // TCP listening port for RLPx
- } `json:"ports"`
- ListenAddr string `json:"listenAddr"`
- Protocols map[string]interface{} `json:"protocols"`
-}
-
-// NodeInfo gathers and returns a collection of metadata known about the host.
-func (srv *Server) NodeInfo() *NodeInfo {
- // Gather and assemble the generic node infos
- node := srv.Self()
- info := &NodeInfo{
- Name: srv.Name,
- Enode: node.URLv4(),
- ID: node.ID().String(),
- IP: node.IP().String(),
- ListenAddr: srv.ListenAddr,
- Protocols: make(map[string]interface{}),
- }
- info.Ports.Discovery = node.UDP()
- info.Ports.Listener = node.TCP()
- info.ENR = node.String()
-
- // Gather all the running protocol infos (only once per protocol type)
- for _, proto := range srv.Protocols {
- if _, ok := info.Protocols[proto.Name]; !ok {
- nodeInfo := interface{}("unknown")
- if query := proto.NodeInfo; query != nil {
- nodeInfo = proto.NodeInfo()
- }
- info.Protocols[proto.Name] = nodeInfo
- }
- }
- return info
-}
-
-// PeersInfo returns an array of metadata objects describing connected peers.
-func (srv *Server) PeersInfo() []*PeerInfo {
- // Gather all the generic and sub-protocol specific infos
- infos := make([]*PeerInfo, 0, srv.PeerCount())
- for _, peer := range srv.Peers() {
- if peer != nil {
- infos = append(infos, peer.Info())
- }
- }
- // Sort the result array alphabetically by node identifier
- for i := 0; i < len(infos); i++ {
- for j := i + 1; j < len(infos); j++ {
- if infos[i].ID > infos[j].ID {
- infos[i], infos[j] = infos[j], infos[i]
- }
- }
- }
- return infos
-}
diff --git a/p2p/server_nat.go b/p2p/server_nat.go
deleted file mode 100644
index 354597cc7a..0000000000
--- a/p2p/server_nat.go
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "net"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/nat"
-)
-
-const (
- portMapDuration = 10 * time.Minute
- portMapRefreshInterval = 8 * time.Minute
- portMapRetryInterval = 5 * time.Minute
- extipRetryInterval = 2 * time.Minute
-)
-
-type portMapping struct {
- protocol string
- name string
- port int
-
- // for use by the portMappingLoop goroutine:
- extPort int // the mapped port returned by the NAT interface
- nextTime mclock.AbsTime
-}
-
-// setupPortMapping starts the port mapping loop if necessary.
-// Note: this needs to be called after the LocalNode instance has been set on the server.
-func (srv *Server) setupPortMapping() {
- // portMappingRegister will receive up to two values: one for the TCP port if
- // listening is enabled, and one more for enabling UDP port mapping if discovery is
- // enabled. We make it buffered to avoid blocking setup while a mapping request is in
- // progress.
- srv.portMappingRegister = make(chan *portMapping, 2)
-
- switch srv.NAT.(type) {
- case nil:
- // No NAT interface configured.
- srv.loopWG.Add(1)
- go srv.consumePortMappingRequests()
-
- case nat.ExtIP:
- // ExtIP doesn't block, set the IP right away.
- ip, _ := srv.NAT.ExternalIP()
- srv.localnode.SetStaticIP(ip)
- srv.loopWG.Add(1)
- go srv.consumePortMappingRequests()
-
- default:
- srv.loopWG.Add(1)
- go srv.portMappingLoop()
- }
-}
-
-func (srv *Server) consumePortMappingRequests() {
- defer srv.loopWG.Done()
- for {
- select {
- case <-srv.quit:
- return
- case <-srv.portMappingRegister:
- }
- }
-}
-
-// portMappingLoop manages port mappings for UDP and TCP.
-func (srv *Server) portMappingLoop() {
- defer srv.loopWG.Done()
-
- newLogger := func(p string, e int, i int) log.Logger {
- return log.New("proto", p, "extport", e, "intport", i, "interface", srv.NAT)
- }
-
- var (
- mappings = make(map[string]*portMapping, 2)
- refresh = mclock.NewAlarm(srv.clock)
- extip = mclock.NewAlarm(srv.clock)
- lastExtIP net.IP
- )
- extip.Schedule(srv.clock.Now())
- defer func() {
- refresh.Stop()
- extip.Stop()
- for _, m := range mappings {
- if m.extPort != 0 {
- log := newLogger(m.protocol, m.extPort, m.port)
- log.Debug("Deleting port mapping")
- srv.NAT.DeleteMapping(m.protocol, m.extPort, m.port)
- }
- }
- }()
-
- for {
- // Schedule refresh of existing mappings.
- for _, m := range mappings {
- refresh.Schedule(m.nextTime)
- }
-
- select {
- case <-srv.quit:
- return
-
- case <-extip.C():
- extip.Schedule(srv.clock.Now().Add(extipRetryInterval))
- ip, err := srv.NAT.ExternalIP()
- if err != nil {
- log.Debug("Couldn't get external IP", "err", err, "interface", srv.NAT)
- } else if !ip.Equal(lastExtIP) {
- log.Debug("External IP changed", "ip", extip, "interface", srv.NAT)
- } else {
- return
- }
- // Here, we either failed to get the external IP, or it has changed.
- lastExtIP = ip
- srv.localnode.SetStaticIP(ip)
- // Ensure port mappings are refreshed in case we have moved to a new network.
- for _, m := range mappings {
- m.nextTime = srv.clock.Now()
- }
-
- case m := <-srv.portMappingRegister:
- if m.protocol != "TCP" && m.protocol != "UDP" {
- panic("unknown NAT protocol name: " + m.protocol)
- }
- mappings[m.protocol] = m
- m.nextTime = srv.clock.Now()
-
- case <-refresh.C():
- for _, m := range mappings {
- if srv.clock.Now() < m.nextTime {
- continue
- }
-
- external := m.port
- if m.extPort != 0 {
- external = m.extPort
- }
- log := newLogger(m.protocol, external, m.port)
-
- log.Trace("Attempting port mapping")
- p, err := srv.NAT.AddMapping(m.protocol, external, m.port, m.name, portMapDuration)
- if err != nil {
- log.Debug("Couldn't add port mapping", "err", err)
- m.extPort = 0
- m.nextTime = srv.clock.Now().Add(portMapRetryInterval)
- continue
- }
- // It was mapped!
- m.extPort = int(p)
- m.nextTime = srv.clock.Now().Add(portMapRefreshInterval)
- if external != m.extPort {
- log = newLogger(m.protocol, m.extPort, m.port)
- log.Info("NAT mapped alternative port")
- } else {
- log.Info("NAT mapped port")
- }
-
- // Update port in local ENR.
- switch m.protocol {
- case "TCP":
- srv.localnode.Set(enr.TCP(m.extPort))
- case "UDP":
- srv.localnode.SetFallbackUDP(m.extPort)
- }
- }
- }
- }
-}
diff --git a/p2p/server_nat_test.go b/p2p/server_nat_test.go
deleted file mode 100644
index de935fcfc5..0000000000
--- a/p2p/server_nat_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "net"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
- "github.com/ethereum/go-ethereum/internal/testlog"
- "github.com/ethereum/go-ethereum/log"
-)
-
-func TestServerPortMapping(t *testing.T) {
- clock := new(mclock.Simulated)
- mockNAT := &mockNAT{mappedPort: 30000}
- srv := Server{
- Config: Config{
- PrivateKey: newkey(),
- NoDial: true,
- ListenAddr: ":0",
- NAT: mockNAT,
- Logger: testlog.Logger(t, log.LvlTrace),
- clock: clock,
- },
- }
- err := srv.Start()
- if err != nil {
- t.Fatal(err)
- }
- defer srv.Stop()
-
- // Wait for the port mapping to be registered. Synchronization with the port mapping
- // goroutine works like this: For each iteration, we allow other goroutines to run and
- // also advance the virtual clock by 1 second. Waiting stops when the NAT interface
- // has received some requests, or when the clock reaches a timeout.
- deadline := clock.Now().Add(portMapRefreshInterval)
- for clock.Now() < deadline && mockNAT.mapRequests.Load() < 2 {
- time.Sleep(10 * time.Millisecond)
- clock.Run(1 * time.Second)
- }
-
- if mockNAT.ipRequests.Load() == 0 {
- t.Fatal("external IP was never requested")
- }
- reqCount := mockNAT.mapRequests.Load()
- if reqCount != 2 {
- t.Error("wrong request count:", reqCount)
- }
- enr := srv.LocalNode().Node()
- if enr.IP().String() != "192.0.2.0" {
- t.Error("wrong IP in ENR:", enr.IP())
- }
- if enr.TCP() != 30000 {
- t.Error("wrong TCP port in ENR:", enr.TCP())
- }
- if enr.UDP() != 30000 {
- t.Error("wrong UDP port in ENR:", enr.UDP())
- }
-}
-
-type mockNAT struct {
- mappedPort uint16
- mapRequests atomic.Int32
- unmapRequests atomic.Int32
- ipRequests atomic.Int32
-}
-
-func (m *mockNAT) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
- m.mapRequests.Add(1)
- return m.mappedPort, nil
-}
-
-func (m *mockNAT) DeleteMapping(protocol string, extport, intport int) error {
- m.unmapRequests.Add(1)
- return nil
-}
-
-func (m *mockNAT) ExternalIP() (net.IP, error) {
- m.ipRequests.Add(1)
- return net.ParseIP("192.0.2.0"), nil
-}
-
-func (m *mockNAT) String() string {
- return "mockNAT"
-}
diff --git a/p2p/server_test.go b/p2p/server_test.go
deleted file mode 100644
index a0491e984a..0000000000
--- a/p2p/server_test.go
+++ /dev/null
@@ -1,631 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "crypto/ecdsa"
- "crypto/sha256"
- "errors"
- "io"
- "math/rand"
- "net"
- "reflect"
- "strconv"
- "strings"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/internal/testlog"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/p2p/rlpx"
-)
-
-type testTransport struct {
- *rlpxTransport
- rpub *ecdsa.PublicKey
- closeErr error
-}
-
-func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn, dialDest *ecdsa.PublicKey) transport {
- wrapped := newRLPX(fd, dialDest).(*rlpxTransport)
- wrapped.conn.InitWithSecrets(rlpx.Secrets{
- AES: make([]byte, 16),
- MAC: make([]byte, 16),
- EgressMAC: sha256.New(),
- IngressMAC: sha256.New(),
- })
- return &testTransport{rpub: rpub, rlpxTransport: wrapped}
-}
-
-func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
- return c.rpub, nil
-}
-
-func (c *testTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) {
- pubkey := crypto.FromECDSAPub(c.rpub)[1:]
- return &protoHandshake{ID: pubkey, Name: "test"}, nil
-}
-
-func (c *testTransport) close(err error) {
- c.conn.Close()
- c.closeErr = err
-}
-
-func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) *Server {
- config := Config{
- Name: "test",
- MaxPeers: 10,
- ListenAddr: "127.0.0.1:0",
- NoDiscovery: true,
- PrivateKey: newkey(),
- Logger: testlog.Logger(t, log.LvlTrace),
- }
- server := &Server{
- Config: config,
- newPeerHook: pf,
- newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport {
- return newTestTransport(remoteKey, fd, dialDest)
- },
- }
- if err := server.Start(); err != nil {
- t.Fatalf("Could not start server: %v", err)
- }
- return server
-}
-
-func TestServerListen(t *testing.T) {
- // start the test server
- connected := make(chan *Peer)
- remid := &newkey().PublicKey
- srv := startTestServer(t, remid, func(p *Peer) {
- if p.ID() != enode.PubkeyToIDV4(remid) {
- t.Error("peer func called with wrong node id")
- }
- connected <- p
- })
- defer close(connected)
- defer srv.Stop()
-
- // dial the test server
- conn, err := net.DialTimeout("tcp", srv.ListenAddr, 5*time.Second)
- if err != nil {
- t.Fatalf("could not dial: %v", err)
- }
- defer conn.Close()
-
- select {
- case peer := <-connected:
- if peer.LocalAddr().String() != conn.RemoteAddr().String() {
- t.Errorf("peer started with wrong conn: got %v, want %v",
- peer.LocalAddr(), conn.RemoteAddr())
- }
- peers := srv.Peers()
- if !reflect.DeepEqual(peers, []*Peer{peer}) {
- t.Errorf("Peers mismatch: got %v, want %v", peers, []*Peer{peer})
- }
- case <-time.After(1 * time.Second):
- t.Error("server did not accept within one second")
- }
-}
-
-func TestServerDial(t *testing.T) {
- // run a one-shot TCP server to handle the connection.
- listener, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatalf("could not setup listener: %v", err)
- }
- defer listener.Close()
- accepted := make(chan net.Conn, 1)
- go func() {
- conn, err := listener.Accept()
- if err != nil {
- return
- }
- accepted <- conn
- }()
-
- // start the server
- connected := make(chan *Peer)
- remid := &newkey().PublicKey
- srv := startTestServer(t, remid, func(p *Peer) { connected <- p })
- defer close(connected)
- defer srv.Stop()
-
- // tell the server to connect
- tcpAddr := listener.Addr().(*net.TCPAddr)
- node := enode.NewV4(remid, tcpAddr.IP, tcpAddr.Port, 0)
- srv.AddPeer(node)
-
- select {
- case conn := <-accepted:
- defer conn.Close()
-
- select {
- case peer := <-connected:
- if peer.ID() != enode.PubkeyToIDV4(remid) {
- t.Errorf("peer has wrong id")
- }
- if peer.Name() != "test" {
- t.Errorf("peer has wrong name")
- }
- if peer.RemoteAddr().String() != conn.LocalAddr().String() {
- t.Errorf("peer started with wrong conn: got %v, want %v",
- peer.RemoteAddr(), conn.LocalAddr())
- }
- peers := srv.Peers()
- if !reflect.DeepEqual(peers, []*Peer{peer}) {
- t.Errorf("Peers mismatch: got %v, want %v", peers, []*Peer{peer})
- }
-
- // Test AddTrustedPeer/RemoveTrustedPeer and changing Trusted flags
- // Particularly for race conditions on changing the flag state.
- if peer := srv.Peers()[0]; peer.Info().Network.Trusted {
- t.Errorf("peer is trusted prematurely: %v", peer)
- }
- done := make(chan bool)
- go func() {
- srv.AddTrustedPeer(node)
- if peer := srv.Peers()[0]; !peer.Info().Network.Trusted {
- t.Errorf("peer is not trusted after AddTrustedPeer: %v", peer)
- }
- srv.RemoveTrustedPeer(node)
- if peer := srv.Peers()[0]; peer.Info().Network.Trusted {
- t.Errorf("peer is trusted after RemoveTrustedPeer: %v", peer)
- }
- done <- true
- }()
- // Trigger potential race conditions
- peer = srv.Peers()[0]
- _ = peer.Inbound()
- _ = peer.Info()
- <-done
- case <-time.After(1 * time.Second):
- t.Error("server did not launch peer within one second")
- }
-
- case <-time.After(1 * time.Second):
- t.Error("server did not connect within one second")
- }
-}
-
-// This test checks that RemovePeer disconnects the peer if it is connected.
-func TestServerRemovePeerDisconnect(t *testing.T) {
- srv1 := &Server{Config: Config{
- PrivateKey: newkey(),
- MaxPeers: 1,
- NoDiscovery: true,
- Logger: testlog.Logger(t, log.LvlTrace).New("server", "1"),
- }}
- srv2 := &Server{Config: Config{
- PrivateKey: newkey(),
- MaxPeers: 1,
- NoDiscovery: true,
- NoDial: true,
- ListenAddr: "127.0.0.1:0",
- Logger: testlog.Logger(t, log.LvlTrace).New("server", "2"),
- }}
- srv1.Start()
- defer srv1.Stop()
- srv2.Start()
- defer srv2.Stop()
-
- s := strings.Split(srv2.ListenAddr, ":")
- if len(s) != 2 {
- t.Fatal("invalid ListenAddr")
- }
- if port, err := strconv.Atoi(s[1]); err == nil {
- srv2.localnode.Set(enr.TCP(uint16(port)))
- }
-
- if !syncAddPeer(srv1, srv2.Self()) {
- t.Fatal("peer not connected")
- }
- srv1.RemovePeer(srv2.Self())
- if srv1.PeerCount() > 0 {
- t.Fatal("removed peer still connected")
- }
-}
-
-// This test checks that connections are disconnected just after the encryption handshake
-// when the server is at capacity. Trusted connections should still be accepted.
-func TestServerAtCap(t *testing.T) {
- trustedNode := newkey()
- trustedID := enode.PubkeyToIDV4(&trustedNode.PublicKey)
- srv := &Server{
- Config: Config{
- PrivateKey: newkey(),
- MaxPeers: 10,
- NoDial: true,
- NoDiscovery: true,
- TrustedNodes: []*enode.Node{newNode(trustedID, "")},
- Logger: testlog.Logger(t, log.LvlTrace),
- },
- }
- if err := srv.Start(); err != nil {
- t.Fatalf("could not start: %v", err)
- }
- defer srv.Stop()
-
- newconn := func(id enode.ID) *conn {
- fd, _ := net.Pipe()
- tx := newTestTransport(&trustedNode.PublicKey, fd, nil)
- node := enode.SignNull(new(enr.Record), id)
- return &conn{fd: fd, transport: tx, flags: inboundConn, node: node, cont: make(chan error)}
- }
-
- // Inject a few connections to fill up the peer set.
- for i := 0; i < 10; i++ {
- c := newconn(randomID())
- if err := srv.checkpoint(c, srv.checkpointAddPeer); err != nil {
- t.Fatalf("could not add conn %d: %v", i, err)
- }
- }
- // Try inserting a non-trusted connection.
- anotherID := randomID()
- c := newconn(anotherID)
- if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != DiscTooManyPeers {
- t.Error("wrong error for insert:", err)
- }
- // Try inserting a trusted connection.
- c = newconn(trustedID)
- if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != nil {
- t.Error("unexpected error for trusted conn @posthandshake:", err)
- }
- if !c.is(trustedConn) {
- t.Error("Server did not set trusted flag")
- }
-
- // Remove from trusted set and try again
- srv.RemoveTrustedPeer(newNode(trustedID, ""))
- c = newconn(trustedID)
- if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != DiscTooManyPeers {
- t.Error("wrong error for insert:", err)
- }
-
- // Add anotherID to trusted set and try again
- srv.AddTrustedPeer(newNode(anotherID, ""))
- c = newconn(anotherID)
- if err := srv.checkpoint(c, srv.checkpointPostHandshake); err != nil {
- t.Error("unexpected error for trusted conn @posthandshake:", err)
- }
- if !c.is(trustedConn) {
- t.Error("Server did not set trusted flag")
- }
-}
-
-func TestServerPeerLimits(t *testing.T) {
- srvkey := newkey()
- clientkey := newkey()
- clientnode := enode.NewV4(&clientkey.PublicKey, nil, 0, 0)
-
- var tp = &setupTransport{
- pubkey: &clientkey.PublicKey,
- phs: protoHandshake{
- ID: crypto.FromECDSAPub(&clientkey.PublicKey)[1:],
- // Force "DiscUselessPeer" due to unmatching caps
- // Caps: []Cap{discard.cap()},
- },
- }
-
- srv := &Server{
- Config: Config{
- PrivateKey: srvkey,
- MaxPeers: 0,
- NoDial: true,
- NoDiscovery: true,
- Protocols: []Protocol{discard},
- Logger: testlog.Logger(t, log.LvlTrace),
- },
- newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { return tp },
- }
- if err := srv.Start(); err != nil {
- t.Fatalf("couldn't start server: %v", err)
- }
- defer srv.Stop()
-
- // Check that server is full (MaxPeers=0)
- flags := dynDialedConn
- dialDest := clientnode
- conn, _ := net.Pipe()
- srv.SetupConn(conn, flags, dialDest)
- if tp.closeErr != DiscTooManyPeers {
- t.Errorf("unexpected close error: %q", tp.closeErr)
- }
- conn.Close()
-
- srv.AddTrustedPeer(clientnode)
-
- // Check that server allows a trusted peer despite being full.
- conn, _ = net.Pipe()
- srv.SetupConn(conn, flags, dialDest)
- if tp.closeErr == DiscTooManyPeers {
- t.Errorf("failed to bypass MaxPeers with trusted node: %q", tp.closeErr)
- }
-
- if tp.closeErr != DiscUselessPeer {
- t.Errorf("unexpected close error: %q", tp.closeErr)
- }
- conn.Close()
-
- srv.RemoveTrustedPeer(clientnode)
-
- // Check that server is full again.
- conn, _ = net.Pipe()
- srv.SetupConn(conn, flags, dialDest)
- if tp.closeErr != DiscTooManyPeers {
- t.Errorf("unexpected close error: %q", tp.closeErr)
- }
- conn.Close()
-}
-
-func TestServerSetupConn(t *testing.T) {
- var (
- clientkey, srvkey = newkey(), newkey()
- clientpub = &clientkey.PublicKey
- srvpub = &srvkey.PublicKey
- )
- tests := []struct {
- dontstart bool
- tt *setupTransport
- flags connFlag
- dialDest *enode.Node
-
- wantCloseErr error
- wantCalls string
- }{
- {
- dontstart: true,
- tt: &setupTransport{pubkey: clientpub},
- wantCalls: "close,",
- wantCloseErr: errServerStopped,
- },
- {
- tt: &setupTransport{pubkey: clientpub, encHandshakeErr: errEncHandshakeError},
- flags: inboundConn,
- wantCalls: "doEncHandshake,close,",
- wantCloseErr: errEncHandshakeError,
- },
- {
- tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: randomID().Bytes()}},
- dialDest: enode.NewV4(clientpub, nil, 0, 0),
- flags: dynDialedConn,
- wantCalls: "doEncHandshake,doProtoHandshake,close,",
- wantCloseErr: DiscUnexpectedIdentity,
- },
- {
- tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: errProtoHandshakeError},
- dialDest: enode.NewV4(clientpub, nil, 0, 0),
- flags: dynDialedConn,
- wantCalls: "doEncHandshake,doProtoHandshake,close,",
- wantCloseErr: errProtoHandshakeError,
- },
- {
- tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}},
- flags: inboundConn,
- wantCalls: "doEncHandshake,close,",
- wantCloseErr: DiscSelf,
- },
- {
- tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: crypto.FromECDSAPub(clientpub)[1:]}},
- flags: inboundConn,
- wantCalls: "doEncHandshake,doProtoHandshake,close,",
- wantCloseErr: DiscUselessPeer,
- },
- }
-
- for i, test := range tests {
- t.Run(test.wantCalls, func(t *testing.T) {
- cfg := Config{
- PrivateKey: srvkey,
- MaxPeers: 10,
- NoDial: true,
- NoDiscovery: true,
- Protocols: []Protocol{discard},
- Logger: testlog.Logger(t, log.LvlTrace),
- }
- srv := &Server{
- Config: cfg,
- newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { return test.tt },
- log: cfg.Logger,
- }
- if !test.dontstart {
- if err := srv.Start(); err != nil {
- t.Fatalf("couldn't start server: %v", err)
- }
- defer srv.Stop()
- }
- p1, _ := net.Pipe()
- srv.SetupConn(p1, test.flags, test.dialDest)
- if !errors.Is(test.tt.closeErr, test.wantCloseErr) {
- t.Errorf("test %d: close error mismatch: got %q, want %q", i, test.tt.closeErr, test.wantCloseErr)
- }
- if test.tt.calls != test.wantCalls {
- t.Errorf("test %d: calls mismatch: got %q, want %q", i, test.tt.calls, test.wantCalls)
- }
- })
- }
-}
-
-type setupTransport struct {
- pubkey *ecdsa.PublicKey
- encHandshakeErr error
- phs protoHandshake
- protoHandshakeErr error
-
- calls string
- closeErr error
-}
-
-func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
- c.calls += "doEncHandshake,"
- return c.pubkey, c.encHandshakeErr
-}
-
-func (c *setupTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) {
- c.calls += "doProtoHandshake,"
- if c.protoHandshakeErr != nil {
- return nil, c.protoHandshakeErr
- }
- return &c.phs, nil
-}
-func (c *setupTransport) close(err error) {
- c.calls += "close,"
- c.closeErr = err
-}
-
-// setupConn shouldn't write to/read from the connection.
-func (c *setupTransport) WriteMsg(Msg) error {
- panic("WriteMsg called on setupTransport")
-}
-func (c *setupTransport) ReadMsg() (Msg, error) {
- panic("ReadMsg called on setupTransport")
-}
-
-func newkey() *ecdsa.PrivateKey {
- key, err := crypto.GenerateKey()
- if err != nil {
- panic("couldn't generate key: " + err.Error())
- }
- return key
-}
-
-func randomID() (id enode.ID) {
- for i := range id {
- id[i] = byte(rand.Intn(255))
- }
- return id
-}
-
-// This test checks that inbound connections are throttled by IP.
-func TestServerInboundThrottle(t *testing.T) {
- const timeout = 5 * time.Second
- newTransportCalled := make(chan struct{})
- srv := &Server{
- Config: Config{
- PrivateKey: newkey(),
- ListenAddr: "127.0.0.1:0",
- MaxPeers: 10,
- NoDial: true,
- NoDiscovery: true,
- Protocols: []Protocol{discard},
- Logger: testlog.Logger(t, log.LvlTrace),
- },
- newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport {
- newTransportCalled <- struct{}{}
- return newRLPX(fd, dialDest)
- },
- listenFunc: func(network, laddr string) (net.Listener, error) {
- fakeAddr := &net.TCPAddr{IP: net.IP{95, 33, 21, 2}, Port: 4444}
- return listenFakeAddr(network, laddr, fakeAddr)
- },
- }
- if err := srv.Start(); err != nil {
- t.Fatal("can't start: ", err)
- }
- defer srv.Stop()
-
- // Dial the test server.
- conn, err := net.DialTimeout("tcp", srv.ListenAddr, timeout)
- if err != nil {
- t.Fatalf("could not dial: %v", err)
- }
- select {
- case <-newTransportCalled:
- // OK
- case <-time.After(timeout):
- t.Error("newTransport not called")
- }
- conn.Close()
-
- // Dial again. This time the server should close the connection immediately.
- connClosed := make(chan struct{}, 1)
- conn, err = net.DialTimeout("tcp", srv.ListenAddr, timeout)
- if err != nil {
- t.Fatalf("could not dial: %v", err)
- }
- defer conn.Close()
- go func() {
- conn.SetDeadline(time.Now().Add(timeout))
- buf := make([]byte, 10)
- if n, err := conn.Read(buf); err != io.EOF || n != 0 {
- t.Errorf("expected io.EOF and n == 0, got error %q and n == %d", err, n)
- }
- connClosed <- struct{}{}
- }()
- select {
- case <-connClosed:
- // OK
- case <-newTransportCalled:
- t.Error("newTransport called for second attempt")
- case <-time.After(timeout):
- t.Error("connection not closed within timeout")
- }
-}
-
-func listenFakeAddr(network, laddr string, remoteAddr net.Addr) (net.Listener, error) {
- l, err := net.Listen(network, laddr)
- if err == nil {
- l = &fakeAddrListener{l, remoteAddr}
- }
- return l, err
-}
-
-// fakeAddrListener is a listener that creates connections with a mocked remote address.
-type fakeAddrListener struct {
- net.Listener
- remoteAddr net.Addr
-}
-
-type fakeAddrConn struct {
- net.Conn
- remoteAddr net.Addr
-}
-
-func (l *fakeAddrListener) Accept() (net.Conn, error) {
- c, err := l.Listener.Accept()
- if err != nil {
- return nil, err
- }
- return &fakeAddrConn{c, l.remoteAddr}, nil
-}
-
-func (c *fakeAddrConn) RemoteAddr() net.Addr {
- return c.remoteAddr
-}
-
-func syncAddPeer(srv *Server, node *enode.Node) bool {
- var (
- ch = make(chan *PeerEvent)
- sub = srv.SubscribeEvents(ch)
- timeout = time.After(2 * time.Second)
- )
- defer sub.Unsubscribe()
- srv.AddPeer(node)
- for {
- select {
- case ev := <-ch:
- if ev.Type == PeerEventTypeAdd && ev.Peer == node.ID() {
- return true
- }
- case <-timeout:
- return false
- }
- }
-}
diff --git a/p2p/simulations/README.md b/p2p/simulations/README.md
deleted file mode 100644
index 023f73a098..0000000000
--- a/p2p/simulations/README.md
+++ /dev/null
@@ -1,169 +0,0 @@
-# devp2p Simulations
-
-The `p2p/simulations` package implements a simulation framework that supports
-creating a collection of devp2p nodes, connecting them to form a
-simulation network, performing simulation actions in that network and then
-extracting useful information.
-
-## Nodes
-
-Each node in a simulation network runs multiple services by wrapping a collection
-of objects which implement the `node.Service` interface meaning they:
-
-* can be started and stopped
-* run p2p protocols
-* expose RPC APIs
-
-This means that any object which implements the `node.Service` interface can be
-used to run a node in the simulation.
-
-## Services
-
-Before running a simulation, a set of service initializers must be registered
-which can then be used to run nodes in the network.
-
-A service initializer is a function with the following signature:
-
-```go
-func(ctx *adapters.ServiceContext) (node.Service, error)
-```
-
-These initializers should be registered by calling the `adapters.RegisterServices`
-function in an `init()` hook:
-
-```go
-func init() {
- adapters.RegisterServices(adapters.Services{
- "service1": initService1,
- "service2": initService2,
- })
-}
-```
-
-## Node Adapters
-
-The simulation framework includes multiple "node adapters" which are
-responsible for creating an environment in which a node runs.
-
-### SimAdapter
-
-The `SimAdapter` runs nodes in-memory, connecting them using an in-memory,
-synchronous `net.Pipe` and connecting to their RPC server using an in-memory
-`rpc.Client`.
-
-### ExecAdapter
-
-The `ExecAdapter` runs nodes as child processes of the running simulation.
-
-It does this by executing the binary which is running the simulation but
-setting `argv[0]` (i.e. the program name) to `p2p-node` which is then
-detected by an init hook in the child process which runs the `node.Service`
-using the devp2p node stack rather than executing `main()`.
-
-The nodes listen for devp2p connections and WebSocket RPC clients on random
-localhost ports.
-
-## Network
-
-A simulation network is created with an ID and default service. The default
-service is used if a node is created without an explicit service. The
-network has exposed methods for creating, starting, stopping, connecting
-and disconnecting nodes. It also emits events when certain actions occur.
-
-### Events
-
-A simulation network emits the following events:
-
-* node event - when nodes are created / started / stopped
-* connection event - when nodes are connected / disconnected
-* message event - when a protocol message is sent between two nodes
-
-The events have a "control" flag which when set indicates that the event is the
-outcome of a controlled simulation action (e.g. creating a node or explicitly
-connecting two nodes).
-
-This is in contrast to a non-control event, otherwise called a "live" event,
-which is the outcome of something happening in the network as a result of a
-control event (e.g. a node actually started up or a connection was actually
-established between two nodes).
-
-Live events are detected by the simulation network by subscribing to node peer
-events via RPC when the nodes start up.
-
-## Testing Framework
-
-The `Simulation` type can be used in tests to perform actions in a simulation
-network and then wait for expectations to be met.
-
-With a running simulation network, the `Simulation.Run` method can be called
-with a `Step` which has the following fields:
-
-* `Action` - a function that performs some action in the network
-
-* `Expect` - an expectation function which returns whether or not a
- given node meets the expectation
-
-* `Trigger` - a channel that receives node IDs which then trigger a check
- of the expectation function to be performed against that node
-
-As a concrete example, consider a simulated network of Ethereum nodes. An
-`Action` could be the sending of a transaction, `Expect` it being included in
-a block, and `Trigger` a check for every block that is mined.
-
-On return, the `Simulation.Run` method returns a `StepResult` which can be used
-to determine if all nodes met the expectation, how long it took them to meet
-the expectation and what network events were emitted during the step run.
-
-## HTTP API
-
-The simulation framework includes a HTTP API that can be used to control the
-simulation.
-
-The API is initialised with a particular node adapter and has the following
-endpoints:
-
-```
-GET / Get network information
-POST /start Start all nodes in the network
-POST /stop Stop all nodes in the network
-GET /events Stream network events
-GET /snapshot Take a network snapshot
-POST /snapshot Load a network snapshot
-POST /nodes Create a node
-GET /nodes Get all nodes in the network
-GET /nodes/:nodeid Get node information
-POST /nodes/:nodeid/start Start a node
-POST /nodes/:nodeid/stop Stop a node
-POST /nodes/:nodeid/conn/:peerid Connect two nodes
-DELETE /nodes/:nodeid/conn/:peerid Disconnect two nodes
-GET /nodes/:nodeid/rpc Make RPC requests to a node via WebSocket
-```
-
-For convenience, `nodeid` in the URL can be the name of a node rather than its
-ID.
-
-## Command line client
-
-`p2psim` is a command line client for the HTTP API, located in
-`cmd/p2psim`.
-
-It provides the following commands:
-
-```
-p2psim show
-p2psim events [--current] [--filter=FILTER]
-p2psim snapshot
-p2psim load
-p2psim node create [--name=NAME] [--services=SERVICES] [--key=KEY]
-p2psim node list
-p2psim node show
-p2psim node start
-p2psim node stop
-p2psim node connect
-p2psim node disconnect
-p2psim node rpc [] [--subscribe]
-```
-
-## Example
-
-See [p2p/simulations/examples/README.md](examples/README.md).
diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go
deleted file mode 100644
index 63cc4936c1..0000000000
--- a/p2p/simulations/adapters/exec.go
+++ /dev/null
@@ -1,564 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package adapters
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net"
- "net/http"
- "os"
- "os/exec"
- "os/signal"
- "path/filepath"
- "strings"
- "sync"
- "syscall"
- "time"
-
- "github.com/ethereum/go-ethereum/internal/reexec"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/gorilla/websocket"
- "golang.org/x/exp/slog"
-)
-
-func init() {
- // Register a reexec function to start a simulation node when the current binary is
- // executed as "p2p-node" (rather than whatever the main() function would normally do).
- reexec.Register("p2p-node", execP2PNode)
-}
-
-// ExecAdapter is a NodeAdapter which runs simulation nodes by executing the current binary
-// as a child process.
-type ExecAdapter struct {
- // BaseDir is the directory under which the data directories for each
- // simulation node are created.
- BaseDir string
-
- nodes map[enode.ID]*ExecNode
-}
-
-// NewExecAdapter returns an ExecAdapter which stores node data in
-// subdirectories of the given base directory
-func NewExecAdapter(baseDir string) *ExecAdapter {
- return &ExecAdapter{
- BaseDir: baseDir,
- nodes: make(map[enode.ID]*ExecNode),
- }
-}
-
-// Name returns the name of the adapter for logging purposes
-func (e *ExecAdapter) Name() string {
- return "exec-adapter"
-}
-
-// NewNode returns a new ExecNode using the given config
-func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
- if len(config.Lifecycles) == 0 {
- return nil, errors.New("node must have at least one service lifecycle")
- }
- for _, service := range config.Lifecycles {
- if _, exists := lifecycleConstructorFuncs[service]; !exists {
- return nil, fmt.Errorf("unknown node service %q", service)
- }
- }
-
- // create the node directory using the first 12 characters of the ID
- // as Unix socket paths cannot be longer than 256 characters
- dir := filepath.Join(e.BaseDir, config.ID.String()[:12])
- if err := os.Mkdir(dir, 0755); err != nil {
- return nil, fmt.Errorf("error creating node directory: %s", err)
- }
-
- err := config.initDummyEnode()
- if err != nil {
- return nil, err
- }
-
- // generate the config
- conf := &execNodeConfig{
- Stack: node.DefaultConfig,
- Node: config,
- }
- if config.DataDir != "" {
- conf.Stack.DataDir = config.DataDir
- } else {
- conf.Stack.DataDir = filepath.Join(dir, "data")
- }
-
- // these parameters are crucial for execadapter node to run correctly
- conf.Stack.WSHost = "127.0.0.1"
- conf.Stack.WSPort = 0
- conf.Stack.WSOrigins = []string{"*"}
- conf.Stack.WSExposeAll = true
- conf.Stack.P2P.EnableMsgEvents = config.EnableMsgEvents
- conf.Stack.P2P.NoDiscovery = true
- conf.Stack.P2P.NAT = nil
-
- // Listen on a localhost port, which we set when we
- // initialise NodeConfig (usually a random port)
- conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port)
-
- node := &ExecNode{
- ID: config.ID,
- Dir: dir,
- Config: conf,
- adapter: e,
- }
- node.newCmd = node.execCommand
- e.nodes[node.ID] = node
- return node, nil
-}
-
-// ExecNode starts a simulation node by exec'ing the current binary and
-// running the configured services
-type ExecNode struct {
- ID enode.ID
- Dir string
- Config *execNodeConfig
- Cmd *exec.Cmd
- Info *p2p.NodeInfo
-
- adapter *ExecAdapter
- client *rpc.Client
- wsAddr string
- newCmd func() *exec.Cmd
-}
-
-// Addr returns the node's enode URL
-func (n *ExecNode) Addr() []byte {
- if n.Info == nil {
- return nil
- }
- return []byte(n.Info.Enode)
-}
-
-// Client returns an rpc.Client which can be used to communicate with the
-// underlying services (it is set once the node has started)
-func (n *ExecNode) Client() (*rpc.Client, error) {
- return n.client, nil
-}
-
-// Start exec's the node passing the ID and service as command line arguments
-// and the node config encoded as JSON in an environment variable.
-func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
- if n.Cmd != nil {
- return errors.New("already started")
- }
- defer func() {
- if err != nil {
- n.Stop()
- }
- }()
-
- // encode a copy of the config containing the snapshot
- confCopy := *n.Config
- confCopy.Snapshots = snapshots
- confCopy.PeerAddrs = make(map[string]string)
- for id, node := range n.adapter.nodes {
- confCopy.PeerAddrs[id.String()] = node.wsAddr
- }
- confData, err := json.Marshal(confCopy)
- if err != nil {
- return fmt.Errorf("error generating node config: %s", err)
- }
- // expose the admin namespace via websocket if it's not enabled
- exposed := confCopy.Stack.WSExposeAll
- if !exposed {
- for _, api := range confCopy.Stack.WSModules {
- if api == "admin" {
- exposed = true
- break
- }
- }
- }
- if !exposed {
- confCopy.Stack.WSModules = append(confCopy.Stack.WSModules, "admin")
- }
- // start the one-shot server that waits for startup information
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- statusURL, statusC := n.waitForStartupJSON(ctx)
-
- // start the node
- cmd := n.newCmd()
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.Env = append(os.Environ(),
- envStatusURL+"="+statusURL,
- envNodeConfig+"="+string(confData),
- )
- if err := cmd.Start(); err != nil {
- return fmt.Errorf("error starting node: %s", err)
- }
- n.Cmd = cmd
-
- // Wait for the node to start.
- status := <-statusC
- if status.Err != "" {
- return errors.New(status.Err)
- }
- client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "")
- if err != nil {
- return fmt.Errorf("can't connect to RPC server: %v", err)
- }
-
- // Node ready :)
- n.client = client
- n.wsAddr = status.WSEndpoint
- n.Info = status.NodeInfo
- return nil
-}
-
-// waitForStartupJSON runs a one-shot HTTP server to receive a startup report.
-func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeStartupJSON) {
- var (
- ch = make(chan nodeStartupJSON, 1)
- quitOnce sync.Once
- srv http.Server
- )
- l, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- ch <- nodeStartupJSON{Err: err.Error()}
- return "", ch
- }
- quit := func(status nodeStartupJSON) {
- quitOnce.Do(func() {
- l.Close()
- ch <- status
- })
- }
- srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- var status nodeStartupJSON
- if err := json.NewDecoder(r.Body).Decode(&status); err != nil {
- status.Err = fmt.Sprintf("can't decode startup report: %v", err)
- }
- quit(status)
- })
- // Run the HTTP server, but don't wait forever and shut it down
- // if the context is canceled.
- go srv.Serve(l)
- go func() {
- <-ctx.Done()
- quit(nodeStartupJSON{Err: "didn't get startup report"})
- }()
-
- url := "http://" + l.Addr().String()
- return url, ch
-}
-
-// execCommand returns a command which runs the node locally by exec'ing
-// the current binary but setting argv[0] to "p2p-node" so that the child
-// runs execP2PNode
-func (n *ExecNode) execCommand() *exec.Cmd {
- return &exec.Cmd{
- Path: reexec.Self(),
- Args: []string{"p2p-node", strings.Join(n.Config.Node.Lifecycles, ","), n.ID.String()},
- }
-}
-
-// Stop stops the node by first sending SIGTERM and then SIGKILL if the node
-// doesn't stop within 5s
-func (n *ExecNode) Stop() error {
- if n.Cmd == nil {
- return nil
- }
- defer func() {
- n.Cmd = nil
- }()
-
- if n.client != nil {
- n.client.Close()
- n.client = nil
- n.wsAddr = ""
- n.Info = nil
- }
-
- if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
- return n.Cmd.Process.Kill()
- }
- waitErr := make(chan error, 1)
- go func() {
- waitErr <- n.Cmd.Wait()
- }()
- select {
- case err := <-waitErr:
- return err
- case <-time.After(5 * time.Second):
- return n.Cmd.Process.Kill()
- }
-}
-
-// NodeInfo returns information about the node
-func (n *ExecNode) NodeInfo() *p2p.NodeInfo {
- info := &p2p.NodeInfo{
- ID: n.ID.String(),
- }
- if n.client != nil {
- n.client.Call(&info, "admin_nodeInfo")
- }
- return info
-}
-
-// ServeRPC serves RPC requests over the given connection by dialling the
-// node's WebSocket address and joining the two connections
-func (n *ExecNode) ServeRPC(clientConn *websocket.Conn) error {
- conn, _, err := websocket.DefaultDialer.Dial(n.wsAddr, nil)
- if err != nil {
- return err
- }
- var wg sync.WaitGroup
- wg.Add(2)
- go wsCopy(&wg, conn, clientConn)
- go wsCopy(&wg, clientConn, conn)
- wg.Wait()
- conn.Close()
- return nil
-}
-
-func wsCopy(wg *sync.WaitGroup, src, dst *websocket.Conn) {
- defer wg.Done()
- for {
- msgType, r, err := src.NextReader()
- if err != nil {
- return
- }
- w, err := dst.NextWriter(msgType)
- if err != nil {
- return
- }
- if _, err = io.Copy(w, r); err != nil {
- return
- }
- }
-}
-
-// Snapshots creates snapshots of the services by calling the
-// simulation_snapshot RPC method
-func (n *ExecNode) Snapshots() (map[string][]byte, error) {
- if n.client == nil {
- return nil, errors.New("RPC not started")
- }
- var snapshots map[string][]byte
- return snapshots, n.client.Call(&snapshots, "simulation_snapshot")
-}
-
-// execNodeConfig is used to serialize the node configuration so it can be
-// passed to the child process as a JSON encoded environment variable
-type execNodeConfig struct {
- Stack node.Config `json:"stack"`
- Node *NodeConfig `json:"node"`
- Snapshots map[string][]byte `json:"snapshots,omitempty"`
- PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
-}
-
-func initLogging() {
- // Initialize the logging by default first.
- var innerHandler slog.Handler
- innerHandler = slog.NewTextHandler(os.Stderr, nil)
- glogger := log.NewGlogHandler(innerHandler)
- glogger.Verbosity(log.LevelInfo)
- log.SetDefault(log.NewLogger(glogger))
-
- confEnv := os.Getenv(envNodeConfig)
- if confEnv == "" {
- return
- }
- var conf execNodeConfig
- if err := json.Unmarshal([]byte(confEnv), &conf); err != nil {
- return
- }
- var writer = os.Stderr
- if conf.Node.LogFile != "" {
- logWriter, err := os.Create(conf.Node.LogFile)
- if err != nil {
- return
- }
- writer = logWriter
- }
- var verbosity = log.LevelInfo
- if conf.Node.LogVerbosity <= log.LevelTrace && conf.Node.LogVerbosity >= log.LevelCrit {
- verbosity = log.FromLegacyLevel(int(conf.Node.LogVerbosity))
- }
- // Reinitialize the logger
- innerHandler = log.NewTerminalHandler(writer, true)
- glogger = log.NewGlogHandler(innerHandler)
- glogger.Verbosity(verbosity)
- log.SetDefault(log.NewLogger(glogger))
-}
-
-// execP2PNode starts a simulation node when the current binary is executed with
-// argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
-// and the node config from an environment variable.
-func execP2PNode() {
- initLogging()
-
- statusURL := os.Getenv(envStatusURL)
- if statusURL == "" {
- log.Crit("missing " + envStatusURL)
- }
-
- // Start the node and gather startup report.
- var status nodeStartupJSON
- stack, stackErr := startExecNodeStack()
- if stackErr != nil {
- status.Err = stackErr.Error()
- } else {
- status.WSEndpoint = stack.WSEndpoint()
- status.NodeInfo = stack.Server().NodeInfo()
- }
-
- // Send status to the host.
- statusJSON, _ := json.Marshal(status)
- resp, err := http.Post(statusURL, "application/json", bytes.NewReader(statusJSON))
- if err != nil {
- log.Crit("Can't post startup info", "url", statusURL, "err", err)
- }
- resp.Body.Close()
- if stackErr != nil {
- os.Exit(1)
- }
-
- // Stop the stack if we get a SIGTERM signal.
- go func() {
- sigc := make(chan os.Signal, 1)
- signal.Notify(sigc, syscall.SIGTERM)
- defer signal.Stop(sigc)
- <-sigc
- log.Info("Received SIGTERM, shutting down...")
- stack.Close()
- }()
- stack.Wait() // Wait for the stack to exit.
-}
-
-func startExecNodeStack() (*node.Node, error) {
- // read the services from argv
- serviceNames := strings.Split(os.Args[1], ",")
-
- // decode the config
- confEnv := os.Getenv(envNodeConfig)
- if confEnv == "" {
- return nil, fmt.Errorf("missing " + envNodeConfig)
- }
- var conf execNodeConfig
- if err := json.Unmarshal([]byte(confEnv), &conf); err != nil {
- return nil, fmt.Errorf("error decoding %s: %v", envNodeConfig, err)
- }
-
- // create enode record
- nodeTcpConn, _ := net.ResolveTCPAddr("tcp", conf.Stack.P2P.ListenAddr)
- if nodeTcpConn.IP == nil {
- nodeTcpConn.IP = net.IPv4(127, 0, 0, 1)
- }
- conf.Node.initEnode(nodeTcpConn.IP, nodeTcpConn.Port, nodeTcpConn.Port)
- conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey
- conf.Stack.Logger = log.New("node.id", conf.Node.ID.String())
-
- // initialize the devp2p stack
- stack, err := node.New(&conf.Stack)
- if err != nil {
- return nil, fmt.Errorf("error creating node stack: %v", err)
- }
-
- // Register the services, collecting them into a map so they can
- // be accessed by the snapshot API.
- services := make(map[string]node.Lifecycle, len(serviceNames))
- for _, name := range serviceNames {
- lifecycleFunc, exists := lifecycleConstructorFuncs[name]
- if !exists {
- return nil, fmt.Errorf("unknown node service %q", err)
- }
- ctx := &ServiceContext{
- RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs},
- Config: conf.Node,
- }
- if conf.Snapshots != nil {
- ctx.Snapshot = conf.Snapshots[name]
- }
- service, err := lifecycleFunc(ctx, stack)
- if err != nil {
- return nil, err
- }
- services[name] = service
- }
-
- // Add the snapshot API.
- stack.RegisterAPIs([]rpc.API{{
- Namespace: "simulation",
- Service: SnapshotAPI{services},
- }})
-
- if err = stack.Start(); err != nil {
- err = fmt.Errorf("error starting stack: %v", err)
- }
- return stack, err
-}
-
-const (
- envStatusURL = "_P2P_STATUS_URL"
- envNodeConfig = "_P2P_NODE_CONFIG"
-)
-
-// nodeStartupJSON is sent to the simulation host after startup.
-type nodeStartupJSON struct {
- Err string
- WSEndpoint string
- NodeInfo *p2p.NodeInfo
-}
-
-// SnapshotAPI provides an RPC method to create snapshots of services
-type SnapshotAPI struct {
- services map[string]node.Lifecycle
-}
-
-func (api SnapshotAPI) Snapshot() (map[string][]byte, error) {
- snapshots := make(map[string][]byte)
- for name, service := range api.services {
- if s, ok := service.(interface {
- Snapshot() ([]byte, error)
- }); ok {
- snap, err := s.Snapshot()
- if err != nil {
- return nil, err
- }
- snapshots[name] = snap
- }
- }
- return snapshots, nil
-}
-
-type wsRPCDialer struct {
- addrs map[string]string
-}
-
-// DialRPC implements the RPCDialer interface by creating a WebSocket RPC
-// client of the given node
-func (w *wsRPCDialer) DialRPC(id enode.ID) (*rpc.Client, error) {
- addr, ok := w.addrs[id.String()]
- if !ok {
- return nil, fmt.Errorf("unknown node: %s", id)
- }
- return rpc.DialWebsocket(context.Background(), addr, "http://localhost")
-}
diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go
deleted file mode 100644
index c52917fd0a..0000000000
--- a/p2p/simulations/adapters/inproc.go
+++ /dev/null
@@ -1,350 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package adapters
-
-import (
- "context"
- "errors"
- "fmt"
- "math"
- "net"
- "sync"
-
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/simulations/pipes"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/gorilla/websocket"
-)
-
-// SimAdapter is a NodeAdapter which creates in-memory simulation nodes and
-// connects them using net.Pipe
-type SimAdapter struct {
- pipe func() (net.Conn, net.Conn, error)
- mtx sync.RWMutex
- nodes map[enode.ID]*SimNode
- lifecycles LifecycleConstructors
-}
-
-// NewSimAdapter creates a SimAdapter which is capable of running in-memory
-// simulation nodes running any of the given services (the services to run on a
-// particular node are passed to the NewNode function in the NodeConfig)
-// the adapter uses a net.Pipe for in-memory simulated network connections
-func NewSimAdapter(services LifecycleConstructors) *SimAdapter {
- return &SimAdapter{
- pipe: pipes.NetPipe,
- nodes: make(map[enode.ID]*SimNode),
- lifecycles: services,
- }
-}
-
-// Name returns the name of the adapter for logging purposes
-func (s *SimAdapter) Name() string {
- return "sim-adapter"
-}
-
-// NewNode returns a new SimNode using the given config
-func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
- s.mtx.Lock()
- defer s.mtx.Unlock()
-
- id := config.ID
- // verify that the node has a private key in the config
- if config.PrivateKey == nil {
- return nil, fmt.Errorf("node is missing private key: %s", id)
- }
-
- // check a node with the ID doesn't already exist
- if _, exists := s.nodes[id]; exists {
- return nil, fmt.Errorf("node already exists: %s", id)
- }
-
- // check the services are valid
- if len(config.Lifecycles) == 0 {
- return nil, errors.New("node must have at least one service")
- }
- for _, service := range config.Lifecycles {
- if _, exists := s.lifecycles[service]; !exists {
- return nil, fmt.Errorf("unknown node service %q", service)
- }
- }
-
- err := config.initDummyEnode()
- if err != nil {
- return nil, err
- }
-
- n, err := node.New(&node.Config{
- P2P: p2p.Config{
- PrivateKey: config.PrivateKey,
- MaxPeers: math.MaxInt32,
- NoDiscovery: true,
- Dialer: s,
- EnableMsgEvents: config.EnableMsgEvents,
- },
- ExternalSigner: config.ExternalSigner,
- Logger: log.New("node.id", id.String()),
- })
- if err != nil {
- return nil, err
- }
-
- simNode := &SimNode{
- ID: id,
- config: config,
- node: n,
- adapter: s,
- running: make(map[string]node.Lifecycle),
- }
- s.nodes[id] = simNode
- return simNode, nil
-}
-
-// Dial implements the p2p.NodeDialer interface by connecting to the node using
-// an in-memory net.Pipe
-func (s *SimAdapter) Dial(ctx context.Context, dest *enode.Node) (conn net.Conn, err error) {
- node, ok := s.GetNode(dest.ID())
- if !ok {
- return nil, fmt.Errorf("unknown node: %s", dest.ID())
- }
- srv := node.Server()
- if srv == nil {
- return nil, fmt.Errorf("node not running: %s", dest.ID())
- }
- // SimAdapter.pipe is net.Pipe (NewSimAdapter)
- pipe1, pipe2, err := s.pipe()
- if err != nil {
- return nil, err
- }
- // this is simulated 'listening'
- // asynchronously call the dialed destination node's p2p server
- // to set up connection on the 'listening' side
- go srv.SetupConn(pipe1, 0, nil)
- return pipe2, nil
-}
-
-// DialRPC implements the RPCDialer interface by creating an in-memory RPC
-// client of the given node
-func (s *SimAdapter) DialRPC(id enode.ID) (*rpc.Client, error) {
- node, ok := s.GetNode(id)
- if !ok {
- return nil, fmt.Errorf("unknown node: %s", id)
- }
- return node.node.Attach(), nil
-}
-
-// GetNode returns the node with the given ID if it exists
-func (s *SimAdapter) GetNode(id enode.ID) (*SimNode, bool) {
- s.mtx.RLock()
- defer s.mtx.RUnlock()
- node, ok := s.nodes[id]
- return node, ok
-}
-
-// SimNode is an in-memory simulation node which connects to other nodes using
-// net.Pipe (see SimAdapter.Dial), running devp2p protocols directly over that
-// pipe
-type SimNode struct {
- lock sync.RWMutex
- ID enode.ID
- config *NodeConfig
- adapter *SimAdapter
- node *node.Node
- running map[string]node.Lifecycle
- client *rpc.Client
- registerOnce sync.Once
-}
-
-// Close closes the underlaying node.Node to release
-// acquired resources.
-func (sn *SimNode) Close() error {
- return sn.node.Close()
-}
-
-// Addr returns the node's discovery address
-func (sn *SimNode) Addr() []byte {
- return []byte(sn.Node().String())
-}
-
-// Node returns a node descriptor representing the SimNode
-func (sn *SimNode) Node() *enode.Node {
- return sn.config.Node()
-}
-
-// Client returns an rpc.Client which can be used to communicate with the
-// underlying services (it is set once the node has started)
-func (sn *SimNode) Client() (*rpc.Client, error) {
- sn.lock.RLock()
- defer sn.lock.RUnlock()
- if sn.client == nil {
- return nil, errors.New("node not started")
- }
- return sn.client, nil
-}
-
-// ServeRPC serves RPC requests over the given connection by creating an
-// in-memory client to the node's RPC server.
-func (sn *SimNode) ServeRPC(conn *websocket.Conn) error {
- handler, err := sn.node.RPCHandler()
- if err != nil {
- return err
- }
- codec := rpc.NewFuncCodec(conn, func(v any, _ bool) error { return conn.WriteJSON(v) }, conn.ReadJSON)
- handler.ServeCodec(codec, 0)
- return nil
-}
-
-// Snapshots creates snapshots of the services by calling the
-// simulation_snapshot RPC method
-func (sn *SimNode) Snapshots() (map[string][]byte, error) {
- sn.lock.RLock()
- services := make(map[string]node.Lifecycle, len(sn.running))
- for name, service := range sn.running {
- services[name] = service
- }
- sn.lock.RUnlock()
- if len(services) == 0 {
- return nil, errors.New("no running services")
- }
- snapshots := make(map[string][]byte)
- for name, service := range services {
- if s, ok := service.(interface {
- Snapshot() ([]byte, error)
- }); ok {
- snap, err := s.Snapshot()
- if err != nil {
- return nil, err
- }
- snapshots[name] = snap
- }
- }
- return snapshots, nil
-}
-
-// Start registers the services and starts the underlying devp2p node
-func (sn *SimNode) Start(snapshots map[string][]byte) error {
- // ensure we only register the services once in the case of the node
- // being stopped and then started again
- var regErr error
- sn.registerOnce.Do(func() {
- for _, name := range sn.config.Lifecycles {
- ctx := &ServiceContext{
- RPCDialer: sn.adapter,
- Config: sn.config,
- }
- if snapshots != nil {
- ctx.Snapshot = snapshots[name]
- }
- serviceFunc := sn.adapter.lifecycles[name]
- service, err := serviceFunc(ctx, sn.node)
- if err != nil {
- regErr = err
- break
- }
- // if the service has already been registered, don't register it again.
- if _, ok := sn.running[name]; ok {
- continue
- }
- sn.running[name] = service
- }
- })
- if regErr != nil {
- return regErr
- }
-
- if err := sn.node.Start(); err != nil {
- return err
- }
-
- // create an in-process RPC client
- client := sn.node.Attach()
- sn.lock.Lock()
- sn.client = client
- sn.lock.Unlock()
-
- return nil
-}
-
-// Stop closes the RPC client and stops the underlying devp2p node
-func (sn *SimNode) Stop() error {
- sn.lock.Lock()
- if sn.client != nil {
- sn.client.Close()
- sn.client = nil
- }
- sn.lock.Unlock()
- return sn.node.Close()
-}
-
-// Service returns a running service by name
-func (sn *SimNode) Service(name string) node.Lifecycle {
- sn.lock.RLock()
- defer sn.lock.RUnlock()
- return sn.running[name]
-}
-
-// Services returns a copy of the underlying services
-func (sn *SimNode) Services() []node.Lifecycle {
- sn.lock.RLock()
- defer sn.lock.RUnlock()
- services := make([]node.Lifecycle, 0, len(sn.running))
- for _, service := range sn.running {
- services = append(services, service)
- }
- return services
-}
-
-// ServiceMap returns a map by names of the underlying services
-func (sn *SimNode) ServiceMap() map[string]node.Lifecycle {
- sn.lock.RLock()
- defer sn.lock.RUnlock()
- services := make(map[string]node.Lifecycle, len(sn.running))
- for name, service := range sn.running {
- services[name] = service
- }
- return services
-}
-
-// Server returns the underlying p2p.Server
-func (sn *SimNode) Server() *p2p.Server {
- return sn.node.Server()
-}
-
-// SubscribeEvents subscribes the given channel to peer events from the
-// underlying p2p.Server
-func (sn *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription {
- srv := sn.Server()
- if srv == nil {
- panic("node not running")
- }
- return srv.SubscribeEvents(ch)
-}
-
-// NodeInfo returns information about the node
-func (sn *SimNode) NodeInfo() *p2p.NodeInfo {
- server := sn.Server()
- if server == nil {
- return &p2p.NodeInfo{
- ID: sn.ID.String(),
- Enode: sn.Node().String(),
- }
- }
- return server.NodeInfo()
-}
diff --git a/p2p/simulations/adapters/inproc_test.go b/p2p/simulations/adapters/inproc_test.go
deleted file mode 100644
index 2a61508fe1..0000000000
--- a/p2p/simulations/adapters/inproc_test.go
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package adapters
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "sync"
- "testing"
-
- "github.com/ethereum/go-ethereum/p2p/simulations/pipes"
-)
-
-func TestTCPPipe(t *testing.T) {
- c1, c2, err := pipes.TCPPipe()
- if err != nil {
- t.Fatal(err)
- }
-
- msgs := 50
- size := 1024
- for i := 0; i < msgs; i++ {
- msg := make([]byte, size)
- binary.PutUvarint(msg, uint64(i))
- if _, err := c1.Write(msg); err != nil {
- t.Fatal(err)
- }
- }
-
- for i := 0; i < msgs; i++ {
- msg := make([]byte, size)
- binary.PutUvarint(msg, uint64(i))
- out := make([]byte, size)
- if _, err := c2.Read(out); err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(msg, out) {
- t.Fatalf("expected %#v, got %#v", msg, out)
- }
- }
-}
-
-func TestTCPPipeBidirections(t *testing.T) {
- c1, c2, err := pipes.TCPPipe()
- if err != nil {
- t.Fatal(err)
- }
-
- msgs := 50
- size := 7
- for i := 0; i < msgs; i++ {
- msg := []byte(fmt.Sprintf("ping %02d", i))
- if _, err := c1.Write(msg); err != nil {
- t.Fatal(err)
- }
- }
-
- for i := 0; i < msgs; i++ {
- expected := []byte(fmt.Sprintf("ping %02d", i))
- out := make([]byte, size)
- if _, err := c2.Read(out); err != nil {
- t.Fatal(err)
- }
-
- if !bytes.Equal(expected, out) {
- t.Fatalf("expected %#v, got %#v", out, expected)
- } else {
- msg := []byte(fmt.Sprintf("pong %02d", i))
- if _, err := c2.Write(msg); err != nil {
- t.Fatal(err)
- }
- }
- }
-
- for i := 0; i < msgs; i++ {
- expected := []byte(fmt.Sprintf("pong %02d", i))
- out := make([]byte, size)
- if _, err := c1.Read(out); err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(expected, out) {
- t.Fatalf("expected %#v, got %#v", out, expected)
- }
- }
-}
-
-func TestNetPipe(t *testing.T) {
- c1, c2, err := pipes.NetPipe()
- if err != nil {
- t.Fatal(err)
- }
-
- msgs := 50
- size := 1024
- var wg sync.WaitGroup
- defer wg.Wait()
-
- // netPipe is blocking, so writes are emitted asynchronously
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- for i := 0; i < msgs; i++ {
- msg := make([]byte, size)
- binary.PutUvarint(msg, uint64(i))
- if _, err := c1.Write(msg); err != nil {
- t.Error(err)
- }
- }
- }()
-
- for i := 0; i < msgs; i++ {
- msg := make([]byte, size)
- binary.PutUvarint(msg, uint64(i))
- out := make([]byte, size)
- if _, err := c2.Read(out); err != nil {
- t.Error(err)
- }
- if !bytes.Equal(msg, out) {
- t.Errorf("expected %#v, got %#v", msg, out)
- }
- }
-}
-
-func TestNetPipeBidirections(t *testing.T) {
- c1, c2, err := pipes.NetPipe()
- if err != nil {
- t.Fatal(err)
- }
-
- msgs := 1000
- size := 8
- pingTemplate := "ping %03d"
- pongTemplate := "pong %03d"
- var wg sync.WaitGroup
- defer wg.Wait()
-
- // netPipe is blocking, so writes are emitted asynchronously
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- for i := 0; i < msgs; i++ {
- msg := []byte(fmt.Sprintf(pingTemplate, i))
- if _, err := c1.Write(msg); err != nil {
- t.Error(err)
- }
- }
- }()
-
- // netPipe is blocking, so reads for pong are emitted asynchronously
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- for i := 0; i < msgs; i++ {
- expected := []byte(fmt.Sprintf(pongTemplate, i))
- out := make([]byte, size)
- if _, err := c1.Read(out); err != nil {
- t.Error(err)
- }
- if !bytes.Equal(expected, out) {
- t.Errorf("expected %#v, got %#v", expected, out)
- }
- }
- }()
-
- // expect to read pings, and respond with pongs to the alternate connection
- for i := 0; i < msgs; i++ {
- expected := []byte(fmt.Sprintf(pingTemplate, i))
-
- out := make([]byte, size)
- _, err := c2.Read(out)
- if err != nil {
- t.Fatal(err)
- }
-
- if !bytes.Equal(expected, out) {
- t.Errorf("expected %#v, got %#v", expected, out)
- } else {
- msg := []byte(fmt.Sprintf(pongTemplate, i))
- if _, err := c2.Write(msg); err != nil {
- t.Fatal(err)
- }
- }
- }
-}
diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go
deleted file mode 100644
index fb8463d221..0000000000
--- a/p2p/simulations/adapters/types.go
+++ /dev/null
@@ -1,326 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package adapters
-
-import (
- "crypto/ecdsa"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "net"
- "os"
- "strconv"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/internal/reexec"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/gorilla/websocket"
- "golang.org/x/exp/slog"
-)
-
-// Node represents a node in a simulation network which is created by a
-// NodeAdapter, for example:
-//
-// - SimNode, an in-memory node in the same process
-// - ExecNode, a child process node
-// - DockerNode, a node running in a Docker container
-type Node interface {
- // Addr returns the node's address (e.g. an Enode URL)
- Addr() []byte
-
- // Client returns the RPC client which is created once the node is
- // up and running
- Client() (*rpc.Client, error)
-
- // ServeRPC serves RPC requests over the given connection
- ServeRPC(*websocket.Conn) error
-
- // Start starts the node with the given snapshots
- Start(snapshots map[string][]byte) error
-
- // Stop stops the node
- Stop() error
-
- // NodeInfo returns information about the node
- NodeInfo() *p2p.NodeInfo
-
- // Snapshots creates snapshots of the running services
- Snapshots() (map[string][]byte, error)
-}
-
-// NodeAdapter is used to create Nodes in a simulation network
-type NodeAdapter interface {
- // Name returns the name of the adapter for logging purposes
- Name() string
-
- // NewNode creates a new node with the given configuration
- NewNode(config *NodeConfig) (Node, error)
-}
-
-// NodeConfig is the configuration used to start a node in a simulation
-// network
-type NodeConfig struct {
- // ID is the node's ID which is used to identify the node in the
- // simulation network
- ID enode.ID
-
- // PrivateKey is the node's private key which is used by the devp2p
- // stack to encrypt communications
- PrivateKey *ecdsa.PrivateKey
-
- // Enable peer events for Msgs
- EnableMsgEvents bool
-
- // Name is a human friendly name for the node like "node01"
- Name string
-
- // Use an existing database instead of a temporary one if non-empty
- DataDir string
-
- // Lifecycles are the names of the service lifecycles which should be run when
- // starting the node (for SimNodes it should be the names of service lifecycles
- // contained in SimAdapter.lifecycles, for other nodes it should be
- // service lifecycles registered by calling the RegisterLifecycle function)
- Lifecycles []string
-
- // Properties are the names of the properties this node should hold
- // within running services (e.g. "bootnode", "lightnode" or any custom values)
- // These values need to be checked and acted upon by node Services
- Properties []string
-
- // ExternalSigner specifies an external URI for a clef-type signer
- ExternalSigner string
-
- // Enode
- node *enode.Node
-
- // ENR Record with entries to overwrite
- Record enr.Record
-
- // function to sanction or prevent suggesting a peer
- Reachable func(id enode.ID) bool
-
- Port uint16
-
- // LogFile is the log file name of the p2p node at runtime.
- //
- // The default value is empty so that the default log writer
- // is the system standard output.
- LogFile string
-
- // LogVerbosity is the log verbosity of the p2p node at runtime.
- //
- // The default verbosity is INFO.
- LogVerbosity slog.Level
-}
-
-// nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding
-// all fields as strings
-type nodeConfigJSON struct {
- ID string `json:"id"`
- PrivateKey string `json:"private_key"`
- Name string `json:"name"`
- Lifecycles []string `json:"lifecycles"`
- Properties []string `json:"properties"`
- EnableMsgEvents bool `json:"enable_msg_events"`
- Port uint16 `json:"port"`
- LogFile string `json:"logfile"`
- LogVerbosity int `json:"log_verbosity"`
-}
-
-// MarshalJSON implements the json.Marshaler interface by encoding the config
-// fields as strings
-func (n *NodeConfig) MarshalJSON() ([]byte, error) {
- confJSON := nodeConfigJSON{
- ID: n.ID.String(),
- Name: n.Name,
- Lifecycles: n.Lifecycles,
- Properties: n.Properties,
- Port: n.Port,
- EnableMsgEvents: n.EnableMsgEvents,
- LogFile: n.LogFile,
- LogVerbosity: int(n.LogVerbosity),
- }
- if n.PrivateKey != nil {
- confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey))
- }
- return json.Marshal(confJSON)
-}
-
-// UnmarshalJSON implements the json.Unmarshaler interface by decoding the json
-// string values into the config fields
-func (n *NodeConfig) UnmarshalJSON(data []byte) error {
- var confJSON nodeConfigJSON
- if err := json.Unmarshal(data, &confJSON); err != nil {
- return err
- }
-
- if confJSON.ID != "" {
- if err := n.ID.UnmarshalText([]byte(confJSON.ID)); err != nil {
- return err
- }
- }
-
- if confJSON.PrivateKey != "" {
- key, err := hex.DecodeString(confJSON.PrivateKey)
- if err != nil {
- return err
- }
- privKey, err := crypto.ToECDSA(key)
- if err != nil {
- return err
- }
- n.PrivateKey = privKey
- }
-
- n.Name = confJSON.Name
- n.Lifecycles = confJSON.Lifecycles
- n.Properties = confJSON.Properties
- n.Port = confJSON.Port
- n.EnableMsgEvents = confJSON.EnableMsgEvents
- n.LogFile = confJSON.LogFile
- n.LogVerbosity = slog.Level(confJSON.LogVerbosity)
-
- return nil
-}
-
-// Node returns the node descriptor represented by the config.
-func (n *NodeConfig) Node() *enode.Node {
- return n.node
-}
-
-// RandomNodeConfig returns node configuration with a randomly generated ID and
-// PrivateKey
-func RandomNodeConfig() *NodeConfig {
- prvkey, err := crypto.GenerateKey()
- if err != nil {
- panic("unable to generate key")
- }
-
- port, err := assignTCPPort()
- if err != nil {
- panic("unable to assign tcp port")
- }
-
- enodId := enode.PubkeyToIDV4(&prvkey.PublicKey)
- return &NodeConfig{
- PrivateKey: prvkey,
- ID: enodId,
- Name: fmt.Sprintf("node_%s", enodId.String()),
- Port: port,
- EnableMsgEvents: true,
- LogVerbosity: log.LvlInfo,
- }
-}
-
-func assignTCPPort() (uint16, error) {
- l, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- return 0, err
- }
- l.Close()
- _, port, err := net.SplitHostPort(l.Addr().String())
- if err != nil {
- return 0, err
- }
- p, err := strconv.ParseUint(port, 10, 16)
- if err != nil {
- return 0, err
- }
- return uint16(p), nil
-}
-
-// ServiceContext is a collection of options and methods which can be utilised
-// when starting services
-type ServiceContext struct {
- RPCDialer
-
- Config *NodeConfig
- Snapshot []byte
-}
-
-// RPCDialer is used when initialising services which need to connect to
-// other nodes in the network (for example a simulated Swarm node which needs
-// to connect to a Geth node to resolve ENS names)
-type RPCDialer interface {
- DialRPC(id enode.ID) (*rpc.Client, error)
-}
-
-// LifecycleConstructor allows a Lifecycle to be constructed during node start-up.
-// While the service-specific package usually takes care of Lifecycle creation and registration,
-// for testing purposes, it is useful to be able to construct a Lifecycle on spot.
-type LifecycleConstructor func(ctx *ServiceContext, stack *node.Node) (node.Lifecycle, error)
-
-// LifecycleConstructors stores LifecycleConstructor functions to call during node start-up.
-type LifecycleConstructors map[string]LifecycleConstructor
-
-// lifecycleConstructorFuncs is a map of registered services which are used to boot devp2p
-// nodes
-var lifecycleConstructorFuncs = make(LifecycleConstructors)
-
-// RegisterLifecycles registers the given Services which can then be used to
-// start devp2p nodes using either the Exec or Docker adapters.
-//
-// It should be called in an init function so that it has the opportunity to
-// execute the services before main() is called.
-func RegisterLifecycles(lifecycles LifecycleConstructors) {
- for name, f := range lifecycles {
- if _, exists := lifecycleConstructorFuncs[name]; exists {
- panic(fmt.Sprintf("node service already exists: %q", name))
- }
- lifecycleConstructorFuncs[name] = f
- }
-
- // now we have registered the services, run reexec.Init() which will
- // potentially start one of the services if the current binary has
- // been exec'd with argv[0] set to "p2p-node"
- if reexec.Init() {
- os.Exit(0)
- }
-}
-
-// adds the host part to the configuration's ENR, signs it
-// creates and the corresponding enode object to the configuration
-func (n *NodeConfig) initEnode(ip net.IP, tcpport int, udpport int) error {
- enrIp := enr.IP(ip)
- n.Record.Set(&enrIp)
- enrTcpPort := enr.TCP(tcpport)
- n.Record.Set(&enrTcpPort)
- enrUdpPort := enr.UDP(udpport)
- n.Record.Set(&enrUdpPort)
-
- err := enode.SignV4(&n.Record, n.PrivateKey)
- if err != nil {
- return fmt.Errorf("unable to generate ENR: %v", err)
- }
- nod, err := enode.New(enode.V4ID{}, &n.Record)
- if err != nil {
- return fmt.Errorf("unable to create enode: %v", err)
- }
- log.Trace("simnode new", "record", n.Record)
- n.node = nod
- return nil
-}
-
-func (n *NodeConfig) initDummyEnode() error {
- return n.initEnode(net.IPv4(127, 0, 0, 1), int(n.Port), 0)
-}
diff --git a/p2p/simulations/connect.go b/p2p/simulations/connect.go
deleted file mode 100644
index ede96b34c1..0000000000
--- a/p2p/simulations/connect.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package simulations
-
-import (
- "errors"
- "strings"
-
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-var (
- ErrNodeNotFound = errors.New("node not found")
-)
-
-// ConnectToLastNode connects the node with provided NodeID
-// to the last node that is up, and avoiding connection to self.
-// It is useful when constructing a chain network topology
-// when Network adds and removes nodes dynamically.
-func (net *Network) ConnectToLastNode(id enode.ID) (err error) {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- ids := net.getUpNodeIDs()
- l := len(ids)
- if l < 2 {
- return nil
- }
- last := ids[l-1]
- if last == id {
- last = ids[l-2]
- }
- return net.connectNotConnected(last, id)
-}
-
-// ConnectToRandomNode connects the node with provided NodeID
-// to a random node that is up.
-func (net *Network) ConnectToRandomNode(id enode.ID) (err error) {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- selected := net.getRandomUpNode(id)
- if selected == nil {
- return ErrNodeNotFound
- }
- return net.connectNotConnected(selected.ID(), id)
-}
-
-// ConnectNodesFull connects all nodes one to another.
-// It provides a complete connectivity in the network
-// which should be rarely needed.
-func (net *Network) ConnectNodesFull(ids []enode.ID) (err error) {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- if ids == nil {
- ids = net.getUpNodeIDs()
- }
- for i, lid := range ids {
- for _, rid := range ids[i+1:] {
- if err = net.connectNotConnected(lid, rid); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-// ConnectNodesChain connects all nodes in a chain topology.
-// If ids argument is nil, all nodes that are up will be connected.
-func (net *Network) ConnectNodesChain(ids []enode.ID) (err error) {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- return net.connectNodesChain(ids)
-}
-
-func (net *Network) connectNodesChain(ids []enode.ID) (err error) {
- if ids == nil {
- ids = net.getUpNodeIDs()
- }
- l := len(ids)
- for i := 0; i < l-1; i++ {
- if err := net.connectNotConnected(ids[i], ids[i+1]); err != nil {
- return err
- }
- }
- return nil
-}
-
-// ConnectNodesRing connects all nodes in a ring topology.
-// If ids argument is nil, all nodes that are up will be connected.
-func (net *Network) ConnectNodesRing(ids []enode.ID) (err error) {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- if ids == nil {
- ids = net.getUpNodeIDs()
- }
- l := len(ids)
- if l < 2 {
- return nil
- }
- if err := net.connectNodesChain(ids); err != nil {
- return err
- }
- return net.connectNotConnected(ids[l-1], ids[0])
-}
-
-// ConnectNodesStar connects all nodes into a star topology
-// If ids argument is nil, all nodes that are up will be connected.
-func (net *Network) ConnectNodesStar(ids []enode.ID, center enode.ID) (err error) {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- if ids == nil {
- ids = net.getUpNodeIDs()
- }
- for _, id := range ids {
- if center == id {
- continue
- }
- if err := net.connectNotConnected(center, id); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (net *Network) connectNotConnected(oneID, otherID enode.ID) error {
- return ignoreAlreadyConnectedErr(net.connect(oneID, otherID))
-}
-
-func ignoreAlreadyConnectedErr(err error) error {
- if err == nil || strings.Contains(err.Error(), "already connected") {
- return nil
- }
- return err
-}
diff --git a/p2p/simulations/connect_test.go b/p2p/simulations/connect_test.go
deleted file mode 100644
index 0154a18b03..0000000000
--- a/p2p/simulations/connect_test.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package simulations
-
-import (
- "testing"
-
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
-)
-
-func newTestNetwork(t *testing.T, nodeCount int) (*Network, []enode.ID) {
- t.Helper()
- adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
- return NewNoopService(nil), nil
- },
- })
-
- // create network
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "noopwoop",
- })
-
- // create and start nodes
- ids := make([]enode.ID, nodeCount)
- for i := range ids {
- conf := adapters.RandomNodeConfig()
- node, err := network.NewNodeWithConfig(conf)
- if err != nil {
- t.Fatalf("error creating node: %s", err)
- }
- if err := network.Start(node.ID()); err != nil {
- t.Fatalf("error starting node: %s", err)
- }
- ids[i] = node.ID()
- }
-
- if len(network.Conns) > 0 {
- t.Fatal("no connections should exist after just adding nodes")
- }
-
- return network, ids
-}
-
-func TestConnectToLastNode(t *testing.T) {
- net, ids := newTestNetwork(t, 10)
- defer net.Shutdown()
-
- first := ids[0]
- if err := net.ConnectToLastNode(first); err != nil {
- t.Fatal(err)
- }
-
- last := ids[len(ids)-1]
- for i, id := range ids {
- if id == first || id == last {
- continue
- }
-
- if net.GetConn(first, id) != nil {
- t.Errorf("connection must not exist with node(ind: %v, id: %v)", i, id)
- }
- }
-
- if net.GetConn(first, last) == nil {
- t.Error("first and last node must be connected")
- }
-}
-
-func TestConnectToRandomNode(t *testing.T) {
- net, ids := newTestNetwork(t, 10)
- defer net.Shutdown()
-
- err := net.ConnectToRandomNode(ids[0])
- if err != nil {
- t.Fatal(err)
- }
-
- var cc int
- for i, a := range ids {
- for _, b := range ids[i:] {
- if net.GetConn(a, b) != nil {
- cc++
- }
- }
- }
-
- if cc != 1 {
- t.Errorf("expected one connection, got %v", cc)
- }
-}
-
-func TestConnectNodesFull(t *testing.T) {
- tests := []struct {
- name string
- nodeCount int
- }{
- {name: "no node", nodeCount: 0},
- {name: "single node", nodeCount: 1},
- {name: "2 nodes", nodeCount: 2},
- {name: "3 nodes", nodeCount: 3},
- {name: "even number of nodes", nodeCount: 12},
- {name: "odd number of nodes", nodeCount: 13},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- net, ids := newTestNetwork(t, test.nodeCount)
- defer net.Shutdown()
-
- err := net.ConnectNodesFull(ids)
- if err != nil {
- t.Fatal(err)
- }
-
- VerifyFull(t, net, ids)
- })
- }
-}
-
-func TestConnectNodesChain(t *testing.T) {
- net, ids := newTestNetwork(t, 10)
- defer net.Shutdown()
-
- err := net.ConnectNodesChain(ids)
- if err != nil {
- t.Fatal(err)
- }
-
- VerifyChain(t, net, ids)
-}
-
-func TestConnectNodesRing(t *testing.T) {
- net, ids := newTestNetwork(t, 10)
- defer net.Shutdown()
-
- err := net.ConnectNodesRing(ids)
- if err != nil {
- t.Fatal(err)
- }
-
- VerifyRing(t, net, ids)
-}
-
-func TestConnectNodesStar(t *testing.T) {
- net, ids := newTestNetwork(t, 10)
- defer net.Shutdown()
-
- pivotIndex := 2
-
- err := net.ConnectNodesStar(ids, ids[pivotIndex])
- if err != nil {
- t.Fatal(err)
- }
-
- VerifyStar(t, net, ids, pivotIndex)
-}
diff --git a/p2p/simulations/events.go b/p2p/simulations/events.go
deleted file mode 100644
index d0d03794ed..0000000000
--- a/p2p/simulations/events.go
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package simulations
-
-import (
- "fmt"
- "time"
-)
-
-// EventType is the type of event emitted by a simulation network
-type EventType string
-
-const (
- // EventTypeNode is the type of event emitted when a node is either
- // created, started or stopped
- EventTypeNode EventType = "node"
-
- // EventTypeConn is the type of event emitted when a connection is
- // is either established or dropped between two nodes
- EventTypeConn EventType = "conn"
-
- // EventTypeMsg is the type of event emitted when a p2p message it
- // sent between two nodes
- EventTypeMsg EventType = "msg"
-)
-
-// Event is an event emitted by a simulation network
-type Event struct {
- // Type is the type of the event
- Type EventType `json:"type"`
-
- // Time is the time the event happened
- Time time.Time `json:"time"`
-
- // Control indicates whether the event is the result of a controlled
- // action in the network
- Control bool `json:"control"`
-
- // Node is set if the type is EventTypeNode
- Node *Node `json:"node,omitempty"`
-
- // Conn is set if the type is EventTypeConn
- Conn *Conn `json:"conn,omitempty"`
-
- // Msg is set if the type is EventTypeMsg
- Msg *Msg `json:"msg,omitempty"`
-
- //Optionally provide data (currently for simulation frontends only)
- Data interface{} `json:"data"`
-}
-
-// NewEvent creates a new event for the given object which should be either a
-// Node, Conn or Msg.
-//
-// The object is copied so that the event represents the state of the object
-// when NewEvent is called.
-func NewEvent(v interface{}) *Event {
- event := &Event{Time: time.Now()}
- switch v := v.(type) {
- case *Node:
- event.Type = EventTypeNode
- event.Node = v.copy()
- case *Conn:
- event.Type = EventTypeConn
- conn := *v
- event.Conn = &conn
- case *Msg:
- event.Type = EventTypeMsg
- msg := *v
- event.Msg = &msg
- default:
- panic(fmt.Sprintf("invalid event type: %T", v))
- }
- return event
-}
-
-// ControlEvent creates a new control event
-func ControlEvent(v interface{}) *Event {
- event := NewEvent(v)
- event.Control = true
- return event
-}
-
-// String returns the string representation of the event
-func (e *Event) String() string {
- switch e.Type {
- case EventTypeNode:
- return fmt.Sprintf(" id: %s up: %t", e.Node.ID().TerminalString(), e.Node.Up())
- case EventTypeConn:
- return fmt.Sprintf(" nodes: %s->%s up: %t", e.Conn.One.TerminalString(), e.Conn.Other.TerminalString(), e.Conn.Up)
- case EventTypeMsg:
- return fmt.Sprintf(" nodes: %s->%s proto: %s, code: %d, received: %t", e.Msg.One.TerminalString(), e.Msg.Other.TerminalString(), e.Msg.Protocol, e.Msg.Code, e.Msg.Received)
- default:
- return ""
- }
-}
diff --git a/p2p/simulations/examples/README.md b/p2p/simulations/examples/README.md
deleted file mode 100644
index 822a48dcb6..0000000000
--- a/p2p/simulations/examples/README.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# devp2p simulation examples
-
-## ping-pong
-
-`ping-pong.go` implements a simulation network which contains nodes running a
-simple "ping-pong" protocol where nodes send a ping message to all their
-connected peers every 10s and receive pong messages in return.
-
-To run the simulation, run `go run ping-pong.go` in one terminal to start the
-simulation API and `./ping-pong.sh` in another to start and connect the nodes:
-
-```
-$ go run ping-pong.go
-INFO [08-15|13:53:49] using sim adapter
-INFO [08-15|13:53:49] starting simulation server on 0.0.0.0:8888...
-```
-
-```
-$ ./ping-pong.sh
----> 13:58:12 creating 10 nodes
-Created node01
-Started node01
-...
-Created node10
-Started node10
----> 13:58:13 connecting node01 to all other nodes
-Connected node01 to node02
-...
-Connected node01 to node10
----> 13:58:14 done
-```
-
-Use the `--adapter` flag to choose the adapter type:
-
-```
-$ go run ping-pong.go --adapter exec
-INFO [08-15|14:01:14] using exec adapter tmpdir=/var/folders/k6/wpsgfg4n23ddbc6f5cnw5qg00000gn/T/p2p-example992833779
-INFO [08-15|14:01:14] starting simulation server on 0.0.0.0:8888...
-```
diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go
deleted file mode 100644
index 70b35ad777..0000000000
--- a/p2p/simulations/examples/ping-pong.go
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package main
-
-import (
- "flag"
- "fmt"
- "io"
- "net/http"
- "os"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/simulations"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
-)
-
-var adapterType = flag.String("adapter", "sim", `node adapter to use (one of "sim", "exec" or "docker")`)
-
-// main() starts a simulation network which contains nodes running a simple
-// ping-pong protocol
-func main() {
- flag.Parse()
-
- // set the log level to Trace
- log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, false)))
-
- // register a single ping-pong service
- services := map[string]adapters.LifecycleConstructor{
- "ping-pong": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
- pps := newPingPongService(ctx.Config.ID)
- stack.RegisterProtocols(pps.Protocols())
- return pps, nil
- },
- }
- adapters.RegisterLifecycles(services)
-
- // create the NodeAdapter
- var adapter adapters.NodeAdapter
-
- switch *adapterType {
-
- case "sim":
- log.Info("using sim adapter")
- adapter = adapters.NewSimAdapter(services)
-
- case "exec":
- tmpdir, err := os.MkdirTemp("", "p2p-example")
- if err != nil {
- log.Crit("error creating temp dir", "err", err)
- }
- defer os.RemoveAll(tmpdir)
- log.Info("using exec adapter", "tmpdir", tmpdir)
- adapter = adapters.NewExecAdapter(tmpdir)
-
- default:
- log.Crit(fmt.Sprintf("unknown node adapter %q", *adapterType))
- }
-
- // start the HTTP API
- log.Info("starting simulation server on 0.0.0.0:8888...")
- network := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
- DefaultService: "ping-pong",
- })
- if err := http.ListenAndServe(":8888", simulations.NewServer(network)); err != nil {
- log.Crit("error starting simulation server", "err", err)
- }
-}
-
-// pingPongService runs a ping-pong protocol between nodes where each node
-// sends a ping to all its connected peers every 10s and receives a pong in
-// return
-type pingPongService struct {
- id enode.ID
- log log.Logger
- received atomic.Int64
-}
-
-func newPingPongService(id enode.ID) *pingPongService {
- return &pingPongService{
- id: id,
- log: log.New("node.id", id),
- }
-}
-
-func (p *pingPongService) Protocols() []p2p.Protocol {
- return []p2p.Protocol{{
- Name: "ping-pong",
- Version: 1,
- Length: 2,
- Run: p.Run,
- NodeInfo: p.Info,
- }}
-}
-
-func (p *pingPongService) Start() error {
- p.log.Info("ping-pong service starting")
- return nil
-}
-
-func (p *pingPongService) Stop() error {
- p.log.Info("ping-pong service stopping")
- return nil
-}
-
-func (p *pingPongService) Info() interface{} {
- return struct {
- Received int64 `json:"received"`
- }{
- p.received.Load(),
- }
-}
-
-const (
- pingMsgCode = iota
- pongMsgCode
-)
-
-// Run implements the ping-pong protocol which sends ping messages to the peer
-// at 10s intervals, and responds to pings with pong messages.
-func (p *pingPongService) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
- log := p.log.New("peer.id", peer.ID())
-
- errC := make(chan error, 1)
- go func() {
- for range time.Tick(10 * time.Second) {
- log.Info("sending ping")
- if err := p2p.Send(rw, pingMsgCode, "PING"); err != nil {
- errC <- err
- return
- }
- }
- }()
- go func() {
- for {
- msg, err := rw.ReadMsg()
- if err != nil {
- errC <- err
- return
- }
- payload, err := io.ReadAll(msg.Payload)
- if err != nil {
- errC <- err
- return
- }
- log.Info("received message", "msg.code", msg.Code, "msg.payload", string(payload))
- p.received.Add(1)
- if msg.Code == pingMsgCode {
- log.Info("sending pong")
- go p2p.Send(rw, pongMsgCode, "PONG")
- }
- }
- }()
- return <-errC
-}
diff --git a/p2p/simulations/examples/ping-pong.sh b/p2p/simulations/examples/ping-pong.sh
deleted file mode 100755
index 47936bd9a0..0000000000
--- a/p2p/simulations/examples/ping-pong.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/bash
-#
-# Boot a ping-pong network simulation using the HTTP API started by ping-pong.go
-
-set -e
-
-main() {
- if ! which p2psim &>/dev/null; then
- fail "missing p2psim binary (you need to build cmd/p2psim and put it in \$PATH)"
- fi
-
- info "creating 10 nodes"
- for i in $(seq 1 10); do
- p2psim node create --name "$(node_name $i)"
- p2psim node start "$(node_name $i)"
- done
-
- info "connecting node01 to all other nodes"
- for i in $(seq 2 10); do
- p2psim node connect "node01" "$(node_name $i)"
- done
-
- info "done"
-}
-
-node_name() {
- local num=$1
- echo "node$(printf '%02d' $num)"
-}
-
-info() {
- echo -e "\033[1;32m---> $(date +%H:%M:%S) ${@}\033[0m"
-}
-
-fail() {
- echo -e "\033[1;31mERROR: ${@}\033[0m" >&2
- exit 1
-}
-
-main "$@"
diff --git a/p2p/simulations/http.go b/p2p/simulations/http.go
deleted file mode 100644
index 34521b4778..0000000000
--- a/p2p/simulations/http.go
+++ /dev/null
@@ -1,743 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package simulations
-
-import (
- "bufio"
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "html"
- "io"
- "net/http"
- "strconv"
- "strings"
- "sync"
-
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/gorilla/websocket"
- "github.com/julienschmidt/httprouter"
-)
-
-// DefaultClient is the default simulation API client which expects the API
-// to be running at http://localhost:8888
-var DefaultClient = NewClient("http://localhost:8888")
-
-// Client is a client for the simulation HTTP API which supports creating
-// and managing simulation networks
-type Client struct {
- URL string
-
- client *http.Client
-}
-
-// NewClient returns a new simulation API client
-func NewClient(url string) *Client {
- return &Client{
- URL: url,
- client: http.DefaultClient,
- }
-}
-
-// GetNetwork returns details of the network
-func (c *Client) GetNetwork() (*Network, error) {
- network := &Network{}
- return network, c.Get("/", network)
-}
-
-// StartNetwork starts all existing nodes in the simulation network
-func (c *Client) StartNetwork() error {
- return c.Post("/start", nil, nil)
-}
-
-// StopNetwork stops all existing nodes in a simulation network
-func (c *Client) StopNetwork() error {
- return c.Post("/stop", nil, nil)
-}
-
-// CreateSnapshot creates a network snapshot
-func (c *Client) CreateSnapshot() (*Snapshot, error) {
- snap := &Snapshot{}
- return snap, c.Get("/snapshot", snap)
-}
-
-// LoadSnapshot loads a snapshot into the network
-func (c *Client) LoadSnapshot(snap *Snapshot) error {
- return c.Post("/snapshot", snap, nil)
-}
-
-// SubscribeOpts is a collection of options to use when subscribing to network
-// events
-type SubscribeOpts struct {
- // Current instructs the server to send events for existing nodes and
- // connections first
- Current bool
-
- // Filter instructs the server to only send a subset of message events
- Filter string
-}
-
-// SubscribeNetwork subscribes to network events which are sent from the server
-// as a server-sent-events stream, optionally receiving events for existing
-// nodes and connections and filtering message events
-func (c *Client) SubscribeNetwork(events chan *Event, opts SubscribeOpts) (event.Subscription, error) {
- url := fmt.Sprintf("%s/events?current=%t&filter=%s", c.URL, opts.Current, opts.Filter)
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- return nil, err
- }
- req.Header.Set("Accept", "text/event-stream")
- res, err := c.client.Do(req)
- if err != nil {
- return nil, err
- }
- if res.StatusCode != http.StatusOK {
- response, _ := io.ReadAll(res.Body)
- res.Body.Close()
- return nil, fmt.Errorf("unexpected HTTP status: %s: %s", res.Status, response)
- }
-
- // define a producer function to pass to event.Subscription
- // which reads server-sent events from res.Body and sends
- // them to the events channel
- producer := func(stop <-chan struct{}) error {
- defer res.Body.Close()
-
- // read lines from res.Body in a goroutine so that we are
- // always reading from the stop channel
- lines := make(chan string)
- errC := make(chan error, 1)
- go func() {
- s := bufio.NewScanner(res.Body)
- for s.Scan() {
- select {
- case lines <- s.Text():
- case <-stop:
- return
- }
- }
- errC <- s.Err()
- }()
-
- // detect any lines which start with "data:", decode the data
- // into an event and send it to the events channel
- for {
- select {
- case line := <-lines:
- if !strings.HasPrefix(line, "data:") {
- continue
- }
- data := strings.TrimSpace(strings.TrimPrefix(line, "data:"))
- event := &Event{}
- if err := json.Unmarshal([]byte(data), event); err != nil {
- return fmt.Errorf("error decoding SSE event: %s", err)
- }
- select {
- case events <- event:
- case <-stop:
- return nil
- }
- case err := <-errC:
- return err
- case <-stop:
- return nil
- }
- }
- }
-
- return event.NewSubscription(producer), nil
-}
-
-// GetNodes returns all nodes which exist in the network
-func (c *Client) GetNodes() ([]*p2p.NodeInfo, error) {
- var nodes []*p2p.NodeInfo
- return nodes, c.Get("/nodes", &nodes)
-}
-
-// CreateNode creates a node in the network using the given configuration
-func (c *Client) CreateNode(config *adapters.NodeConfig) (*p2p.NodeInfo, error) {
- node := &p2p.NodeInfo{}
- return node, c.Post("/nodes", config, node)
-}
-
-// GetNode returns details of a node
-func (c *Client) GetNode(nodeID string) (*p2p.NodeInfo, error) {
- node := &p2p.NodeInfo{}
- return node, c.Get(fmt.Sprintf("/nodes/%s", nodeID), node)
-}
-
-// StartNode starts a node
-func (c *Client) StartNode(nodeID string) error {
- return c.Post(fmt.Sprintf("/nodes/%s/start", nodeID), nil, nil)
-}
-
-// StopNode stops a node
-func (c *Client) StopNode(nodeID string) error {
- return c.Post(fmt.Sprintf("/nodes/%s/stop", nodeID), nil, nil)
-}
-
-// ConnectNode connects a node to a peer node
-func (c *Client) ConnectNode(nodeID, peerID string) error {
- return c.Post(fmt.Sprintf("/nodes/%s/conn/%s", nodeID, peerID), nil, nil)
-}
-
-// DisconnectNode disconnects a node from a peer node
-func (c *Client) DisconnectNode(nodeID, peerID string) error {
- return c.Delete(fmt.Sprintf("/nodes/%s/conn/%s", nodeID, peerID))
-}
-
-// RPCClient returns an RPC client connected to a node
-func (c *Client) RPCClient(ctx context.Context, nodeID string) (*rpc.Client, error) {
- baseURL := strings.Replace(c.URL, "http", "ws", 1)
- return rpc.DialWebsocket(ctx, fmt.Sprintf("%s/nodes/%s/rpc", baseURL, nodeID), "")
-}
-
-// Get performs a HTTP GET request decoding the resulting JSON response
-// into "out"
-func (c *Client) Get(path string, out interface{}) error {
- return c.Send(http.MethodGet, path, nil, out)
-}
-
-// Post performs a HTTP POST request sending "in" as the JSON body and
-// decoding the resulting JSON response into "out"
-func (c *Client) Post(path string, in, out interface{}) error {
- return c.Send(http.MethodPost, path, in, out)
-}
-
-// Delete performs a HTTP DELETE request
-func (c *Client) Delete(path string) error {
- return c.Send(http.MethodDelete, path, nil, nil)
-}
-
-// Send performs a HTTP request, sending "in" as the JSON request body and
-// decoding the JSON response into "out"
-func (c *Client) Send(method, path string, in, out interface{}) error {
- var body []byte
- if in != nil {
- var err error
- body, err = json.Marshal(in)
- if err != nil {
- return err
- }
- }
- req, err := http.NewRequest(method, c.URL+path, bytes.NewReader(body))
- if err != nil {
- return err
- }
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Accept", "application/json")
- res, err := c.client.Do(req)
- if err != nil {
- return err
- }
- defer res.Body.Close()
- if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
- response, _ := io.ReadAll(res.Body)
- return fmt.Errorf("unexpected HTTP status: %s: %s", res.Status, response)
- }
- if out != nil {
- if err := json.NewDecoder(res.Body).Decode(out); err != nil {
- return err
- }
- }
- return nil
-}
-
-// Server is an HTTP server providing an API to manage a simulation network
-type Server struct {
- router *httprouter.Router
- network *Network
- mockerStop chan struct{} // when set, stops the current mocker
- mockerMtx sync.Mutex // synchronises access to the mockerStop field
-}
-
-// NewServer returns a new simulation API server
-func NewServer(network *Network) *Server {
- s := &Server{
- router: httprouter.New(),
- network: network,
- }
-
- s.OPTIONS("/", s.Options)
- s.GET("/", s.GetNetwork)
- s.POST("/start", s.StartNetwork)
- s.POST("/stop", s.StopNetwork)
- s.POST("/mocker/start", s.StartMocker)
- s.POST("/mocker/stop", s.StopMocker)
- s.GET("/mocker", s.GetMockers)
- s.POST("/reset", s.ResetNetwork)
- s.GET("/events", s.StreamNetworkEvents)
- s.GET("/snapshot", s.CreateSnapshot)
- s.POST("/snapshot", s.LoadSnapshot)
- s.POST("/nodes", s.CreateNode)
- s.GET("/nodes", s.GetNodes)
- s.GET("/nodes/:nodeid", s.GetNode)
- s.POST("/nodes/:nodeid/start", s.StartNode)
- s.POST("/nodes/:nodeid/stop", s.StopNode)
- s.POST("/nodes/:nodeid/conn/:peerid", s.ConnectNode)
- s.DELETE("/nodes/:nodeid/conn/:peerid", s.DisconnectNode)
- s.GET("/nodes/:nodeid/rpc", s.NodeRPC)
-
- return s
-}
-
-// GetNetwork returns details of the network
-func (s *Server) GetNetwork(w http.ResponseWriter, req *http.Request) {
- s.JSON(w, http.StatusOK, s.network)
-}
-
-// StartNetwork starts all nodes in the network
-func (s *Server) StartNetwork(w http.ResponseWriter, req *http.Request) {
- if err := s.network.StartAll(); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.WriteHeader(http.StatusOK)
-}
-
-// StopNetwork stops all nodes in the network
-func (s *Server) StopNetwork(w http.ResponseWriter, req *http.Request) {
- if err := s.network.StopAll(); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.WriteHeader(http.StatusOK)
-}
-
-// StartMocker starts the mocker node simulation
-func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) {
- s.mockerMtx.Lock()
- defer s.mockerMtx.Unlock()
- if s.mockerStop != nil {
- http.Error(w, "mocker already running", http.StatusInternalServerError)
- return
- }
- mockerType := req.FormValue("mocker-type")
- mockerFn := LookupMocker(mockerType)
- if mockerFn == nil {
- http.Error(w, fmt.Sprintf("unknown mocker type %q", html.EscapeString(mockerType)), http.StatusBadRequest)
- return
- }
- nodeCount, err := strconv.Atoi(req.FormValue("node-count"))
- if err != nil {
- http.Error(w, "invalid node-count provided", http.StatusBadRequest)
- return
- }
- s.mockerStop = make(chan struct{})
- go mockerFn(s.network, s.mockerStop, nodeCount)
-
- w.WriteHeader(http.StatusOK)
-}
-
-// StopMocker stops the mocker node simulation
-func (s *Server) StopMocker(w http.ResponseWriter, req *http.Request) {
- s.mockerMtx.Lock()
- defer s.mockerMtx.Unlock()
- if s.mockerStop == nil {
- http.Error(w, "stop channel not initialized", http.StatusInternalServerError)
- return
- }
- close(s.mockerStop)
- s.mockerStop = nil
-
- w.WriteHeader(http.StatusOK)
-}
-
-// GetMockers returns a list of available mockers
-func (s *Server) GetMockers(w http.ResponseWriter, req *http.Request) {
- list := GetMockerList()
- s.JSON(w, http.StatusOK, list)
-}
-
-// ResetNetwork resets all properties of a network to its initial (empty) state
-func (s *Server) ResetNetwork(w http.ResponseWriter, req *http.Request) {
- s.network.Reset()
-
- w.WriteHeader(http.StatusOK)
-}
-
-// StreamNetworkEvents streams network events as a server-sent-events stream
-func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) {
- events := make(chan *Event)
- sub := s.network.events.Subscribe(events)
- defer sub.Unsubscribe()
-
- // write writes the given event and data to the stream like:
- //
- // event:
- // data:
- //
- write := func(event, data string) {
- fmt.Fprintf(w, "event: %s\n", event)
- fmt.Fprintf(w, "data: %s\n\n", data)
- if fw, ok := w.(http.Flusher); ok {
- fw.Flush()
- }
- }
- writeEvent := func(event *Event) error {
- data, err := json.Marshal(event)
- if err != nil {
- return err
- }
- write("network", string(data))
- return nil
- }
- writeErr := func(err error) {
- write("error", err.Error())
- }
-
- // check if filtering has been requested
- var filters MsgFilters
- if filterParam := req.URL.Query().Get("filter"); filterParam != "" {
- var err error
- filters, err = NewMsgFilters(filterParam)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
- }
-
- w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
- w.WriteHeader(http.StatusOK)
- fmt.Fprintf(w, "\n\n")
- if fw, ok := w.(http.Flusher); ok {
- fw.Flush()
- }
-
- // optionally send the existing nodes and connections
- if req.URL.Query().Get("current") == "true" {
- snap, err := s.network.Snapshot()
- if err != nil {
- writeErr(err)
- return
- }
- for _, node := range snap.Nodes {
- event := NewEvent(&node.Node)
- if err := writeEvent(event); err != nil {
- writeErr(err)
- return
- }
- }
- for _, conn := range snap.Conns {
- conn := conn
- event := NewEvent(&conn)
- if err := writeEvent(event); err != nil {
- writeErr(err)
- return
- }
- }
- }
-
- clientGone := req.Context().Done()
- for {
- select {
- case event := <-events:
- // only send message events which match the filters
- if event.Msg != nil && !filters.Match(event.Msg) {
- continue
- }
- if err := writeEvent(event); err != nil {
- writeErr(err)
- return
- }
- case <-clientGone:
- return
- }
- }
-}
-
-// NewMsgFilters constructs a collection of message filters from a URL query
-// parameter.
-//
-// The parameter is expected to be a dash-separated list of individual filters,
-// each having the format ':', where is the name of a
-// protocol and is a comma-separated list of message codes.
-//
-// A message code of '*' or '-1' is considered a wildcard and matches any code.
-func NewMsgFilters(filterParam string) (MsgFilters, error) {
- filters := make(MsgFilters)
- for _, filter := range strings.Split(filterParam, "-") {
- proto, codes, found := strings.Cut(filter, ":")
- if !found || proto == "" || codes == "" {
- return nil, fmt.Errorf("invalid message filter: %s", filter)
- }
-
- for _, code := range strings.Split(codes, ",") {
- if code == "*" || code == "-1" {
- filters[MsgFilter{Proto: proto, Code: -1}] = struct{}{}
- continue
- }
- n, err := strconv.ParseUint(code, 10, 64)
- if err != nil {
- return nil, fmt.Errorf("invalid message code: %s", code)
- }
- filters[MsgFilter{Proto: proto, Code: int64(n)}] = struct{}{}
- }
- }
- return filters, nil
-}
-
-// MsgFilters is a collection of filters which are used to filter message
-// events
-type MsgFilters map[MsgFilter]struct{}
-
-// Match checks if the given message matches any of the filters
-func (m MsgFilters) Match(msg *Msg) bool {
- // check if there is a wildcard filter for the message's protocol
- if _, ok := m[MsgFilter{Proto: msg.Protocol, Code: -1}]; ok {
- return true
- }
-
- // check if there is a filter for the message's protocol and code
- if _, ok := m[MsgFilter{Proto: msg.Protocol, Code: int64(msg.Code)}]; ok {
- return true
- }
-
- return false
-}
-
-// MsgFilter is used to filter message events based on protocol and message
-// code
-type MsgFilter struct {
- // Proto is matched against a message's protocol
- Proto string
-
- // Code is matched against a message's code, with -1 matching all codes
- Code int64
-}
-
-// CreateSnapshot creates a network snapshot
-func (s *Server) CreateSnapshot(w http.ResponseWriter, req *http.Request) {
- snap, err := s.network.Snapshot()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- s.JSON(w, http.StatusOK, snap)
-}
-
-// LoadSnapshot loads a snapshot into the network
-func (s *Server) LoadSnapshot(w http.ResponseWriter, req *http.Request) {
- snap := &Snapshot{}
- if err := json.NewDecoder(req.Body).Decode(snap); err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- if err := s.network.Load(snap); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- s.JSON(w, http.StatusOK, s.network)
-}
-
-// CreateNode creates a node in the network using the given configuration
-func (s *Server) CreateNode(w http.ResponseWriter, req *http.Request) {
- config := &adapters.NodeConfig{}
-
- err := json.NewDecoder(req.Body).Decode(config)
- if err != nil && !errors.Is(err, io.EOF) {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- node, err := s.network.NewNodeWithConfig(config)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- s.JSON(w, http.StatusCreated, node.NodeInfo())
-}
-
-// GetNodes returns all nodes which exist in the network
-func (s *Server) GetNodes(w http.ResponseWriter, req *http.Request) {
- nodes := s.network.GetNodes()
-
- infos := make([]*p2p.NodeInfo, len(nodes))
- for i, node := range nodes {
- infos[i] = node.NodeInfo()
- }
-
- s.JSON(w, http.StatusOK, infos)
-}
-
-// GetNode returns details of a node
-func (s *Server) GetNode(w http.ResponseWriter, req *http.Request) {
- node := req.Context().Value("node").(*Node)
-
- s.JSON(w, http.StatusOK, node.NodeInfo())
-}
-
-// StartNode starts a node
-func (s *Server) StartNode(w http.ResponseWriter, req *http.Request) {
- node := req.Context().Value("node").(*Node)
-
- if err := s.network.Start(node.ID()); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- s.JSON(w, http.StatusOK, node.NodeInfo())
-}
-
-// StopNode stops a node
-func (s *Server) StopNode(w http.ResponseWriter, req *http.Request) {
- node := req.Context().Value("node").(*Node)
-
- if err := s.network.Stop(node.ID()); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- s.JSON(w, http.StatusOK, node.NodeInfo())
-}
-
-// ConnectNode connects a node to a peer node
-func (s *Server) ConnectNode(w http.ResponseWriter, req *http.Request) {
- node := req.Context().Value("node").(*Node)
- peer := req.Context().Value("peer").(*Node)
-
- if err := s.network.Connect(node.ID(), peer.ID()); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- s.JSON(w, http.StatusOK, node.NodeInfo())
-}
-
-// DisconnectNode disconnects a node from a peer node
-func (s *Server) DisconnectNode(w http.ResponseWriter, req *http.Request) {
- node := req.Context().Value("node").(*Node)
- peer := req.Context().Value("peer").(*Node)
-
- if err := s.network.Disconnect(node.ID(), peer.ID()); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- s.JSON(w, http.StatusOK, node.NodeInfo())
-}
-
-// Options responds to the OPTIONS HTTP method by returning a 200 OK response
-// with the "Access-Control-Allow-Headers" header set to "Content-Type"
-func (s *Server) Options(w http.ResponseWriter, req *http.Request) {
- w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
- w.WriteHeader(http.StatusOK)
-}
-
-var wsUpgrade = websocket.Upgrader{
- CheckOrigin: func(*http.Request) bool { return true },
-}
-
-// NodeRPC forwards RPC requests to a node in the network via a WebSocket
-// connection
-func (s *Server) NodeRPC(w http.ResponseWriter, req *http.Request) {
- conn, err := wsUpgrade.Upgrade(w, req, nil)
- if err != nil {
- return
- }
- defer conn.Close()
- node := req.Context().Value("node").(*Node)
- node.ServeRPC(conn)
-}
-
-// ServeHTTP implements the http.Handler interface by delegating to the
-// underlying httprouter.Router
-func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- s.router.ServeHTTP(w, req)
-}
-
-// GET registers a handler for GET requests to a particular path
-func (s *Server) GET(path string, handle http.HandlerFunc) {
- s.router.GET(path, s.wrapHandler(handle))
-}
-
-// POST registers a handler for POST requests to a particular path
-func (s *Server) POST(path string, handle http.HandlerFunc) {
- s.router.POST(path, s.wrapHandler(handle))
-}
-
-// DELETE registers a handler for DELETE requests to a particular path
-func (s *Server) DELETE(path string, handle http.HandlerFunc) {
- s.router.DELETE(path, s.wrapHandler(handle))
-}
-
-// OPTIONS registers a handler for OPTIONS requests to a particular path
-func (s *Server) OPTIONS(path string, handle http.HandlerFunc) {
- s.router.OPTIONS("/*path", s.wrapHandler(handle))
-}
-
-// JSON sends "data" as a JSON HTTP response
-func (s *Server) JSON(w http.ResponseWriter, status int, data interface{}) {
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(status)
- json.NewEncoder(w).Encode(data)
-}
-
-// wrapHandler returns an httprouter.Handle which wraps an http.HandlerFunc by
-// populating request.Context with any objects from the URL params
-func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
- return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
-
- ctx := req.Context()
-
- if id := params.ByName("nodeid"); id != "" {
- var nodeID enode.ID
- var node *Node
- if nodeID.UnmarshalText([]byte(id)) == nil {
- node = s.network.GetNode(nodeID)
- } else {
- node = s.network.GetNodeByName(id)
- }
- if node == nil {
- http.NotFound(w, req)
- return
- }
- ctx = context.WithValue(ctx, "node", node)
- }
-
- if id := params.ByName("peerid"); id != "" {
- var peerID enode.ID
- var peer *Node
- if peerID.UnmarshalText([]byte(id)) == nil {
- peer = s.network.GetNode(peerID)
- } else {
- peer = s.network.GetNodeByName(id)
- }
- if peer == nil {
- http.NotFound(w, req)
- return
- }
- ctx = context.WithValue(ctx, "peer", peer)
- }
-
- handler(w, req.WithContext(ctx))
- }
-}
diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go
deleted file mode 100644
index c53a49797b..0000000000
--- a/p2p/simulations/http_test.go
+++ /dev/null
@@ -1,871 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package simulations
-
-import (
- "context"
- "flag"
- "fmt"
- "math/rand"
- "net/http/httptest"
- "os"
- "reflect"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/mattn/go-colorable"
- "golang.org/x/exp/slog"
-)
-
-func TestMain(m *testing.M) {
- loglevel := flag.Int("loglevel", 2, "verbosity of logs")
-
- flag.Parse()
- log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.Level(*loglevel), true)))
- os.Exit(m.Run())
-}
-
-// testService implements the node.Service interface and provides protocols
-// and APIs which are useful for testing nodes in a simulation network
-type testService struct {
- id enode.ID
-
- // peerCount is incremented once a peer handshake has been performed
- peerCount int64
-
- peers map[enode.ID]*testPeer
- peersMtx sync.Mutex
-
- // state stores []byte which is used to test creating and loading
- // snapshots
- state atomic.Value
-}
-
-func newTestService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
- svc := &testService{
- id: ctx.Config.ID,
- peers: make(map[enode.ID]*testPeer),
- }
- svc.state.Store(ctx.Snapshot)
-
- stack.RegisterProtocols(svc.Protocols())
- stack.RegisterAPIs(svc.APIs())
- return svc, nil
-}
-
-type testPeer struct {
- testReady chan struct{}
- dumReady chan struct{}
-}
-
-func (t *testService) peer(id enode.ID) *testPeer {
- t.peersMtx.Lock()
- defer t.peersMtx.Unlock()
- if peer, ok := t.peers[id]; ok {
- return peer
- }
- peer := &testPeer{
- testReady: make(chan struct{}),
- dumReady: make(chan struct{}),
- }
- t.peers[id] = peer
- return peer
-}
-
-func (t *testService) Protocols() []p2p.Protocol {
- return []p2p.Protocol{
- {
- Name: "test",
- Version: 1,
- Length: 3,
- Run: t.RunTest,
- },
- {
- Name: "dum",
- Version: 1,
- Length: 1,
- Run: t.RunDum,
- },
- {
- Name: "prb",
- Version: 1,
- Length: 1,
- Run: t.RunPrb,
- },
- }
-}
-
-func (t *testService) APIs() []rpc.API {
- return []rpc.API{{
- Namespace: "test",
- Version: "1.0",
- Service: &TestAPI{
- state: &t.state,
- peerCount: &t.peerCount,
- },
- }}
-}
-
-func (t *testService) Start() error {
- return nil
-}
-
-func (t *testService) Stop() error {
- return nil
-}
-
-// handshake performs a peer handshake by sending and expecting an empty
-// message with the given code
-func (t *testService) handshake(rw p2p.MsgReadWriter, code uint64) error {
- errc := make(chan error, 2)
- go func() { errc <- p2p.SendItems(rw, code) }()
- go func() { errc <- p2p.ExpectMsg(rw, code, struct{}{}) }()
- for i := 0; i < 2; i++ {
- if err := <-errc; err != nil {
- return err
- }
- }
- return nil
-}
-
-func (t *testService) RunTest(p *p2p.Peer, rw p2p.MsgReadWriter) error {
- peer := t.peer(p.ID())
-
- // perform three handshakes with three different message codes,
- // used to test message sending and filtering
- if err := t.handshake(rw, 2); err != nil {
- return err
- }
- if err := t.handshake(rw, 1); err != nil {
- return err
- }
- if err := t.handshake(rw, 0); err != nil {
- return err
- }
-
- // close the testReady channel so that other protocols can run
- close(peer.testReady)
-
- // track the peer
- atomic.AddInt64(&t.peerCount, 1)
- defer atomic.AddInt64(&t.peerCount, -1)
-
- // block until the peer is dropped
- for {
- _, err := rw.ReadMsg()
- if err != nil {
- return err
- }
- }
-}
-
-func (t *testService) RunDum(p *p2p.Peer, rw p2p.MsgReadWriter) error {
- peer := t.peer(p.ID())
-
- // wait for the test protocol to perform its handshake
- <-peer.testReady
-
- // perform a handshake
- if err := t.handshake(rw, 0); err != nil {
- return err
- }
-
- // close the dumReady channel so that other protocols can run
- close(peer.dumReady)
-
- // block until the peer is dropped
- for {
- _, err := rw.ReadMsg()
- if err != nil {
- return err
- }
- }
-}
-func (t *testService) RunPrb(p *p2p.Peer, rw p2p.MsgReadWriter) error {
- peer := t.peer(p.ID())
-
- // wait for the dum protocol to perform its handshake
- <-peer.dumReady
-
- // perform a handshake
- if err := t.handshake(rw, 0); err != nil {
- return err
- }
-
- // block until the peer is dropped
- for {
- _, err := rw.ReadMsg()
- if err != nil {
- return err
- }
- }
-}
-
-func (t *testService) Snapshot() ([]byte, error) {
- return t.state.Load().([]byte), nil
-}
-
-// TestAPI provides a test API to:
-// * get the peer count
-// * get and set an arbitrary state byte slice
-// * get and increment a counter
-// * subscribe to counter increment events
-type TestAPI struct {
- state *atomic.Value
- peerCount *int64
- counter int64
- feed event.Feed
-}
-
-func (t *TestAPI) PeerCount() int64 {
- return atomic.LoadInt64(t.peerCount)
-}
-
-func (t *TestAPI) Get() int64 {
- return atomic.LoadInt64(&t.counter)
-}
-
-func (t *TestAPI) Add(delta int64) {
- atomic.AddInt64(&t.counter, delta)
- t.feed.Send(delta)
-}
-
-func (t *TestAPI) GetState() []byte {
- return t.state.Load().([]byte)
-}
-
-func (t *TestAPI) SetState(state []byte) {
- t.state.Store(state)
-}
-
-func (t *TestAPI) Events(ctx context.Context) (*rpc.Subscription, error) {
- notifier, supported := rpc.NotifierFromContext(ctx)
- if !supported {
- return nil, rpc.ErrNotificationsUnsupported
- }
-
- rpcSub := notifier.CreateSubscription()
-
- go func() {
- events := make(chan int64)
- sub := t.feed.Subscribe(events)
- defer sub.Unsubscribe()
-
- for {
- select {
- case event := <-events:
- notifier.Notify(rpcSub.ID, event)
- case <-sub.Err():
- return
- case <-rpcSub.Err():
- return
- case <-notifier.Closed():
- return
- }
- }
- }()
-
- return rpcSub, nil
-}
-
-var testServices = adapters.LifecycleConstructors{
- "test": newTestService,
-}
-
-func testHTTPServer(t *testing.T) (*Network, *httptest.Server) {
- t.Helper()
- adapter := adapters.NewSimAdapter(testServices)
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "test",
- })
- return network, httptest.NewServer(NewServer(network))
-}
-
-// TestHTTPNetwork tests interacting with a simulation network using the HTTP
-// API
-func TestHTTPNetwork(t *testing.T) {
- // start the server
- network, s := testHTTPServer(t)
- defer s.Close()
-
- // subscribe to events so we can check them later
- client := NewClient(s.URL)
- events := make(chan *Event, 100)
- var opts SubscribeOpts
- sub, err := client.SubscribeNetwork(events, opts)
- if err != nil {
- t.Fatalf("error subscribing to network events: %s", err)
- }
- defer sub.Unsubscribe()
-
- // check we can retrieve details about the network
- gotNetwork, err := client.GetNetwork()
- if err != nil {
- t.Fatalf("error getting network: %s", err)
- }
- if gotNetwork.ID != network.ID {
- t.Fatalf("expected network to have ID %q, got %q", network.ID, gotNetwork.ID)
- }
-
- // start a simulation network
- nodeIDs := startTestNetwork(t, client)
-
- // check we got all the events
- x := &expectEvents{t, events, sub}
- x.expect(
- x.nodeEvent(nodeIDs[0], false),
- x.nodeEvent(nodeIDs[1], false),
- x.nodeEvent(nodeIDs[0], true),
- x.nodeEvent(nodeIDs[1], true),
- x.connEvent(nodeIDs[0], nodeIDs[1], false),
- x.connEvent(nodeIDs[0], nodeIDs[1], true),
- )
-
- // reconnect the stream and check we get the current nodes and conns
- events = make(chan *Event, 100)
- opts.Current = true
- sub, err = client.SubscribeNetwork(events, opts)
- if err != nil {
- t.Fatalf("error subscribing to network events: %s", err)
- }
- defer sub.Unsubscribe()
- x = &expectEvents{t, events, sub}
- x.expect(
- x.nodeEvent(nodeIDs[0], true),
- x.nodeEvent(nodeIDs[1], true),
- x.connEvent(nodeIDs[0], nodeIDs[1], true),
- )
-}
-
-func startTestNetwork(t *testing.T, client *Client) []string {
- // create two nodes
- nodeCount := 2
- nodeIDs := make([]string, nodeCount)
- for i := 0; i < nodeCount; i++ {
- config := adapters.RandomNodeConfig()
- node, err := client.CreateNode(config)
- if err != nil {
- t.Fatalf("error creating node: %s", err)
- }
- nodeIDs[i] = node.ID
- }
-
- // check both nodes exist
- nodes, err := client.GetNodes()
- if err != nil {
- t.Fatalf("error getting nodes: %s", err)
- }
- if len(nodes) != nodeCount {
- t.Fatalf("expected %d nodes, got %d", nodeCount, len(nodes))
- }
- for i, nodeID := range nodeIDs {
- if nodes[i].ID != nodeID {
- t.Fatalf("expected node %d to have ID %q, got %q", i, nodeID, nodes[i].ID)
- }
- node, err := client.GetNode(nodeID)
- if err != nil {
- t.Fatalf("error getting node %d: %s", i, err)
- }
- if node.ID != nodeID {
- t.Fatalf("expected node %d to have ID %q, got %q", i, nodeID, node.ID)
- }
- }
-
- // start both nodes
- for _, nodeID := range nodeIDs {
- if err := client.StartNode(nodeID); err != nil {
- t.Fatalf("error starting node %q: %s", nodeID, err)
- }
- }
-
- // connect the nodes
- for i := 0; i < nodeCount-1; i++ {
- peerId := i + 1
- if i == nodeCount-1 {
- peerId = 0
- }
- if err := client.ConnectNode(nodeIDs[i], nodeIDs[peerId]); err != nil {
- t.Fatalf("error connecting nodes: %s", err)
- }
- }
-
- return nodeIDs
-}
-
-type expectEvents struct {
- *testing.T
-
- events chan *Event
- sub event.Subscription
-}
-
-func (t *expectEvents) nodeEvent(id string, up bool) *Event {
- config := &adapters.NodeConfig{ID: enode.HexID(id)}
- return &Event{Type: EventTypeNode, Node: newNode(nil, config, up)}
-}
-
-func (t *expectEvents) connEvent(one, other string, up bool) *Event {
- return &Event{
- Type: EventTypeConn,
- Conn: &Conn{
- One: enode.HexID(one),
- Other: enode.HexID(other),
- Up: up,
- },
- }
-}
-
-func (t *expectEvents) expectMsgs(expected map[MsgFilter]int) {
- actual := make(map[MsgFilter]int)
- timeout := time.After(10 * time.Second)
-loop:
- for {
- select {
- case event := <-t.events:
- t.Logf("received %s event: %v", event.Type, event)
-
- if event.Type != EventTypeMsg || event.Msg.Received {
- continue loop
- }
- if event.Msg == nil {
- t.Fatal("expected event.Msg to be set")
- }
- filter := MsgFilter{
- Proto: event.Msg.Protocol,
- Code: int64(event.Msg.Code),
- }
- actual[filter]++
- if actual[filter] > expected[filter] {
- t.Fatalf("received too many msgs for filter: %v", filter)
- }
- if reflect.DeepEqual(actual, expected) {
- return
- }
-
- case err := <-t.sub.Err():
- t.Fatalf("network stream closed unexpectedly: %s", err)
-
- case <-timeout:
- t.Fatal("timed out waiting for expected events")
- }
- }
-}
-
-func (t *expectEvents) expect(events ...*Event) {
- t.Helper()
- timeout := time.After(10 * time.Second)
- i := 0
- for {
- select {
- case event := <-t.events:
- t.Logf("received %s event: %v", event.Type, event)
-
- expected := events[i]
- if event.Type != expected.Type {
- t.Fatalf("expected event %d to have type %q, got %q", i, expected.Type, event.Type)
- }
-
- switch expected.Type {
- case EventTypeNode:
- if event.Node == nil {
- t.Fatal("expected event.Node to be set")
- }
- if event.Node.ID() != expected.Node.ID() {
- t.Fatalf("expected node event %d to have id %q, got %q", i, expected.Node.ID().TerminalString(), event.Node.ID().TerminalString())
- }
- if event.Node.Up() != expected.Node.Up() {
- t.Fatalf("expected node event %d to have up=%t, got up=%t", i, expected.Node.Up(), event.Node.Up())
- }
-
- case EventTypeConn:
- if event.Conn == nil {
- t.Fatal("expected event.Conn to be set")
- }
- if event.Conn.One != expected.Conn.One {
- t.Fatalf("expected conn event %d to have one=%q, got one=%q", i, expected.Conn.One.TerminalString(), event.Conn.One.TerminalString())
- }
- if event.Conn.Other != expected.Conn.Other {
- t.Fatalf("expected conn event %d to have other=%q, got other=%q", i, expected.Conn.Other.TerminalString(), event.Conn.Other.TerminalString())
- }
- if event.Conn.Up != expected.Conn.Up {
- t.Fatalf("expected conn event %d to have up=%t, got up=%t", i, expected.Conn.Up, event.Conn.Up)
- }
- }
-
- i++
- if i == len(events) {
- return
- }
-
- case err := <-t.sub.Err():
- t.Fatalf("network stream closed unexpectedly: %s", err)
-
- case <-timeout:
- t.Fatal("timed out waiting for expected events")
- }
- }
-}
-
-// TestHTTPNodeRPC tests calling RPC methods on nodes via the HTTP API
-func TestHTTPNodeRPC(t *testing.T) {
- // start the server
- _, s := testHTTPServer(t)
- defer s.Close()
-
- // start a node in the network
- client := NewClient(s.URL)
-
- config := adapters.RandomNodeConfig()
- node, err := client.CreateNode(config)
- if err != nil {
- t.Fatalf("error creating node: %s", err)
- }
- if err := client.StartNode(node.ID); err != nil {
- t.Fatalf("error starting node: %s", err)
- }
-
- // create two RPC clients
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
- rpcClient1, err := client.RPCClient(ctx, node.ID)
- if err != nil {
- t.Fatalf("error getting node RPC client: %s", err)
- }
- rpcClient2, err := client.RPCClient(ctx, node.ID)
- if err != nil {
- t.Fatalf("error getting node RPC client: %s", err)
- }
-
- // subscribe to events using client 1
- events := make(chan int64, 1)
- sub, err := rpcClient1.Subscribe(ctx, "test", events, "events")
- if err != nil {
- t.Fatalf("error subscribing to events: %s", err)
- }
- defer sub.Unsubscribe()
-
- // call some RPC methods using client 2
- if err := rpcClient2.CallContext(ctx, nil, "test_add", 10); err != nil {
- t.Fatalf("error calling RPC method: %s", err)
- }
- var result int64
- if err := rpcClient2.CallContext(ctx, &result, "test_get"); err != nil {
- t.Fatalf("error calling RPC method: %s", err)
- }
- if result != 10 {
- t.Fatalf("expected result to be 10, got %d", result)
- }
-
- // check we got an event from client 1
- select {
- case event := <-events:
- if event != 10 {
- t.Fatalf("expected event to be 10, got %d", event)
- }
- case <-ctx.Done():
- t.Fatal(ctx.Err())
- }
-}
-
-// TestHTTPSnapshot tests creating and loading network snapshots
-func TestHTTPSnapshot(t *testing.T) {
- // start the server
- network, s := testHTTPServer(t)
- defer s.Close()
-
- var eventsDone = make(chan struct{}, 1)
- count := 1
- eventsDoneChan := make(chan *Event)
- eventSub := network.Events().Subscribe(eventsDoneChan)
- go func() {
- defer eventSub.Unsubscribe()
- for event := range eventsDoneChan {
- if event.Type == EventTypeConn && !event.Control {
- count--
- if count == 0 {
- eventsDone <- struct{}{}
- return
- }
- }
- }
- }()
-
- // create a two-node network
- client := NewClient(s.URL)
- nodeCount := 2
- nodes := make([]*p2p.NodeInfo, nodeCount)
- for i := 0; i < nodeCount; i++ {
- config := adapters.RandomNodeConfig()
- node, err := client.CreateNode(config)
- if err != nil {
- t.Fatalf("error creating node: %s", err)
- }
- if err := client.StartNode(node.ID); err != nil {
- t.Fatalf("error starting node: %s", err)
- }
- nodes[i] = node
- }
- if err := client.ConnectNode(nodes[0].ID, nodes[1].ID); err != nil {
- t.Fatalf("error connecting nodes: %s", err)
- }
-
- // store some state in the test services
- states := make([]string, nodeCount)
- for i, node := range nodes {
- rpc, err := client.RPCClient(context.Background(), node.ID)
- if err != nil {
- t.Fatalf("error getting RPC client: %s", err)
- }
- defer rpc.Close()
- state := fmt.Sprintf("%x", rand.Int())
- if err := rpc.Call(nil, "test_setState", []byte(state)); err != nil {
- t.Fatalf("error setting service state: %s", err)
- }
- states[i] = state
- }
- <-eventsDone
- // create a snapshot
- snap, err := client.CreateSnapshot()
- if err != nil {
- t.Fatalf("error creating snapshot: %s", err)
- }
- for i, state := range states {
- gotState := snap.Nodes[i].Snapshots["test"]
- if string(gotState) != state {
- t.Fatalf("expected snapshot state %q, got %q", state, gotState)
- }
- }
-
- // create another network
- network2, s := testHTTPServer(t)
- defer s.Close()
- client = NewClient(s.URL)
- count = 1
- eventSub = network2.Events().Subscribe(eventsDoneChan)
- go func() {
- defer eventSub.Unsubscribe()
- for event := range eventsDoneChan {
- if event.Type == EventTypeConn && !event.Control {
- count--
- if count == 0 {
- eventsDone <- struct{}{}
- return
- }
- }
- }
- }()
-
- // subscribe to events so we can check them later
- events := make(chan *Event, 100)
- var opts SubscribeOpts
- sub, err := client.SubscribeNetwork(events, opts)
- if err != nil {
- t.Fatalf("error subscribing to network events: %s", err)
- }
- defer sub.Unsubscribe()
-
- // load the snapshot
- if err := client.LoadSnapshot(snap); err != nil {
- t.Fatalf("error loading snapshot: %s", err)
- }
- <-eventsDone
-
- // check the nodes and connection exists
- net, err := client.GetNetwork()
- if err != nil {
- t.Fatalf("error getting network: %s", err)
- }
- if len(net.Nodes) != nodeCount {
- t.Fatalf("expected network to have %d nodes, got %d", nodeCount, len(net.Nodes))
- }
- for i, node := range nodes {
- id := net.Nodes[i].ID().String()
- if id != node.ID {
- t.Fatalf("expected node %d to have ID %s, got %s", i, node.ID, id)
- }
- }
- if len(net.Conns) != 1 {
- t.Fatalf("expected network to have 1 connection, got %d", len(net.Conns))
- }
- conn := net.Conns[0]
- if conn.One.String() != nodes[0].ID {
- t.Fatalf("expected connection to have one=%q, got one=%q", nodes[0].ID, conn.One)
- }
- if conn.Other.String() != nodes[1].ID {
- t.Fatalf("expected connection to have other=%q, got other=%q", nodes[1].ID, conn.Other)
- }
- if !conn.Up {
- t.Fatal("should be up")
- }
-
- // check the node states were restored
- for i, node := range nodes {
- rpc, err := client.RPCClient(context.Background(), node.ID)
- if err != nil {
- t.Fatalf("error getting RPC client: %s", err)
- }
- defer rpc.Close()
- var state []byte
- if err := rpc.Call(&state, "test_getState"); err != nil {
- t.Fatalf("error getting service state: %s", err)
- }
- if string(state) != states[i] {
- t.Fatalf("expected snapshot state %q, got %q", states[i], state)
- }
- }
-
- // check we got all the events
- x := &expectEvents{t, events, sub}
- x.expect(
- x.nodeEvent(nodes[0].ID, false),
- x.nodeEvent(nodes[0].ID, true),
- x.nodeEvent(nodes[1].ID, false),
- x.nodeEvent(nodes[1].ID, true),
- x.connEvent(nodes[0].ID, nodes[1].ID, false),
- x.connEvent(nodes[0].ID, nodes[1].ID, true),
- )
-}
-
-// TestMsgFilterPassMultiple tests streaming message events using a filter
-// with multiple protocols
-func TestMsgFilterPassMultiple(t *testing.T) {
- // start the server
- _, s := testHTTPServer(t)
- defer s.Close()
-
- // subscribe to events with a message filter
- client := NewClient(s.URL)
- events := make(chan *Event, 10)
- opts := SubscribeOpts{
- Filter: "prb:0-test:0",
- }
- sub, err := client.SubscribeNetwork(events, opts)
- if err != nil {
- t.Fatalf("error subscribing to network events: %s", err)
- }
- defer sub.Unsubscribe()
-
- // start a simulation network
- startTestNetwork(t, client)
-
- // check we got the expected events
- x := &expectEvents{t, events, sub}
- x.expectMsgs(map[MsgFilter]int{
- {"test", 0}: 2,
- {"prb", 0}: 2,
- })
-}
-
-// TestMsgFilterPassWildcard tests streaming message events using a filter
-// with a code wildcard
-func TestMsgFilterPassWildcard(t *testing.T) {
- // start the server
- _, s := testHTTPServer(t)
- defer s.Close()
-
- // subscribe to events with a message filter
- client := NewClient(s.URL)
- events := make(chan *Event, 10)
- opts := SubscribeOpts{
- Filter: "prb:0,2-test:*",
- }
- sub, err := client.SubscribeNetwork(events, opts)
- if err != nil {
- t.Fatalf("error subscribing to network events: %s", err)
- }
- defer sub.Unsubscribe()
-
- // start a simulation network
- startTestNetwork(t, client)
-
- // check we got the expected events
- x := &expectEvents{t, events, sub}
- x.expectMsgs(map[MsgFilter]int{
- {"test", 2}: 2,
- {"test", 1}: 2,
- {"test", 0}: 2,
- {"prb", 0}: 2,
- })
-}
-
-// TestMsgFilterPassSingle tests streaming message events using a filter
-// with a single protocol and code
-func TestMsgFilterPassSingle(t *testing.T) {
- // start the server
- _, s := testHTTPServer(t)
- defer s.Close()
-
- // subscribe to events with a message filter
- client := NewClient(s.URL)
- events := make(chan *Event, 10)
- opts := SubscribeOpts{
- Filter: "dum:0",
- }
- sub, err := client.SubscribeNetwork(events, opts)
- if err != nil {
- t.Fatalf("error subscribing to network events: %s", err)
- }
- defer sub.Unsubscribe()
-
- // start a simulation network
- startTestNetwork(t, client)
-
- // check we got the expected events
- x := &expectEvents{t, events, sub}
- x.expectMsgs(map[MsgFilter]int{
- {"dum", 0}: 2,
- })
-}
-
-// TestMsgFilterPassSingle tests streaming message events using an invalid
-// filter
-func TestMsgFilterFailBadParams(t *testing.T) {
- // start the server
- _, s := testHTTPServer(t)
- defer s.Close()
-
- client := NewClient(s.URL)
- events := make(chan *Event, 10)
- opts := SubscribeOpts{
- Filter: "foo:",
- }
- _, err := client.SubscribeNetwork(events, opts)
- if err == nil {
- t.Fatalf("expected event subscription to fail but succeeded!")
- }
-
- opts.Filter = "bzz:aa"
- _, err = client.SubscribeNetwork(events, opts)
- if err == nil {
- t.Fatalf("expected event subscription to fail but succeeded!")
- }
-
- opts.Filter = "invalid"
- _, err = client.SubscribeNetwork(events, opts)
- if err == nil {
- t.Fatalf("expected event subscription to fail but succeeded!")
- }
-}
diff --git a/p2p/simulations/mocker.go b/p2p/simulations/mocker.go
deleted file mode 100644
index 0dc04e65f9..0000000000
--- a/p2p/simulations/mocker.go
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package simulations simulates p2p networks.
-// A mocker simulates starting and stopping real nodes in a network.
-package simulations
-
-import (
- "fmt"
- "math/rand"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
-)
-
-// a map of mocker names to its function
-var mockerList = map[string]func(net *Network, quit chan struct{}, nodeCount int){
- "startStop": startStop,
- "probabilistic": probabilistic,
- "boot": boot,
-}
-
-// LookupMocker looks a mocker by its name, returns the mockerFn
-func LookupMocker(mockerType string) func(net *Network, quit chan struct{}, nodeCount int) {
- return mockerList[mockerType]
-}
-
-// GetMockerList returns a list of mockers (keys of the map)
-// Useful for frontend to build available mocker selection
-func GetMockerList() []string {
- list := make([]string, 0, len(mockerList))
- for k := range mockerList {
- list = append(list, k)
- }
- return list
-}
-
-// The boot mockerFn only connects the node in a ring and doesn't do anything else
-func boot(net *Network, quit chan struct{}, nodeCount int) {
- _, err := connectNodesInRing(net, nodeCount)
- if err != nil {
- panic("Could not startup node network for mocker")
- }
-}
-
-// The startStop mockerFn stops and starts nodes in a defined period (ticker)
-func startStop(net *Network, quit chan struct{}, nodeCount int) {
- nodes, err := connectNodesInRing(net, nodeCount)
- if err != nil {
- panic("Could not startup node network for mocker")
- }
- tick := time.NewTicker(10 * time.Second)
- defer tick.Stop()
- for {
- select {
- case <-quit:
- log.Info("Terminating simulation loop")
- return
- case <-tick.C:
- id := nodes[rand.Intn(len(nodes))]
- log.Info("stopping node", "id", id)
- if err := net.Stop(id); err != nil {
- log.Error("error stopping node", "id", id, "err", err)
- return
- }
-
- select {
- case <-quit:
- log.Info("Terminating simulation loop")
- return
- case <-time.After(3 * time.Second):
- }
-
- log.Debug("starting node", "id", id)
- if err := net.Start(id); err != nil {
- log.Error("error starting node", "id", id, "err", err)
- return
- }
- }
- }
-}
-
-// The probabilistic mocker func has a more probabilistic pattern
-// (the implementation could probably be improved):
-// nodes are connected in a ring, then a varying number of random nodes is selected,
-// mocker then stops and starts them in random intervals, and continues the loop
-func probabilistic(net *Network, quit chan struct{}, nodeCount int) {
- nodes, err := connectNodesInRing(net, nodeCount)
- if err != nil {
- select {
- case <-quit:
- //error may be due to abortion of mocking; so the quit channel is closed
- return
- default:
- panic("Could not startup node network for mocker")
- }
- }
- for {
- select {
- case <-quit:
- log.Info("Terminating simulation loop")
- return
- default:
- }
- var lowid, highid int
- var wg sync.WaitGroup
- randWait := time.Duration(rand.Intn(5000)+1000) * time.Millisecond
- rand1 := rand.Intn(nodeCount - 1)
- rand2 := rand.Intn(nodeCount - 1)
- if rand1 <= rand2 {
- lowid = rand1
- highid = rand2
- } else if rand1 > rand2 {
- highid = rand1
- lowid = rand2
- }
- var steps = highid - lowid
- wg.Add(steps)
- for i := lowid; i < highid; i++ {
- select {
- case <-quit:
- log.Info("Terminating simulation loop")
- return
- case <-time.After(randWait):
- }
- log.Debug(fmt.Sprintf("node %v shutting down", nodes[i]))
- err := net.Stop(nodes[i])
- if err != nil {
- log.Error("Error stopping node", "node", nodes[i])
- wg.Done()
- continue
- }
- go func(id enode.ID) {
- time.Sleep(randWait)
- err := net.Start(id)
- if err != nil {
- log.Error("Error starting node", "node", id)
- }
- wg.Done()
- }(nodes[i])
- }
- wg.Wait()
- }
-}
-
-// connect nodeCount number of nodes in a ring
-func connectNodesInRing(net *Network, nodeCount int) ([]enode.ID, error) {
- ids := make([]enode.ID, nodeCount)
- for i := 0; i < nodeCount; i++ {
- conf := adapters.RandomNodeConfig()
- node, err := net.NewNodeWithConfig(conf)
- if err != nil {
- log.Error("Error creating a node!", "err", err)
- return nil, err
- }
- ids[i] = node.ID()
- }
-
- for _, id := range ids {
- if err := net.Start(id); err != nil {
- log.Error("Error starting a node!", "err", err)
- return nil, err
- }
- log.Debug(fmt.Sprintf("node %v starting up", id))
- }
- for i, id := range ids {
- peerID := ids[(i+1)%len(ids)]
- if err := net.Connect(id, peerID); err != nil {
- log.Error("Error connecting a node to a peer!", "err", err)
- return nil, err
- }
- }
-
- return ids, nil
-}
diff --git a/p2p/simulations/mocker_test.go b/p2p/simulations/mocker_test.go
deleted file mode 100644
index 0112ee5cfd..0000000000
--- a/p2p/simulations/mocker_test.go
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package simulations simulates p2p networks.
-// A mocker simulates starting and stopping real nodes in a network.
-package simulations
-
-import (
- "encoding/json"
- "net/http"
- "net/url"
- "strconv"
- "sync"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-func TestMocker(t *testing.T) {
- //start the simulation HTTP server
- _, s := testHTTPServer(t)
- defer s.Close()
-
- //create a client
- client := NewClient(s.URL)
-
- //start the network
- err := client.StartNetwork()
- if err != nil {
- t.Fatalf("Could not start test network: %s", err)
- }
- //stop the network to terminate
- defer func() {
- err = client.StopNetwork()
- if err != nil {
- t.Fatalf("Could not stop test network: %s", err)
- }
- }()
-
- //get the list of available mocker types
- resp, err := http.Get(s.URL + "/mocker")
- if err != nil {
- t.Fatalf("Could not get mocker list: %s", err)
- }
- defer resp.Body.Close()
-
- if resp.StatusCode != 200 {
- t.Fatalf("Invalid Status Code received, expected 200, got %d", resp.StatusCode)
- }
-
- //check the list is at least 1 in size
- var mockerlist []string
- err = json.NewDecoder(resp.Body).Decode(&mockerlist)
- if err != nil {
- t.Fatalf("Error decoding JSON mockerlist: %s", err)
- }
-
- if len(mockerlist) < 1 {
- t.Fatalf("No mockers available")
- }
-
- nodeCount := 10
- var wg sync.WaitGroup
-
- events := make(chan *Event, 10)
- var opts SubscribeOpts
- sub, err := client.SubscribeNetwork(events, opts)
- defer sub.Unsubscribe()
-
- // wait until all nodes are started and connected
- // store every node up event in a map (value is irrelevant, mimic Set datatype)
- nodemap := make(map[enode.ID]bool)
- nodesComplete := false
- connCount := 0
- wg.Add(1)
- go func() {
- defer wg.Done()
-
- for connCount < (nodeCount-1)*2 {
- select {
- case event := <-events:
- if isNodeUp(event) {
- //add the correspondent node ID to the map
- nodemap[event.Node.Config.ID] = true
- //this means all nodes got a nodeUp event, so we can continue the test
- if len(nodemap) == nodeCount {
- nodesComplete = true
- }
- } else if event.Conn != nil && nodesComplete {
- connCount += 1
- }
- case <-time.After(30 * time.Second):
- t.Errorf("Timeout waiting for nodes being started up!")
- return
- }
- }
- }()
-
- //take the last element of the mockerlist as the default mocker-type to ensure one is enabled
- mockertype := mockerlist[len(mockerlist)-1]
- //still, use hardcoded "probabilistic" one if available ;)
- for _, m := range mockerlist {
- if m == "probabilistic" {
- mockertype = m
- break
- }
- }
- //start the mocker with nodeCount number of nodes
- resp, err = http.PostForm(s.URL+"/mocker/start", url.Values{"mocker-type": {mockertype}, "node-count": {strconv.Itoa(nodeCount)}})
- if err != nil {
- t.Fatalf("Could not start mocker: %s", err)
- }
- resp.Body.Close()
- if resp.StatusCode != 200 {
- t.Fatalf("Invalid Status Code received for starting mocker, expected 200, got %d", resp.StatusCode)
- }
-
- wg.Wait()
-
- //check there are nodeCount number of nodes in the network
- nodesInfo, err := client.GetNodes()
- if err != nil {
- t.Fatalf("Could not get nodes list: %s", err)
- }
-
- if len(nodesInfo) != nodeCount {
- t.Fatalf("Expected %d number of nodes, got: %d", nodeCount, len(nodesInfo))
- }
-
- //stop the mocker
- resp, err = http.Post(s.URL+"/mocker/stop", "", nil)
- if err != nil {
- t.Fatalf("Could not stop mocker: %s", err)
- }
- resp.Body.Close()
- if resp.StatusCode != 200 {
- t.Fatalf("Invalid Status Code received for stopping mocker, expected 200, got %d", resp.StatusCode)
- }
-
- //reset the network
- resp, err = http.Post(s.URL+"/reset", "", nil)
- if err != nil {
- t.Fatalf("Could not reset network: %s", err)
- }
- resp.Body.Close()
-
- //now the number of nodes in the network should be zero
- nodesInfo, err = client.GetNodes()
- if err != nil {
- t.Fatalf("Could not get nodes list: %s", err)
- }
-
- if len(nodesInfo) != 0 {
- t.Fatalf("Expected empty list of nodes, got: %d", len(nodesInfo))
- }
-}
-
-func isNodeUp(event *Event) bool {
- return event.Node != nil && event.Node.Up()
-}
diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go
deleted file mode 100644
index 4735e5cfa6..0000000000
--- a/p2p/simulations/network.go
+++ /dev/null
@@ -1,1090 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package simulations
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "math/rand"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
-)
-
-var DialBanTimeout = 200 * time.Millisecond
-
-// NetworkConfig defines configuration options for starting a Network
-type NetworkConfig struct {
- ID string `json:"id"`
- DefaultService string `json:"default_service,omitempty"`
-}
-
-// Network models a p2p simulation network which consists of a collection of
-// simulated nodes and the connections which exist between them.
-//
-// The Network has a single NodeAdapter which is responsible for actually
-// starting nodes and connecting them together.
-//
-// The Network emits events when nodes are started and stopped, when they are
-// connected and disconnected, and also when messages are sent between nodes.
-type Network struct {
- NetworkConfig
-
- Nodes []*Node `json:"nodes"`
- nodeMap map[enode.ID]int
-
- // Maps a node property string to node indexes of all nodes that hold this property
- propertyMap map[string][]int
-
- Conns []*Conn `json:"conns"`
- connMap map[string]int
-
- nodeAdapter adapters.NodeAdapter
- events event.Feed
- lock sync.RWMutex
- quitc chan struct{}
-}
-
-// NewNetwork returns a Network which uses the given NodeAdapter and NetworkConfig
-func NewNetwork(nodeAdapter adapters.NodeAdapter, conf *NetworkConfig) *Network {
- return &Network{
- NetworkConfig: *conf,
- nodeAdapter: nodeAdapter,
- nodeMap: make(map[enode.ID]int),
- propertyMap: make(map[string][]int),
- connMap: make(map[string]int),
- quitc: make(chan struct{}),
- }
-}
-
-// Events returns the output event feed of the Network.
-func (net *Network) Events() *event.Feed {
- return &net.events
-}
-
-// NewNodeWithConfig adds a new node to the network with the given config,
-// returning an error if a node with the same ID or name already exists
-func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- if conf.Reachable == nil {
- conf.Reachable = func(otherID enode.ID) bool {
- _, err := net.InitConn(conf.ID, otherID)
- if err != nil && bytes.Compare(conf.ID.Bytes(), otherID.Bytes()) < 0 {
- return false
- }
- return true
- }
- }
-
- // check the node doesn't already exist
- if node := net.getNode(conf.ID); node != nil {
- return nil, fmt.Errorf("node with ID %q already exists", conf.ID)
- }
- if node := net.getNodeByName(conf.Name); node != nil {
- return nil, fmt.Errorf("node with name %q already exists", conf.Name)
- }
-
- // if no services are configured, use the default service
- if len(conf.Lifecycles) == 0 {
- conf.Lifecycles = []string{net.DefaultService}
- }
-
- // use the NodeAdapter to create the node
- adapterNode, err := net.nodeAdapter.NewNode(conf)
- if err != nil {
- return nil, err
- }
- node := newNode(adapterNode, conf, false)
- log.Trace("Node created", "id", conf.ID)
-
- nodeIndex := len(net.Nodes)
- net.nodeMap[conf.ID] = nodeIndex
- net.Nodes = append(net.Nodes, node)
-
- // Register any node properties with the network-level propertyMap
- for _, property := range conf.Properties {
- net.propertyMap[property] = append(net.propertyMap[property], nodeIndex)
- }
-
- // emit a "control" event
- net.events.Send(ControlEvent(node))
-
- return node, nil
-}
-
-// Config returns the network configuration
-func (net *Network) Config() *NetworkConfig {
- return &net.NetworkConfig
-}
-
-// StartAll starts all nodes in the network
-func (net *Network) StartAll() error {
- for _, node := range net.Nodes {
- if node.Up() {
- continue
- }
- if err := net.Start(node.ID()); err != nil {
- return err
- }
- }
- return nil
-}
-
-// StopAll stops all nodes in the network
-func (net *Network) StopAll() error {
- for _, node := range net.Nodes {
- if !node.Up() {
- continue
- }
- if err := net.Stop(node.ID()); err != nil {
- return err
- }
- }
- return nil
-}
-
-// Start starts the node with the given ID
-func (net *Network) Start(id enode.ID) error {
- return net.startWithSnapshots(id, nil)
-}
-
-// startWithSnapshots starts the node with the given ID using the give
-// snapshots
-func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) error {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- node := net.getNode(id)
- if node == nil {
- return fmt.Errorf("node %v does not exist", id)
- }
- if node.Up() {
- return fmt.Errorf("node %v already up", id)
- }
- log.Trace("Starting node", "id", id, "adapter", net.nodeAdapter.Name())
- if err := node.Start(snapshots); err != nil {
- log.Warn("Node startup failed", "id", id, "err", err)
- return err
- }
- node.SetUp(true)
- log.Info("Started node", "id", id)
- ev := NewEvent(node)
- net.events.Send(ev)
-
- // subscribe to peer events
- client, err := node.Client()
- if err != nil {
- return fmt.Errorf("error getting rpc client for node %v: %s", id, err)
- }
- events := make(chan *p2p.PeerEvent)
- sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents")
- if err != nil {
- return fmt.Errorf("error getting peer events for node %v: %s", id, err)
- }
- go net.watchPeerEvents(id, events, sub)
- return nil
-}
-
-// watchPeerEvents reads peer events from the given channel and emits
-// corresponding network events
-func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub event.Subscription) {
- defer func() {
- sub.Unsubscribe()
-
- // assume the node is now down
- net.lock.Lock()
- defer net.lock.Unlock()
-
- node := net.getNode(id)
- if node == nil {
- return
- }
- node.SetUp(false)
- ev := NewEvent(node)
- net.events.Send(ev)
- }()
- for {
- select {
- case event, ok := <-events:
- if !ok {
- return
- }
- peer := event.Peer
- switch event.Type {
- case p2p.PeerEventTypeAdd:
- net.DidConnect(id, peer)
-
- case p2p.PeerEventTypeDrop:
- net.DidDisconnect(id, peer)
-
- case p2p.PeerEventTypeMsgSend:
- net.DidSend(id, peer, event.Protocol, *event.MsgCode)
-
- case p2p.PeerEventTypeMsgRecv:
- net.DidReceive(peer, id, event.Protocol, *event.MsgCode)
- }
-
- case err := <-sub.Err():
- if err != nil {
- log.Error("Error in peer event subscription", "id", id, "err", err)
- }
- return
- }
- }
-}
-
-// Stop stops the node with the given ID
-func (net *Network) Stop(id enode.ID) error {
- // IMPORTANT: node.Stop() must NOT be called under net.lock as
- // node.Reachable() closure has a reference to the network and
- // calls net.InitConn() what also locks the network. => DEADLOCK
- // That holds until the following ticket is not resolved:
-
- var err error
-
- node, err := func() (*Node, error) {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- node := net.getNode(id)
- if node == nil {
- return nil, fmt.Errorf("node %v does not exist", id)
- }
- if !node.Up() {
- return nil, fmt.Errorf("node %v already down", id)
- }
- node.SetUp(false)
- return node, nil
- }()
- if err != nil {
- return err
- }
-
- err = node.Stop() // must be called without net.lock
-
- net.lock.Lock()
- defer net.lock.Unlock()
-
- if err != nil {
- node.SetUp(true)
- return err
- }
- log.Info("Stopped node", "id", id, "err", err)
- ev := ControlEvent(node)
- net.events.Send(ev)
- return nil
-}
-
-// Connect connects two nodes together by calling the "admin_addPeer" RPC
-// method on the "one" node so that it connects to the "other" node
-func (net *Network) Connect(oneID, otherID enode.ID) error {
- net.lock.Lock()
- defer net.lock.Unlock()
- return net.connect(oneID, otherID)
-}
-
-func (net *Network) connect(oneID, otherID enode.ID) error {
- log.Debug("Connecting nodes with addPeer", "id", oneID, "other", otherID)
- conn, err := net.initConn(oneID, otherID)
- if err != nil {
- return err
- }
- client, err := conn.one.Client()
- if err != nil {
- return err
- }
- net.events.Send(ControlEvent(conn))
- return client.Call(nil, "admin_addPeer", string(conn.other.Addr()))
-}
-
-// Disconnect disconnects two nodes by calling the "admin_removePeer" RPC
-// method on the "one" node so that it disconnects from the "other" node
-func (net *Network) Disconnect(oneID, otherID enode.ID) error {
- conn := net.GetConn(oneID, otherID)
- if conn == nil {
- return fmt.Errorf("connection between %v and %v does not exist", oneID, otherID)
- }
- if !conn.Up {
- return fmt.Errorf("%v and %v already disconnected", oneID, otherID)
- }
- client, err := conn.one.Client()
- if err != nil {
- return err
- }
- net.events.Send(ControlEvent(conn))
- return client.Call(nil, "admin_removePeer", string(conn.other.Addr()))
-}
-
-// DidConnect tracks the fact that the "one" node connected to the "other" node
-func (net *Network) DidConnect(one, other enode.ID) error {
- net.lock.Lock()
- defer net.lock.Unlock()
- conn, err := net.getOrCreateConn(one, other)
- if err != nil {
- return fmt.Errorf("connection between %v and %v does not exist", one, other)
- }
- if conn.Up {
- return fmt.Errorf("%v and %v already connected", one, other)
- }
- conn.Up = true
- net.events.Send(NewEvent(conn))
- return nil
-}
-
-// DidDisconnect tracks the fact that the "one" node disconnected from the
-// "other" node
-func (net *Network) DidDisconnect(one, other enode.ID) error {
- net.lock.Lock()
- defer net.lock.Unlock()
- conn := net.getConn(one, other)
- if conn == nil {
- return fmt.Errorf("connection between %v and %v does not exist", one, other)
- }
- if !conn.Up {
- return fmt.Errorf("%v and %v already disconnected", one, other)
- }
- conn.Up = false
- conn.initiated = time.Now().Add(-DialBanTimeout)
- net.events.Send(NewEvent(conn))
- return nil
-}
-
-// DidSend tracks the fact that "sender" sent a message to "receiver"
-func (net *Network) DidSend(sender, receiver enode.ID, proto string, code uint64) error {
- msg := &Msg{
- One: sender,
- Other: receiver,
- Protocol: proto,
- Code: code,
- Received: false,
- }
- net.events.Send(NewEvent(msg))
- return nil
-}
-
-// DidReceive tracks the fact that "receiver" received a message from "sender"
-func (net *Network) DidReceive(sender, receiver enode.ID, proto string, code uint64) error {
- msg := &Msg{
- One: sender,
- Other: receiver,
- Protocol: proto,
- Code: code,
- Received: true,
- }
- net.events.Send(NewEvent(msg))
- return nil
-}
-
-// GetNode gets the node with the given ID, returning nil if the node does not
-// exist
-func (net *Network) GetNode(id enode.ID) *Node {
- net.lock.RLock()
- defer net.lock.RUnlock()
- return net.getNode(id)
-}
-
-func (net *Network) getNode(id enode.ID) *Node {
- i, found := net.nodeMap[id]
- if !found {
- return nil
- }
- return net.Nodes[i]
-}
-
-// GetNodeByName gets the node with the given name, returning nil if the node does
-// not exist
-func (net *Network) GetNodeByName(name string) *Node {
- net.lock.RLock()
- defer net.lock.RUnlock()
- return net.getNodeByName(name)
-}
-
-func (net *Network) getNodeByName(name string) *Node {
- for _, node := range net.Nodes {
- if node.Config.Name == name {
- return node
- }
- }
- return nil
-}
-
-// GetNodeIDs returns the IDs of all existing nodes
-// Nodes can optionally be excluded by specifying their enode.ID.
-func (net *Network) GetNodeIDs(excludeIDs ...enode.ID) []enode.ID {
- net.lock.RLock()
- defer net.lock.RUnlock()
-
- return net.getNodeIDs(excludeIDs)
-}
-
-func (net *Network) getNodeIDs(excludeIDs []enode.ID) []enode.ID {
- // Get all current nodeIDs
- nodeIDs := make([]enode.ID, 0, len(net.nodeMap))
- for id := range net.nodeMap {
- nodeIDs = append(nodeIDs, id)
- }
-
- if len(excludeIDs) > 0 {
- // Return the difference of nodeIDs and excludeIDs
- return filterIDs(nodeIDs, excludeIDs)
- }
- return nodeIDs
-}
-
-// GetNodes returns the existing nodes.
-// Nodes can optionally be excluded by specifying their enode.ID.
-func (net *Network) GetNodes(excludeIDs ...enode.ID) []*Node {
- net.lock.RLock()
- defer net.lock.RUnlock()
-
- return net.getNodes(excludeIDs)
-}
-
-func (net *Network) getNodes(excludeIDs []enode.ID) []*Node {
- if len(excludeIDs) > 0 {
- nodeIDs := net.getNodeIDs(excludeIDs)
- return net.getNodesByID(nodeIDs)
- }
- return net.Nodes
-}
-
-// GetNodesByID returns existing nodes with the given enode.IDs.
-// If a node doesn't exist with a given enode.ID, it is ignored.
-func (net *Network) GetNodesByID(nodeIDs []enode.ID) []*Node {
- net.lock.RLock()
- defer net.lock.RUnlock()
-
- return net.getNodesByID(nodeIDs)
-}
-
-func (net *Network) getNodesByID(nodeIDs []enode.ID) []*Node {
- nodes := make([]*Node, 0, len(nodeIDs))
- for _, id := range nodeIDs {
- node := net.getNode(id)
- if node != nil {
- nodes = append(nodes, node)
- }
- }
-
- return nodes
-}
-
-// GetNodesByProperty returns existing nodes that have the given property string registered in their NodeConfig
-func (net *Network) GetNodesByProperty(property string) []*Node {
- net.lock.RLock()
- defer net.lock.RUnlock()
-
- return net.getNodesByProperty(property)
-}
-
-func (net *Network) getNodesByProperty(property string) []*Node {
- nodes := make([]*Node, 0, len(net.propertyMap[property]))
- for _, nodeIndex := range net.propertyMap[property] {
- nodes = append(nodes, net.Nodes[nodeIndex])
- }
-
- return nodes
-}
-
-// GetNodeIDsByProperty returns existing node's enode IDs that have the given property string registered in the NodeConfig
-func (net *Network) GetNodeIDsByProperty(property string) []enode.ID {
- net.lock.RLock()
- defer net.lock.RUnlock()
-
- return net.getNodeIDsByProperty(property)
-}
-
-func (net *Network) getNodeIDsByProperty(property string) []enode.ID {
- nodeIDs := make([]enode.ID, 0, len(net.propertyMap[property]))
- for _, nodeIndex := range net.propertyMap[property] {
- node := net.Nodes[nodeIndex]
- nodeIDs = append(nodeIDs, node.ID())
- }
-
- return nodeIDs
-}
-
-// GetRandomUpNode returns a random node on the network, which is running.
-func (net *Network) GetRandomUpNode(excludeIDs ...enode.ID) *Node {
- net.lock.RLock()
- defer net.lock.RUnlock()
- return net.getRandomUpNode(excludeIDs...)
-}
-
-// GetRandomUpNode returns a random node on the network, which is running.
-func (net *Network) getRandomUpNode(excludeIDs ...enode.ID) *Node {
- return net.getRandomNode(net.getUpNodeIDs(), excludeIDs)
-}
-
-func (net *Network) getUpNodeIDs() (ids []enode.ID) {
- for _, node := range net.Nodes {
- if node.Up() {
- ids = append(ids, node.ID())
- }
- }
- return ids
-}
-
-// GetRandomDownNode returns a random node on the network, which is stopped.
-func (net *Network) GetRandomDownNode(excludeIDs ...enode.ID) *Node {
- net.lock.RLock()
- defer net.lock.RUnlock()
- return net.getRandomNode(net.getDownNodeIDs(), excludeIDs)
-}
-
-func (net *Network) getDownNodeIDs() (ids []enode.ID) {
- for _, node := range net.Nodes {
- if !node.Up() {
- ids = append(ids, node.ID())
- }
- }
- return ids
-}
-
-// GetRandomNode returns a random node on the network, regardless of whether it is running or not
-func (net *Network) GetRandomNode(excludeIDs ...enode.ID) *Node {
- net.lock.RLock()
- defer net.lock.RUnlock()
- return net.getRandomNode(net.getNodeIDs(nil), excludeIDs) // no need to exclude twice
-}
-
-func (net *Network) getRandomNode(ids []enode.ID, excludeIDs []enode.ID) *Node {
- filtered := filterIDs(ids, excludeIDs)
-
- l := len(filtered)
- if l == 0 {
- return nil
- }
- return net.getNode(filtered[rand.Intn(l)])
-}
-
-func filterIDs(ids []enode.ID, excludeIDs []enode.ID) []enode.ID {
- exclude := make(map[enode.ID]bool)
- for _, id := range excludeIDs {
- exclude[id] = true
- }
- var filtered []enode.ID
- for _, id := range ids {
- if _, found := exclude[id]; !found {
- filtered = append(filtered, id)
- }
- }
- return filtered
-}
-
-// GetConn returns the connection which exists between "one" and "other"
-// regardless of which node initiated the connection
-func (net *Network) GetConn(oneID, otherID enode.ID) *Conn {
- net.lock.RLock()
- defer net.lock.RUnlock()
- return net.getConn(oneID, otherID)
-}
-
-// GetOrCreateConn is like GetConn but creates the connection if it doesn't
-// already exist
-func (net *Network) GetOrCreateConn(oneID, otherID enode.ID) (*Conn, error) {
- net.lock.Lock()
- defer net.lock.Unlock()
- return net.getOrCreateConn(oneID, otherID)
-}
-
-func (net *Network) getOrCreateConn(oneID, otherID enode.ID) (*Conn, error) {
- if conn := net.getConn(oneID, otherID); conn != nil {
- return conn, nil
- }
-
- one := net.getNode(oneID)
- if one == nil {
- return nil, fmt.Errorf("node %v does not exist", oneID)
- }
- other := net.getNode(otherID)
- if other == nil {
- return nil, fmt.Errorf("node %v does not exist", otherID)
- }
- conn := &Conn{
- One: oneID,
- Other: otherID,
- one: one,
- other: other,
- }
- label := ConnLabel(oneID, otherID)
- net.connMap[label] = len(net.Conns)
- net.Conns = append(net.Conns, conn)
- return conn, nil
-}
-
-func (net *Network) getConn(oneID, otherID enode.ID) *Conn {
- label := ConnLabel(oneID, otherID)
- i, found := net.connMap[label]
- if !found {
- return nil
- }
- return net.Conns[i]
-}
-
-// InitConn retrieves the connection model for the connection between
-// peers 'oneID' and 'otherID', or creates a new one if it does not exist
-// the order of nodes does not matter, i.e., Conn(i,j) == Conn(j, i)
-// it checks if the connection is already up, and if the nodes are running
-// NOTE:
-// it also checks whether there has been recent attempt to connect the peers
-// this is cheating as the simulation is used as an oracle and know about
-// remote peers attempt to connect to a node which will then not initiate the connection
-func (net *Network) InitConn(oneID, otherID enode.ID) (*Conn, error) {
- net.lock.Lock()
- defer net.lock.Unlock()
- return net.initConn(oneID, otherID)
-}
-
-func (net *Network) initConn(oneID, otherID enode.ID) (*Conn, error) {
- if oneID == otherID {
- return nil, fmt.Errorf("refusing to connect to self %v", oneID)
- }
- conn, err := net.getOrCreateConn(oneID, otherID)
- if err != nil {
- return nil, err
- }
- if conn.Up {
- return nil, fmt.Errorf("%v and %v already connected", oneID, otherID)
- }
- if time.Since(conn.initiated) < DialBanTimeout {
- return nil, fmt.Errorf("connection between %v and %v recently attempted", oneID, otherID)
- }
-
- err = conn.nodesUp()
- if err != nil {
- log.Trace("Nodes not up", "err", err)
- return nil, fmt.Errorf("nodes not up: %v", err)
- }
- log.Debug("Connection initiated", "id", oneID, "other", otherID)
- conn.initiated = time.Now()
- return conn, nil
-}
-
-// Shutdown stops all nodes in the network and closes the quit channel
-func (net *Network) Shutdown() {
- for _, node := range net.Nodes {
- log.Debug("Stopping node", "id", node.ID())
- if err := node.Stop(); err != nil {
- log.Warn("Can't stop node", "id", node.ID(), "err", err)
- }
- }
- close(net.quitc)
-}
-
-// Reset resets all network properties:
-// empties the nodes and the connection list
-func (net *Network) Reset() {
- net.lock.Lock()
- defer net.lock.Unlock()
-
- //re-initialize the maps
- net.connMap = make(map[string]int)
- net.nodeMap = make(map[enode.ID]int)
- net.propertyMap = make(map[string][]int)
-
- net.Nodes = nil
- net.Conns = nil
-}
-
-// Node is a wrapper around adapters.Node which is used to track the status
-// of a node in the network
-type Node struct {
- adapters.Node `json:"-"`
-
- // Config if the config used to created the node
- Config *adapters.NodeConfig `json:"config"`
-
- // up tracks whether or not the node is running
- up bool
- upMu *sync.RWMutex
-}
-
-func newNode(an adapters.Node, ac *adapters.NodeConfig, up bool) *Node {
- return &Node{Node: an, Config: ac, up: up, upMu: new(sync.RWMutex)}
-}
-
-func (n *Node) copy() *Node {
- configCpy := *n.Config
- return newNode(n.Node, &configCpy, n.Up())
-}
-
-// Up returns whether the node is currently up (online)
-func (n *Node) Up() bool {
- n.upMu.RLock()
- defer n.upMu.RUnlock()
- return n.up
-}
-
-// SetUp sets the up (online) status of the nodes with the given value
-func (n *Node) SetUp(up bool) {
- n.upMu.Lock()
- defer n.upMu.Unlock()
- n.up = up
-}
-
-// ID returns the ID of the node
-func (n *Node) ID() enode.ID {
- return n.Config.ID
-}
-
-// String returns a log-friendly string
-func (n *Node) String() string {
- return fmt.Sprintf("Node %v", n.ID().TerminalString())
-}
-
-// NodeInfo returns information about the node
-func (n *Node) NodeInfo() *p2p.NodeInfo {
- // avoid a panic if the node is not started yet
- if n.Node == nil {
- return nil
- }
- info := n.Node.NodeInfo()
- info.Name = n.Config.Name
- return info
-}
-
-// MarshalJSON implements the json.Marshaler interface so that the encoded
-// JSON includes the NodeInfo
-func (n *Node) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- Info *p2p.NodeInfo `json:"info,omitempty"`
- Config *adapters.NodeConfig `json:"config,omitempty"`
- Up bool `json:"up"`
- }{
- Info: n.NodeInfo(),
- Config: n.Config,
- Up: n.Up(),
- })
-}
-
-// UnmarshalJSON implements json.Unmarshaler interface so that we don't lose Node.up
-// status. IMPORTANT: The implementation is incomplete; we lose p2p.NodeInfo.
-func (n *Node) UnmarshalJSON(raw []byte) error {
- // TODO: How should we turn back NodeInfo into n.Node?
- // Ticket: https://github.com/ethersphere/go-ethereum/issues/1177
- var node struct {
- Config *adapters.NodeConfig `json:"config,omitempty"`
- Up bool `json:"up"`
- }
- if err := json.Unmarshal(raw, &node); err != nil {
- return err
- }
- *n = *newNode(nil, node.Config, node.Up)
- return nil
-}
-
-// Conn represents a connection between two nodes in the network
-type Conn struct {
- // One is the node which initiated the connection
- One enode.ID `json:"one"`
-
- // Other is the node which the connection was made to
- Other enode.ID `json:"other"`
-
- // Up tracks whether or not the connection is active
- Up bool `json:"up"`
- // Registers when the connection was grabbed to dial
- initiated time.Time
-
- one *Node
- other *Node
-}
-
-// nodesUp returns whether both nodes are currently up
-func (c *Conn) nodesUp() error {
- if !c.one.Up() {
- return fmt.Errorf("one %v is not up", c.One)
- }
- if !c.other.Up() {
- return fmt.Errorf("other %v is not up", c.Other)
- }
- return nil
-}
-
-// String returns a log-friendly string
-func (c *Conn) String() string {
- return fmt.Sprintf("Conn %v->%v", c.One.TerminalString(), c.Other.TerminalString())
-}
-
-// Msg represents a p2p message sent between two nodes in the network
-type Msg struct {
- One enode.ID `json:"one"`
- Other enode.ID `json:"other"`
- Protocol string `json:"protocol"`
- Code uint64 `json:"code"`
- Received bool `json:"received"`
-}
-
-// String returns a log-friendly string
-func (m *Msg) String() string {
- return fmt.Sprintf("Msg(%d) %v->%v", m.Code, m.One.TerminalString(), m.Other.TerminalString())
-}
-
-// ConnLabel generates a deterministic string which represents a connection
-// between two nodes, used to compare if two connections are between the same
-// nodes
-func ConnLabel(source, target enode.ID) string {
- var first, second enode.ID
- if bytes.Compare(source.Bytes(), target.Bytes()) > 0 {
- first = target
- second = source
- } else {
- first = source
- second = target
- }
- return fmt.Sprintf("%v-%v", first, second)
-}
-
-// Snapshot represents the state of a network at a single point in time and can
-// be used to restore the state of a network
-type Snapshot struct {
- Nodes []NodeSnapshot `json:"nodes,omitempty"`
- Conns []Conn `json:"conns,omitempty"`
-}
-
-// NodeSnapshot represents the state of a node in the network
-type NodeSnapshot struct {
- Node Node `json:"node,omitempty"`
-
- // Snapshots is arbitrary data gathered from calling node.Snapshots()
- Snapshots map[string][]byte `json:"snapshots,omitempty"`
-}
-
-// Snapshot creates a network snapshot
-func (net *Network) Snapshot() (*Snapshot, error) {
- return net.snapshot(nil, nil)
-}
-
-func (net *Network) SnapshotWithServices(addServices []string, removeServices []string) (*Snapshot, error) {
- return net.snapshot(addServices, removeServices)
-}
-
-func (net *Network) snapshot(addServices []string, removeServices []string) (*Snapshot, error) {
- net.lock.Lock()
- defer net.lock.Unlock()
- snap := &Snapshot{
- Nodes: make([]NodeSnapshot, len(net.Nodes)),
- }
- for i, node := range net.Nodes {
- snap.Nodes[i] = NodeSnapshot{Node: *node.copy()}
- if !node.Up() {
- continue
- }
- snapshots, err := node.Snapshots()
- if err != nil {
- return nil, err
- }
- snap.Nodes[i].Snapshots = snapshots
- for _, addSvc := range addServices {
- haveSvc := false
- for _, svc := range snap.Nodes[i].Node.Config.Lifecycles {
- if svc == addSvc {
- haveSvc = true
- break
- }
- }
- if !haveSvc {
- snap.Nodes[i].Node.Config.Lifecycles = append(snap.Nodes[i].Node.Config.Lifecycles, addSvc)
- }
- }
- if len(removeServices) > 0 {
- var cleanedServices []string
- for _, svc := range snap.Nodes[i].Node.Config.Lifecycles {
- haveSvc := false
- for _, rmSvc := range removeServices {
- if rmSvc == svc {
- haveSvc = true
- break
- }
- }
- if !haveSvc {
- cleanedServices = append(cleanedServices, svc)
- }
- }
- snap.Nodes[i].Node.Config.Lifecycles = cleanedServices
- }
- }
- for _, conn := range net.Conns {
- if conn.Up {
- snap.Conns = append(snap.Conns, *conn)
- }
- }
- return snap, nil
-}
-
-// longrunning tests may need a longer timeout
-var snapshotLoadTimeout = 900 * time.Second
-
-// Load loads a network snapshot
-func (net *Network) Load(snap *Snapshot) error {
- // Start nodes.
- for _, n := range snap.Nodes {
- if _, err := net.NewNodeWithConfig(n.Node.Config); err != nil {
- return err
- }
- if !n.Node.Up() {
- continue
- }
- if err := net.startWithSnapshots(n.Node.Config.ID, n.Snapshots); err != nil {
- return err
- }
- }
-
- // Prepare connection events counter.
- allConnected := make(chan struct{}) // closed when all connections are established
- done := make(chan struct{}) // ensures that the event loop goroutine is terminated
- defer close(done)
-
- // Subscribe to event channel.
- // It needs to be done outside of the event loop goroutine (created below)
- // to ensure that the event channel is blocking before connect calls are made.
- events := make(chan *Event)
- sub := net.Events().Subscribe(events)
- defer sub.Unsubscribe()
-
- go func() {
- // Expected number of connections.
- total := len(snap.Conns)
- // Set of all established connections from the snapshot, not other connections.
- // Key array element 0 is the connection One field value, and element 1 connection Other field.
- connections := make(map[[2]enode.ID]struct{}, total)
-
- for {
- select {
- case e := <-events:
- // Ignore control events as they do not represent
- // connect or disconnect (Up) state change.
- if e.Control {
- continue
- }
- // Detect only connection events.
- if e.Type != EventTypeConn {
- continue
- }
- connection := [2]enode.ID{e.Conn.One, e.Conn.Other}
- // Nodes are still not connected or have been disconnected.
- if !e.Conn.Up {
- // Delete the connection from the set of established connections.
- // This will prevent false positive in case disconnections happen.
- delete(connections, connection)
- log.Warn("load snapshot: unexpected disconnection", "one", e.Conn.One, "other", e.Conn.Other)
- continue
- }
- // Check that the connection is from the snapshot.
- for _, conn := range snap.Conns {
- if conn.One == e.Conn.One && conn.Other == e.Conn.Other {
- // Add the connection to the set of established connections.
- connections[connection] = struct{}{}
- if len(connections) == total {
- // Signal that all nodes are connected.
- close(allConnected)
- return
- }
-
- break
- }
- }
- case <-done:
- // Load function returned, terminate this goroutine.
- return
- }
- }
- }()
-
- // Start connecting.
- for _, conn := range snap.Conns {
- if !net.GetNode(conn.One).Up() || !net.GetNode(conn.Other).Up() {
- //in this case, at least one of the nodes of a connection is not up,
- //so it would result in the snapshot `Load` to fail
- continue
- }
- if err := net.Connect(conn.One, conn.Other); err != nil {
- return err
- }
- }
-
- select {
- // Wait until all connections from the snapshot are established.
- case <-allConnected:
- // Make sure that we do not wait forever.
- case <-time.After(snapshotLoadTimeout):
- return errors.New("snapshot connections not established")
- }
- return nil
-}
-
-// Subscribe reads control events from a channel and executes them
-func (net *Network) Subscribe(events chan *Event) {
- for {
- select {
- case event, ok := <-events:
- if !ok {
- return
- }
- if event.Control {
- net.executeControlEvent(event)
- }
- case <-net.quitc:
- return
- }
- }
-}
-
-func (net *Network) executeControlEvent(event *Event) {
- log.Trace("Executing control event", "type", event.Type, "event", event)
- switch event.Type {
- case EventTypeNode:
- if err := net.executeNodeEvent(event); err != nil {
- log.Error("Error executing node event", "event", event, "err", err)
- }
- case EventTypeConn:
- if err := net.executeConnEvent(event); err != nil {
- log.Error("Error executing conn event", "event", event, "err", err)
- }
- case EventTypeMsg:
- log.Warn("Ignoring control msg event")
- }
-}
-
-func (net *Network) executeNodeEvent(e *Event) error {
- if !e.Node.Up() {
- return net.Stop(e.Node.ID())
- }
-
- if _, err := net.NewNodeWithConfig(e.Node.Config); err != nil {
- return err
- }
- return net.Start(e.Node.ID())
-}
-
-func (net *Network) executeConnEvent(e *Event) error {
- if e.Conn.Up {
- return net.Connect(e.Conn.One, e.Conn.Other)
- }
- return net.Disconnect(e.Conn.One, e.Conn.Other)
-}
diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go
deleted file mode 100644
index 4ed1e4e6c3..0000000000
--- a/p2p/simulations/network_test.go
+++ /dev/null
@@ -1,872 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package simulations
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "reflect"
- "strconv"
- "strings"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
-)
-
-// Tests that a created snapshot with a minimal service only contains the expected connections
-// and that a network when loaded with this snapshot only contains those same connections
-func TestSnapshot(t *testing.T) {
- // PART I
- // create snapshot from ring network
-
- // this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting
- adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
- return NewNoopService(nil), nil
- },
- })
-
- // create network
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "noopwoop",
- })
- // \todo consider making a member of network, set to true threadsafe when shutdown
- runningOne := true
- defer func() {
- if runningOne {
- network.Shutdown()
- }
- }()
-
- // create and start nodes
- nodeCount := 20
- ids := make([]enode.ID, nodeCount)
- for i := 0; i < nodeCount; i++ {
- conf := adapters.RandomNodeConfig()
- node, err := network.NewNodeWithConfig(conf)
- if err != nil {
- t.Fatalf("error creating node: %s", err)
- }
- if err := network.Start(node.ID()); err != nil {
- t.Fatalf("error starting node: %s", err)
- }
- ids[i] = node.ID()
- }
-
- // subscribe to peer events
- evC := make(chan *Event)
- sub := network.Events().Subscribe(evC)
- defer sub.Unsubscribe()
-
- // connect nodes in a ring
- // spawn separate thread to avoid deadlock in the event listeners
- connectErr := make(chan error, 1)
- go func() {
- for i, id := range ids {
- peerID := ids[(i+1)%len(ids)]
- if err := network.Connect(id, peerID); err != nil {
- connectErr <- err
- return
- }
- }
- }()
-
- // collect connection events up to expected number
- ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
- defer cancel()
- checkIds := make(map[enode.ID][]enode.ID)
- connEventCount := nodeCount
-OUTER:
- for {
- select {
- case <-ctx.Done():
- t.Fatal(ctx.Err())
- case err := <-connectErr:
- t.Fatal(err)
- case ev := <-evC:
- if ev.Type == EventTypeConn && !ev.Control {
- // fail on any disconnect
- if !ev.Conn.Up {
- t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
- }
- checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
- checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
- connEventCount--
- log.Debug("ev", "count", connEventCount)
- if connEventCount == 0 {
- break OUTER
- }
- }
- }
- }
-
- // create snapshot of current network
- snap, err := network.Snapshot()
- if err != nil {
- t.Fatal(err)
- }
- j, err := json.Marshal(snap)
- if err != nil {
- t.Fatal(err)
- }
- log.Debug("snapshot taken", "nodes", len(snap.Nodes), "conns", len(snap.Conns), "json", string(j))
-
- // verify that the snap element numbers check out
- if len(checkIds) != len(snap.Conns) || len(checkIds) != len(snap.Nodes) {
- t.Fatalf("snapshot wrong node,conn counts %d,%d != %d", len(snap.Nodes), len(snap.Conns), len(checkIds))
- }
-
- // shut down sim network
- runningOne = false
- sub.Unsubscribe()
- network.Shutdown()
-
- // check that we have all the expected connections in the snapshot
- for nodid, nodConns := range checkIds {
- for _, nodConn := range nodConns {
- var match bool
- for _, snapConn := range snap.Conns {
- if snapConn.One == nodid && snapConn.Other == nodConn {
- match = true
- break
- } else if snapConn.Other == nodid && snapConn.One == nodConn {
- match = true
- break
- }
- }
- if !match {
- t.Fatalf("snapshot missing conn %v -> %v", nodid, nodConn)
- }
- }
- }
- log.Info("snapshot checked")
-
- // PART II
- // load snapshot and verify that exactly same connections are formed
-
- adapter = adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
- return NewNoopService(nil), nil
- },
- })
- network = NewNetwork(adapter, &NetworkConfig{
- DefaultService: "noopwoop",
- })
- defer func() {
- network.Shutdown()
- }()
-
- // subscribe to peer events
- // every node up and conn up event will generate one additional control event
- // therefore multiply the count by two
- evC = make(chan *Event, (len(snap.Conns)*2)+(len(snap.Nodes)*2))
- sub = network.Events().Subscribe(evC)
- defer sub.Unsubscribe()
-
- // load the snapshot
- // spawn separate thread to avoid deadlock in the event listeners
- err = network.Load(snap)
- if err != nil {
- t.Fatal(err)
- }
-
- // collect connection events up to expected number
- ctx, cancel = context.WithTimeout(context.TODO(), time.Second*3)
- defer cancel()
-
- connEventCount = nodeCount
-
-OuterTwo:
- for {
- select {
- case <-ctx.Done():
- t.Fatal(ctx.Err())
- case ev := <-evC:
- if ev.Type == EventTypeConn && !ev.Control {
- // fail on any disconnect
- if !ev.Conn.Up {
- t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
- }
- log.Debug("conn", "on", ev.Conn.One, "other", ev.Conn.Other)
- checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
- checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
- connEventCount--
- log.Debug("ev", "count", connEventCount)
- if connEventCount == 0 {
- break OuterTwo
- }
- }
- }
- }
-
- // check that we have all expected connections in the network
- for _, snapConn := range snap.Conns {
- var match bool
- for nodid, nodConns := range checkIds {
- for _, nodConn := range nodConns {
- if snapConn.One == nodid && snapConn.Other == nodConn {
- match = true
- break
- } else if snapConn.Other == nodid && snapConn.One == nodConn {
- match = true
- break
- }
- }
- }
- if !match {
- t.Fatalf("network missing conn %v -> %v", snapConn.One, snapConn.Other)
- }
- }
-
- // verify that network didn't generate any other additional connection events after the ones we have collected within a reasonable period of time
- ctx, cancel = context.WithTimeout(context.TODO(), time.Second)
- defer cancel()
- select {
- case <-ctx.Done():
- case ev := <-evC:
- if ev.Type == EventTypeConn {
- t.Fatalf("Superfluous conn found %v -> %v", ev.Conn.One, ev.Conn.Other)
- }
- }
-
- // This test validates if all connections from the snapshot
- // are created in the network.
- t.Run("conns after load", func(t *testing.T) {
- // Create new network.
- n := NewNetwork(
- adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
- return NewNoopService(nil), nil
- },
- }),
- &NetworkConfig{
- DefaultService: "noopwoop",
- },
- )
- defer n.Shutdown()
-
- // Load the same snapshot.
- err := n.Load(snap)
- if err != nil {
- t.Fatal(err)
- }
-
- // Check every connection from the snapshot
- // if it is in the network, too.
- for _, c := range snap.Conns {
- if n.GetConn(c.One, c.Other) == nil {
- t.Errorf("missing connection: %s -> %s", c.One, c.Other)
- }
- }
- })
-}
-
-// TestNetworkSimulation creates a multi-node simulation network with each node
-// connected in a ring topology, checks that all nodes successfully handshake
-// with each other and that a snapshot fully represents the desired topology
-func TestNetworkSimulation(t *testing.T) {
- // create simulation network with 20 testService nodes
- adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "test": newTestService,
- })
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "test",
- })
- defer network.Shutdown()
- nodeCount := 20
- ids := make([]enode.ID, nodeCount)
- for i := 0; i < nodeCount; i++ {
- conf := adapters.RandomNodeConfig()
- node, err := network.NewNodeWithConfig(conf)
- if err != nil {
- t.Fatalf("error creating node: %s", err)
- }
- if err := network.Start(node.ID()); err != nil {
- t.Fatalf("error starting node: %s", err)
- }
- ids[i] = node.ID()
- }
-
- // perform a check which connects the nodes in a ring (so each node is
- // connected to exactly two peers) and then checks that all nodes
- // performed two handshakes by checking their peerCount
- action := func(_ context.Context) error {
- for i, id := range ids {
- peerID := ids[(i+1)%len(ids)]
- if err := network.Connect(id, peerID); err != nil {
- return err
- }
- }
- return nil
- }
- check := func(ctx context.Context, id enode.ID) (bool, error) {
- // check we haven't run out of time
- select {
- case <-ctx.Done():
- return false, ctx.Err()
- default:
- }
-
- // get the node
- node := network.GetNode(id)
- if node == nil {
- return false, fmt.Errorf("unknown node: %s", id)
- }
-
- // check it has exactly two peers
- client, err := node.Client()
- if err != nil {
- return false, err
- }
- var peerCount int64
- if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil {
- return false, err
- }
- switch {
- case peerCount < 2:
- return false, nil
- case peerCount == 2:
- return true, nil
- default:
- return false, fmt.Errorf("unexpected peerCount: %d", peerCount)
- }
- }
-
- timeout := 30 * time.Second
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
-
- // trigger a check every 100ms
- trigger := make(chan enode.ID)
- go triggerChecks(ctx, ids, trigger, 100*time.Millisecond)
-
- result := NewSimulation(network).Run(ctx, &Step{
- Action: action,
- Trigger: trigger,
- Expect: &Expectation{
- Nodes: ids,
- Check: check,
- },
- })
- if result.Error != nil {
- t.Fatalf("simulation failed: %s", result.Error)
- }
-
- // take a network snapshot and check it contains the correct topology
- snap, err := network.Snapshot()
- if err != nil {
- t.Fatal(err)
- }
- if len(snap.Nodes) != nodeCount {
- t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes))
- }
- if len(snap.Conns) != nodeCount {
- t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns))
- }
- for i, id := range ids {
- conn := snap.Conns[i]
- if conn.One != id {
- t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One)
- }
- peerID := ids[(i+1)%len(ids)]
- if conn.Other != peerID {
- t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other)
- }
- }
-}
-
-func createTestNodes(count int, network *Network) (nodes []*Node, err error) {
- for i := 0; i < count; i++ {
- nodeConf := adapters.RandomNodeConfig()
- node, err := network.NewNodeWithConfig(nodeConf)
- if err != nil {
- return nil, err
- }
- if err := network.Start(node.ID()); err != nil {
- return nil, err
- }
-
- nodes = append(nodes, node)
- }
-
- return nodes, nil
-}
-
-func createTestNodesWithProperty(property string, count int, network *Network) (propertyNodes []*Node, err error) {
- for i := 0; i < count; i++ {
- nodeConf := adapters.RandomNodeConfig()
- nodeConf.Properties = append(nodeConf.Properties, property)
-
- node, err := network.NewNodeWithConfig(nodeConf)
- if err != nil {
- return nil, err
- }
- if err := network.Start(node.ID()); err != nil {
- return nil, err
- }
-
- propertyNodes = append(propertyNodes, node)
- }
-
- return propertyNodes, nil
-}
-
-// TestGetNodeIDs creates a set of nodes and attempts to retrieve their IDs,.
-// It then tests again whilst excluding a node ID from being returned.
-// If a node ID is not returned, or more node IDs than expected are returned, the test fails.
-func TestGetNodeIDs(t *testing.T) {
- adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "test": newTestService,
- })
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "test",
- })
- defer network.Shutdown()
-
- numNodes := 5
- nodes, err := createTestNodes(numNodes, network)
- if err != nil {
- t.Fatalf("Could not create test nodes %v", err)
- }
-
- gotNodeIDs := network.GetNodeIDs()
- if len(gotNodeIDs) != numNodes {
- t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodeIDs))
- }
-
- for _, node1 := range nodes {
- match := false
- for _, node2ID := range gotNodeIDs {
- if bytes.Equal(node1.ID().Bytes(), node2ID.Bytes()) {
- match = true
- break
- }
- }
-
- if !match {
- t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String())
- }
- }
-
- excludeNodeID := nodes[3].ID()
- gotNodeIDsExcl := network.GetNodeIDs(excludeNodeID)
- if len(gotNodeIDsExcl) != numNodes-1 {
- t.Fatalf("Expected one less node ID to be returned")
- }
- for _, nodeID := range gotNodeIDsExcl {
- if bytes.Equal(excludeNodeID.Bytes(), nodeID.Bytes()) {
- t.Fatalf("GetNodeIDs returned the node ID we excluded, ID: %s", nodeID.String())
- }
- }
-}
-
-// TestGetNodes creates a set of nodes and attempts to retrieve them again.
-// It then tests again whilst excluding a node from being returned.
-// If a node is not returned, or more nodes than expected are returned, the test fails.
-func TestGetNodes(t *testing.T) {
- adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "test": newTestService,
- })
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "test",
- })
- defer network.Shutdown()
-
- numNodes := 5
- nodes, err := createTestNodes(numNodes, network)
- if err != nil {
- t.Fatalf("Could not create test nodes %v", err)
- }
-
- gotNodes := network.GetNodes()
- if len(gotNodes) != numNodes {
- t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodes))
- }
-
- for _, node1 := range nodes {
- match := false
- for _, node2 := range gotNodes {
- if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) {
- match = true
- break
- }
- }
-
- if !match {
- t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String())
- }
- }
-
- excludeNodeID := nodes[3].ID()
- gotNodesExcl := network.GetNodes(excludeNodeID)
- if len(gotNodesExcl) != numNodes-1 {
- t.Fatalf("Expected one less node to be returned")
- }
- for _, node := range gotNodesExcl {
- if bytes.Equal(excludeNodeID.Bytes(), node.ID().Bytes()) {
- t.Fatalf("GetNodes returned the node we excluded, ID: %s", node.ID().String())
- }
- }
-}
-
-// TestGetNodesByID creates a set of nodes and attempts to retrieve a subset of them by ID
-// If a node is not returned, or more nodes than expected are returned, the test fails.
-func TestGetNodesByID(t *testing.T) {
- adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "test": newTestService,
- })
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "test",
- })
- defer network.Shutdown()
-
- numNodes := 5
- nodes, err := createTestNodes(numNodes, network)
- if err != nil {
- t.Fatalf("Could not create test nodes: %v", err)
- }
-
- numSubsetNodes := 2
- subsetNodes := nodes[0:numSubsetNodes]
- var subsetNodeIDs []enode.ID
- for _, node := range subsetNodes {
- subsetNodeIDs = append(subsetNodeIDs, node.ID())
- }
-
- gotNodesByID := network.GetNodesByID(subsetNodeIDs)
- if len(gotNodesByID) != numSubsetNodes {
- t.Fatalf("Expected %d nodes, got %d", numSubsetNodes, len(gotNodesByID))
- }
-
- for _, node1 := range subsetNodes {
- match := false
- for _, node2 := range gotNodesByID {
- if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) {
- match = true
- break
- }
- }
-
- if !match {
- t.Fatalf("A created node was not returned by GetNodesByID(), ID: %s", node1.ID().String())
- }
- }
-}
-
-// TestGetNodesByProperty creates a subset of nodes with a property assigned.
-// GetNodesByProperty is then checked for correctness by comparing the nodes returned to those initially created.
-// If a node with a property is not found, or more nodes than expected are returned, the test fails.
-func TestGetNodesByProperty(t *testing.T) {
- adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "test": newTestService,
- })
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "test",
- })
- defer network.Shutdown()
-
- numNodes := 3
- _, err := createTestNodes(numNodes, network)
- if err != nil {
- t.Fatalf("Failed to create nodes: %v", err)
- }
-
- numPropertyNodes := 3
- propertyTest := "test"
- propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network)
- if err != nil {
- t.Fatalf("Failed to create nodes with property: %v", err)
- }
-
- gotNodesByProperty := network.GetNodesByProperty(propertyTest)
- if len(gotNodesByProperty) != numPropertyNodes {
- t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodesByProperty))
- }
-
- for _, node1 := range propertyNodes {
- match := false
- for _, node2 := range gotNodesByProperty {
- if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) {
- match = true
- break
- }
- }
-
- if !match {
- t.Fatalf("A created node with property was not returned by GetNodesByProperty(), ID: %s", node1.ID().String())
- }
- }
-}
-
-// TestGetNodeIDsByProperty creates a subset of nodes with a property assigned.
-// GetNodeIDsByProperty is then checked for correctness by comparing the node IDs returned to those initially created.
-// If a node ID with a property is not found, or more nodes IDs than expected are returned, the test fails.
-func TestGetNodeIDsByProperty(t *testing.T) {
- adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "test": newTestService,
- })
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "test",
- })
- defer network.Shutdown()
-
- numNodes := 3
- _, err := createTestNodes(numNodes, network)
- if err != nil {
- t.Fatalf("Failed to create nodes: %v", err)
- }
-
- numPropertyNodes := 3
- propertyTest := "test"
- propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network)
- if err != nil {
- t.Fatalf("Failed to created nodes with property: %v", err)
- }
-
- gotNodeIDsByProperty := network.GetNodeIDsByProperty(propertyTest)
- if len(gotNodeIDsByProperty) != numPropertyNodes {
- t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodeIDsByProperty))
- }
-
- for _, node1 := range propertyNodes {
- match := false
- id1 := node1.ID()
- for _, id2 := range gotNodeIDsByProperty {
- if bytes.Equal(id1.Bytes(), id2.Bytes()) {
- match = true
- break
- }
- }
-
- if !match {
- t.Fatalf("Not all nodes IDs were returned by GetNodeIDsByProperty(), ID: %s", id1.String())
- }
- }
-}
-
-func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) {
- tick := time.NewTicker(interval)
- defer tick.Stop()
- for {
- select {
- case <-tick.C:
- for _, id := range ids {
- select {
- case trigger <- id:
- case <-ctx.Done():
- return
- }
- }
- case <-ctx.Done():
- return
- }
- }
-}
-
-// \todo: refactor to implement snapshots
-// and connect configuration methods once these are moved from
-// swarm/network/simulations/connect.go
-func BenchmarkMinimalService(b *testing.B) {
- b.Run("ring/32", benchmarkMinimalServiceTmp)
-}
-
-func benchmarkMinimalServiceTmp(b *testing.B) {
- // stop timer to discard setup time pollution
- args := strings.Split(b.Name(), "/")
- nodeCount, err := strconv.ParseInt(args[2], 10, 16)
- if err != nil {
- b.Fatal(err)
- }
-
- for i := 0; i < b.N; i++ {
- // this is a minimal service, whose protocol will close a channel upon run of protocol
- // making it possible to bench the time it takes for the service to start and protocol actually to be run
- protoCMap := make(map[enode.ID]map[enode.ID]chan struct{})
- adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
- "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
- protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{})
- svc := NewNoopService(protoCMap[ctx.Config.ID])
- return svc, nil
- },
- })
-
- // create network
- network := NewNetwork(adapter, &NetworkConfig{
- DefaultService: "noopwoop",
- })
- defer network.Shutdown()
-
- // create and start nodes
- ids := make([]enode.ID, nodeCount)
- for i := 0; i < int(nodeCount); i++ {
- conf := adapters.RandomNodeConfig()
- node, err := network.NewNodeWithConfig(conf)
- if err != nil {
- b.Fatalf("error creating node: %s", err)
- }
- if err := network.Start(node.ID()); err != nil {
- b.Fatalf("error starting node: %s", err)
- }
- ids[i] = node.ID()
- }
-
- // ready, set, go
- b.ResetTimer()
-
- // connect nodes in a ring
- for i, id := range ids {
- peerID := ids[(i+1)%len(ids)]
- if err := network.Connect(id, peerID); err != nil {
- b.Fatal(err)
- }
- }
-
- // wait for all protocols to signal to close down
- ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
- defer cancel()
- for nodid, peers := range protoCMap {
- for peerid, peerC := range peers {
- log.Debug("getting ", "node", nodid, "peer", peerid)
- select {
- case <-ctx.Done():
- b.Fatal(ctx.Err())
- case <-peerC:
- }
- }
- }
- }
-}
-
-func TestNode_UnmarshalJSON(t *testing.T) {
- t.Run("up_field", func(t *testing.T) {
- runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONUpField())
- })
- t.Run("config_field", func(t *testing.T) {
- runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONConfigField())
- })
-}
-
-func runNodeUnmarshalJSON(t *testing.T, tests []nodeUnmarshalTestCase) {
- t.Helper()
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var got *Node
- if err := json.Unmarshal([]byte(tt.marshaled), &got); err != nil {
- expectErrorMessageToContain(t, err, tt.wantErr)
- got = nil
- }
- expectNodeEquality(t, got, tt.want)
- })
- }
-}
-
-type nodeUnmarshalTestCase struct {
- name string
- marshaled string
- want *Node
- wantErr string
-}
-
-func expectErrorMessageToContain(t *testing.T, got error, want string) {
- t.Helper()
- if got == nil && want == "" {
- return
- }
-
- if got == nil && want != "" {
- t.Errorf("error was expected, got: nil, want: %v", want)
- return
- }
-
- if !strings.Contains(got.Error(), want) {
- t.Errorf(
- "unexpected error message, got %v, want: %v",
- want,
- got,
- )
- }
-}
-
-func expectNodeEquality(t *testing.T, got, want *Node) {
- t.Helper()
- if !reflect.DeepEqual(got, want) {
- t.Errorf("Node.UnmarshalJSON() = %v, want %v", got, want)
- }
-}
-
-func casesNodeUnmarshalJSONUpField() []nodeUnmarshalTestCase {
- return []nodeUnmarshalTestCase{
- {
- name: "empty json",
- marshaled: "{}",
- want: newNode(nil, nil, false),
- },
- {
- name: "a stopped node",
- marshaled: "{\"up\": false}",
- want: newNode(nil, nil, false),
- },
- {
- name: "a running node",
- marshaled: "{\"up\": true}",
- want: newNode(nil, nil, true),
- },
- {
- name: "invalid JSON value on valid key",
- marshaled: "{\"up\": foo}",
- wantErr: "invalid character",
- },
- {
- name: "invalid JSON key and value",
- marshaled: "{foo: bar}",
- wantErr: "invalid character",
- },
- {
- name: "bool value expected but got something else (string)",
- marshaled: "{\"up\": \"true\"}",
- wantErr: "cannot unmarshal string into Go struct",
- },
- }
-}
-
-func casesNodeUnmarshalJSONConfigField() []nodeUnmarshalTestCase {
- // Don't do a big fuss around testing, as adapters.NodeConfig should
- // handle it's own serialization. Just do a sanity check.
- return []nodeUnmarshalTestCase{
- {
- name: "Config field is omitted",
- marshaled: "{}",
- want: newNode(nil, nil, false),
- },
- {
- name: "Config field is nil",
- marshaled: "{\"config\": null}",
- want: newNode(nil, nil, false),
- },
- {
- name: "a non default Config field",
- marshaled: "{\"config\":{\"name\":\"node_ecdd0\",\"port\":44665}}",
- want: newNode(nil, &adapters.NodeConfig{Name: "node_ecdd0", Port: 44665}, false),
- },
- }
-}
diff --git a/p2p/simulations/pipes/pipes.go b/p2p/simulations/pipes/pipes.go
deleted file mode 100644
index ec277c0d14..0000000000
--- a/p2p/simulations/pipes/pipes.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package pipes
-
-import (
- "net"
-)
-
-// NetPipe wraps net.Pipe in a signature returning an error
-func NetPipe() (net.Conn, net.Conn, error) {
- p1, p2 := net.Pipe()
- return p1, p2, nil
-}
-
-// TCPPipe creates an in process full duplex pipe based on a localhost TCP socket
-func TCPPipe() (net.Conn, net.Conn, error) {
- l, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- return nil, nil, err
- }
- defer l.Close()
-
- var aconn net.Conn
- aerr := make(chan error, 1)
- go func() {
- var err error
- aconn, err = l.Accept()
- aerr <- err
- }()
-
- dconn, err := net.Dial("tcp", l.Addr().String())
- if err != nil {
- <-aerr
- return nil, nil, err
- }
- if err := <-aerr; err != nil {
- dconn.Close()
- return nil, nil, err
- }
- return aconn, dconn, nil
-}
diff --git a/p2p/simulations/simulation.go b/p2p/simulations/simulation.go
deleted file mode 100644
index ae62c42b9c..0000000000
--- a/p2p/simulations/simulation.go
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package simulations
-
-import (
- "context"
- "time"
-
- "github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-// Simulation provides a framework for running actions in a simulated network
-// and then waiting for expectations to be met
-type Simulation struct {
- network *Network
-}
-
-// NewSimulation returns a new simulation which runs in the given network
-func NewSimulation(network *Network) *Simulation {
- return &Simulation{
- network: network,
- }
-}
-
-// Run performs a step of the simulation by performing the step's action and
-// then waiting for the step's expectation to be met
-func (s *Simulation) Run(ctx context.Context, step *Step) (result *StepResult) {
- result = newStepResult()
-
- result.StartedAt = time.Now()
- defer func() { result.FinishedAt = time.Now() }()
-
- // watch network events for the duration of the step
- stop := s.watchNetwork(result)
- defer stop()
-
- // perform the action
- if err := step.Action(ctx); err != nil {
- result.Error = err
- return
- }
-
- // wait for all node expectations to either pass, error or timeout
- nodes := make(map[enode.ID]struct{}, len(step.Expect.Nodes))
- for _, id := range step.Expect.Nodes {
- nodes[id] = struct{}{}
- }
- for len(result.Passes) < len(nodes) {
- select {
- case id := <-step.Trigger:
- // skip if we aren't checking the node
- if _, ok := nodes[id]; !ok {
- continue
- }
-
- // skip if the node has already passed
- if _, ok := result.Passes[id]; ok {
- continue
- }
-
- // run the node expectation check
- pass, err := step.Expect.Check(ctx, id)
- if err != nil {
- result.Error = err
- return
- }
- if pass {
- result.Passes[id] = time.Now()
- }
- case <-ctx.Done():
- result.Error = ctx.Err()
- return
- }
- }
-
- return
-}
-
-func (s *Simulation) watchNetwork(result *StepResult) func() {
- stop := make(chan struct{})
- done := make(chan struct{})
- events := make(chan *Event)
- sub := s.network.Events().Subscribe(events)
- go func() {
- defer close(done)
- defer sub.Unsubscribe()
- for {
- select {
- case event := <-events:
- result.NetworkEvents = append(result.NetworkEvents, event)
- case <-stop:
- return
- }
- }
- }()
- return func() {
- close(stop)
- <-done
- }
-}
-
-type Step struct {
- // Action is the action to perform for this step
- Action func(context.Context) error
-
- // Trigger is a channel which receives node ids and triggers an
- // expectation check for that node
- Trigger chan enode.ID
-
- // Expect is the expectation to wait for when performing this step
- Expect *Expectation
-}
-
-type Expectation struct {
- // Nodes is a list of nodes to check
- Nodes []enode.ID
-
- // Check checks whether a given node meets the expectation
- Check func(context.Context, enode.ID) (bool, error)
-}
-
-func newStepResult() *StepResult {
- return &StepResult{
- Passes: make(map[enode.ID]time.Time),
- }
-}
-
-type StepResult struct {
- // Error is the error encountered whilst running the step
- Error error
-
- // StartedAt is the time the step started
- StartedAt time.Time
-
- // FinishedAt is the time the step finished
- FinishedAt time.Time
-
- // Passes are the timestamps of the successful node expectations
- Passes map[enode.ID]time.Time
-
- // NetworkEvents are the network events which occurred during the step
- NetworkEvents []*Event
-}
diff --git a/p2p/simulations/test.go b/p2p/simulations/test.go
deleted file mode 100644
index 0edb07b127..0000000000
--- a/p2p/simulations/test.go
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package simulations
-
-import (
- "testing"
-
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/p2p/enr"
- "github.com/ethereum/go-ethereum/rpc"
-)
-
-// NoopService is the service that does not do anything
-// but implements node.Service interface.
-type NoopService struct {
- c map[enode.ID]chan struct{}
-}
-
-func NewNoopService(ackC map[enode.ID]chan struct{}) *NoopService {
- return &NoopService{
- c: ackC,
- }
-}
-
-func (t *NoopService) Protocols() []p2p.Protocol {
- return []p2p.Protocol{
- {
- Name: "noop",
- Version: 666,
- Length: 0,
- Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
- if t.c != nil {
- t.c[peer.ID()] = make(chan struct{})
- close(t.c[peer.ID()])
- }
- rw.ReadMsg()
- return nil
- },
- NodeInfo: func() interface{} {
- return struct{}{}
- },
- PeerInfo: func(id enode.ID) interface{} {
- return struct{}{}
- },
- Attributes: []enr.Entry{},
- },
- }
-}
-
-func (t *NoopService) APIs() []rpc.API {
- return []rpc.API{}
-}
-
-func (t *NoopService) Start() error {
- return nil
-}
-
-func (t *NoopService) Stop() error {
- return nil
-}
-
-func VerifyRing(t *testing.T, net *Network, ids []enode.ID) {
- t.Helper()
- n := len(ids)
- for i := 0; i < n; i++ {
- for j := i + 1; j < n; j++ {
- c := net.GetConn(ids[i], ids[j])
- if i == j-1 || (i == 0 && j == n-1) {
- if c == nil {
- t.Errorf("nodes %v and %v are not connected, but they should be", i, j)
- }
- } else {
- if c != nil {
- t.Errorf("nodes %v and %v are connected, but they should not be", i, j)
- }
- }
- }
- }
-}
-
-func VerifyChain(t *testing.T, net *Network, ids []enode.ID) {
- t.Helper()
- n := len(ids)
- for i := 0; i < n; i++ {
- for j := i + 1; j < n; j++ {
- c := net.GetConn(ids[i], ids[j])
- if i == j-1 {
- if c == nil {
- t.Errorf("nodes %v and %v are not connected, but they should be", i, j)
- }
- } else {
- if c != nil {
- t.Errorf("nodes %v and %v are connected, but they should not be", i, j)
- }
- }
- }
- }
-}
-
-func VerifyFull(t *testing.T, net *Network, ids []enode.ID) {
- t.Helper()
- n := len(ids)
- var connections int
- for i, lid := range ids {
- for _, rid := range ids[i+1:] {
- if net.GetConn(lid, rid) != nil {
- connections++
- }
- }
- }
-
- want := n * (n - 1) / 2
- if connections != want {
- t.Errorf("wrong number of connections, got: %v, want: %v", connections, want)
- }
-}
-
-func VerifyStar(t *testing.T, net *Network, ids []enode.ID, centerIndex int) {
- t.Helper()
- n := len(ids)
- for i := 0; i < n; i++ {
- for j := i + 1; j < n; j++ {
- c := net.GetConn(ids[i], ids[j])
- if i == centerIndex || j == centerIndex {
- if c == nil {
- t.Errorf("nodes %v and %v are not connected, but they should be", i, j)
- }
- } else {
- if c != nil {
- t.Errorf("nodes %v and %v are connected, but they should not be", i, j)
- }
- }
- }
- }
-}
diff --git a/p2p/tracker/tracker.go b/p2p/tracker/tracker.go
deleted file mode 100644
index 6a733b9ba5..0000000000
--- a/p2p/tracker/tracker.go
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2021 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package tracker
-
-import (
- "container/list"
- "fmt"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
-)
-
-const (
- // trackedGaugeName is the prefix of the per-packet request tracking.
- trackedGaugeName = "p2p/tracked"
-
- // lostMeterName is the prefix of the per-packet request expirations.
- lostMeterName = "p2p/lost"
-
- // staleMeterName is the prefix of the per-packet stale responses.
- staleMeterName = "p2p/stale"
-
- // waitHistName is the prefix of the per-packet (req only) waiting time histograms.
- waitHistName = "p2p/wait"
-
- // maxTrackedPackets is a huge number to act as a failsafe on the number of
- // pending requests the node will track. It should never be hit unless an
- // attacker figures out a way to spin requests.
- maxTrackedPackets = 100000
-)
-
-// request tracks sent network requests which have not yet received a response.
-type request struct {
- peer string
- version uint // Protocol version
-
- reqCode uint64 // Protocol message code of the request
- resCode uint64 // Protocol message code of the expected response
-
- time time.Time // Timestamp when the request was made
- expire *list.Element // Expiration marker to untrack it
-}
-
-// Tracker is a pending network request tracker to measure how much time it takes
-// a remote peer to respond.
-type Tracker struct {
- protocol string // Protocol capability identifier for the metrics
- timeout time.Duration // Global timeout after which to drop a tracked packet
-
- pending map[uint64]*request // Currently pending requests
- expire *list.List // Linked list tracking the expiration order
- wake *time.Timer // Timer tracking the expiration of the next item
-
- lock sync.Mutex // Lock protecting from concurrent updates
-}
-
-// New creates a new network request tracker to monitor how much time it takes to
-// fill certain requests and how individual peers perform.
-func New(protocol string, timeout time.Duration) *Tracker {
- return &Tracker{
- protocol: protocol,
- timeout: timeout,
- pending: make(map[uint64]*request),
- expire: list.New(),
- }
-}
-
-// Track adds a network request to the tracker to wait for a response to arrive
-// or until the request it cancelled or times out.
-func (t *Tracker) Track(peer string, version uint, reqCode uint64, resCode uint64, id uint64) {
- if !metrics.Enabled {
- return
- }
- t.lock.Lock()
- defer t.lock.Unlock()
-
- // If there's a duplicate request, we've just random-collided (or more probably,
- // we have a bug), report it. We could also add a metric, but we're not really
- // expecting ourselves to be buggy, so a noisy warning should be enough.
- if _, ok := t.pending[id]; ok {
- log.Error("Network request id collision", "protocol", t.protocol, "version", version, "code", reqCode, "id", id)
- return
- }
- // If we have too many pending requests, bail out instead of leaking memory
- if pending := len(t.pending); pending >= maxTrackedPackets {
- log.Error("Request tracker exceeded allowance", "pending", pending, "peer", peer, "protocol", t.protocol, "version", version, "code", reqCode)
- return
- }
- // Id doesn't exist yet, start tracking it
- t.pending[id] = &request{
- peer: peer,
- version: version,
- reqCode: reqCode,
- resCode: resCode,
- time: time.Now(),
- expire: t.expire.PushBack(id),
- }
- g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, version, reqCode)
- metrics.GetOrRegisterGauge(g, nil).Inc(1)
-
- // If we've just inserted the first item, start the expiration timer
- if t.wake == nil {
- t.wake = time.AfterFunc(t.timeout, t.clean)
- }
-}
-
-// clean is called automatically when a preset time passes without a response
-// being delivered for the first network request.
-func (t *Tracker) clean() {
- t.lock.Lock()
- defer t.lock.Unlock()
-
- // Expire anything within a certain threshold (might be no items at all if
- // we raced with the delivery)
- for t.expire.Len() > 0 {
- // Stop iterating if the next pending request is still alive
- var (
- head = t.expire.Front()
- id = head.Value.(uint64)
- req = t.pending[id]
- )
- if time.Since(req.time) < t.timeout+5*time.Millisecond {
- break
- }
- // Nope, dead, drop it
- t.expire.Remove(head)
- delete(t.pending, id)
-
- g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, req.version, req.reqCode)
- metrics.GetOrRegisterGauge(g, nil).Dec(1)
-
- m := fmt.Sprintf("%s/%s/%d/%#02x", lostMeterName, t.protocol, req.version, req.reqCode)
- metrics.GetOrRegisterMeter(m, nil).Mark(1)
- }
- t.schedule()
-}
-
-// schedule starts a timer to trigger on the expiration of the first network
-// packet.
-func (t *Tracker) schedule() {
- if t.expire.Len() == 0 {
- t.wake = nil
- return
- }
- t.wake = time.AfterFunc(time.Until(t.pending[t.expire.Front().Value.(uint64)].time.Add(t.timeout)), t.clean)
-}
-
-// Fulfil fills a pending request, if any is available, reporting on various metrics.
-func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) {
- if !metrics.Enabled {
- return
- }
- t.lock.Lock()
- defer t.lock.Unlock()
-
- // If it's a non existing request, track as stale response
- req, ok := t.pending[id]
- if !ok {
- m := fmt.Sprintf("%s/%s/%d/%#02x", staleMeterName, t.protocol, version, code)
- metrics.GetOrRegisterMeter(m, nil).Mark(1)
- return
- }
- // If the response is funky, it might be some active attack
- if req.peer != peer || req.version != version || req.resCode != code {
- log.Warn("Network response id collision",
- "have", fmt.Sprintf("%s:%s/%d:%d", peer, t.protocol, version, code),
- "want", fmt.Sprintf("%s:%s/%d:%d", peer, t.protocol, req.version, req.resCode),
- )
- return
- }
- // Everything matches, mark the request serviced and meter it
- t.expire.Remove(req.expire)
- delete(t.pending, id)
- if req.expire.Prev() == nil {
- if t.wake.Stop() {
- t.schedule()
- }
- }
- g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, req.version, req.reqCode)
- metrics.GetOrRegisterGauge(g, nil).Dec(1)
-
- h := fmt.Sprintf("%s/%s/%d/%#02x", waitHistName, t.protocol, req.version, req.reqCode)
- sampler := func() metrics.Sample {
- return metrics.ResettingSample(
- metrics.NewExpDecaySample(1028, 0.015),
- )
- }
- metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(req.time).Microseconds())
-}
diff --git a/p2p/transport.go b/p2p/transport.go
deleted file mode 100644
index 4f6bb569bf..0000000000
--- a/p2p/transport.go
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "bytes"
- "crypto/ecdsa"
- "fmt"
- "io"
- "net"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/bitutil"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/p2p/rlpx"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-const (
- // total timeout for encryption handshake and protocol
- // handshake in both directions.
- handshakeTimeout = 5 * time.Second
-
- // This is the timeout for sending the disconnect reason.
- // This is shorter than the usual timeout because we don't want
- // to wait if the connection is known to be bad anyway.
- discWriteTimeout = 1 * time.Second
-)
-
-// rlpxTransport is the transport used by actual (non-test) connections.
-// It wraps an RLPx connection with locks and read/write deadlines.
-type rlpxTransport struct {
- rmu, wmu sync.Mutex
- wbuf bytes.Buffer
- conn *rlpx.Conn
-}
-
-func newRLPX(conn net.Conn, dialDest *ecdsa.PublicKey) transport {
- return &rlpxTransport{conn: rlpx.NewConn(conn, dialDest)}
-}
-
-func (t *rlpxTransport) ReadMsg() (Msg, error) {
- t.rmu.Lock()
- defer t.rmu.Unlock()
-
- var msg Msg
- t.conn.SetReadDeadline(time.Now().Add(frameReadTimeout))
- code, data, wireSize, err := t.conn.Read()
- if err == nil {
- // Protocol messages are dispatched to subprotocol handlers asynchronously,
- // but package rlpx may reuse the returned 'data' buffer on the next call
- // to Read. Copy the message data to avoid this being an issue.
- data = common.CopyBytes(data)
- msg = Msg{
- ReceivedAt: time.Now(),
- Code: code,
- Size: uint32(len(data)),
- meterSize: uint32(wireSize),
- Payload: bytes.NewReader(data),
- }
- }
- return msg, err
-}
-
-func (t *rlpxTransport) WriteMsg(msg Msg) error {
- t.wmu.Lock()
- defer t.wmu.Unlock()
-
- // Copy message data to write buffer.
- t.wbuf.Reset()
- if _, err := io.CopyN(&t.wbuf, msg.Payload, int64(msg.Size)); err != nil {
- return err
- }
-
- // Write the message.
- t.conn.SetWriteDeadline(time.Now().Add(frameWriteTimeout))
- size, err := t.conn.Write(msg.Code, t.wbuf.Bytes())
- if err != nil {
- return err
- }
-
- // Set metrics.
- msg.meterSize = size
- if metrics.Enabled && msg.meterCap.Name != "" { // don't meter non-subprotocol messages
- m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode)
- metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize))
- metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1)
- }
- return nil
-}
-
-func (t *rlpxTransport) close(err error) {
- t.wmu.Lock()
- defer t.wmu.Unlock()
-
- // Tell the remote end why we're disconnecting if possible.
- // We only bother doing this if the underlying connection supports
- // setting a timeout tough.
- if t.conn != nil {
- if r, ok := err.(DiscReason); ok && r != DiscNetworkError {
- deadline := time.Now().Add(discWriteTimeout)
- if err := t.conn.SetWriteDeadline(deadline); err == nil {
- // Connection supports write deadline.
- t.wbuf.Reset()
- rlp.Encode(&t.wbuf, []DiscReason{r})
- t.conn.Write(discMsg, t.wbuf.Bytes())
- }
- }
- }
- t.conn.Close()
-}
-
-func (t *rlpxTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
- t.conn.SetDeadline(time.Now().Add(handshakeTimeout))
- return t.conn.Handshake(prv)
-}
-
-func (t *rlpxTransport) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) {
- // Writing our handshake happens concurrently, we prefer
- // returning the handshake read error. If the remote side
- // disconnects us early with a valid reason, we should return it
- // as the error so it can be tracked elsewhere.
- werr := make(chan error, 1)
- go func() { werr <- Send(t, handshakeMsg, our) }()
- if their, err = readProtocolHandshake(t); err != nil {
- <-werr // make sure the write terminates too
- return nil, err
- }
- if err := <-werr; err != nil {
- return nil, fmt.Errorf("write error: %v", err)
- }
- // If the protocol version supports Snappy encoding, upgrade immediately
- t.conn.SetSnappy(their.Version >= snappyProtocolVersion)
-
- return their, nil
-}
-
-func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) {
- msg, err := rw.ReadMsg()
- if err != nil {
- return nil, err
- }
- if msg.Size > baseProtocolMaxMsgSize {
- return nil, fmt.Errorf("message too big")
- }
- if msg.Code == discMsg {
- // Disconnect before protocol handshake is valid according to the
- // spec and we send it ourself if the post-handshake checks fail.
- // We can't return the reason directly, though, because it is echoed
- // back otherwise. Wrap it in a string instead.
- var reason [1]DiscReason
- rlp.Decode(msg.Payload, &reason)
- return nil, reason[0]
- }
- if msg.Code != handshakeMsg {
- return nil, fmt.Errorf("expected handshake, got %x", msg.Code)
- }
- var hs protoHandshake
- if err := msg.Decode(&hs); err != nil {
- return nil, err
- }
- if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) {
- return nil, DiscInvalidIdentity
- }
- return &hs, nil
-}
diff --git a/p2p/transport_test.go b/p2p/transport_test.go
deleted file mode 100644
index 24e06c5a06..0000000000
--- a/p2p/transport_test.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "errors"
- "reflect"
- "sync"
- "testing"
-
- "github.com/davecgh/go-spew/spew"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/p2p/simulations/pipes"
-)
-
-func TestProtocolHandshake(t *testing.T) {
- var (
- prv0, _ = crypto.GenerateKey()
- pub0 = crypto.FromECDSAPub(&prv0.PublicKey)[1:]
- hs0 = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}}
-
- prv1, _ = crypto.GenerateKey()
- pub1 = crypto.FromECDSAPub(&prv1.PublicKey)[1:]
- hs1 = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}}
-
- wg sync.WaitGroup
- )
-
- fd0, fd1, err := pipes.TCPPipe()
- if err != nil {
- t.Fatal(err)
- }
-
- wg.Add(2)
- go func() {
- defer wg.Done()
- defer fd0.Close()
- frame := newRLPX(fd0, &prv1.PublicKey)
- rpubkey, err := frame.doEncHandshake(prv0)
- if err != nil {
- t.Errorf("dial side enc handshake failed: %v", err)
- return
- }
- if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) {
- t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey)
- return
- }
-
- phs, err := frame.doProtoHandshake(hs0)
- if err != nil {
- t.Errorf("dial side proto handshake error: %v", err)
- return
- }
- phs.Rest = nil
- if !reflect.DeepEqual(phs, hs1) {
- t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1))
- return
- }
- frame.close(DiscQuitting)
- }()
- go func() {
- defer wg.Done()
- defer fd1.Close()
- rlpx := newRLPX(fd1, nil)
- rpubkey, err := rlpx.doEncHandshake(prv1)
- if err != nil {
- t.Errorf("listen side enc handshake failed: %v", err)
- return
- }
- if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) {
- t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey)
- return
- }
-
- phs, err := rlpx.doProtoHandshake(hs1)
- if err != nil {
- t.Errorf("listen side proto handshake error: %v", err)
- return
- }
- phs.Rest = nil
- if !reflect.DeepEqual(phs, hs0) {
- t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0))
- return
- }
-
- if err := ExpectMsg(rlpx, discMsg, []DiscReason{DiscQuitting}); err != nil {
- t.Errorf("error receiving disconnect: %v", err)
- }
- }()
- wg.Wait()
-}
-
-func TestProtocolHandshakeErrors(t *testing.T) {
- tests := []struct {
- code uint64
- msg interface{}
- err error
- }{
- {
- code: discMsg,
- msg: []DiscReason{DiscQuitting},
- err: DiscQuitting,
- },
- {
- code: 0x989898,
- msg: []byte{1},
- err: errors.New("expected handshake, got 989898"),
- },
- {
- code: handshakeMsg,
- msg: make([]byte, baseProtocolMaxMsgSize+2),
- err: errors.New("message too big"),
- },
- {
- code: handshakeMsg,
- msg: []byte{1, 2, 3},
- err: newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"),
- },
- {
- code: handshakeMsg,
- msg: &protoHandshake{Version: 3},
- err: DiscInvalidIdentity,
- },
- }
-
- for i, test := range tests {
- p1, p2 := MsgPipe()
- go Send(p1, test.code, test.msg)
- _, err := readProtocolHandshake(p2)
- if !reflect.DeepEqual(err, test.err) {
- t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err)
- }
- }
-}
diff --git a/p2p/util.go b/p2p/util.go
deleted file mode 100644
index 2c8f322a66..0000000000
--- a/p2p/util.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "container/heap"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-// expHeap tracks strings and their expiry time.
-type expHeap []expItem
-
-// expItem is an entry in addrHistory.
-type expItem struct {
- item string
- exp mclock.AbsTime
-}
-
-// nextExpiry returns the next expiry time.
-func (h *expHeap) nextExpiry() mclock.AbsTime {
- return (*h)[0].exp
-}
-
-// add adds an item and sets its expiry time.
-func (h *expHeap) add(item string, exp mclock.AbsTime) {
- heap.Push(h, expItem{item, exp})
-}
-
-// contains checks whether an item is present.
-func (h expHeap) contains(item string) bool {
- for _, v := range h {
- if v.item == item {
- return true
- }
- }
- return false
-}
-
-// expire removes items with expiry time before 'now'.
-func (h *expHeap) expire(now mclock.AbsTime, onExp func(string)) {
- for h.Len() > 0 && h.nextExpiry() < now {
- item := heap.Pop(h)
- if onExp != nil {
- onExp(item.(expItem).item)
- }
- }
-}
-
-// heap.Interface boilerplate
-func (h expHeap) Len() int { return len(h) }
-func (h expHeap) Less(i, j int) bool { return h[i].exp < h[j].exp }
-func (h expHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
-func (h *expHeap) Push(x interface{}) { *h = append(*h, x.(expItem)) }
-func (h *expHeap) Pop() interface{} {
- old := *h
- n := len(old)
- x := old[n-1]
- old[n-1] = expItem{}
- *h = old[0 : n-1]
- return x
-}
diff --git a/p2p/util_test.go b/p2p/util_test.go
deleted file mode 100644
index cc0d2b215f..0000000000
--- a/p2p/util_test.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package p2p
-
-import (
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common/mclock"
-)
-
-func TestExpHeap(t *testing.T) {
- var h expHeap
-
- var (
- basetime = mclock.AbsTime(10)
- exptimeA = basetime.Add(2 * time.Second)
- exptimeB = basetime.Add(3 * time.Second)
- exptimeC = basetime.Add(4 * time.Second)
- )
- h.add("b", exptimeB)
- h.add("a", exptimeA)
- h.add("c", exptimeC)
-
- if h.nextExpiry() != exptimeA {
- t.Fatal("wrong nextExpiry")
- }
- if !h.contains("a") || !h.contains("b") || !h.contains("c") {
- t.Fatal("heap doesn't contain all live items")
- }
-
- h.expire(exptimeA.Add(1), nil)
- if h.nextExpiry() != exptimeB {
- t.Fatal("wrong nextExpiry")
- }
- if h.contains("a") {
- t.Fatal("heap contains a even though it has already expired")
- }
- if !h.contains("b") || !h.contains("c") {
- t.Fatal("heap doesn't contain all live items")
- }
-}