aboutsummaryrefslogtreecommitdiffstats
path: root/p2p
diff options
context:
space:
mode:
authorFelix Lange <fjl@users.noreply.github.com>2018-09-25 06:59:00 +0800
committerGitHub <noreply@github.com>2018-09-25 06:59:00 +0800
commit30cd5c18549f645002aedb4c00e5bab683cb0835 (patch)
tree9a9098c6ff5a746758660295dfc1880d22e75434 /p2p
parent0ae462fb80b8a95e38af08d894ea9ecf9e45f2e7 (diff)
downloaddexon-30cd5c18549f645002aedb4c00e5bab683cb0835.tar
dexon-30cd5c18549f645002aedb4c00e5bab683cb0835.tar.gz
dexon-30cd5c18549f645002aedb4c00e5bab683cb0835.tar.bz2
dexon-30cd5c18549f645002aedb4c00e5bab683cb0835.tar.lz
dexon-30cd5c18549f645002aedb4c00e5bab683cb0835.tar.xz
dexon-30cd5c18549f645002aedb4c00e5bab683cb0835.tar.zst
dexon-30cd5c18549f645002aedb4c00e5bab683cb0835.zip
all: new p2p node representation (#17643)
Package p2p/enode provides a generalized representation of p2p nodes which can contain arbitrary information in key/value pairs. It is also the new home for the node database. The "v4" identity scheme is also moved here from p2p/enr to remove the dependency on Ethereum crypto from that package. Record signature handling is changed significantly. The identity scheme registry is removed and acceptable schemes must be passed to any method that needs identity. This means records must now be validated explicitly after decoding. The enode API is designed to make signature handling easy and safe: most APIs around the codebase work with enode.Node, which is a wrapper around a valid record. Going from enr.Record to enode.Node requires a valid signature. * p2p/discover: port to p2p/enode This ports the discovery code to the new node representation in p2p/enode. The wire protocol is unchanged, this can be considered a refactoring change. The Kademlia table can now deal with nodes using an arbitrary identity scheme. This requires a few incompatible API changes: - Table.Lookup is not available anymore. It used to take a public key as argument because v4 protocol requires one. Its replacement is LookupRandom. - Table.Resolve takes *enode.Node instead of NodeID. This is also for v4 protocol compatibility because nodes cannot be looked up by ID alone. - Types Node and NodeID are gone. Further commits in the series will be fixes all over the the codebase to deal with those removals. * p2p: port to p2p/enode and discovery changes This adapts package p2p to the changes in p2p/discover. All uses of discover.Node and discover.NodeID are replaced by their equivalents from p2p/enode. New API is added to retrieve the enode.Node instance of a peer. The behavior of Server.Self with discovery disabled is improved. It now tries much harder to report a working IP address, falling back to 127.0.0.1 if no suitable address can be determined through other means. These changes were needed for tests of other packages later in the series. * p2p/simulations, p2p/testing: port to p2p/enode No surprises here, mostly replacements of discover.Node, discover.NodeID with their new equivalents. The 'interesting' API changes are: - testing.ProtocolSession tracks complete nodes, not just their IDs. - adapters.NodeConfig has a new method to create a complete node. These changes were needed to make swarm tests work. Note that the NodeID change makes the code incompatible with old simulation snapshots. * whisper/whisperv5, whisper/whisperv6: port to p2p/enode This port was easy because whisper uses []byte for node IDs and URL strings in the API. * eth: port to p2p/enode Again, easy to port because eth uses strings for node IDs and doesn't care about node information in any way. * les: port to p2p/enode Apart from replacing discover.NodeID with enode.ID, most changes are in the server pool code. It now deals with complete nodes instead of (Pubkey, IP, Port) triples. The database format is unchanged for now, but we should probably change it to use the node database later. * node: port to p2p/enode This change simply replaces discover.Node and discover.NodeID with their new equivalents. * swarm/network: port to p2p/enode Swarm has its own node address representation, BzzAddr, containing both an overlay address (the hash of a secp256k1 public key) and an underlay address (enode:// URL). There are no changes to the BzzAddr format in this commit, but certain operations such as creating a BzzAddr from a node ID are now impossible because node IDs aren't public keys anymore. Most swarm-related changes in the series remove uses of NewAddrFromNodeID, replacing it with NewAddr which takes a complete node as argument. ToOverlayAddr is removed because we can just use the node ID directly.
Diffstat (limited to 'p2p')
-rw-r--r--p2p/dial.go102
-rw-r--r--p2p/dial_test.go443
-rw-r--r--p2p/discover/node.go422
-rw-r--r--p2p/discover/table.go224
-rw-r--r--p2p/discover/table_test.go560
-rw-r--r--p2p/discover/table_util_test.go167
-rw-r--r--p2p/discover/udp.go122
-rw-r--r--p2p/discover/udp_test.go82
-rw-r--r--p2p/enode/idscheme.go (renamed from p2p/enr/idscheme.go)126
-rw-r--r--p2p/enode/idscheme_test.go74
-rw-r--r--p2p/enode/node.go248
-rw-r--r--p2p/enode/node_test.go62
-rw-r--r--p2p/enode/nodedb.go (renamed from p2p/discover/database.go)159
-rw-r--r--p2p/enode/nodedb_test.go (renamed from p2p/discover/database_test.go)218
-rw-r--r--p2p/enode/urlv4.go194
-rw-r--r--p2p/enode/urlv4_test.go (renamed from p2p/discover/node_test.go)190
-rw-r--r--p2p/enr/enr.go168
-rw-r--r--p2p/enr/enr_test.go124
-rw-r--r--p2p/enr/entries.go26
-rw-r--r--p2p/enr/idscheme_test.go36
-rw-r--r--p2p/message.go6
-rw-r--r--p2p/peer.go36
-rw-r--r--p2p/peer_test.go4
-rw-r--r--p2p/protocol.go4
-rw-r--r--p2p/protocols/protocol_test.go44
-rw-r--r--p2p/rlpx.go55
-rw-r--r--p2p/rlpx_test.go43
-rw-r--r--p2p/server.go197
-rw-r--r--p2p/server_test.go130
-rw-r--r--p2p/simulations/adapters/docker.go4
-rw-r--r--p2p/simulations/adapters/exec.go10
-rw-r--r--p2p/simulations/adapters/inproc.go28
-rw-r--r--p2p/simulations/adapters/types.go19
-rw-r--r--p2p/simulations/examples/ping-pong.go6
-rw-r--r--p2p/simulations/http.go8
-rw-r--r--p2p/simulations/http_test.go16
-rw-r--r--p2p/simulations/mocker.go8
-rw-r--r--p2p/simulations/mocker_test.go4
-rw-r--r--p2p/simulations/network.go64
-rw-r--r--p2p/simulations/network_test.go10
-rw-r--r--p2p/simulations/simulation.go14
-rw-r--r--p2p/testing/peerpool.go12
-rw-r--r--p2p/testing/protocolsession.go34
-rw-r--r--p2p/testing/protocoltester.go12
44 files changed, 2388 insertions, 2127 deletions
diff --git a/p2p/dial.go b/p2p/dial.go
index 4baca7d93..359cdbcbb 100644
--- a/p2p/dial.go
+++ b/p2p/dial.go
@@ -18,14 +18,13 @@ package p2p
import (
"container/heap"
- "crypto/rand"
"errors"
"fmt"
"net"
"time"
"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/netutil"
)
@@ -50,7 +49,7 @@ const (
// 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(*discover.Node) (net.Conn, error)
+ Dial(*enode.Node) (net.Conn, error)
}
// TCPDialer implements the NodeDialer interface by using a net.Dialer to
@@ -60,8 +59,8 @@ type TCPDialer struct {
}
// Dial creates a TCP connection to the node
-func (t TCPDialer) Dial(dest *discover.Node) (net.Conn, error) {
- addr := &net.TCPAddr{IP: dest.IP, Port: int(dest.TCP)}
+func (t TCPDialer) Dial(dest *enode.Node) (net.Conn, error) {
+ addr := &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()}
return t.Dialer.Dial("tcp", addr.String())
}
@@ -74,22 +73,22 @@ type dialstate struct {
netrestrict *netutil.Netlist
lookupRunning bool
- dialing map[discover.NodeID]connFlag
- lookupBuf []*discover.Node // current discovery lookup results
- randomNodes []*discover.Node // filled from Table
- static map[discover.NodeID]*dialTask
+ dialing map[enode.ID]connFlag
+ lookupBuf []*enode.Node // current discovery lookup results
+ randomNodes []*enode.Node // filled from Table
+ static map[enode.ID]*dialTask
hist *dialHistory
- start time.Time // time when the dialer was first used
- bootnodes []*discover.Node // default dials when there are no peers
+ start time.Time // time when the dialer was first used
+ bootnodes []*enode.Node // default dials when there are no peers
}
type discoverTable interface {
- Self() *discover.Node
+ Self() *enode.Node
Close()
- Resolve(target discover.NodeID) *discover.Node
- Lookup(target discover.NodeID) []*discover.Node
- ReadRandomNodes([]*discover.Node) int
+ Resolve(*enode.Node) *enode.Node
+ LookupRandom() []*enode.Node
+ ReadRandomNodes([]*enode.Node) int
}
// the dial history remembers recent dials.
@@ -97,7 +96,7 @@ type dialHistory []pastDial
// pastDial is an entry in the dial history.
type pastDial struct {
- id discover.NodeID
+ id enode.ID
exp time.Time
}
@@ -109,7 +108,7 @@ type task interface {
// fields cannot be accessed while the task is running.
type dialTask struct {
flags connFlag
- dest *discover.Node
+ dest *enode.Node
lastResolved time.Time
resolveDelay time.Duration
}
@@ -118,7 +117,7 @@ type dialTask struct {
// Only one discoverTask is active at any time.
// discoverTask.Do performs a random lookup.
type discoverTask struct {
- results []*discover.Node
+ results []*enode.Node
}
// A waitExpireTask is generated if there are no other tasks
@@ -127,15 +126,15 @@ type waitExpireTask struct {
time.Duration
}
-func newDialState(static []*discover.Node, bootnodes []*discover.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist) *dialstate {
+func newDialState(static []*enode.Node, bootnodes []*enode.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist) *dialstate {
s := &dialstate{
maxDynDials: maxdyn,
ntab: ntab,
netrestrict: netrestrict,
- static: make(map[discover.NodeID]*dialTask),
- dialing: make(map[discover.NodeID]connFlag),
- bootnodes: make([]*discover.Node, len(bootnodes)),
- randomNodes: make([]*discover.Node, maxdyn/2),
+ static: make(map[enode.ID]*dialTask),
+ dialing: make(map[enode.ID]connFlag),
+ bootnodes: make([]*enode.Node, len(bootnodes)),
+ randomNodes: make([]*enode.Node, maxdyn/2),
hist: new(dialHistory),
}
copy(s.bootnodes, bootnodes)
@@ -145,32 +144,32 @@ func newDialState(static []*discover.Node, bootnodes []*discover.Node, ntab disc
return s
}
-func (s *dialstate) addStatic(n *discover.Node) {
+func (s *dialstate) addStatic(n *enode.Node) {
// This overwrites the task instead of updating an existing
// entry, giving users the opportunity to force a resolve operation.
- s.static[n.ID] = &dialTask{flags: staticDialedConn, dest: n}
+ s.static[n.ID()] = &dialTask{flags: staticDialedConn, dest: n}
}
-func (s *dialstate) removeStatic(n *discover.Node) {
+func (s *dialstate) removeStatic(n *enode.Node) {
// This removes a task so future attempts to connect will not be made.
- delete(s.static, n.ID)
+ delete(s.static, n.ID())
// This removes a previous dial timestamp so that application
// can force a server to reconnect with chosen peer immediately.
- s.hist.remove(n.ID)
+ s.hist.remove(n.ID())
}
-func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now time.Time) []task {
+func (s *dialstate) newTasks(nRunning int, peers map[enode.ID]*Peer, now time.Time) []task {
if s.start.IsZero() {
s.start = now
}
var newtasks []task
- addDial := func(flag connFlag, n *discover.Node) bool {
+ addDial := func(flag connFlag, n *enode.Node) bool {
if err := s.checkDial(n, peers); err != nil {
- log.Trace("Skipping dial candidate", "id", n.ID, "addr", &net.TCPAddr{IP: n.IP, Port: int(n.TCP)}, "err", err)
+ log.Trace("Skipping dial candidate", "id", n.ID(), "addr", &net.TCPAddr{IP: n.IP(), Port: n.TCP()}, "err", err)
return false
}
- s.dialing[n.ID] = flag
+ s.dialing[n.ID()] = flag
newtasks = append(newtasks, &dialTask{flags: flag, dest: n})
return true
}
@@ -196,8 +195,8 @@ func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now
err := s.checkDial(t.dest, peers)
switch err {
case errNotWhitelisted, errSelf:
- log.Warn("Removing static dial candidate", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)}, "err", err)
- delete(s.static, t.dest.ID)
+ log.Warn("Removing static dial candidate", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()}, "err", err)
+ delete(s.static, t.dest.ID())
case nil:
s.dialing[id] = t.flags
newtasks = append(newtasks, t)
@@ -260,18 +259,18 @@ var (
errNotWhitelisted = errors.New("not contained in netrestrict whitelist")
)
-func (s *dialstate) checkDial(n *discover.Node, peers map[discover.NodeID]*Peer) error {
- _, dialing := s.dialing[n.ID]
+func (s *dialstate) checkDial(n *enode.Node, peers map[enode.ID]*Peer) error {
+ _, dialing := s.dialing[n.ID()]
switch {
case dialing:
return errAlreadyDialing
- case peers[n.ID] != nil:
+ case peers[n.ID()] != nil:
return errAlreadyConnected
- case s.ntab != nil && n.ID == s.ntab.Self().ID:
+ case s.ntab != nil && n.ID() == s.ntab.Self().ID():
return errSelf
- case s.netrestrict != nil && !s.netrestrict.Contains(n.IP):
+ case s.netrestrict != nil && !s.netrestrict.Contains(n.IP()):
return errNotWhitelisted
- case s.hist.contains(n.ID):
+ case s.hist.contains(n.ID()):
return errRecentlyDialed
}
return nil
@@ -280,8 +279,8 @@ func (s *dialstate) checkDial(n *discover.Node, peers map[discover.NodeID]*Peer)
func (s *dialstate) taskDone(t task, now time.Time) {
switch t := t.(type) {
case *dialTask:
- s.hist.add(t.dest.ID, now.Add(dialHistoryExpiration))
- delete(s.dialing, t.dest.ID)
+ s.hist.add(t.dest.ID(), now.Add(dialHistoryExpiration))
+ delete(s.dialing, t.dest.ID())
case *discoverTask:
s.lookupRunning = false
s.lookupBuf = append(s.lookupBuf, t.results...)
@@ -323,7 +322,7 @@ func (t *dialTask) resolve(srv *Server) bool {
if time.Since(t.lastResolved) < t.resolveDelay {
return false
}
- resolved := srv.ntab.Resolve(t.dest.ID)
+ resolved := srv.ntab.Resolve(t.dest)
t.lastResolved = time.Now()
if resolved == nil {
t.resolveDelay *= 2
@@ -336,7 +335,7 @@ func (t *dialTask) resolve(srv *Server) bool {
// The node was found.
t.resolveDelay = initialResolveDelay
t.dest = resolved
- log.Debug("Resolved node", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)})
+ log.Debug("Resolved node", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()})
return true
}
@@ -345,7 +344,7 @@ type dialError struct {
}
// dial performs the actual connection attempt.
-func (t *dialTask) dial(srv *Server, dest *discover.Node) error {
+func (t *dialTask) dial(srv *Server, dest *enode.Node) error {
fd, err := srv.Dialer.Dial(dest)
if err != nil {
return &dialError{err}
@@ -355,7 +354,8 @@ func (t *dialTask) dial(srv *Server, dest *discover.Node) error {
}
func (t *dialTask) String() string {
- return fmt.Sprintf("%v %x %v:%d", t.flags, t.dest.ID[:8], t.dest.IP, t.dest.TCP)
+ id := t.dest.ID()
+ return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], t.dest.IP(), t.dest.TCP())
}
func (t *discoverTask) Do(srv *Server) {
@@ -367,9 +367,7 @@ func (t *discoverTask) Do(srv *Server) {
time.Sleep(next.Sub(now))
}
srv.lastLookup = time.Now()
- var target discover.NodeID
- rand.Read(target[:])
- t.results = srv.ntab.Lookup(target)
+ t.results = srv.ntab.LookupRandom()
}
func (t *discoverTask) String() string {
@@ -391,11 +389,11 @@ func (t waitExpireTask) String() string {
func (h dialHistory) min() pastDial {
return h[0]
}
-func (h *dialHistory) add(id discover.NodeID, exp time.Time) {
+func (h *dialHistory) add(id enode.ID, exp time.Time) {
heap.Push(h, pastDial{id, exp})
}
-func (h *dialHistory) remove(id discover.NodeID) bool {
+func (h *dialHistory) remove(id enode.ID) bool {
for i, v := range *h {
if v.id == id {
heap.Remove(h, i)
@@ -404,7 +402,7 @@ func (h *dialHistory) remove(id discover.NodeID) bool {
}
return false
}
-func (h dialHistory) contains(id discover.NodeID) bool {
+func (h dialHistory) contains(id enode.ID) bool {
for _, v := range h {
if v.id == id {
return true
diff --git a/p2p/dial_test.go b/p2p/dial_test.go
index 2a7941fc6..2de2c5999 100644
--- a/p2p/dial_test.go
+++ b/p2p/dial_test.go
@@ -24,7 +24,8 @@ import (
"time"
"github.com/davecgh/go-spew/spew"
- "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/netutil"
)
@@ -48,10 +49,10 @@ func runDialTest(t *testing.T, test dialtest) {
vtime time.Time
running int
)
- pm := func(ps []*Peer) map[discover.NodeID]*Peer {
- m := make(map[discover.NodeID]*Peer)
+ pm := func(ps []*Peer) map[enode.ID]*Peer {
+ m := make(map[enode.ID]*Peer)
for _, p := range ps {
- m[p.rw.id] = p
+ m[p.ID()] = p
}
return m
}
@@ -69,6 +70,7 @@ func runDialTest(t *testing.T, test dialtest) {
t.Errorf("round %d: new tasks mismatch:\ngot %v\nwant %v\nstate: %v\nrunning: %v\n",
i, spew.Sdump(new), spew.Sdump(round.new), spew.Sdump(test.init), spew.Sdump(running))
}
+ t.Log("tasks:", spew.Sdump(new))
// Time advances by 16 seconds on every round.
vtime = vtime.Add(16 * time.Second)
@@ -76,13 +78,13 @@ func runDialTest(t *testing.T, test dialtest) {
}
}
-type fakeTable []*discover.Node
+type fakeTable []*enode.Node
-func (t fakeTable) Self() *discover.Node { return new(discover.Node) }
-func (t fakeTable) Close() {}
-func (t fakeTable) Lookup(discover.NodeID) []*discover.Node { return nil }
-func (t fakeTable) Resolve(discover.NodeID) *discover.Node { return nil }
-func (t fakeTable) ReadRandomNodes(buf []*discover.Node) int { return copy(buf, t) }
+func (t fakeTable) Self() *enode.Node { return new(enode.Node) }
+func (t fakeTable) Close() {}
+func (t fakeTable) LookupRandom() []*enode.Node { return nil }
+func (t fakeTable) Resolve(*enode.Node) *enode.Node { return nil }
+func (t fakeTable) ReadRandomNodes(buf []*enode.Node) int { return copy(buf, t) }
// This test checks that dynamic dials are launched from discovery results.
func TestDialStateDynDial(t *testing.T) {
@@ -92,63 +94,63 @@ func TestDialStateDynDial(t *testing.T) {
// A discovery query is launched.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
},
new: []task{&discoverTask{}},
},
// Dynamic dials are launched when it completes.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
},
done: []task{
- &discoverTask{results: []*discover.Node{
- {ID: uintID(2)}, // this one is already connected and not dialed.
- {ID: uintID(3)},
- {ID: uintID(4)},
- {ID: uintID(5)},
- {ID: uintID(6)}, // these are not tried because max dyn dials is 5
- {ID: uintID(7)}, // ...
+ &discoverTask{results: []*enode.Node{
+ newNode(uintID(2), nil), // this one is already connected and not dialed.
+ newNode(uintID(3), nil),
+ newNode(uintID(4), nil),
+ newNode(uintID(5), nil),
+ newNode(uintID(6), nil), // these are not tried because max dyn dials is 5
+ newNode(uintID(7), nil), // ...
}},
},
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
},
},
// Some of the dials complete but no new ones are launched yet because
// the sum of active dial count and dynamic peer count is == maxDynDials.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(3)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(4)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}},
},
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
},
},
// No new dial tasks are launched in the this round because
// maxDynDials has been reached.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(3)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(4)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}},
},
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
},
new: []task{
&waitExpireTask{Duration: 14 * time.Second},
@@ -158,14 +160,14 @@ func TestDialStateDynDial(t *testing.T) {
// results from last discovery lookup are reused.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(3)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(4)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(3), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}},
},
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(6)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(6), nil)},
},
},
// More peers (3,4) drop off and dial for ID 6 completes.
@@ -173,15 +175,15 @@ func TestDialStateDynDial(t *testing.T) {
// and a new one is spawned because more candidates are needed.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}},
},
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(6)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(6), nil)},
},
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(7), nil)},
&discoverTask{},
},
},
@@ -190,23 +192,23 @@ func TestDialStateDynDial(t *testing.T) {
// no new is started.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(7)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(7), nil)}},
},
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(7), nil)},
},
},
// Finish the running node discovery with an empty set. A new lookup
// should be immediately requested.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(0)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(5)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(7)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(0), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(5), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(7), nil)}},
},
done: []task{
&discoverTask{},
@@ -221,17 +223,17 @@ func TestDialStateDynDial(t *testing.T) {
// Tests that bootnodes are dialed if no peers are connectd, but not otherwise.
func TestDialStateDynDialBootnode(t *testing.T) {
- bootnodes := []*discover.Node{
- {ID: uintID(1)},
- {ID: uintID(2)},
- {ID: uintID(3)},
+ bootnodes := []*enode.Node{
+ newNode(uintID(1), nil),
+ newNode(uintID(2), nil),
+ newNode(uintID(3), nil),
}
table := fakeTable{
- {ID: uintID(4)},
- {ID: uintID(5)},
- {ID: uintID(6)},
- {ID: uintID(7)},
- {ID: uintID(8)},
+ newNode(uintID(4), nil),
+ newNode(uintID(5), nil),
+ newNode(uintID(6), nil),
+ newNode(uintID(7), nil),
+ newNode(uintID(8), nil),
}
runDialTest(t, dialtest{
init: newDialState(nil, bootnodes, table, 5, nil),
@@ -239,16 +241,16 @@ func TestDialStateDynDialBootnode(t *testing.T) {
// 2 dynamic dials attempted, bootnodes pending fallback interval
{
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
&discoverTask{},
},
},
// No dials succeed, bootnodes still pending fallback interval
{
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
},
},
// No dials succeed, bootnodes still pending fallback interval
@@ -256,51 +258,51 @@ func TestDialStateDynDialBootnode(t *testing.T) {
// No dials succeed, 2 dynamic dials attempted and 1 bootnode too as fallback interval was reached
{
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
},
},
// No dials succeed, 2nd bootnode is attempted
{
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
},
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)},
},
},
// No dials succeed, 3rd bootnode is attempted
{
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)},
},
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)},
},
},
// No dials succeed, 1st bootnode is attempted again, expired random nodes retried
{
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)},
},
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
},
},
// Random dial succeeds, no more bootnodes are attempted
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(4)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(4), nil)}},
},
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
},
},
},
@@ -311,14 +313,14 @@ func TestDialStateDynDialFromTable(t *testing.T) {
// This table always returns the same random nodes
// in the order given below.
table := fakeTable{
- {ID: uintID(1)},
- {ID: uintID(2)},
- {ID: uintID(3)},
- {ID: uintID(4)},
- {ID: uintID(5)},
- {ID: uintID(6)},
- {ID: uintID(7)},
- {ID: uintID(8)},
+ newNode(uintID(1), nil),
+ newNode(uintID(2), nil),
+ newNode(uintID(3), nil),
+ newNode(uintID(4), nil),
+ newNode(uintID(5), nil),
+ newNode(uintID(6), nil),
+ newNode(uintID(7), nil),
+ newNode(uintID(8), nil),
}
runDialTest(t, dialtest{
@@ -327,63 +329,63 @@ func TestDialStateDynDialFromTable(t *testing.T) {
// 5 out of 8 of the nodes returned by ReadRandomNodes are dialed.
{
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
&discoverTask{},
},
},
// Dialing nodes 1,2 succeeds. Dials from the lookup are launched.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
},
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
- &discoverTask{results: []*discover.Node{
- {ID: uintID(10)},
- {ID: uintID(11)},
- {ID: uintID(12)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(2), nil)},
+ &discoverTask{results: []*enode.Node{
+ newNode(uintID(10), nil),
+ newNode(uintID(11), nil),
+ newNode(uintID(12), nil),
}},
},
new: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(10), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(11), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(12), nil)},
&discoverTask{},
},
},
// Dialing nodes 3,4,5 fails. The dials from the lookup succeed.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(10)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(11)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(12)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}},
},
done: []task{
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}},
- &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(3), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(5), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(10), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(11), nil)},
+ &dialTask{flags: dynDialedConn, dest: newNode(uintID(12), nil)},
},
},
// Waiting for expiry. No waitExpireTask is launched because the
// discovery query is still running.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(10)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(11)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(12)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}},
},
},
// Nodes 3,4 are not tried again because only the first two
@@ -391,30 +393,38 @@ func TestDialStateDynDialFromTable(t *testing.T) {
// already connected.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(10)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(11)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(12)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(10), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(11), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(12), nil)}},
},
},
},
})
}
+func newNode(id enode.ID, ip net.IP) *enode.Node {
+ var r enr.Record
+ if ip != nil {
+ r.Set(enr.IP(ip))
+ }
+ return enode.SignNull(&r, id)
+}
+
// This test checks that candidates that do not match the netrestrict list are not dialed.
func TestDialStateNetRestrict(t *testing.T) {
// This table always returns the same random nodes
// in the order given below.
table := fakeTable{
- {ID: uintID(1), IP: net.ParseIP("127.0.0.1")},
- {ID: uintID(2), IP: net.ParseIP("127.0.0.2")},
- {ID: uintID(3), IP: net.ParseIP("127.0.0.3")},
- {ID: uintID(4), IP: net.ParseIP("127.0.0.4")},
- {ID: uintID(5), IP: net.ParseIP("127.0.2.5")},
- {ID: uintID(6), IP: net.ParseIP("127.0.2.6")},
- {ID: uintID(7), IP: net.ParseIP("127.0.2.7")},
- {ID: uintID(8), IP: net.ParseIP("127.0.2.8")},
+ newNode(uintID(1), net.ParseIP("127.0.0.1")),
+ newNode(uintID(2), net.ParseIP("127.0.0.2")),
+ newNode(uintID(3), net.ParseIP("127.0.0.3")),
+ newNode(uintID(4), net.ParseIP("127.0.0.4")),
+ newNode(uintID(5), net.ParseIP("127.0.2.5")),
+ newNode(uintID(6), net.ParseIP("127.0.2.6")),
+ newNode(uintID(7), net.ParseIP("127.0.2.7")),
+ newNode(uintID(8), net.ParseIP("127.0.2.8")),
}
restrict := new(netutil.Netlist)
restrict.Add("127.0.2.0/24")
@@ -434,12 +444,12 @@ func TestDialStateNetRestrict(t *testing.T) {
// This test checks that static dials are launched.
func TestDialStateStaticDial(t *testing.T) {
- wantStatic := []*discover.Node{
- {ID: uintID(1)},
- {ID: uintID(2)},
- {ID: uintID(3)},
- {ID: uintID(4)},
- {ID: uintID(5)},
+ wantStatic := []*enode.Node{
+ newNode(uintID(1), nil),
+ newNode(uintID(2), nil),
+ newNode(uintID(3), nil),
+ newNode(uintID(4), nil),
+ newNode(uintID(5), nil),
}
runDialTest(t, dialtest{
@@ -449,40 +459,40 @@ func TestDialStateStaticDial(t *testing.T) {
// aren't yet connected.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
},
new: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(5), nil)},
},
},
// No new tasks are launched in this round because all static
// nodes are either connected or still being dialed.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(3)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}},
},
done: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)},
},
},
// No new dial tasks are launched because all static
// nodes are now connected.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(3)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(4)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(5)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(4), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}},
},
done: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}},
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(5), nil)},
},
new: []task{
&waitExpireTask{Duration: 14 * time.Second},
@@ -491,24 +501,24 @@ func TestDialStateStaticDial(t *testing.T) {
// Wait a round for dial history to expire, no new tasks should spawn.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(3)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(4)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(5)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(4), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}},
},
},
// If a static node is dropped, it should be immediately redialed,
// irrespective whether it was originally static or dynamic.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(3)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(5)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(3), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(5), nil)}},
},
new: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(4), nil)},
},
},
},
@@ -517,9 +527,9 @@ func TestDialStateStaticDial(t *testing.T) {
// This test checks that static peers will be redialed immediately if they were re-added to a static list.
func TestDialStaticAfterReset(t *testing.T) {
- wantStatic := []*discover.Node{
- {ID: uintID(1)},
- {ID: uintID(2)},
+ wantStatic := []*enode.Node{
+ newNode(uintID(1), nil),
+ newNode(uintID(2), nil),
}
rounds := []round{
@@ -527,19 +537,19 @@ func TestDialStaticAfterReset(t *testing.T) {
{
peers: nil,
new: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)},
},
},
// No new dial tasks, all peers are connected.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(1)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(2)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(2), nil)}},
},
done: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)},
},
new: []task{
&waitExpireTask{Duration: 30 * time.Second},
@@ -561,10 +571,10 @@ func TestDialStaticAfterReset(t *testing.T) {
// This test checks that past dials are not retried for some time.
func TestDialStateCache(t *testing.T) {
- wantStatic := []*discover.Node{
- {ID: uintID(1)},
- {ID: uintID(2)},
- {ID: uintID(3)},
+ wantStatic := []*enode.Node{
+ newNode(uintID(1), nil),
+ newNode(uintID(2), nil),
+ newNode(uintID(3), nil),
}
runDialTest(t, dialtest{
@@ -575,32 +585,32 @@ func TestDialStateCache(t *testing.T) {
{
peers: nil,
new: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)},
},
},
// No new tasks are launched in this round because all static
// nodes are either connected or still being dialed.
{
peers: []*Peer{
- {rw: &conn{flags: staticDialedConn, id: uintID(1)}},
- {rw: &conn{flags: staticDialedConn, id: uintID(2)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: staticDialedConn, node: newNode(uintID(2), nil)}},
},
done: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(1), nil)},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(2), nil)},
},
},
// A salvage task is launched to wait for node 3's history
// entry to expire.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
},
done: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)},
},
new: []task{
&waitExpireTask{Duration: 14 * time.Second},
@@ -609,18 +619,18 @@ func TestDialStateCache(t *testing.T) {
// Still waiting for node 3's entry to expire in the cache.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
},
},
// The cache entry for node 3 has expired and is retried.
{
peers: []*Peer{
- {rw: &conn{flags: dynDialedConn, id: uintID(1)}},
- {rw: &conn{flags: dynDialedConn, id: uintID(2)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(1), nil)}},
+ {rw: &conn{flags: dynDialedConn, node: newNode(uintID(2), nil)}},
},
new: []task{
- &dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
+ &dialTask{flags: staticDialedConn, dest: newNode(uintID(3), nil)},
},
},
},
@@ -628,12 +638,12 @@ func TestDialStateCache(t *testing.T) {
}
func TestDialResolve(t *testing.T) {
- resolved := discover.NewNode(uintID(1), net.IP{127, 0, 55, 234}, 3333, 4444)
+ resolved := newNode(uintID(1), net.IP{127, 0, 55, 234})
table := &resolveMock{answer: resolved}
state := newDialState(nil, nil, table, 0, nil)
// Check that the task is generated with an incomplete ID.
- dest := discover.NewNode(uintID(1), nil, 0, 0)
+ dest := newNode(uintID(1), nil)
state.addStatic(dest)
tasks := state.newTasks(0, nil, time.Time{})
if !reflect.DeepEqual(tasks, []task{&dialTask{flags: staticDialedConn, dest: dest}}) {
@@ -644,7 +654,7 @@ func TestDialResolve(t *testing.T) {
config := Config{Dialer: TCPDialer{&net.Dialer{Deadline: time.Now().Add(-5 * time.Minute)}}}
srv := &Server{ntab: table, Config: config}
tasks[0].Do(srv)
- if !reflect.DeepEqual(table.resolveCalls, []discover.NodeID{dest.ID}) {
+ if !reflect.DeepEqual(table.resolveCalls, []*enode.Node{dest}) {
t.Fatalf("wrong resolve calls, got %v", table.resolveCalls)
}
@@ -672,25 +682,24 @@ next:
return true
}
-func uintID(i uint32) discover.NodeID {
- var id discover.NodeID
+func uintID(i uint32) enode.ID {
+ var id enode.ID
binary.BigEndian.PutUint32(id[:], i)
return id
}
// implements discoverTable for TestDialResolve
type resolveMock struct {
- resolveCalls []discover.NodeID
- answer *discover.Node
+ resolveCalls []*enode.Node
+ answer *enode.Node
}
-func (t *resolveMock) Resolve(id discover.NodeID) *discover.Node {
- t.resolveCalls = append(t.resolveCalls, id)
+func (t *resolveMock) Resolve(n *enode.Node) *enode.Node {
+ t.resolveCalls = append(t.resolveCalls, n)
return t.answer
}
-func (t *resolveMock) Self() *discover.Node { return new(discover.Node) }
-func (t *resolveMock) Close() {}
-func (t *resolveMock) Bootstrap([]*discover.Node) {}
-func (t *resolveMock) Lookup(discover.NodeID) []*discover.Node { return nil }
-func (t *resolveMock) ReadRandomNodes(buf []*discover.Node) int { return 0 }
+func (t *resolveMock) Self() *enode.Node { return new(enode.Node) }
+func (t *resolveMock) Close() {}
+func (t *resolveMock) LookupRandom() []*enode.Node { return nil }
+func (t *resolveMock) ReadRandomNodes(buf []*enode.Node) int { return 0 }
diff --git a/p2p/discover/node.go b/p2p/discover/node.go
index 3b0c84115..7ddf04fe8 100644
--- a/p2p/discover/node.go
+++ b/p2p/discover/node.go
@@ -18,415 +18,87 @@ package discover
import (
"crypto/ecdsa"
- "crypto/elliptic"
- "encoding/hex"
"errors"
- "fmt"
"math/big"
- "math/rand"
"net"
- "net/url"
- "regexp"
- "strconv"
- "strings"
"time"
- "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
+ "github.com/ethereum/go-ethereum/p2p/enode"
)
-const NodeIDBits = 512
-
-// Node represents a host on the network.
+// node represents a host on the network.
// The fields of Node may not be modified.
-type Node struct {
- IP net.IP // len 4 for IPv4 or 16 for IPv6
- UDP, TCP uint16 // port numbers
- ID NodeID // the node's public key
-
- // This is a cached copy of sha3(ID) which is used for node
- // distance calculations. This is part of Node in order to make it
- // possible to write tests that need a node at a certain distance.
- // In those tests, the content of sha will not actually correspond
- // with ID.
- sha common.Hash
-
- // Time when the node was added to the table.
- addedAt time.Time
-}
-
-// NewNode creates a new node. It is mostly meant to be used for
-// testing purposes.
-func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node {
- if ipv4 := ip.To4(); ipv4 != nil {
- ip = ipv4
- }
- return &Node{
- IP: ip,
- UDP: udpPort,
- TCP: tcpPort,
- ID: id,
- sha: crypto.Keccak256Hash(id[:]),
- }
-}
-
-func (n *Node) addr() *net.UDPAddr {
- return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)}
-}
-
-// Incomplete returns true for nodes with no IP address.
-func (n *Node) Incomplete() bool {
- return n.IP == nil
-}
-
-// checks whether n is a valid complete node.
-func (n *Node) validateComplete() error {
- if n.Incomplete() {
- return errors.New("incomplete node")
- }
- if n.UDP == 0 {
- return errors.New("missing UDP port")
- }
- if n.TCP == 0 {
- return errors.New("missing TCP port")
- }
- if n.IP.IsMulticast() || n.IP.IsUnspecified() {
- return errors.New("invalid IP (multicast/unspecified)")
- }
- _, err := n.ID.Pubkey() // validate the key (on curve, etc.)
- return err
-}
-
-// The string representation of a Node is a URL.
-// Please see ParseNode for a description of the format.
-func (n *Node) String() string {
- u := url.URL{Scheme: "enode"}
- if n.Incomplete() {
- u.Host = fmt.Sprintf("%x", n.ID[:])
- } else {
- addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)}
- u.User = url.User(fmt.Sprintf("%x", n.ID[:]))
- u.Host = addr.String()
- if n.UDP != n.TCP {
- u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP))
- }
- }
- return u.String()
-}
-
-var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
-
-// ParseNode parses a node designator.
-//
-// There are two basic forms of node designators
-// - 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://<hex node id>
-// <hex node id>
-//
-// 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, DNS domain names are not allowed.
-// 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://<hex node id>@10.3.58.6:30303?discport=30301
-func ParseNode(rawurl string) (*Node, error) {
- if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
- id, err := HexID(m[1])
- if err != nil {
- return nil, fmt.Errorf("invalid node ID (%v)", err)
- }
- return NewNode(id, nil, 0, 0), nil
- }
- return parseComplete(rawurl)
+type node struct {
+ enode.Node
+ addedAt time.Time // time when the node was added to the table
}
-func parseComplete(rawurl string) (*Node, error) {
- var (
- id NodeID
- ip net.IP
- 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 = HexID(u.User.String()); err != nil {
- return nil, fmt.Errorf("invalid node ID (%v)", err)
- }
- // Parse the IP address.
- host, port, err := net.SplitHostPort(u.Host)
- if err != nil {
- return nil, fmt.Errorf("invalid host: %v", err)
- }
- if ip = net.ParseIP(host); ip == nil {
- return nil, errors.New("invalid IP address")
- }
- // 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(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 NewNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil
-}
-
-// MustParseNode parses a node URL. It panics if the URL is not valid.
-func MustParseNode(rawurl string) *Node {
- n, err := ParseNode(rawurl)
- if err != nil {
- panic("invalid node URL: " + err.Error())
- }
- return n
-}
+type encPubkey [64]byte
-// MarshalText implements encoding.TextMarshaler.
-func (n *Node) MarshalText() ([]byte, error) {
- return []byte(n.String()), nil
+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
}
-// UnmarshalText implements encoding.TextUnmarshaler.
-func (n *Node) UnmarshalText(text []byte) error {
- dec, err := ParseNode(string(text))
- if err == nil {
- *n = *dec
- }
- return err
-}
-
-// NodeID is a unique identifier for each node.
-// The node identifier is a marshaled elliptic curve public key.
-type NodeID [NodeIDBits / 8]byte
-
-// Bytes returns a byte slice representation of the NodeID
-func (n NodeID) Bytes() []byte {
- return n[:]
-}
-
-// NodeID prints as a long hexadecimal number.
-func (n NodeID) String() string {
- return fmt.Sprintf("%x", n[:])
-}
-
-// The Go syntax representation of a NodeID is a call to HexID.
-func (n NodeID) GoString() string {
- return fmt.Sprintf("discover.HexID(\"%x\")", n[:])
-}
-
-// TerminalString returns a shortened hex string for terminal logging.
-func (n NodeID) TerminalString() string {
- return hex.EncodeToString(n[:8])
-}
-
-// MarshalText implements the encoding.TextMarshaler interface.
-func (n NodeID) MarshalText() ([]byte, error) {
- return []byte(hex.EncodeToString(n[:])), nil
-}
-
-// UnmarshalText implements the encoding.TextUnmarshaler interface.
-func (n *NodeID) UnmarshalText(text []byte) error {
- id, err := HexID(string(text))
- if err != nil {
- return err
- }
- *n = id
- return nil
-}
-
-// BytesID converts a byte slice to a NodeID
-func BytesID(b []byte) (NodeID, error) {
- var id NodeID
- if len(b) != len(id) {
- return id, fmt.Errorf("wrong length, want %d bytes", len(id))
- }
- copy(id[:], b)
- return id, nil
-}
-
-// MustBytesID converts a byte slice to a NodeID.
-// It panics if the byte slice is not a valid NodeID.
-func MustBytesID(b []byte) NodeID {
- id, err := BytesID(b)
- if err != nil {
- panic(err)
+func decodePubkey(e encPubkey) (*ecdsa.PublicKey, error) {
+ p := &ecdsa.PublicKey{Curve: crypto.S256(), 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 secp256k1 curve point")
}
- return id
+ return p, nil
}
-// HexID converts a hex string to a NodeID.
-// The string may be prefixed with 0x.
-func HexID(in string) (NodeID, error) {
- var id NodeID
- 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
+func (e encPubkey) id() enode.ID {
+ return enode.ID(crypto.Keccak256Hash(e[:]))
}
-// MustHexID converts a hex string to a NodeID.
-// It panics if the string is not a valid NodeID.
-func MustHexID(in string) NodeID {
- id, err := HexID(in)
+// recoverNodeKey computes the public key used to sign the
+// given hash from the signature.
+func recoverNodeKey(hash, sig []byte) (key encPubkey, err error) {
+ pubkey, err := secp256k1.RecoverPubkey(hash, sig)
if err != nil {
- panic(err)
+ return key, err
}
- return id
+ copy(key[:], pubkey[1:])
+ return key, nil
}
-// PubkeyID returns a marshaled representation of the given public key.
-func PubkeyID(pub *ecdsa.PublicKey) NodeID {
- var id NodeID
- pbytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y)
- if len(pbytes)-1 != len(id) {
- panic(fmt.Errorf("need %d bit pubkey, got %d bits", (len(id)+1)*8, len(pbytes)))
- }
- copy(id[:], pbytes[1:])
- return id
+func wrapNode(n *enode.Node) *node {
+ return &node{Node: *n}
}
-// Pubkey returns the public key represented by the node ID.
-// It returns an error if the ID is not a point on the curve.
-func (id NodeID) Pubkey() (*ecdsa.PublicKey, error) {
- p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)}
- half := len(id) / 2
- p.X.SetBytes(id[:half])
- p.Y.SetBytes(id[half:])
- if !p.Curve.IsOnCurve(p.X, p.Y) {
- return nil, errors.New("id is invalid secp256k1 curve point")
+func wrapNodes(ns []*enode.Node) []*node {
+ result := make([]*node, len(ns))
+ for i, n := range ns {
+ result[i] = wrapNode(n)
}
- return p, nil
+ return result
}
-// recoverNodeID computes the public key used to sign the
-// given hash from the signature.
-func recoverNodeID(hash, sig []byte) (id NodeID, err error) {
- pubkey, err := secp256k1.RecoverPubkey(hash, sig)
- if err != nil {
- return id, err
- }
- if len(pubkey)-1 != len(id) {
- return id, fmt.Errorf("recovered pubkey has %d bits, want %d bits", len(pubkey)*8, (len(id)+1)*8)
- }
- for i := range id {
- id[i] = pubkey[i+1]
- }
- return id, nil
+func unwrapNode(n *node) *enode.Node {
+ return &n.Node
}
-// 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 common.Hash) 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
- }
+func unwrapNodes(ns []*node) []*enode.Node {
+ result := make([]*enode.Node, len(ns))
+ for i, n := range ns {
+ result[i] = unwrapNode(n)
}
- return 0
+ return result
}
-// table of leading zero counts for bytes [0..255]
-var lzcount = [256]int{
- 8, 7, 6, 6, 5, 5, 5, 5,
- 4, 4, 4, 4, 4, 4, 4, 4,
- 3, 3, 3, 3, 3, 3, 3, 3,
- 3, 3, 3, 3, 3, 3, 3, 3,
- 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2,
- 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,
- 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,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
+func (n *node) addr() *net.UDPAddr {
+ return &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
}
-// logdist returns the logarithmic distance between a and b, log2(a ^ b).
-func logdist(a, b common.Hash) int {
- lz := 0
- for i := range a {
- x := a[i] ^ b[i]
- if x == 0 {
- lz += 8
- } else {
- lz += lzcount[x]
- break
- }
- }
- return len(a)*8 - lz
-}
-
-// hashAtDistance returns a random hash such that logdist(a, b) == n
-func hashAtDistance(a common.Hash, n int) (b common.Hash) {
- 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 (n *node) String() string {
+ return n.Node.String()
}
diff --git a/p2p/discover/table.go b/p2p/discover/table.go
index a130b5494..7a3e41de1 100644
--- a/p2p/discover/table.go
+++ b/p2p/discover/table.go
@@ -23,6 +23,7 @@
package discover
import (
+ "crypto/ecdsa"
crand "crypto/rand"
"encoding/binary"
"fmt"
@@ -35,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"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/netutil"
)
@@ -65,49 +67,44 @@ const (
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
+ nursery []*node // bootstrap nodes
rand *mrand.Rand // source of randomness, periodically reseeded
ips netutil.DistinctNetSet
- db *nodeDB // database of known nodes
+ db *enode.DB // database of known nodes
refreshReq chan chan struct{}
initDone chan struct{}
closeReq chan struct{}
closed chan struct{}
- nodeAddedHook func(*Node) // for testing
+ nodeAddedHook func(*node) // for testing
net transport
- self *Node // metadata of the local node
+ self *node // metadata of the local node
}
// transport is implemented by the UDP transport.
// it is an interface so we can test without opening lots of UDP
// sockets and without generating a private key.
type transport interface {
- ping(NodeID, *net.UDPAddr) error
- findnode(toid NodeID, addr *net.UDPAddr, target NodeID) ([]*Node, error)
+ ping(enode.ID, *net.UDPAddr) error
+ findnode(toid enode.ID, addr *net.UDPAddr, target encPubkey) ([]*node, error)
close()
}
// 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
+ entries []*node // live entries, sorted by time of last contact
+ replacements []*node // recently seen nodes to be used if revalidation fails
ips netutil.DistinctNetSet
}
-func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string, bootnodes []*Node) (*Table, error) {
- // If no node database was given, use an in-memory one
- db, err := newNodeDB(nodeDBPath, nodeDBVersion, ourID)
- if err != nil {
- return nil, err
- }
+func newTable(t transport, self *enode.Node, db *enode.DB, bootnodes []*enode.Node) (*Table, error) {
tab := &Table{
net: t,
db: db,
- self: NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)),
+ self: wrapNode(self),
refreshReq: make(chan chan struct{}),
initDone: make(chan struct{}),
closeReq: make(chan struct{}),
@@ -125,10 +122,7 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string
}
tab.seedRand()
tab.loadSeedNodes()
- // Start the background expiration goroutine after loading seeds so that the search for
- // seed nodes also considers older nodes that would otherwise be removed by the
- // expiration.
- tab.db.ensureExpirer()
+
go tab.loop()
return tab, nil
}
@@ -143,15 +137,13 @@ func (tab *Table) seedRand() {
}
// Self returns the local node.
-// The returned node should not be modified by the caller.
-func (tab *Table) Self() *Node {
- return tab.self
+func (tab *Table) Self() *enode.Node {
+ return unwrapNode(tab.self)
}
-// ReadRandomNodes fills the given slice with random nodes from the
-// table. It will not write the same node more than once. The nodes in
-// the slice are copies and can be modified by the caller.
-func (tab *Table) ReadRandomNodes(buf []*Node) (n int) {
+// ReadRandomNodes fills the given slice with random nodes from the table. The results
+// are guaranteed to be unique for a single invocation, no node will appear twice.
+func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) {
if !tab.isInitDone() {
return 0
}
@@ -159,7 +151,7 @@ func (tab *Table) ReadRandomNodes(buf []*Node) (n int) {
defer tab.mutex.Unlock()
// Find all non-empty buckets and get a fresh slice of their entries.
- var buckets [][]*Node
+ var buckets [][]*node
for _, b := range &tab.buckets {
if len(b.entries) > 0 {
buckets = append(buckets, b.entries)
@@ -177,7 +169,7 @@ func (tab *Table) ReadRandomNodes(buf []*Node) (n int) {
var i, j int
for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) {
b := buckets[j]
- buf[i] = &(*b[0])
+ buf[i] = unwrapNode(b[0])
buckets[j] = b[1:]
if len(b) == 1 {
buckets = append(buckets[:j], buckets[j+1:]...)
@@ -202,20 +194,13 @@ func (tab *Table) Close() {
// 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 []*Node) error {
+func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
for _, n := range nodes {
- if err := n.validateComplete(); err != nil {
- return fmt.Errorf("bad bootstrap/fallback node %q (%v)", n, err)
+ if err := n.ValidateComplete(); err != nil {
+ return fmt.Errorf("bad bootstrap node %q: %v", n, err)
}
}
- tab.nursery = make([]*Node, 0, len(nodes))
- for _, n := range nodes {
- cpy := *n
- // Recompute cpy.sha because the node might not have been
- // created by NewNode or ParseNode.
- cpy.sha = crypto.Keccak256Hash(n.ID[:])
- tab.nursery = append(tab.nursery, &cpy)
- }
+ tab.nursery = wrapNodes(nodes)
return nil
}
@@ -231,47 +216,48 @@ func (tab *Table) isInitDone() bool {
// Resolve searches for a specific node with the given ID.
// It returns nil if the node could not be found.
-func (tab *Table) Resolve(targetID NodeID) *Node {
+func (tab *Table) Resolve(n *enode.Node) *enode.Node {
// If the node is present in the local table, no
// network interaction is required.
- hash := crypto.Keccak256Hash(targetID[:])
+ hash := n.ID()
tab.mutex.Lock()
cl := tab.closest(hash, 1)
tab.mutex.Unlock()
- if len(cl.entries) > 0 && cl.entries[0].ID == targetID {
- return cl.entries[0]
+ if len(cl.entries) > 0 && cl.entries[0].ID() == hash {
+ return unwrapNode(cl.entries[0])
}
// Otherwise, do a network lookup.
- result := tab.Lookup(targetID)
+ result := tab.lookup(encodePubkey(n.Pubkey()), true)
for _, n := range result {
- if n.ID == targetID {
- return n
+ if n.ID() == hash {
+ return unwrapNode(n)
}
}
return nil
}
-// 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.
-func (tab *Table) Lookup(targetID NodeID) []*Node {
- return tab.lookup(targetID, true)
+// LookupRandom finds random nodes in the network.
+func (tab *Table) LookupRandom() []*enode.Node {
+ var target encPubkey
+ crand.Read(target[:])
+ return unwrapNodes(tab.lookup(target, true))
}
-func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node {
+// 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.
+func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node {
var (
- target = crypto.Keccak256Hash(targetID[:])
- asked = make(map[NodeID]bool)
- seen = make(map[NodeID]bool)
- reply = make(chan []*Node, alpha)
+ target = enode.ID(crypto.Keccak256Hash(targetKey[:]))
+ asked = make(map[enode.ID]bool)
+ seen = make(map[enode.ID]bool)
+ reply = make(chan []*node, alpha)
pendingQueries = 0
result *nodesByDistance
)
// don't query further if we hit ourself.
// unlikely to happen often in practice.
- asked[tab.self.ID] = true
+ asked[tab.self.ID()] = true
for {
tab.mutex.Lock()
@@ -293,10 +279,10 @@ func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node {
// ask the alpha closest nodes that we haven't asked yet
for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ {
n := result.entries[i]
- if !asked[n.ID] {
- asked[n.ID] = true
+ if !asked[n.ID()] {
+ asked[n.ID()] = true
pendingQueries++
- go tab.findnode(n, targetID, reply)
+ go tab.findnode(n, targetKey, reply)
}
}
if pendingQueries == 0 {
@@ -305,8 +291,8 @@ func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node {
}
// wait for the next reply
for _, n := range <-reply {
- if n != nil && !seen[n.ID] {
- seen[n.ID] = true
+ if n != nil && !seen[n.ID()] {
+ seen[n.ID()] = true
result.push(n, bucketSize)
}
}
@@ -315,19 +301,19 @@ func (tab *Table) lookup(targetID NodeID, refreshIfEmpty bool) []*Node {
return result.entries
}
-func (tab *Table) findnode(n *Node, targetID NodeID, reply chan<- []*Node) {
- fails := tab.db.findFails(n.ID)
- r, err := tab.net.findnode(n.ID, n.addr(), targetID)
+func (tab *Table) findnode(n *node, targetKey encPubkey, reply chan<- []*node) {
+ fails := tab.db.FindFails(n.ID())
+ r, err := tab.net.findnode(n.ID(), n.addr(), targetKey)
if err != nil || len(r) == 0 {
fails++
- tab.db.updateFindFails(n.ID, fails)
- log.Trace("Findnode failed", "id", n.ID, "failcount", fails, "err", err)
+ tab.db.UpdateFindFails(n.ID(), fails)
+ log.Trace("Findnode failed", "id", n.ID(), "failcount", fails, "err", err)
if fails >= maxFindnodeFailures {
- log.Trace("Too many findnode failures, dropping", "id", n.ID, "failcount", fails)
+ log.Trace("Too many findnode failures, dropping", "id", n.ID(), "failcount", fails)
tab.delete(n)
}
} else if fails > 0 {
- tab.db.updateFindFails(n.ID, fails-1)
+ tab.db.UpdateFindFails(n.ID(), fails-1)
}
// Grab as many nodes as possible. Some of them might not be alive anymore, but we'll
@@ -405,7 +391,6 @@ loop:
for _, ch := range waiting {
close(ch)
}
- tab.db.close()
close(tab.closed)
}
@@ -421,7 +406,11 @@ func (tab *Table) doRefresh(done chan struct{}) {
tab.loadSeedNodes()
// Run self lookup to discover new neighbor nodes.
- tab.lookup(tab.self.ID, false)
+ // We can only do this if we have a secp256k1 identity.
+ var key ecdsa.PublicKey
+ if err := tab.self.Load((*enode.Secp256k1)(&key)); err == nil {
+ tab.lookup(encodePubkey(&key), false)
+ }
// The Kademlia paper specifies that the bucket refresh should
// perform a lookup in the least recently used bucket. We cannot
@@ -430,19 +419,19 @@ func (tab *Table) doRefresh(done chan struct{}) {
// sha3 preimage that falls into a chosen bucket.
// We perform a few lookups with a random target instead.
for i := 0; i < 3; i++ {
- var target NodeID
+ var target encPubkey
crand.Read(target[:])
tab.lookup(target, false)
}
}
func (tab *Table) loadSeedNodes() {
- seeds := tab.db.querySeeds(seedCount, seedMaxAge)
+ seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge))
seeds = append(seeds, tab.nursery...)
for i := range seeds {
seed := seeds[i]
- age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.lastPongReceived(seed.ID)) }}
- log.Debug("Found seed node in database", "id", seed.ID, "addr", seed.addr(), "age", age)
+ age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID())) }}
+ log.Debug("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
tab.add(seed)
}
}
@@ -459,28 +448,28 @@ func (tab *Table) doRevalidate(done chan<- struct{}) {
}
// Ping the selected node and wait for a pong.
- err := tab.net.ping(last.ID, last.addr())
+ err := tab.net.ping(last.ID(), last.addr())
tab.mutex.Lock()
defer tab.mutex.Unlock()
b := tab.buckets[bi]
if err == nil {
// The node responded, move it to the front.
- log.Trace("Revalidated node", "b", bi, "id", last.ID)
+ log.Debug("Revalidated node", "b", bi, "id", last.ID())
b.bump(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 {
- log.Trace("Replaced dead node", "b", bi, "id", last.ID, "ip", last.IP, "r", r.ID, "rip", r.IP)
+ log.Debug("Replaced dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "r", r.ID(), "rip", r.IP())
} else {
- log.Trace("Removed dead node", "b", bi, "id", last.ID, "ip", last.IP)
+ log.Debug("Removed dead node", "b", bi, "id", last.ID(), "ip", last.IP())
}
}
// nodeToRevalidate returns the last node in a random, non-empty bucket.
-func (tab *Table) nodeToRevalidate() (n *Node, bi int) {
+func (tab *Table) nodeToRevalidate() (n *node, bi int) {
tab.mutex.Lock()
defer tab.mutex.Unlock()
@@ -511,7 +500,7 @@ func (tab *Table) copyLiveNodes() {
for _, b := range &tab.buckets {
for _, n := range b.entries {
if now.Sub(n.addedAt) >= seedMinTableTime {
- tab.db.updateNode(n)
+ tab.db.UpdateNode(unwrapNode(n))
}
}
}
@@ -519,7 +508,7 @@ func (tab *Table) copyLiveNodes() {
// closest returns the n nodes in the table that are closest to the
// given id. The caller must hold tab.mutex.
-func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance {
+func (tab *Table) closest(target enode.ID, nresults int) *nodesByDistance {
// This is a very wasteful way to find the closest nodes but
// obviously correct. I believe that tree-based buckets would make
// this easier to implement efficiently.
@@ -540,8 +529,8 @@ func (tab *Table) len() (n int) {
}
// bucket returns the bucket for the given node ID hash.
-func (tab *Table) bucket(sha common.Hash) *bucket {
- d := logdist(tab.self.sha, sha)
+func (tab *Table) bucket(id enode.ID) *bucket {
+ d := enode.LogDist(tab.self.ID(), id)
if d <= bucketMinDistance {
return tab.buckets[0]
}
@@ -553,11 +542,14 @@ func (tab *Table) bucket(sha common.Hash) *bucket {
// least recently active node in the bucket does not respond to a ping packet.
//
// The caller must not hold tab.mutex.
-func (tab *Table) add(n *Node) {
+func (tab *Table) add(n *node) {
+ if n.ID() == tab.self.ID() {
+ return
+ }
+
tab.mutex.Lock()
defer tab.mutex.Unlock()
-
- b := tab.bucket(n.sha)
+ b := tab.bucket(n.ID())
if !tab.bumpOrAdd(b, n) {
// Node is not in table. Add it to the replacement list.
tab.addReplacement(b, n)
@@ -570,7 +562,7 @@ func (tab *Table) add(n *Node) {
// table could be filled by just sending ping repeatedly.
//
// The caller must not hold tab.mutex.
-func (tab *Table) addThroughPing(n *Node) {
+func (tab *Table) addThroughPing(n *node) {
if !tab.isInitDone() {
return
}
@@ -579,15 +571,15 @@ func (tab *Table) addThroughPing(n *Node) {
// stuff 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 (tab *Table) stuff(nodes []*Node) {
+func (tab *Table) stuff(nodes []*node) {
tab.mutex.Lock()
defer tab.mutex.Unlock()
for _, n := range nodes {
- if n.ID == tab.self.ID {
+ if n.ID() == tab.self.ID() {
continue // don't add self
}
- b := tab.bucket(n.sha)
+ b := tab.bucket(n.ID())
if len(b.entries) < bucketSize {
tab.bumpOrAdd(b, n)
}
@@ -595,11 +587,11 @@ func (tab *Table) stuff(nodes []*Node) {
}
// delete removes an entry from the node table. It is used to evacuate dead nodes.
-func (tab *Table) delete(node *Node) {
+func (tab *Table) delete(node *node) {
tab.mutex.Lock()
defer tab.mutex.Unlock()
- tab.deleteInBucket(tab.bucket(node.sha), node)
+ tab.deleteInBucket(tab.bucket(node.ID()), node)
}
func (tab *Table) addIP(b *bucket, ip net.IP) bool {
@@ -626,27 +618,27 @@ func (tab *Table) removeIP(b *bucket, ip net.IP) {
b.ips.Remove(ip)
}
-func (tab *Table) addReplacement(b *bucket, n *Node) {
+func (tab *Table) addReplacement(b *bucket, n *node) {
for _, e := range b.replacements {
- if e.ID == n.ID {
+ if e.ID() == n.ID() {
return // already in list
}
}
- if !tab.addIP(b, n.IP) {
+ if !tab.addIP(b, n.IP()) {
return
}
- var removed *Node
+ var removed *node
b.replacements, removed = pushNode(b.replacements, n, maxReplacements)
if removed != nil {
- tab.removeIP(b, removed.IP)
+ 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 {
+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
}
@@ -658,15 +650,15 @@ func (tab *Table) replace(b *bucket, last *Node) *Node {
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)
+ tab.removeIP(b, last.IP())
return r
}
// bump moves the given node to the front of the bucket entry list
// if it is contained in that list.
-func (b *bucket) bump(n *Node) bool {
+func (b *bucket) bump(n *node) bool {
for i := range b.entries {
- if b.entries[i].ID == n.ID {
+ if b.entries[i].ID() == n.ID() {
// move it to the front
copy(b.entries[1:], b.entries[:i])
b.entries[0] = n
@@ -678,11 +670,11 @@ func (b *bucket) bump(n *Node) bool {
// bumpOrAdd moves n to the front of the bucket entry list or adds it if the list isn't
// full. The return value is true if n is in the bucket.
-func (tab *Table) bumpOrAdd(b *bucket, n *Node) bool {
+func (tab *Table) bumpOrAdd(b *bucket, n *node) bool {
if b.bump(n) {
return true
}
- if len(b.entries) >= bucketSize || !tab.addIP(b, n.IP) {
+ if len(b.entries) >= bucketSize || !tab.addIP(b, n.IP()) {
return false
}
b.entries, _ = pushNode(b.entries, n, bucketSize)
@@ -694,13 +686,13 @@ func (tab *Table) bumpOrAdd(b *bucket, n *Node) bool {
return true
}
-func (tab *Table) deleteInBucket(b *bucket, n *Node) {
+func (tab *Table) deleteInBucket(b *bucket, n *node) {
b.entries = deleteNode(b.entries, n)
- tab.removeIP(b, n.IP)
+ tab.removeIP(b, n.IP())
}
// pushNode adds n to the front of list, keeping at most max items.
-func pushNode(list []*Node, n *Node, max int) ([]*Node, *Node) {
+func pushNode(list []*node, n *node, max int) ([]*node, *node) {
if len(list) < max {
list = append(list, nil)
}
@@ -711,9 +703,9 @@ func pushNode(list []*Node, n *Node, max int) ([]*Node, *Node) {
}
// deleteNode removes n from list.
-func deleteNode(list []*Node, n *Node) []*Node {
+func deleteNode(list []*node, n *node) []*node {
for i := range list {
- if list[i].ID == n.ID {
+ if list[i].ID() == n.ID() {
return append(list[:i], list[i+1:]...)
}
}
@@ -723,14 +715,14 @@ func deleteNode(list []*Node, n *Node) []*Node {
// nodesByDistance is a list of nodes, ordered by
// distance to target.
type nodesByDistance struct {
- entries []*Node
- target common.Hash
+ 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) {
+func (h *nodesByDistance) push(n *node, maxElems int) {
ix := sort.Search(len(h.entries), func(i int) bool {
- return distcmp(h.target, h.entries[i].sha, n.sha) > 0
+ return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
})
if len(h.entries) < maxElems {
h.entries = append(h.entries, n)
diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go
index ed55ebd9a..e8631024b 100644
--- a/p2p/discover/table_test.go
+++ b/p2p/discover/table_test.go
@@ -20,7 +20,6 @@ import (
"crypto/ecdsa"
"fmt"
"math/rand"
- "sync"
"net"
"reflect"
@@ -28,8 +27,9 @@ import (
"testing/quick"
"time"
- "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/p2p/enode"
+ "github.com/ethereum/go-ethereum/p2p/enr"
)
func TestTable_pingReplace(t *testing.T) {
@@ -49,24 +49,26 @@ func TestTable_pingReplace(t *testing.T) {
func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) {
transport := newPingRecorder()
- tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil)
+ tab, db := newTestTable(transport)
defer tab.Close()
+ defer db.Close()
<-tab.initDone
// Fill up the sender's bucket.
- pingSender := NewNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99)
+ pingKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
+ pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{}, 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
+ transport.dead[last.ID()] = !lastInBucketIsResponding
+ transport.dead[pingSender.ID()] = !newNodeIsResponding
tab.add(pingSender)
tab.doRevalidate(make(chan struct{}, 1))
tab.doRevalidate(make(chan struct{}, 1))
- if !transport.pinged[last.ID] {
+ 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")
}
@@ -77,14 +79,14 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
if !lastInBucketIsResponding && !newNodeIsResponding {
wantSize--
}
- if l := len(tab.bucket(pingSender.sha).entries); l != wantSize {
- t.Errorf("wrong bucket size after add: got %d, want %d", l, 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.sha).entries, last.ID); found != lastInBucketIsResponding {
+ 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.sha).entries, pingSender.ID); found != wantNewEntry {
+ if found := contains(tab.bucket(pingSender.ID()).entries, pingSender.ID()); found != wantNewEntry {
t.Errorf("new entry found: %t, want: %t", found, wantNewEntry)
}
}
@@ -97,9 +99,9 @@ func TestBucket_bumpNoDuplicates(t *testing.T) {
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)
+ nodes := make([]*node, n)
for i := range nodes {
- nodes[i] = nodeAtDistance(common.Hash{}, 200)
+ nodes[i] = nodeAtDistance(enode.ID{}, 200, intIP(200))
}
args[0] = reflect.ValueOf(nodes)
// generate random bump positions.
@@ -111,8 +113,8 @@ func TestBucket_bumpNoDuplicates(t *testing.T) {
},
}
- prop := func(nodes []*Node, bumps []int) (ok bool) {
- b := &bucket{entries: make([]*Node, len(nodes))}
+ prop := func(nodes []*node, bumps []int) (ok bool) {
+ b := &bucket{entries: make([]*node, len(nodes))}
copy(b.entries, nodes)
for i, pos := range bumps {
b.bump(b.entries[pos])
@@ -134,12 +136,12 @@ func TestBucket_bumpNoDuplicates(t *testing.T) {
// This checks that the table-wide IP limit is applied correctly.
func TestTable_IPLimit(t *testing.T) {
transport := newPingRecorder()
- tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil)
+ tab, db := newTestTable(transport)
defer tab.Close()
+ defer db.Close()
for i := 0; i < tableIPLimit+1; i++ {
- n := nodeAtDistance(tab.self.sha, i)
- n.IP = net.IP{172, 0, 1, byte(i)}
+ n := nodeAtDistance(tab.self.ID(), i, net.IP{172, 0, 1, byte(i)})
tab.add(n)
}
if tab.len() > tableIPLimit {
@@ -147,16 +149,16 @@ func TestTable_IPLimit(t *testing.T) {
}
}
-// This checks that the table-wide IP limit is applied correctly.
+// This checks that the per-bucket IP limit is applied correctly.
func TestTable_BucketIPLimit(t *testing.T) {
transport := newPingRecorder()
- tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil)
+ tab, db := newTestTable(transport)
defer tab.Close()
+ defer db.Close()
d := 3
for i := 0; i < bucketIPLimit+1; i++ {
- n := nodeAtDistance(tab.self.sha, d)
- n.IP = net.IP{172, 0, 1, byte(i)}
+ n := nodeAtDistance(tab.self.ID(), d, net.IP{172, 0, 1, byte(i)})
tab.add(n)
}
if tab.len() > bucketIPLimit {
@@ -164,69 +166,18 @@ func TestTable_BucketIPLimit(t *testing.T) {
}
}
-// fillBucket inserts nodes into the given bucket until
-// it is full. The node's IDs dont correspond to their
-// hashes.
-func fillBucket(tab *Table, n *Node) (last *Node) {
- ld := logdist(tab.self.sha, n.sha)
- b := tab.bucket(n.sha)
- for len(b.entries) < bucketSize {
- b.entries = append(b.entries, nodeAtDistance(tab.self.sha, ld))
- }
- return b.entries[bucketSize-1]
-}
-
-// nodeAtDistance creates a node for which logdist(base, n.sha) == ld.
-// The node's ID does not correspond to n.sha.
-func nodeAtDistance(base common.Hash, ld int) (n *Node) {
- n = new(Node)
- n.sha = hashAtDistance(base, ld)
- n.IP = net.IP{byte(ld), 0, 2, byte(ld)}
- copy(n.ID[:], n.sha[:]) // ensure the node still has a unique ID
- return n
-}
-
-type pingRecorder struct {
- mu sync.Mutex
- dead, pinged map[NodeID]bool
-}
-
-func newPingRecorder() *pingRecorder {
- return &pingRecorder{
- dead: make(map[NodeID]bool),
- pinged: make(map[NodeID]bool),
- }
-}
-
-func (t *pingRecorder) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) {
- return nil, nil
-}
-
-func (t *pingRecorder) ping(toid NodeID, toaddr *net.UDPAddr) error {
- t.mu.Lock()
- defer t.mu.Unlock()
-
- t.pinged[toid] = true
- if t.dead[toid] {
- return errTimeout
- } else {
- return nil
- }
-}
-
-func (t *pingRecorder) close() {}
-
func TestTable_closest(t *testing.T) {
t.Parallel()
test := func(test *closeTest) bool {
// for any node table, Target and N
transport := newPingRecorder()
- tab, _ := newTable(transport, test.Self, &net.UDPAddr{}, "", nil)
+ tab, db := newTestTable(transport)
defer tab.Close()
+ defer db.Close()
tab.stuff(test.All)
- // check that doClosest(Target, N) returns nodes
+ // check that closest(Target, N) returns nodes
result := tab.closest(test.Target, test.N).entries
if hasDuplicates(result) {
t.Errorf("result contains duplicates")
@@ -252,15 +203,15 @@ func TestTable_closest(t *testing.T) {
// 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) {
+ if contains(result, n.ID()) {
continue // don't run the check below for nodes in result
}
- farthestResult := result[len(result)-1].sha
- if distcmp(test.Target, n.sha, farthestResult) < 0 {
+ 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)
+ t.Logf(" ID: %v", n.ID())
return false
}
}
@@ -277,25 +228,26 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) {
MaxCount: 200,
Rand: rand.New(rand.NewSource(time.Now().Unix())),
Values: func(args []reflect.Value, rand *rand.Rand) {
- args[0] = reflect.ValueOf(make([]*Node, rand.Intn(1000)))
+ args[0] = reflect.ValueOf(make([]*enode.Node, rand.Intn(1000)))
},
}
- test := func(buf []*Node) bool {
+ test := func(buf []*enode.Node) bool {
transport := newPingRecorder()
- tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}, "", nil)
+ tab, db := newTestTable(transport)
defer tab.Close()
+ defer db.Close()
<-tab.initDone
for i := 0; i < len(buf); i++ {
ld := cfg.Rand.Intn(len(tab.buckets))
- tab.stuff([]*Node{nodeAtDistance(tab.self.sha, ld)})
+ tab.stuff([]*node{nodeAtDistance(tab.self.ID(), ld, intIP(ld))})
}
gotN := tab.ReadRandomNodes(buf)
if gotN != tab.len() {
t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.len())
return false
}
- if hasDuplicates(buf[:gotN]) {
+ if hasDuplicates(wrapNodes(buf[:gotN])) {
t.Errorf("result contains duplicates")
return false
}
@@ -307,41 +259,43 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) {
}
type closeTest struct {
- Self NodeID
- Target common.Hash
- All []*Node
+ Self enode.ID
+ Target enode.ID
+ All []*node
N int
}
func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
t := &closeTest{
- Self: gen(NodeID{}, rand).(NodeID),
- Target: gen(common.Hash{}, rand).(common.Hash),
+ Self: gen(enode.ID{}, rand).(enode.ID),
+ Target: gen(enode.ID{}, rand).(enode.ID),
N: rand.Intn(bucketSize),
}
- for _, id := range gen([]NodeID{}, rand).([]NodeID) {
- t.All = append(t.All, &Node{ID: id})
+ for _, id := range gen([]enode.ID{}, rand).([]enode.ID) {
+ n := enode.SignNull(new(enr.Record), id)
+ t.All = append(t.All, wrapNode(n))
}
return reflect.ValueOf(t)
}
func TestTable_Lookup(t *testing.T) {
- self := nodeAtDistance(common.Hash{}, 0)
- tab, _ := newTable(lookupTestnet, self.ID, &net.UDPAddr{}, "", nil)
+ tab, db := newTestTable(lookupTestnet)
defer tab.Close()
+ defer db.Close()
// lookup on empty table returns no nodes
- if results := tab.Lookup(lookupTestnet.target); len(results) > 0 {
+ if results := tab.lookup(lookupTestnet.target, false); len(results) > 0 {
t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results)
}
// seed table with initial node (otherwise lookup will terminate immediately)
- seed := NewNode(lookupTestnet.dists[256][0], net.IP{}, 256, 0)
- tab.stuff([]*Node{seed})
+ seedKey, _ := decodePubkey(lookupTestnet.dists[256][0])
+ seed := wrapNode(enode.NewV4(seedKey, net.IP{}, 0, 256))
+ tab.stuff([]*node{seed})
- results := tab.Lookup(lookupTestnet.target)
+ results := tab.lookup(lookupTestnet.target, true)
t.Logf("results:")
for _, e := range results {
- t.Logf(" ld=%d, %x", logdist(lookupTestnet.targetSha, e.sha), e.sha[:])
+ t.Logf(" ld=%d, %x", enode.LogDist(lookupTestnet.targetSha, e.ID()), e.ID().Bytes())
}
if len(results) != bucketSize {
t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize)
@@ -358,235 +312,236 @@ func TestTable_Lookup(t *testing.T) {
// This is the test network for the Lookup test.
// The nodes were obtained by running testnet.mine with a random NodeID as target.
var lookupTestnet = &preminedTestnet{
- target: MustHexID("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"),
- targetSha: common.Hash{0x5c, 0x94, 0x4e, 0xe5, 0x1c, 0x5a, 0xe9, 0xf7, 0x2a, 0x95, 0xec, 0xcb, 0x8a, 0xed, 0x3, 0x74, 0xee, 0xcb, 0x51, 0x19, 0xd7, 0x20, 0xcb, 0xea, 0x68, 0x13, 0xe8, 0xe0, 0xd6, 0xad, 0x92, 0x61},
- dists: [257][]NodeID{
+ target: hexEncPubkey("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"),
+ targetSha: enode.HexID("5c944ee51c5ae9f72a95eccb8aed0374eecb5119d720cbea6813e8e0d6ad9261"),
+ dists: [257][]encPubkey{
240: {
- MustHexID("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"),
- MustHexID("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"),
+ hexEncPubkey("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"),
+ hexEncPubkey("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"),
},
244: {
- MustHexID("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"),
+ hexEncPubkey("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"),
},
246: {
- MustHexID("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"),
- MustHexID("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"),
- MustHexID("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"),
+ hexEncPubkey("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"),
+ hexEncPubkey("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"),
+ hexEncPubkey("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"),
},
247: {
- MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
- MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
- MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
- MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
- MustHexID("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"),
- MustHexID("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"),
- MustHexID("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"),
- MustHexID("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"),
- MustHexID("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"),
- MustHexID("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"),
+ hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
+ hexEncPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
+ hexEncPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
+ hexEncPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
+ hexEncPubkey("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"),
+ hexEncPubkey("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"),
+ hexEncPubkey("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"),
+ hexEncPubkey("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"),
+ hexEncPubkey("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"),
+ hexEncPubkey("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"),
},
248: {
- MustHexID("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"),
- MustHexID("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"),
- MustHexID("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"),
- MustHexID("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"),
- MustHexID("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"),
- MustHexID("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"),
- MustHexID("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"),
- MustHexID("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"),
- MustHexID("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"),
- MustHexID("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"),
- MustHexID("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"),
- MustHexID("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"),
- MustHexID("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"),
- MustHexID("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"),
- MustHexID("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"),
- MustHexID("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"),
+ hexEncPubkey("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"),
+ hexEncPubkey("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"),
+ hexEncPubkey("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"),
+ hexEncPubkey("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"),
+ hexEncPubkey("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"),
+ hexEncPubkey("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"),
+ hexEncPubkey("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"),
+ hexEncPubkey("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"),
+ hexEncPubkey("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"),
+ hexEncPubkey("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"),
+ hexEncPubkey("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"),
+ hexEncPubkey("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"),
+ hexEncPubkey("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"),
+ hexEncPubkey("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"),
+ hexEncPubkey("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"),
+ hexEncPubkey("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"),
},
249: {
- MustHexID("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"),
- MustHexID("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"),
- MustHexID("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"),
- MustHexID("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"),
- MustHexID("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"),
- MustHexID("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"),
- MustHexID("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"),
- MustHexID("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"),
- MustHexID("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"),
- MustHexID("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"),
- MustHexID("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"),
- MustHexID("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"),
- MustHexID("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"),
- MustHexID("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"),
- MustHexID("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"),
- MustHexID("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"),
+ hexEncPubkey("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"),
+ hexEncPubkey("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"),
+ hexEncPubkey("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"),
+ hexEncPubkey("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"),
+ hexEncPubkey("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"),
+ hexEncPubkey("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"),
+ hexEncPubkey("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"),
+ hexEncPubkey("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"),
+ hexEncPubkey("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"),
+ hexEncPubkey("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"),
+ hexEncPubkey("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"),
+ hexEncPubkey("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"),
+ hexEncPubkey("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"),
+ hexEncPubkey("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"),
+ hexEncPubkey("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"),
+ hexEncPubkey("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"),
},
250: {
- MustHexID("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"),
- MustHexID("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"),
- MustHexID("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"),
- MustHexID("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"),
- MustHexID("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"),
- MustHexID("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"),
- MustHexID("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"),
- MustHexID("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"),
- MustHexID("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"),
- MustHexID("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"),
- MustHexID("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"),
- MustHexID("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"),
- MustHexID("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"),
- MustHexID("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"),
- MustHexID("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"),
- MustHexID("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"),
+ hexEncPubkey("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"),
+ hexEncPubkey("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"),
+ hexEncPubkey("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"),
+ hexEncPubkey("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"),
+ hexEncPubkey("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"),
+ hexEncPubkey("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"),
+ hexEncPubkey("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"),
+ hexEncPubkey("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"),
+ hexEncPubkey("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"),
+ hexEncPubkey("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"),
+ hexEncPubkey("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"),
+ hexEncPubkey("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"),
+ hexEncPubkey("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"),
+ hexEncPubkey("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"),
+ hexEncPubkey("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"),
+ hexEncPubkey("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"),
},
251: {
- MustHexID("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"),
- MustHexID("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"),
- MustHexID("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"),
- MustHexID("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"),
- MustHexID("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"),
- MustHexID("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"),
- MustHexID("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"),
- MustHexID("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"),
- MustHexID("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"),
- MustHexID("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"),
- MustHexID("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"),
- MustHexID("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"),
- MustHexID("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"),
- MustHexID("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"),
- MustHexID("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"),
- MustHexID("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"),
+ hexEncPubkey("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"),
+ hexEncPubkey("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"),
+ hexEncPubkey("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"),
+ hexEncPubkey("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"),
+ hexEncPubkey("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"),
+ hexEncPubkey("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"),
+ hexEncPubkey("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"),
+ hexEncPubkey("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"),
+ hexEncPubkey("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"),
+ hexEncPubkey("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"),
+ hexEncPubkey("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"),
+ hexEncPubkey("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"),
+ hexEncPubkey("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"),
+ hexEncPubkey("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"),
+ hexEncPubkey("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"),
+ hexEncPubkey("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"),
},
252: {
- MustHexID("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"),
- MustHexID("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"),
- MustHexID("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"),
- MustHexID("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"),
- MustHexID("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"),
- MustHexID("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"),
- MustHexID("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"),
- MustHexID("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"),
- MustHexID("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"),
- MustHexID("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"),
- MustHexID("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"),
- MustHexID("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"),
- MustHexID("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"),
- MustHexID("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"),
- MustHexID("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"),
- MustHexID("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"),
+ hexEncPubkey("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"),
+ hexEncPubkey("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"),
+ hexEncPubkey("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"),
+ hexEncPubkey("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"),
+ hexEncPubkey("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"),
+ hexEncPubkey("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"),
+ hexEncPubkey("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"),
+ hexEncPubkey("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"),
+ hexEncPubkey("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"),
+ hexEncPubkey("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"),
+ hexEncPubkey("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"),
+ hexEncPubkey("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"),
+ hexEncPubkey("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"),
+ hexEncPubkey("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"),
+ hexEncPubkey("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"),
+ hexEncPubkey("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"),
},
253: {
- MustHexID("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"),
- MustHexID("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"),
- MustHexID("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"),
- MustHexID("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"),
- MustHexID("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"),
- MustHexID("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"),
- MustHexID("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"),
- MustHexID("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"),
- MustHexID("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"),
- MustHexID("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"),
- MustHexID("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"),
- MustHexID("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"),
- MustHexID("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"),
- MustHexID("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"),
- MustHexID("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"),
- MustHexID("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"),
+ hexEncPubkey("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"),
+ hexEncPubkey("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"),
+ hexEncPubkey("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"),
+ hexEncPubkey("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"),
+ hexEncPubkey("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"),
+ hexEncPubkey("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"),
+ hexEncPubkey("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"),
+ hexEncPubkey("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"),
+ hexEncPubkey("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"),
+ hexEncPubkey("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"),
+ hexEncPubkey("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"),
+ hexEncPubkey("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"),
+ hexEncPubkey("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"),
+ hexEncPubkey("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"),
+ hexEncPubkey("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"),
+ hexEncPubkey("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"),
},
254: {
- MustHexID("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"),
- MustHexID("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"),
- MustHexID("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"),
- MustHexID("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"),
- MustHexID("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"),
- MustHexID("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"),
- MustHexID("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"),
- MustHexID("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"),
- MustHexID("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"),
- MustHexID("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"),
- MustHexID("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"),
- MustHexID("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"),
- MustHexID("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"),
- MustHexID("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"),
- MustHexID("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"),
- MustHexID("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"),
+ hexEncPubkey("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"),
+ hexEncPubkey("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"),
+ hexEncPubkey("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"),
+ hexEncPubkey("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"),
+ hexEncPubkey("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"),
+ hexEncPubkey("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"),
+ hexEncPubkey("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"),
+ hexEncPubkey("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"),
+ hexEncPubkey("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"),
+ hexEncPubkey("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"),
+ hexEncPubkey("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"),
+ hexEncPubkey("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"),
+ hexEncPubkey("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"),
+ hexEncPubkey("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"),
+ hexEncPubkey("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"),
+ hexEncPubkey("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"),
},
255: {
- MustHexID("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"),
- MustHexID("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"),
- MustHexID("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"),
- MustHexID("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"),
- MustHexID("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"),
- MustHexID("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"),
- MustHexID("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"),
- MustHexID("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"),
- MustHexID("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"),
- MustHexID("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"),
- MustHexID("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"),
- MustHexID("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"),
- MustHexID("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"),
- MustHexID("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"),
- MustHexID("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"),
- MustHexID("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"),
+ hexEncPubkey("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"),
+ hexEncPubkey("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"),
+ hexEncPubkey("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"),
+ hexEncPubkey("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"),
+ hexEncPubkey("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"),
+ hexEncPubkey("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"),
+ hexEncPubkey("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"),
+ hexEncPubkey("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"),
+ hexEncPubkey("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"),
+ hexEncPubkey("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"),
+ hexEncPubkey("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"),
+ hexEncPubkey("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"),
+ hexEncPubkey("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"),
+ hexEncPubkey("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"),
+ hexEncPubkey("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"),
+ hexEncPubkey("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"),
},
256: {
- MustHexID("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"),
- MustHexID("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"),
- MustHexID("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"),
- MustHexID("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"),
- MustHexID("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"),
- MustHexID("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"),
- MustHexID("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"),
- MustHexID("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"),
- MustHexID("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"),
- MustHexID("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"),
- MustHexID("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"),
- MustHexID("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"),
- MustHexID("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"),
- MustHexID("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"),
- MustHexID("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"),
- MustHexID("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"),
+ hexEncPubkey("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"),
+ hexEncPubkey("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"),
+ hexEncPubkey("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"),
+ hexEncPubkey("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"),
+ hexEncPubkey("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"),
+ hexEncPubkey("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"),
+ hexEncPubkey("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"),
+ hexEncPubkey("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"),
+ hexEncPubkey("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"),
+ hexEncPubkey("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"),
+ hexEncPubkey("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"),
+ hexEncPubkey("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"),
+ hexEncPubkey("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"),
+ hexEncPubkey("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"),
+ hexEncPubkey("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"),
+ hexEncPubkey("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"),
},
},
}
type preminedTestnet struct {
- target NodeID
- targetSha common.Hash // sha3(target)
- dists [hashBits + 1][]NodeID
+ target encPubkey
+ targetSha enode.ID // sha3(target)
+ dists [hashBits + 1][]encPubkey
}
-func (tn *preminedTestnet) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) {
+func (tn *preminedTestnet) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) {
// current log distance is encoded in port number
// fmt.Println("findnode query at dist", toaddr.Port)
if toaddr.Port == 0 {
panic("query to node at distance 0")
}
- next := uint16(toaddr.Port) - 1
- var result []*Node
- for i, id := range tn.dists[toaddr.Port] {
- result = append(result, NewNode(id, net.ParseIP("127.0.0.1"), next, uint16(i)))
+ next := toaddr.Port - 1
+ var result []*node
+ for i, ekey := range tn.dists[toaddr.Port] {
+ key, _ := decodePubkey(ekey)
+ node := wrapNode(enode.NewV4(key, net.ParseIP("127.0.0.1"), i, next))
+ result = append(result, node)
}
return result, nil
}
-func (*preminedTestnet) close() {}
-func (*preminedTestnet) waitping(from NodeID) error { return nil }
-func (*preminedTestnet) ping(toid NodeID, toaddr *net.UDPAddr) error { return nil }
+func (*preminedTestnet) close() {}
+func (*preminedTestnet) waitping(from enode.ID) error { return nil }
+func (*preminedTestnet) ping(toid enode.ID, toaddr *net.UDPAddr) error { return nil }
// mine generates a testnet struct literal with nodes at
// various distances to the given target.
-func (tn *preminedTestnet) mine(target NodeID) {
+func (tn *preminedTestnet) mine(target encPubkey) {
tn.target = target
- tn.targetSha = crypto.Keccak256Hash(tn.target[:])
+ tn.targetSha = tn.target.id()
found := 0
for found < bucketSize*10 {
k := newkey()
- id := PubkeyID(&k.PublicKey)
- sha := crypto.Keccak256Hash(id[:])
- ld := logdist(tn.targetSha, sha)
+ key := encodePubkey(&k.PublicKey)
+ ld := enode.LogDist(tn.targetSha, key.id())
if len(tn.dists[ld]) < bucketSize {
- tn.dists[ld] = append(tn.dists[ld], id)
+ tn.dists[ld] = append(tn.dists[ld], key)
fmt.Println("found ID with ld", ld)
found++
}
@@ -594,14 +549,14 @@ func (tn *preminedTestnet) mine(target NodeID) {
fmt.Println("&preminedTestnet{")
fmt.Printf(" target: %#v,\n", tn.target)
fmt.Printf(" targetSha: %#v,\n", tn.targetSha)
- fmt.Printf(" dists: [%d][]NodeID{\n", len(tn.dists))
+ fmt.Printf(" dists: [%d][]encPubkey{\n", len(tn.dists))
for ld, ns := range tn.dists {
if len(ns) == 0 {
continue
}
- fmt.Printf(" %d: []NodeID{\n", ld)
+ fmt.Printf(" %d: []encPubkey{\n", ld)
for _, n := range ns {
- fmt.Printf(" MustHexID(\"%x\"),\n", n[:])
+ fmt.Printf(" hexEncPubkey(\"%x\"),\n", n[:])
}
fmt.Println(" },")
}
@@ -609,40 +564,6 @@ func (tn *preminedTestnet) mine(target NodeID) {
fmt.Println("}")
}
-func hasDuplicates(slice []*Node) bool {
- seen := make(map[NodeID]bool)
- 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
-}
-
-func sortedByDistanceTo(distbase common.Hash, slice []*Node) bool {
- var last common.Hash
- for i, e := range slice {
- if i > 0 && distcmp(distbase, e.sha, last) < 0 {
- return false
- }
- last = e.sha
- }
- return true
-}
-
-func contains(ns []*Node, id NodeID) bool {
- for _, n := range ns {
- if n.ID == id {
- return true
- }
- }
- return false
-}
-
// 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{} {
@@ -653,6 +574,13 @@ func gen(typ interface{}, rand *rand.Rand) interface{} {
return v.Interface()
}
+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 {
diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go
new file mode 100644
index 000000000..05ae0b6c0
--- /dev/null
+++ b/p2p/discover/table_util_test.go
@@ -0,0 +1,167 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+package discover
+
+import (
+ "crypto/ecdsa"
+ "encoding/hex"
+ "fmt"
+ "math/rand"
+ "net"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/p2p/enode"
+ "github.com/ethereum/go-ethereum/p2p/enr"
+)
+
+func newTestTable(t transport) (*Table, *enode.DB) {
+ var r enr.Record
+ r.Set(enr.IP{0, 0, 0, 0})
+ n := enode.SignNull(&r, enode.ID{})
+ db, _ := enode.OpenDB("")
+ tab, _ := newTable(t, n, db, nil)
+ 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))
+ return wrapNode(enode.SignNull(&r, idAtDistance(base, ld)))
+}
+
+// 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]
+}
+
+type pingRecorder struct {
+ mu sync.Mutex
+ dead, pinged map[enode.ID]bool
+}
+
+func newPingRecorder() *pingRecorder {
+ return &pingRecorder{
+ dead: make(map[enode.ID]bool),
+ pinged: make(map[enode.ID]bool),
+ }
+}
+
+func (t *pingRecorder) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) {
+ return nil, nil
+}
+
+func (t *pingRecorder) waitping(from enode.ID) error {
+ return nil // remote always pings
+}
+
+func (t *pingRecorder) ping(toid enode.ID, toaddr *net.UDPAddr) error {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ t.pinged[toid] = true
+ if t.dead[toid] {
+ return errTimeout
+ } else {
+ return nil
+ }
+}
+
+func (t *pingRecorder) close() {}
+
+func hasDuplicates(slice []*node) bool {
+ seen := make(map[enode.ID]bool)
+ 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
+}
+
+func contains(ns []*node, id enode.ID) bool {
+ for _, n := range ns {
+ if n.ID() == id {
+ return true
+ }
+ }
+ return false
+}
+
+func sortedByDistanceTo(distbase enode.ID, slice []*node) bool {
+ var last enode.ID
+ for i, e := range slice {
+ if i > 0 && enode.DistCmp(distbase, e.ID(), last) < 0 {
+ return false
+ }
+ last = e.ID()
+ }
+ return true
+}
+
+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
+}
+
+func hexPubkey(h string) *ecdsa.PublicKey {
+ k, err := decodePubkey(hexEncPubkey(h))
+ if err != nil {
+ panic(err)
+ }
+ return k
+}
diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go
index 0ff47c5e4..45fcce282 100644
--- a/p2p/discover/udp.go
+++ b/p2p/discover/udp.go
@@ -27,6 +27,7 @@ import (
"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/nat"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/rlp"
@@ -46,8 +47,9 @@ var (
// Timeouts
const (
- respTimeout = 500 * time.Millisecond
- expiration = 20 * time.Second
+ respTimeout = 500 * time.Millisecond
+ expiration = 20 * time.Second
+ bondExpiration = 24 * time.Hour
ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP
ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning
@@ -87,7 +89,7 @@ type (
// findnode is a query for nodes close to the given target.
findnode struct {
- Target NodeID // doesn't need to be an actual public key
+ Target encPubkey
Expiration uint64
// Ignore additional fields (for forward compatibility).
Rest []rlp.RawValue `rlp:"tail"`
@@ -105,7 +107,7 @@ type (
IP net.IP // len 4 for IPv4 or 16 for IPv6
UDP uint16 // for discovery protocol
TCP uint16 // for RLPx protocol
- ID NodeID
+ ID encPubkey
}
rpcEndpoint struct {
@@ -123,7 +125,7 @@ func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint {
return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
}
-func (t *udp) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) {
+func (t *udp) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) {
if rn.UDP <= 1024 {
return nil, errors.New("low port")
}
@@ -133,17 +135,26 @@ func (t *udp) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) {
if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
return nil, errors.New("not contained in netrestrict whitelist")
}
- n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP)
- err := n.validateComplete()
+ key, err := decodePubkey(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) rpcNode {
- return rpcNode{ID: n.ID, IP: n.IP, UDP: n.UDP, TCP: n.TCP}
+func nodeToRPC(n *node) rpcNode {
+ var key ecdsa.PublicKey
+ var ekey encPubkey
+ if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
+ ekey = encodePubkey(&key)
+ }
+ return rpcNode{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())}
}
type packet interface {
- handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error
+ handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error
name() string
}
@@ -181,7 +192,7 @@ type udp struct {
// to all the callback functions for that node.
type pending struct {
// these fields must match in the reply.
- from NodeID
+ from enode.ID
ptype byte
// time when the request must complete
@@ -199,7 +210,7 @@ type pending struct {
}
type reply struct {
- from NodeID
+ from enode.ID
ptype byte
data interface{}
// loop indicates whether there was
@@ -222,7 +233,7 @@ type Config struct {
AnnounceAddr *net.UDPAddr // local address announced in the DHT
NodeDBPath string // if set, the node database is stored at this filesystem location
NetRestrict *netutil.Netlist // network whitelist
- Bootnodes []*Node // list of bootstrap nodes
+ Bootnodes []*enode.Node // list of bootstrap nodes
Unhandled chan<- ReadPacket // unhandled packets are sent on this channel
}
@@ -237,6 +248,16 @@ func ListenUDP(c conn, cfg Config) (*Table, error) {
}
func newUDP(c conn, cfg Config) (*Table, *udp, error) {
+ realaddr := c.LocalAddr().(*net.UDPAddr)
+ if cfg.AnnounceAddr != nil {
+ realaddr = cfg.AnnounceAddr
+ }
+ self := enode.NewV4(&cfg.PrivateKey.PublicKey, realaddr.IP, realaddr.Port, realaddr.Port)
+ db, err := enode.OpenDB(cfg.NodeDBPath)
+ if err != nil {
+ return nil, nil, err
+ }
+
udp := &udp{
conn: c,
priv: cfg.PrivateKey,
@@ -245,13 +266,9 @@ func newUDP(c conn, cfg Config) (*Table, *udp, error) {
gotreply: make(chan reply),
addpending: make(chan *pending),
}
- realaddr := c.LocalAddr().(*net.UDPAddr)
- if cfg.AnnounceAddr != nil {
- realaddr = cfg.AnnounceAddr
- }
// TODO: separate TCP port
udp.ourEndpoint = makeEndpoint(realaddr, uint16(realaddr.Port))
- tab, err := newTable(udp, PubkeyID(&cfg.PrivateKey.PublicKey), realaddr, cfg.NodeDBPath, cfg.Bootnodes)
+ tab, err := newTable(udp, self, db, cfg.Bootnodes)
if err != nil {
return nil, nil, err
}
@@ -265,17 +282,18 @@ func newUDP(c conn, cfg Config) (*Table, *udp, error) {
func (t *udp) close() {
close(t.closing)
t.conn.Close()
+ t.db.Close()
// TODO: wait for the loops to end.
}
// ping sends a ping message to the given node and waits for a reply.
-func (t *udp) ping(toid NodeID, toaddr *net.UDPAddr) error {
+func (t *udp) ping(toid enode.ID, toaddr *net.UDPAddr) error {
return <-t.sendPing(toid, toaddr, nil)
}
// sendPing sends a ping message to the given node and invokes the callback
// when the reply arrives.
-func (t *udp) sendPing(toid NodeID, toaddr *net.UDPAddr, callback func()) <-chan error {
+func (t *udp) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-chan error {
req := &ping{
Version: 4,
From: t.ourEndpoint,
@@ -299,21 +317,21 @@ func (t *udp) sendPing(toid NodeID, toaddr *net.UDPAddr, callback func()) <-chan
return errc
}
-func (t *udp) waitping(from NodeID) error {
+func (t *udp) waitping(from enode.ID) error {
return <-t.pending(from, pingPacket, func(interface{}) bool { return true })
}
// findnode sends a findnode request to the given node and waits until
// the node has sent up to k neighbors.
-func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) {
+func (t *udp) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) {
// If we haven't seen a ping from the destination node for a while, it won't remember
// our endpoint proof and reject findnode. Solicit a ping first.
- if time.Since(t.db.lastPingReceived(toid)) > nodeDBNodeExpiration {
+ if time.Since(t.db.LastPingReceived(toid)) > bondExpiration {
t.ping(toid, toaddr)
t.waitping(toid)
}
- nodes := make([]*Node, 0, bucketSize)
+ nodes := make([]*node, 0, bucketSize)
nreceived := 0
errc := t.pending(toid, neighborsPacket, func(r interface{}) bool {
reply := r.(*neighbors)
@@ -337,7 +355,7 @@ func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node
// pending adds a reply callback to the pending reply queue.
// see the documentation of type pending for a detailed explanation.
-func (t *udp) pending(id NodeID, ptype byte, callback func(interface{}) bool) <-chan error {
+func (t *udp) pending(id enode.ID, ptype byte, callback func(interface{}) bool) <-chan error {
ch := make(chan error, 1)
p := &pending{from: id, ptype: ptype, callback: callback, errc: ch}
select {
@@ -349,7 +367,7 @@ func (t *udp) pending(id NodeID, ptype byte, callback func(interface{}) bool) <-
return ch
}
-func (t *udp) handleReply(from NodeID, ptype byte, req packet) bool {
+func (t *udp) handleReply(from enode.ID, ptype byte, req packet) bool {
matched := make(chan bool, 1)
select {
case t.gotreply <- reply{from, ptype, req, matched}:
@@ -563,19 +581,20 @@ func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error {
return err
}
-func decodePacket(buf []byte) (packet, NodeID, []byte, error) {
+func decodePacket(buf []byte) (packet, encPubkey, []byte, error) {
if len(buf) < headSize+1 {
- return nil, NodeID{}, nil, errPacketTooSmall
+ return nil, encPubkey{}, nil, errPacketTooSmall
}
hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:]
shouldhash := crypto.Keccak256(buf[macSize:])
if !bytes.Equal(hash, shouldhash) {
- return nil, NodeID{}, nil, errBadHash
+ return nil, encPubkey{}, nil, errBadHash
}
- fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig)
+ fromKey, err := recoverNodeKey(crypto.Keccak256(buf[headSize:]), sig)
if err != nil {
- return nil, NodeID{}, hash, err
+ return nil, fromKey, hash, err
}
+
var req packet
switch ptype := sigdata[0]; ptype {
case pingPacket:
@@ -587,56 +606,59 @@ func decodePacket(buf []byte) (packet, NodeID, []byte, error) {
case neighborsPacket:
req = new(neighbors)
default:
- return nil, fromID, hash, fmt.Errorf("unknown type: %d", ptype)
+ return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype)
}
s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
err = s.Decode(req)
- return req, fromID, hash, err
+ return req, fromKey, hash, err
}
-func (req *ping) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error {
+func (req *ping) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error {
if expired(req.Expiration) {
return errExpired
}
+ key, err := decodePubkey(fromKey)
+ if err != nil {
+ return fmt.Errorf("invalid public key: %v", err)
+ }
t.send(from, pongPacket, &pong{
To: makeEndpoint(from, req.From.TCP),
ReplyTok: mac,
Expiration: uint64(time.Now().Add(expiration).Unix()),
})
- t.handleReply(fromID, pingPacket, req)
-
- // Add the node to the table. Before doing so, ensure that we have a recent enough pong
- // recorded in the database so their findnode requests will be accepted later.
- n := NewNode(fromID, from.IP, uint16(from.Port), req.From.TCP)
- if time.Since(t.db.lastPongReceived(fromID)) > nodeDBNodeExpiration {
- t.sendPing(fromID, from, func() { t.addThroughPing(n) })
+ n := wrapNode(enode.NewV4(key, from.IP, int(req.From.TCP), from.Port))
+ t.handleReply(n.ID(), pingPacket, req)
+ if time.Since(t.db.LastPongReceived(n.ID())) > bondExpiration {
+ t.sendPing(n.ID(), from, func() { t.addThroughPing(n) })
} else {
t.addThroughPing(n)
}
- t.db.updateLastPingReceived(fromID, time.Now())
+ t.db.UpdateLastPingReceived(n.ID(), time.Now())
return nil
}
func (req *ping) name() string { return "PING/v4" }
-func (req *pong) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error {
+func (req *pong) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error {
if expired(req.Expiration) {
return errExpired
}
+ fromID := fromKey.id()
if !t.handleReply(fromID, pongPacket, req) {
return errUnsolicitedReply
}
- t.db.updateLastPongReceived(fromID, time.Now())
+ t.db.UpdateLastPongReceived(fromID, time.Now())
return nil
}
func (req *pong) name() string { return "PONG/v4" }
-func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error {
+func (req *findnode) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error {
if expired(req.Expiration) {
return errExpired
}
- if !t.db.hasBond(fromID) {
+ fromID := fromKey.id()
+ if time.Since(t.db.LastPongReceived(fromID)) > bondExpiration {
// 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
@@ -645,7 +667,7 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte
// findnode) to the victim.
return errUnknownNode
}
- target := crypto.Keccak256Hash(req.Target[:])
+ target := enode.ID(crypto.Keccak256Hash(req.Target[:]))
t.mutex.Lock()
closest := t.closest(target, bucketSize).entries
t.mutex.Unlock()
@@ -655,7 +677,7 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte
// Send neighbors in chunks with at most maxNeighbors per packet
// to stay below the 1280 byte limit.
for _, n := range closest {
- if netutil.CheckRelayIP(from.IP, n.IP) == nil {
+ if netutil.CheckRelayIP(from.IP, n.IP()) == nil {
p.Nodes = append(p.Nodes, nodeToRPC(n))
}
if len(p.Nodes) == maxNeighbors {
@@ -672,11 +694,11 @@ func (req *findnode) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte
func (req *findnode) name() string { return "FINDNODE/v4" }
-func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) error {
+func (req *neighbors) handle(t *udp, from *net.UDPAddr, fromKey encPubkey, mac []byte) error {
if expired(req.Expiration) {
return errExpired
}
- if !t.handleReply(fromID, neighborsPacket, req) {
+ if !t.handleReply(fromKey.id(), neighborsPacket, req) {
return errUnsolicitedReply
}
return nil
diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go
index b4363a12b..da95c4f5c 100644
--- a/p2p/discover/udp_test.go
+++ b/p2p/discover/udp_test.go
@@ -36,6 +36,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp"
)
@@ -46,7 +47,7 @@ func init() {
// shared test variables
var (
futureExp = uint64(time.Now().Add(10 * time.Hour).Unix())
- testTarget = NodeID{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
+ testTarget = encPubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
testRemote = rpcEndpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2}
testLocalAnnounced = rpcEndpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4}
testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6}
@@ -136,7 +137,7 @@ func TestUDP_pingTimeout(t *testing.T) {
defer test.table.Close()
toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
- toid := NodeID{1, 2, 3, 4}
+ toid := enode.ID{1, 2, 3, 4}
if err := test.udp.ping(toid, toaddr); err != errTimeout {
t.Error("expected timeout error, got", err)
}
@@ -220,8 +221,8 @@ func TestUDP_findnodeTimeout(t *testing.T) {
defer test.table.Close()
toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
- toid := NodeID{1, 2, 3, 4}
- target := NodeID{4, 5, 6, 7}
+ toid := enode.ID{1, 2, 3, 4}
+ target := encPubkey{4, 5, 6, 7}
result, err := test.udp.findnode(toid, toaddr, target)
if err != errTimeout {
t.Error("expected timeout error, got", err)
@@ -238,28 +239,30 @@ func TestUDP_findnode(t *testing.T) {
// 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.
- targetHash := crypto.Keccak256Hash(testTarget[:])
- nodes := &nodesByDistance{target: targetHash}
+ nodes := &nodesByDistance{target: testTarget.id()}
for i := 0; i < bucketSize; i++ {
- nodes.push(nodeAtDistance(test.table.self.sha, i+2), bucketSize)
+ key := newkey()
+ n := wrapNode(enode.NewV4(&key.PublicKey, net.IP{10, 13, 0, 1}, 0, i))
+ nodes.push(n, bucketSize)
}
test.table.stuff(nodes.entries)
// ensure there's a bond with the test node,
// findnode won't be accepted otherwise.
- test.table.db.updateLastPongReceived(PubkeyID(&test.remotekey.PublicKey), time.Now())
+ remoteID := encodePubkey(&test.remotekey.PublicKey).id()
+ test.table.db.UpdateLastPongReceived(remoteID, time.Now())
// check that closest neighbors are returned.
test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp})
- expected := test.table.closest(targetHash, bucketSize)
+ expected := test.table.closest(testTarget.id(), bucketSize)
- waitNeighbors := func(want []*Node) {
+ waitNeighbors := func(want []*node) {
test.waitPacketOut(func(p *neighbors) {
if len(p.Nodes) != len(want) {
t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize)
}
for i := range p.Nodes {
- if p.Nodes[i].ID != want[i].ID {
+ if p.Nodes[i].ID.id() != want[i].ID() {
t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, p.Nodes[i], expected.entries[i])
}
}
@@ -273,12 +276,13 @@ func TestUDP_findnodeMultiReply(t *testing.T) {
test := newUDPTest(t)
defer test.table.Close()
- rid := PubkeyID(&test.remotekey.PublicKey)
- test.table.db.updateLastPingReceived(rid, time.Now())
+ rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey)
+ test.table.db.UpdateLastPingReceived(rid, time.Now())
// queue a pending findnode request
- resultc, errc := make(chan []*Node), make(chan error)
+ resultc, errc := make(chan []*node), make(chan error)
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
@@ -296,11 +300,11 @@ func TestUDP_findnodeMultiReply(t *testing.T) {
})
// send the reply as two packets.
- list := []*Node{
- MustParseNode("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304"),
- MustParseNode("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303"),
- MustParseNode("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17"),
- MustParseNode("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303"),
+ list := []*node{
+ wrapNode(enode.MustParseV4("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")),
+ wrapNode(enode.MustParseV4("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")),
+ wrapNode(enode.MustParseV4("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")),
+ wrapNode(enode.MustParseV4("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")),
}
rpclist := make([]rpcNode, len(list))
for i := range list {
@@ -325,8 +329,8 @@ func TestUDP_findnodeMultiReply(t *testing.T) {
func TestUDP_successfulPing(t *testing.T) {
test := newUDPTest(t)
- added := make(chan *Node, 1)
- test.table.nodeAddedHook = func(n *Node) { added <- n }
+ added := make(chan *node, 1)
+ test.table.nodeAddedHook = func(n *node) { added <- n }
defer test.table.Close()
// The remote side sends a ping packet to initiate the exchange.
@@ -370,18 +374,18 @@ func TestUDP_successfulPing(t *testing.T) {
// pong packet.
select {
case n := <-added:
- rid := PubkeyID(&test.remotekey.PublicKey)
- if n.ID != rid {
- t.Errorf("node has wrong ID: got %v, want %v", n.ID, rid)
+ 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.IP().Equal(test.remoteaddr.IP) {
+ t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.IP)
}
- if int(n.UDP) != test.remoteaddr.Port {
- t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP, test.remoteaddr.Port)
+ if int(n.UDP()) != test.remoteaddr.Port {
+ t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port)
}
- if n.TCP != testRemote.TCP {
- t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP, testRemote.TCP)
+ 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")
@@ -434,7 +438,7 @@ var testPackets = []struct {
{
input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396",
wantPacket: &findnode{
- Target: MustHexID("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"),
+ Target: hexEncPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"),
Expiration: 1136239445,
Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}},
},
@@ -444,25 +448,25 @@ var testPackets = []struct {
wantPacket: &neighbors{
Nodes: []rpcNode{
{
- ID: MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
+ ID: hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
IP: net.ParseIP("99.33.22.55").To4(),
UDP: 4444,
TCP: 4445,
},
{
- ID: MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
+ ID: hexEncPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
IP: net.ParseIP("1.2.3.4").To4(),
UDP: 1,
TCP: 1,
},
{
- ID: MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
+ ID: hexEncPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
UDP: 3333,
TCP: 3333,
},
{
- ID: MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
+ ID: hexEncPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"),
UDP: 999,
TCP: 1000,
@@ -476,14 +480,14 @@ var testPackets = []struct {
func TestForwardCompatibility(t *testing.T) {
testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
- wantNodeID := PubkeyID(&testkey.PublicKey)
+ 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, nodeid, _, err := decodePacket(input)
+ packet, nodekey, _, err := decodePacket(input)
if err != nil {
t.Errorf("did not accept packet %s\n%v", test.input, err)
continue
@@ -491,8 +495,8 @@ func TestForwardCompatibility(t *testing.T) {
if !reflect.DeepEqual(packet, test.wantPacket) {
t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket))
}
- if nodeid != wantNodeID {
- t.Errorf("got id %v\nwant id %v", nodeid, wantNodeID)
+ if nodekey != wantNodeKey {
+ t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey)
}
}
}
diff --git a/p2p/enr/idscheme.go b/p2p/enode/idscheme.go
index efaf68041..9b495fd4f 100644
--- a/p2p/enr/idscheme.go
+++ b/p2p/enode/idscheme.go
@@ -14,57 +14,38 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-package enr
+package enode
import (
"crypto/ecdsa"
"fmt"
- "sync"
+ "io"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
+ "github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/rlp"
)
-// Registry of known identity schemes.
-var schemes sync.Map
-
-// 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
-}
-
-// RegisterIdentityScheme adds an identity scheme to the global registry.
-func RegisterIdentityScheme(name string, scheme IdentityScheme) {
- if _, loaded := schemes.LoadOrStore(name, scheme); loaded {
- panic("identity scheme " + name + " already registered")
- }
+// List of known secure identity schemes.
+var ValidSchemes = enr.SchemeMap{
+ "v4": V4ID{},
}
-// FindIdentityScheme resolves name to an identity scheme in the global registry.
-func FindIdentityScheme(name string) IdentityScheme {
- s, ok := schemes.Load(name)
- if !ok {
- return nil
- }
- return s.(IdentityScheme)
+var ValidSchemesForTesting = enr.SchemeMap{
+ "v4": V4ID{},
+ "null": NullID{},
}
// v4ID is the "v4" identity scheme.
-type v4ID struct{}
-
-func init() {
- RegisterIdentityScheme("v4", v4ID{})
-}
+type V4ID struct{}
// SignV4 signs a record using the v4 scheme.
-func SignV4(r *Record, privkey *ecdsa.PrivateKey) error {
+func SignV4(r *enr.Record, privkey *ecdsa.PrivateKey) error {
// Copy r to avoid modifying it if signing fails.
cpy := *r
- cpy.Set(ID("v4"))
+ cpy.Set(enr.ID("v4"))
cpy.Set(Secp256k1(privkey.PublicKey))
h := sha3.NewKeccak256()
@@ -74,18 +55,13 @@ func SignV4(r *Record, privkey *ecdsa.PrivateKey) error {
return err
}
sig = sig[:len(sig)-1] // remove v
- if err = cpy.SetSig("v4", sig); err == nil {
+ if err = cpy.SetSig(V4ID{}, sig); err == nil {
*r = cpy
}
return err
}
-// s256raw is an unparsed secp256k1 public key entry.
-type s256raw []byte
-
-func (s256raw) ENRKey() string { return "secp256k1" }
-
-func (v4ID) Verify(r *Record, sig []byte) error {
+func (V4ID) Verify(r *enr.Record, sig []byte) error {
var entry s256raw
if err := r.Load(&entry); err != nil {
return err
@@ -96,12 +72,12 @@ func (v4ID) Verify(r *Record, sig []byte) error {
h := sha3.NewKeccak256()
rlp.Encode(h, r.AppendElements(nil))
if !crypto.VerifySignature(entry, h.Sum(nil), sig) {
- return errInvalidSig
+ return enr.ErrInvalidSig
}
return nil
}
-func (v4ID) NodeAddr(r *Record) []byte {
+func (V4ID) NodeAddr(r *enr.Record) []byte {
var pubkey Secp256k1
err := r.Load(&pubkey)
if err != nil {
@@ -112,3 +88,73 @@ func (v4ID) NodeAddr(r *Record) []byte {
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
new file mode 100644
index 000000000..0910e6e83
--- /dev/null
+++ b/p2p/enode/idscheme_test.go
@@ -0,0 +1,74 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+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/node.go b/p2p/enode/node.go
new file mode 100644
index 000000000..84088fcd2
--- /dev/null
+++ b/p2p/enode/node.go
@@ -0,0 +1,248 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+package enode
+
+import (
+ "crypto/ecdsa"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "math/bits"
+ "math/rand"
+ "net"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/p2p/enr"
+)
+
+// 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
+}
+
+// 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.
+func (n *Node) IP() net.IP {
+ var ip net.IP
+ n.Load((*enr.IP)(&ip))
+ return ip
+}
+
+// UDP returns the UDP port of the node.
+func (n *Node) UDP() int {
+ var port enr.UDP
+ n.Load(&port)
+ return int(port)
+}
+
+// UDP 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
+}
+
+// checks whether n is a valid complete node.
+func (n *Node) ValidateComplete() error {
+ if n.Incomplete() {
+ return errors.New("incomplete node")
+ }
+ 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)
+}
+
+// The string representation of a Node is a URL.
+// Please see ParseNode for a description of the format.
+func (n *Node) String() string {
+ return n.v4URL()
+}
+
+// MarshalText implements encoding.TextMarshaler.
+func (n *Node) MarshalText() ([]byte, error) {
+ return []byte(n.v4URL()), nil
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler.
+func (n *Node) UnmarshalText(text []byte) error {
+ dec, err := ParseV4(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[:])
+}
+
+// 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
+}
+
+// RandomID returns a random ID b such that logdist(a, b) == n.
+func RandomID(a ID, n int) (b 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
+}
diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go
new file mode 100644
index 000000000..861a70bd6
--- /dev/null
+++ b/p2p/enode/node_test.go
@@ -0,0 +1,62 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+package enode
+
+import (
+ "encoding/hex"
+ "fmt"
+ "testing"
+
+ "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.IP{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.IP): &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)
+ }
+ }
+}
diff --git a/p2p/discover/database.go b/p2p/enode/nodedb.go
index 22554145f..a929b75d7 100644
--- a/p2p/discover/database.go
+++ b/p2p/enode/nodedb.go
@@ -14,20 +14,17 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-// Contains the node database, storing previously seen nodes and any collected
-// metadata about them for QoS purposes.
-
-package discover
+package enode
import (
"bytes"
"crypto/rand"
"encoding/binary"
+ "fmt"
"os"
"sync"
"time"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/syndtr/goleveldb/leveldb"
@@ -39,16 +36,16 @@ import (
)
var (
- nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element.
+ nodeDBNilID = ID{} // Special node ID to use as a nil element.
nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
nodeDBCleanupCycle = time.Hour // Time period for running the expiration task.
- nodeDBVersion = 5
+ nodeDBVersion = 6
)
-// nodeDB stores all nodes we know about.
-type nodeDB struct {
+// 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
- self NodeID // Own node id to prevent adding it into the database
runner sync.Once // Ensures we can start at most one expirer
quit chan struct{} // Channel to signal the expiring thread to stop
}
@@ -64,33 +61,27 @@ var (
nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail"
)
-// newNodeDB creates a new 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 newNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
+// 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 newMemoryNodeDB(self)
+ return newMemoryDB()
}
- return newPersistentNodeDB(path, version, self)
+ return newPersistentDB(path)
}
-// newMemoryNodeDB creates a new in-memory node database without a persistent
-// backend.
-func newMemoryNodeDB(self NodeID) (*nodeDB, error) {
+// 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 &nodeDB{
- lvl: db,
- self: self,
- quit: make(chan struct{}),
- }, nil
+ 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 newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
+func newPersistentDB(path string) (*DB, error) {
opts := &opt.Options{OpenFilesCacheCapacity: 5}
db, err := leveldb.OpenFile(path, opts)
if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
@@ -102,7 +93,7 @@ func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error)
// 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(version))]
+ currentVer = currentVer[:binary.PutVarint(currentVer, int64(nodeDBVersion))]
blob, err := db.Get(nodeDBVersionKey, nil)
switch err {
@@ -120,30 +111,26 @@ func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error)
if err = os.RemoveAll(path); err != nil {
return nil, err
}
- return newPersistentNodeDB(path, version, self)
+ return newPersistentDB(path)
}
}
- return &nodeDB{
- lvl: db,
- self: self,
- quit: make(chan struct{}),
- }, nil
+ return &DB{lvl: db, quit: make(chan struct{})}, nil
}
// makeKey generates the leveldb key-blob from a node id and its particular
// field of interest.
-func makeKey(id NodeID, field string) []byte {
- if bytes.Equal(id[:], nodeDBNilNodeID[:]) {
+func makeKey(id ID, field string) []byte {
+ if bytes.Equal(id[:], nodeDBNilID[:]) {
return []byte(field)
}
return append(nodeDBItemPrefix, append(id[:], field...)...)
}
// splitKey tries to split a database key into a node id and a field part.
-func splitKey(key []byte) (id NodeID, field string) {
+func splitKey(key []byte) (id ID, field string) {
// If the key is not of a node, return it plainly
if !bytes.HasPrefix(key, nodeDBItemPrefix) {
- return NodeID{}, string(key)
+ return ID{}, string(key)
}
// Otherwise split the id and field
item := key[len(nodeDBItemPrefix):]
@@ -155,7 +142,7 @@ func splitKey(key []byte) (id NodeID, field string) {
// fetchInt64 retrieves an integer instance associated with a particular
// database key.
-func (db *nodeDB) fetchInt64(key []byte) int64 {
+func (db *DB) fetchInt64(key []byte) int64 {
blob, err := db.lvl.Get(key, nil)
if err != nil {
return 0
@@ -169,39 +156,43 @@ func (db *nodeDB) fetchInt64(key []byte) int64 {
// storeInt64 update a specific database entry to the current time instance as a
// unix timestamp.
-func (db *nodeDB) storeInt64(key []byte, n int64) error {
+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)
}
-// node retrieves a node with a given id from the database.
-func (db *nodeDB) node(id NodeID) *Node {
+// Node retrieves a node with a given id from the database.
+func (db *DB) Node(id ID) *Node {
blob, err := db.lvl.Get(makeKey(id, nodeDBDiscoverRoot), nil)
if err != nil {
return nil
}
+ return mustDecodeNode(id[:], blob)
+}
+
+func mustDecodeNode(id, data []byte) *Node {
node := new(Node)
- if err := rlp.DecodeBytes(blob, node); err != nil {
- log.Error("Failed to decode node RLP", "err", err)
- return nil
+ if err := rlp.DecodeBytes(data, &node.r); err != nil {
+ panic(fmt.Errorf("p2p/enode: can't decode node %x in DB: %v", id, err))
}
- node.sha = crypto.Keccak256Hash(node.ID[:])
+ // Restore node id cache.
+ copy(node.id[:], id)
return node
}
-// updateNode inserts - potentially overwriting - a node into the peer database.
-func (db *nodeDB) updateNode(node *Node) error {
- blob, err := rlp.EncodeToBytes(node)
+// UpdateNode inserts - potentially overwriting - a node into the peer database.
+func (db *DB) UpdateNode(node *Node) error {
+ blob, err := rlp.EncodeToBytes(&node.r)
if err != nil {
return err
}
- return db.lvl.Put(makeKey(node.ID, nodeDBDiscoverRoot), blob, nil)
+ return db.lvl.Put(makeKey(node.ID(), nodeDBDiscoverRoot), blob, nil)
}
-// deleteNode deletes all information/keys associated with a node.
-func (db *nodeDB) deleteNode(id NodeID) error {
+// DeleteNode deletes all information/keys associated with a node.
+func (db *DB) DeleteNode(id ID) error {
deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil)
for deleter.Next() {
if err := db.lvl.Delete(deleter.Key(), nil); err != nil {
@@ -220,13 +211,13 @@ func (db *nodeDB) deleteNode(id NodeID) error {
// 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 *nodeDB) ensureExpirer() {
+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 *nodeDB) expirer() {
+func (db *DB) expirer() {
tick := time.NewTicker(nodeDBCleanupCycle)
defer tick.Stop()
for {
@@ -243,7 +234,7 @@ func (db *nodeDB) expirer() {
// expireNodes iterates over the database and deletes all nodes that have not
// been seen (i.e. received a pong from) for some allotted time.
-func (db *nodeDB) expireNodes() error {
+func (db *DB) expireNodes() error {
threshold := time.Now().Add(-nodeDBNodeExpiration)
// Find discovered nodes that are older than the allowance
@@ -257,60 +248,56 @@ func (db *nodeDB) expireNodes() error {
continue
}
// Skip the node if not expired yet (and not self)
- if !bytes.Equal(id[:], db.self[:]) {
- if seen := db.lastPongReceived(id); seen.After(threshold) {
- continue
- }
+ if seen := db.LastPongReceived(id); seen.After(threshold) {
+ continue
}
// Otherwise delete all associated information
- db.deleteNode(id)
+ db.DeleteNode(id)
}
return nil
}
-// lastPingReceived retrieves the time of the last ping packet sent by the remote node.
-func (db *nodeDB) lastPingReceived(id NodeID) time.Time {
+// LastPingReceived retrieves the time of the last ping packet received from
+// a remote node.
+func (db *DB) LastPingReceived(id ID) time.Time {
return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0)
}
-// updateLastPing updates the last time remote node pinged us.
-func (db *nodeDB) updateLastPingReceived(id NodeID, instance time.Time) error {
+// UpdateLastPingReceived updates the last time we tried contacting a remote node.
+func (db *DB) UpdateLastPingReceived(id ID, instance time.Time) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix())
}
-// lastPongReceived retrieves the time of the last successful pong from remote node.
-func (db *nodeDB) lastPongReceived(id NodeID) time.Time {
+// LastPongReceived retrieves the time of the last successful pong from remote node.
+func (db *DB) LastPongReceived(id ID) time.Time {
+ // Launch expirer
+ db.ensureExpirer()
return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0)
}
-// hasBond reports whether the given node is considered bonded.
-func (db *nodeDB) hasBond(id NodeID) bool {
- return time.Since(db.lastPongReceived(id)) < nodeDBNodeExpiration
-}
-
-// updateLastPongReceived updates the last pong time of a node.
-func (db *nodeDB) updateLastPongReceived(id NodeID, instance time.Time) error {
+// UpdateLastPongReceived updates the last pong time of a node.
+func (db *DB) UpdateLastPongReceived(id ID, instance time.Time) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
}
-// findFails retrieves the number of findnode failures since bonding.
-func (db *nodeDB) findFails(id NodeID) int {
+// FindFails retrieves the number of findnode failures since bonding.
+func (db *DB) FindFails(id ID) int {
return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails)))
}
-// updateFindFails updates the number of findnode failures since bonding.
-func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
+// UpdateFindFails updates the number of findnode failures since bonding.
+func (db *DB) UpdateFindFails(id ID, fails int) error {
return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails))
}
-// querySeeds retrieves random nodes to be used as potential seed nodes
+// QuerySeeds retrieves random nodes to be used as potential seed nodes
// for bootstrapping.
-func (db *nodeDB) querySeeds(n int, maxAge time.Duration) []*Node {
+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 NodeID
+ id ID
)
defer it.Release()
@@ -329,14 +316,11 @@ seek:
id[0] = 0
continue seek // iterator exhausted
}
- if n.ID == db.self {
- continue seek
- }
- if now.Sub(db.lastPongReceived(n.ID)) > maxAge {
+ if now.Sub(db.LastPongReceived(n.ID())) > maxAge {
continue seek
}
for i := range nodes {
- if nodes[i].ID == n.ID {
+ if nodes[i].ID() == n.ID() {
continue seek // duplicate
}
}
@@ -353,18 +337,13 @@ func nextNode(it iterator.Iterator) *Node {
if field != nodeDBDiscoverRoot {
continue
}
- var n Node
- if err := rlp.DecodeBytes(it.Value(), &n); err != nil {
- log.Warn("Failed to decode node RLP", "id", id, "err", err)
- continue
- }
- return &n
+ return mustDecodeNode(id[:], it.Value())
}
return nil
}
// close flushes and closes the database files.
-func (db *nodeDB) close() {
+func (db *DB) Close() {
close(db.quit)
db.lvl.Close()
}
diff --git a/p2p/discover/database_test.go b/p2p/enode/nodedb_test.go
index 27974344e..57a0424d8 100644
--- a/p2p/discover/database_test.go
+++ b/p2p/enode/nodedb_test.go
@@ -14,10 +14,11 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-package discover
+package enode
import (
"bytes"
+ "fmt"
"io/ioutil"
"net"
"os"
@@ -28,24 +29,21 @@ import (
)
var nodeDBKeyTests = []struct {
- id NodeID
+ id ID
field string
key []byte
}{
{
- id: NodeID{},
+ id: ID{},
field: "version",
key: []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field
},
{
- id: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ id: HexID("51232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
field: ":discover",
- key: []byte{0x6e, 0x3a, // prefix
- 0x1d, 0xd9, 0xd6, 0x5c, 0x45, 0x52, 0xb5, 0xeb, // node id
- 0x43, 0xd5, 0xad, 0x55, 0xa2, 0xee, 0x3f, 0x56, //
- 0xc6, 0xcb, 0xc1, 0xc6, 0x4a, 0x5c, 0x8d, 0x65, //
- 0x9f, 0x51, 0xfc, 0xd5, 0x1b, 0xac, 0xe2, 0x43, //
- 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, //
+ key: []byte{
+ 0x6e, 0x3a, // prefix
+ 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, //
@@ -54,7 +52,7 @@ var nodeDBKeyTests = []struct {
},
}
-func TestNodeDBKeys(t *testing.T) {
+func TestDBKeys(t *testing.T) {
for i, tt := range nodeDBKeyTests {
if key := makeKey(tt.id, tt.field); !bytes.Equal(key, tt.key) {
t.Errorf("make test %d: key mismatch: have 0x%x, want 0x%x", i, key, tt.key)
@@ -78,9 +76,9 @@ var nodeDBInt64Tests = []struct {
{key: []byte{0x03}, value: 3},
}
-func TestNodeDBInt64(t *testing.T) {
- db, _ := newNodeDB("", nodeDBVersion, NodeID{})
- defer db.close()
+func TestDBInt64(t *testing.T) {
+ db, _ := OpenDB("")
+ defer db.Close()
tests := nodeDBInt64Tests
for i := 0; i < len(tests); i++ {
@@ -101,9 +99,9 @@ func TestNodeDBInt64(t *testing.T) {
}
}
-func TestNodeDBFetchStore(t *testing.T) {
- node := NewNode(
- MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+func TestDBFetchStore(t *testing.T) {
+ node := NewV4(
+ hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{192, 168, 0, 1},
30303,
30303,
@@ -111,47 +109,47 @@ func TestNodeDBFetchStore(t *testing.T) {
inst := time.Now()
num := 314
- db, _ := newNodeDB("", nodeDBVersion, NodeID{})
- defer db.close()
+ db, _ := OpenDB("")
+ defer db.Close()
// Check fetch/store operations on a node ping object
- if stored := db.lastPingReceived(node.ID); stored.Unix() != 0 {
+ if stored := db.LastPingReceived(node.ID()); stored.Unix() != 0 {
t.Errorf("ping: non-existing object: %v", stored)
}
- if err := db.updateLastPingReceived(node.ID, inst); err != nil {
+ if err := db.UpdateLastPingReceived(node.ID(), inst); err != nil {
t.Errorf("ping: failed to update: %v", err)
}
- if stored := db.lastPingReceived(node.ID); stored.Unix() != inst.Unix() {
+ if stored := db.LastPingReceived(node.ID()); 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); stored.Unix() != 0 {
+ if stored := db.LastPongReceived(node.ID()); stored.Unix() != 0 {
t.Errorf("pong: non-existing object: %v", stored)
}
- if err := db.updateLastPongReceived(node.ID, inst); err != nil {
+ if err := db.UpdateLastPongReceived(node.ID(), inst); err != nil {
t.Errorf("pong: failed to update: %v", err)
}
- if stored := db.lastPongReceived(node.ID); stored.Unix() != inst.Unix() {
+ if stored := db.LastPongReceived(node.ID()); 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); stored != 0 {
+ if stored := db.FindFails(node.ID()); stored != 0 {
t.Errorf("find-node fails: non-existing object: %v", stored)
}
- if err := db.updateFindFails(node.ID, num); err != nil {
+ if err := db.UpdateFindFails(node.ID(), num); err != nil {
t.Errorf("find-node fails: failed to update: %v", err)
}
- if stored := db.findFails(node.ID); stored != num {
+ if stored := db.FindFails(node.ID()); 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 {
+ if stored := db.Node(node.ID()); stored != nil {
t.Errorf("node: non-existing object: %v", stored)
}
- if err := db.updateNode(node); err != nil {
+ if err := db.UpdateNode(node); err != nil {
t.Errorf("node: failed to update: %v", err)
}
- if stored := db.node(node.ID); stored == nil {
+ 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)
@@ -165,8 +163,8 @@ var nodeDBSeedQueryNodes = []struct {
// This one should not be in the result set because its last
// pong time is too far in the past.
{
- node: NewNode(
- MustHexID("0x84d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ node: NewV4(
+ hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{127, 0, 0, 3},
30303,
30303,
@@ -176,8 +174,8 @@ var nodeDBSeedQueryNodes = []struct {
// This one shouldn't be in in the result set because its
// nodeID is the local node's ID.
{
- node: NewNode(
- MustHexID("0x57d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ node: NewV4(
+ hexPubkey("ff93ff820abacd4351b0f14e47b324bc82ff014c226f3f66a53535734a3c150e7e38ca03ef0964ba55acddc768f5e99cd59dea95ddd4defbab1339c92fa319b2"),
net.IP{127, 0, 0, 3},
30303,
30303,
@@ -187,8 +185,8 @@ var nodeDBSeedQueryNodes = []struct {
// These should be in the result set.
{
- node: NewNode(
- MustHexID("0x22d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ node: NewV4(
+ hexPubkey("c2b5eb3f5dde05f815b63777809ee3e7e0cbb20035a6b00ce327191e6eaa8f26a8d461c9112b7ab94698e7361fa19fd647e603e73239002946d76085b6f928d6"),
net.IP{127, 0, 0, 1},
30303,
30303,
@@ -196,8 +194,8 @@ var nodeDBSeedQueryNodes = []struct {
pong: time.Now().Add(-2 * time.Second),
},
{
- node: NewNode(
- MustHexID("0x44d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ node: NewV4(
+ hexPubkey("6ca1d400c8ddf8acc94bcb0dd254911ad71a57bed5e0ae5aa205beed59b28c2339908e97990c493499613cff8ecf6c3dc7112a8ead220cdcd00d8847ca3db755"),
net.IP{127, 0, 0, 2},
30303,
30303,
@@ -205,56 +203,91 @@ var nodeDBSeedQueryNodes = []struct {
pong: time.Now().Add(-3 * time.Second),
},
{
- node: NewNode(
- MustHexID("0xe2d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ 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 TestNodeDBSeedQuery(t *testing.T) {
- db, _ := newNodeDB("", nodeDBVersion, nodeDBSeedQueryNodes[1].node.ID)
- defer db.close()
+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 {
- t.Fatalf("node %d: failed to insert: %v", i, err)
+ 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.pong); err != nil {
- t.Fatalf("node %d: failed to insert bondTime: %v", i, err)
+ if err := db.UpdateLastPongReceived(seed.node.ID(), 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[NodeID]struct{})
+ seeds := db.QuerySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour)
+ have := make(map[ID]struct{})
for _, seed := range seeds {
- have[seed.ID] = struct{}{}
+ have[seed.ID()] = struct{}{}
}
- want := make(map[NodeID]struct{})
- for _, seed := range nodeDBSeedQueryNodes[2:] {
- want[seed.node.ID] = struct{}{}
+ want := make(map[ID]struct{})
+ for _, seed := range nodeDBSeedQueryNodes[1:] {
+ want[seed.node.ID()] = struct{}{}
}
if len(seeds) != len(want) {
- t.Errorf("seed count mismatch: have %v, want %v", 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 {
- t.Errorf("extra seed: %v", id)
+ return fmt.Errorf("extra seed: %v", id)
}
}
for id := range want {
if _, ok := have[id]; !ok {
- t.Errorf("missing seed: %v", id)
+ return fmt.Errorf("missing seed: %v", id)
}
}
+ return nil
}
-func TestNodeDBPersistency(t *testing.T) {
+func TestDBPersistency(t *testing.T) {
root, err := ioutil.TempDir("", "nodedb-")
if err != nil {
t.Fatalf("failed to create temporary data folder: %v", err)
@@ -267,34 +300,24 @@ func TestNodeDBPersistency(t *testing.T) {
)
// Create a persistent database and store some values
- db, err := newNodeDB(filepath.Join(root, "database"), nodeDBVersion, NodeID{})
+ 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()
+ db.Close()
// Reopen the database and check the value
- db, err = newNodeDB(filepath.Join(root, "database"), nodeDBVersion, NodeID{})
+ 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()
-
- // Change the database version and check flush
- db, err = newNodeDB(filepath.Join(root, "database"), nodeDBVersion+1, NodeID{})
- if err != nil {
- t.Fatalf("failed to open persistent database: %v", err)
- }
- if val := db.fetchInt64(testKey); val != 0 {
- t.Fatalf("value mismatch: have %v, want %v", val, 0)
- }
- db.close()
+ db.Close()
}
var nodeDBExpirationNodes = []struct {
@@ -303,8 +326,8 @@ var nodeDBExpirationNodes = []struct {
exp bool
}{
{
- node: NewNode(
- MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ node: NewV4(
+ hexPubkey("8d110e2ed4b446d9b5fb50f117e5f37fb7597af455e1dab0e6f045a6eeaa786a6781141659020d38bdc5e698ed3d4d2bafa8b5061810dfa63e8ac038db2e9b67"),
net.IP{127, 0, 0, 1},
30303,
30303,
@@ -312,8 +335,8 @@ var nodeDBExpirationNodes = []struct {
pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute),
exp: false,
}, {
- node: NewNode(
- MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ node: NewV4(
+ hexPubkey("913a205579c32425b220dfba999d215066e5bdbf900226b11da1907eae5e93eb40616d47412cf819664e9eacbdfcca6b0c6e07e09847a38472d4be46ab0c3672"),
net.IP{127, 0, 0, 2},
30303,
30303,
@@ -323,16 +346,16 @@ var nodeDBExpirationNodes = []struct {
},
}
-func TestNodeDBExpiration(t *testing.T) {
- db, _ := newNodeDB("", nodeDBVersion, NodeID{})
- defer db.close()
+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 err := db.updateNode(seed.node); err != nil {
+ 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.pong); err != nil {
+ if err := db.UpdateLastPongReceived(seed.node.ID(), seed.pong); err != nil {
t.Fatalf("node %d: failed to update bondTime: %v", i, err)
}
}
@@ -341,40 +364,9 @@ func TestNodeDBExpiration(t *testing.T) {
t.Fatalf("failed to expire nodes: %v", err)
}
for i, seed := range nodeDBExpirationNodes {
- node := db.node(seed.node.ID)
+ node := db.Node(seed.node.ID())
if (node == nil && !seed.exp) || (node != nil && seed.exp) {
t.Errorf("node %d: expiration mismatch: have %v, want %v", i, node, seed.exp)
}
}
}
-
-func TestNodeDBSelfExpiration(t *testing.T) {
- // Find a node in the tests that shouldn't expire, and assign it as self
- var self NodeID
- for _, node := range nodeDBExpirationNodes {
- if !node.exp {
- self = node.node.ID
- break
- }
- }
- db, _ := newNodeDB("", nodeDBVersion, self)
- defer db.close()
-
- // Add all the test nodes and set their last pong time
- for i, seed := range nodeDBExpirationNodes {
- 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.pong); err != nil {
- t.Fatalf("node %d: failed to update bondTime: %v", i, err)
- }
- }
- // Expire the nodes and make sure self has been evacuated too
- if err := db.expireNodes(); err != nil {
- t.Fatalf("failed to expire nodes: %v", err)
- }
- node := db.node(self)
- if node != nil {
- t.Errorf("self not evacuated")
- }
-}
diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go
new file mode 100644
index 000000000..50e9485d0
--- /dev/null
+++ b/p2p/enode/urlv4.go
@@ -0,0 +1,194 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+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]+)$")
+
+// 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://<hex node id>
+// <hex node id>
+//
+// 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, DNS domain names are not allowed.
+// 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://<hex node id>@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 node ID (%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 ip != nil {
+ 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
+}
+
+func parseComplete(rawurl string) (*Node, error) {
+ var (
+ id *ecdsa.PublicKey
+ ip net.IP
+ 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 node ID (%v)", err)
+ }
+ // Parse the IP address.
+ host, port, err := net.SplitHostPort(u.Host)
+ if err != nil {
+ return nil, fmt.Errorf("invalid host: %v", err)
+ }
+ if ip = net.ParseIP(host); ip == nil {
+ return nil, errors.New("invalid IP address")
+ }
+ // 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(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) v4URL() 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/discover/node_test.go b/p2p/enode/urlv4_test.go
index ed8db4dc6..3680ab6b7 100644
--- a/p2p/discover/node_test.go
+++ b/p2p/enode/urlv4_test.go
@@ -1,4 +1,4 @@
-// Copyright 2015 The go-ethereum Authors
+// 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
@@ -14,45 +14,19 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
-package discover
+package enode
import (
"bytes"
- "fmt"
+ "crypto/ecdsa"
"math/big"
- "math/rand"
"net"
"reflect"
"strings"
"testing"
"testing/quick"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
)
-func ExampleNewNode() {
- id := MustHexID("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439")
-
- // Complete nodes contain UDP and TCP endpoints:
- n1 := NewNode(id, net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 52150, 30303)
- fmt.Println("n1:", n1)
- fmt.Println("n1.Incomplete() ->", n1.Incomplete())
-
- // An incomplete node can be created by passing zero values
- // for all parameters except id.
- n2 := NewNode(id, nil, 0, 0)
- fmt.Println("n2:", n2)
- fmt.Println("n2.Incomplete() ->", n2.Incomplete())
-
- // Output:
- // n1: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:30303?discport=52150
- // n1.Incomplete() -> false
- // n2: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439
- // n2.Incomplete() -> true
-}
-
var parseNodeTests = []struct {
rawurl string
wantError string
@@ -81,8 +55,8 @@ var parseNodeTests = []struct {
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
- wantResult: NewNode(
- MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ wantResult: NewV4(
+ hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{0x7f, 0x0, 0x0, 0x1},
52150,
52150,
@@ -90,8 +64,8 @@ var parseNodeTests = []struct {
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
- wantResult: NewNode(
- MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ wantResult: NewV4(
+ hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.ParseIP("::"),
52150,
52150,
@@ -99,8 +73,8 @@ var parseNodeTests = []struct {
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150",
- wantResult: NewNode(
- MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ wantResult: NewV4(
+ hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
52150,
52150,
@@ -108,25 +82,25 @@ var parseNodeTests = []struct {
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334",
- wantResult: NewNode(
- MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ wantResult: NewV4(
+ hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{0x7f, 0x0, 0x0, 0x1},
- 22334,
52150,
+ 22334,
),
},
// Incomplete nodes with no address.
{
rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
- wantResult: NewNode(
- MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ wantResult: NewV4(
+ hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
nil, 0, 0,
),
},
{
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
- wantResult: NewNode(
- MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
+ wantResult: NewV4(
+ hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
nil, 0, 0,
),
},
@@ -146,9 +120,17 @@ var parseNodeTests = []struct {
},
}
+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 := ParseNode(test.rawurl)
+ n, err := ParseV4(test.rawurl)
if test.wantError != "" {
if err == nil {
t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError)
@@ -163,7 +145,7 @@ func TestParseNode(t *testing.T) {
continue
}
if !reflect.DeepEqual(n, test.wantResult) {
- t.Errorf("test %q:\n result mismatch:\ngot: %#v, want: %#v", test.rawurl, n, test.wantResult)
+ t.Errorf("test %q:\n result mismatch:\ngot: %#v\nwant: %#v", test.rawurl, n, test.wantResult)
}
}
}
@@ -181,9 +163,9 @@ func TestNodeString(t *testing.T) {
}
func TestHexID(t *testing.T) {
- ref := NodeID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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 := MustHexID("0x000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
- id2 := MustHexID("000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
+ 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[:])
@@ -193,17 +175,14 @@ func TestHexID(t *testing.T) {
}
}
-func TestNodeID_textEncoding(t *testing.T) {
- ref := NodeID{
+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, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40,
- 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50,
- 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x60,
- 0x61, 0x62, 0x63, 0x64,
+ 0x31, 0x32,
}
- hex := "01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364"
+ hex := "0102030405060708091011121314151617181920212223242526272829303132"
text, err := ref.MarshalText()
if err != nil {
@@ -213,7 +192,7 @@ func TestNodeID_textEncoding(t *testing.T) {
t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text)
}
- id := new(NodeID)
+ id := new(ID)
if err := id.UnmarshalText(text); err != nil {
t.Fatal(err)
}
@@ -222,114 +201,43 @@ func TestNodeID_textEncoding(t *testing.T) {
}
}
-func TestNodeID_recover(t *testing.T) {
- prv := newkey()
- hash := make([]byte, 32)
- sig, err := crypto.Sign(hash, prv)
- if err != nil {
- t.Fatalf("signing error: %v", err)
- }
-
- pub := PubkeyID(&prv.PublicKey)
- recpub, err := recoverNodeID(hash, sig)
- if err != nil {
- t.Fatalf("recovery error: %v", err)
- }
- if pub != recpub {
- t.Errorf("recovered wrong pubkey:\ngot: %v\nwant: %v", recpub, pub)
- }
-
- ecdsa, err := pub.Pubkey()
- if err != nil {
- t.Errorf("Pubkey error: %v", err)
- }
- if !reflect.DeepEqual(ecdsa, &prv.PublicKey) {
- t.Errorf("Pubkey mismatch:\n got: %#v\n want: %#v", ecdsa, &prv.PublicKey)
- }
-}
-
-func TestNodeID_pubkeyBad(t *testing.T) {
- ecdsa, err := NodeID{}.Pubkey()
- if err == nil {
- t.Error("expected error for zero ID")
- }
- if ecdsa != nil {
- t.Error("expected nil result")
- }
-}
-
func TestNodeID_distcmp(t *testing.T) {
- distcmpBig := func(target, a, b common.Hash) int {
+ 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, quickcfg()); err != nil {
+ if err := quick.CheckEqual(DistCmp, distcmpBig, nil); err != nil {
t.Error(err)
}
}
-// the random tests is likely to miss the case where they're equal.
+// The random tests is likely to miss the case where a and b are equal,
+// this test checks it explicitly.
func TestNodeID_distcmpEqual(t *testing.T) {
- base := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
- x := common.Hash{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")
+ 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 TestNodeID_logdist(t *testing.T) {
- logdistBig := func(a, b common.Hash) int {
+ 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, quickcfg()); err != nil {
+ if err := quick.CheckEqual(LogDist, logdistBig, nil); err != nil {
t.Error(err)
}
}
-// the random tests is likely to miss the case where they're equal.
+// The random tests is likely to miss the case where a and b are equal,
+// this test checks it explicitly.
func TestNodeID_logdistEqual(t *testing.T) {
- x := common.Hash{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")
- }
-}
-
-func TestNodeID_hashAtDistance(t *testing.T) {
- // we don't use quick.Check here because its output isn't
- // very helpful when the test fails.
- cfg := quickcfg()
- for i := 0; i < cfg.MaxCount; i++ {
- a := gen(common.Hash{}, cfg.Rand).(common.Hash)
- dist := cfg.Rand.Intn(len(common.Hash{}) * 8)
- result := hashAtDistance(a, dist)
- actualdist := logdist(result, a)
-
- if dist != actualdist {
- t.Log("a: ", a)
- t.Log("result:", result)
- t.Fatalf("#%d: distance of result is %d, want %d", i, actualdist, dist)
- }
- }
-}
-
-func quickcfg() *quick.Config {
- return &quick.Config{
- MaxCount: 5000,
- Rand: rand.New(rand.NewSource(time.Now().Unix())),
- }
-}
-
-// TODO: The Generate method can be dropped when we require Go >= 1.5
-// because testing/quick learned to generate arrays in 1.5.
-
-func (NodeID) Generate(rand *rand.Rand, size int) reflect.Value {
- var id NodeID
- m := rand.Intn(len(id))
- for i := len(id) - 1; i > m; i-- {
- id[i] = byte(rand.Uint32())
+ 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")
}
- return reflect.ValueOf(id)
}
diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go
index 48683471d..251caf458 100644
--- a/p2p/enr/enr.go
+++ b/p2p/enr/enr.go
@@ -15,14 +15,20 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// 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.
-//
-// Records contain named keys. To store and retrieve key/values in a record, use the Entry
+// 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.
//
-// Records must be signed before transmitting them to another node. Decoding a record verifies
-// its signature. When creating a record, set the entries you want, then call Sign to add the
-// signature. Modifying a record invalidates the signature.
+// 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
@@ -40,8 +46,7 @@ import (
const SizeLimit = 300 // maximum encoded size of a node record in bytes
var (
- errNoID = errors.New("unknown or unspecified identity scheme")
- errInvalidSig = errors.New("invalid signature")
+ 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")
@@ -50,6 +55,32 @@ var (
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
@@ -64,11 +95,6 @@ type pair struct {
v rlp.RawValue
}
-// Signed reports whether the record has a valid signature.
-func (r *Record) Signed() bool {
- return r.signature != nil
-}
-
// Seq returns the sequence number.
func (r *Record) Seq() uint64 {
return r.seq
@@ -140,7 +166,7 @@ func (r *Record) invalidate() {
// EncodeRLP implements rlp.Encoder. Encoding fails if
// the record is unsigned.
func (r Record) EncodeRLP(w io.Writer) error {
- if !r.Signed() {
+ if r.signature == nil {
return errEncodeUnsigned
}
_, err := w.Write(r.raw)
@@ -149,25 +175,34 @@ func (r Record) EncodeRLP(w io.Writer) error {
// DecodeRLP implements rlp.Decoder. Decoding verifies the signature.
func (r *Record) DecodeRLP(s *rlp.Stream) error {
- raw, err := s.Raw()
+ 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 errTooBig
+ return dec, raw, errTooBig
}
// Decode the RLP container.
- dec := Record{raw: raw}
s = rlp.NewStream(bytes.NewReader(raw), 0)
if _, err := s.List(); err != nil {
- return err
+ return dec, raw, err
}
if err = s.Decode(&dec.signature); err != nil {
- return err
+ return dec, raw, err
}
if err = s.Decode(&dec.seq); err != nil {
- return err
+ return dec, raw, err
}
// The rest of the record contains sorted k/v pairs.
var prevkey string
@@ -177,73 +212,68 @@ func (r *Record) DecodeRLP(s *rlp.Stream) error {
if err == rlp.EOL {
break
}
- return err
+ return dec, raw, err
}
if err := s.Decode(&kv.v); err != nil {
if err == rlp.EOL {
- return errIncompletePair
+ return dec, raw, errIncompletePair
}
- return err
+ return dec, raw, err
}
if i > 0 {
if kv.k == prevkey {
- return errDuplicateKey
+ return dec, raw, errDuplicateKey
}
if kv.k < prevkey {
- return errNotSorted
+ return dec, raw, errNotSorted
}
}
dec.pairs = append(dec.pairs, kv)
prevkey = kv.k
}
- if err := s.ListEnd(); err != nil {
- return err
- }
+ return dec, raw, s.ListEnd()
+}
- _, scheme := dec.idScheme()
- if scheme == nil {
- return errNoID
- }
- if err := scheme.Verify(&dec, dec.signature); err != nil {
- return err
- }
- *r = dec
- return nil
+// 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)
}
-// NodeAddr returns the node address. The return value will be nil if the record is
-// unsigned or uses an unknown identity scheme.
-func (r *Record) NodeAddr() []byte {
- _, scheme := r.idScheme()
- if scheme == nil {
- return nil
- }
- return scheme.NodeAddr(r)
+// 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.
-func (r *Record) SetSig(idscheme string, sig []byte) error {
- // Check that "id" is set and matches the given scheme. This panics because
- // inconsitencies here are always implementation bugs in the signing function calling
- // this method.
- id, s := r.idScheme()
- if s == nil {
- panic(errNoID)
- }
- if id != idscheme {
- panic(fmt.Errorf("identity scheme mismatch in Sign: record has %s, want %s", id, idscheme))
- }
-
- // Verify against the scheme.
- if err := s.Verify(r, sig); err != nil {
- return err
- }
- raw, err := r.encode(sig)
- if err != nil {
- return err
+//
+// 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
}
- r.signature, r.raw = sig, raw
return nil
}
@@ -268,11 +298,3 @@ func (r *Record) encode(sig []byte) (raw []byte, err error) {
}
return raw, nil
}
-
-func (r *Record) idScheme() (string, IdentityScheme) {
- var id ID
- if err := r.Load(&id); err != nil {
- return "", nil
- }
- return string(id), FindIdentityScheme(string(id))
-}
diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go
index d1d088756..9bf22478d 100644
--- a/p2p/enr/enr_test.go
+++ b/p2p/enr/enr_test.go
@@ -18,23 +18,17 @@ package enr
import (
"bytes"
- "encoding/hex"
+ "encoding/binary"
"fmt"
"math/rand"
"testing"
"time"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-var (
- privkey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
- pubkey = &privkey.PublicKey
-)
-
var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
func randomString(strlen int) string {
@@ -87,18 +81,6 @@ func TestGetSetUDP(t *testing.T) {
assert.Equal(t, port, port2)
}
-// TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the Secp256k1 key.
-func TestGetSetSecp256k1(t *testing.T) {
- var r 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)
-}
-
func TestLoadErrors(t *testing.T) {
var r Record
ip4 := IP{127, 0, 0, 1}
@@ -167,23 +149,20 @@ func TestSortedGetAndSet(t *testing.T) {
func TestDirty(t *testing.T) {
var r Record
- if r.Signed() {
- t.Error("Signed returned true for zero record")
- }
if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned {
t.Errorf("expected errEncodeUnsigned, got %#v", err)
}
- require.NoError(t, SignV4(&r, privkey))
- if !r.Signed() {
- t.Error("Signed return false for signed record")
+ 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 r.Signed() {
- t.Error("Signed returned true for modified record")
+ 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)
@@ -210,7 +189,7 @@ func TestSignEncodeAndDecode(t *testing.T) {
var r Record
r.Set(UDP(30303))
r.Set(IP{127, 0, 0, 1})
- require.NoError(t, SignV4(&r, privkey))
+ require.NoError(t, signTest([]byte{5}, &r))
blob, err := rlp.EncodeToBytes(r)
require.NoError(t, err)
@@ -224,48 +203,6 @@ func TestSignEncodeAndDecode(t *testing.T) {
assert.Equal(t, blob, blob2)
}
-func TestNodeAddr(t *testing.T) {
- var r Record
- if addr := r.NodeAddr(); 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(r.NodeAddr()))
-}
-
-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 Record
- if err := rlp.DecodeBytes(pyRecord, &r); err != nil {
- t.Fatalf("can't decode: %v", err)
- }
-
- var (
- wantAddr, _ = hex.DecodeString("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
- wantSeq = uint64(1)
- wantIP = IP{127, 0, 0, 1}
- wantUDP = UDP(30303)
- )
- if r.Seq() != wantSeq {
- t.Errorf("wrong seq: got %d, want %d", r.Seq(), wantSeq)
- }
- if addr := r.NodeAddr(); !bytes.Equal(addr, wantAddr) {
- t.Errorf("wrong addr: got %x, want %x", addr, wantAddr)
- }
- want := map[Entry]interface{}{new(IP): &wantIP, new(UDP): &wantUDP}
- for k, v := range want {
- desc := fmt.Sprintf("loading key %q", k.ENRKey())
- if assert.NoError(t, r.Load(k), desc) {
- assert.Equal(t, k, v, desc)
- }
- }
-}
-
// TestRecordTooBig tests that records bigger than SizeLimit bytes cannot be signed.
func TestRecordTooBig(t *testing.T) {
var r Record
@@ -273,13 +210,13 @@ func TestRecordTooBig(t *testing.T) {
// set a big value for random key, expect error
r.Set(WithEntry(key, randomString(SizeLimit)))
- if err := SignV4(&r, privkey); err != errTooBig {
+ 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, SignV4(&r, privkey))
+ require.NoError(t, signTest([]byte{5}, &r))
}
// TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs.
@@ -295,7 +232,7 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) {
r.Set(WithEntry(key, &value))
}
- require.NoError(t, SignV4(&r, privkey))
+ require.NoError(t, signTest([]byte{5}, &r))
_, err := rlp.EncodeToBytes(r)
require.NoError(t, err)
@@ -308,11 +245,40 @@ func TestSignEncodeAndDecodeRandom(t *testing.T) {
}
}
-func BenchmarkDecode(b *testing.B) {
- var r Record
- for i := 0; i < b.N; i++ {
- rlp.DecodeBytes(pyRecord, &r)
+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
}
- b.StopTimer()
- r.NodeAddr()
+ return id
}
diff --git a/p2p/enr/entries.go b/p2p/enr/entries.go
index 71c7653a2..347990ab6 100644
--- a/p2p/enr/entries.go
+++ b/p2p/enr/entries.go
@@ -17,12 +17,10 @@
package enr
import (
- "crypto/ecdsa"
"fmt"
"io"
"net"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
@@ -98,30 +96,6 @@ func (v *IP) DecodeRLP(s *rlp.Stream) error {
return nil
}
-// 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
-}
-
// KeyError is an error related to a key.
type KeyError struct {
Key string
diff --git a/p2p/enr/idscheme_test.go b/p2p/enr/idscheme_test.go
deleted file mode 100644
index d790e12f1..000000000
--- a/p2p/enr/idscheme_test.go
+++ /dev/null
@@ -1,36 +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 <http://www.gnu.org/licenses/>.
-
-package enr
-
-import (
- "crypto/ecdsa"
- "math/big"
- "testing"
-)
-
-// 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 Record
- if err := SignV4(&r, invalidKey); err == nil {
- t.Fatal("expected error from SignV4")
- }
- if len(r.pairs) > 0 {
- t.Fatal("expected empty record, have", r.pairs)
- }
-}
diff --git a/p2p/message.go b/p2p/message.go
index a4eac54d3..ac12666e4 100644
--- a/p2p/message.go
+++ b/p2p/message.go
@@ -26,7 +26,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp"
)
@@ -253,13 +253,13 @@ type msgEventer struct {
MsgReadWriter
feed *event.Feed
- peerID discover.NodeID
+ peerID enode.ID
Protocol string
}
// newMsgEventer returns a msgEventer which sends message events to the given
// feed
-func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID discover.NodeID, proto string) *msgEventer {
+func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID enode.ID, proto string) *msgEventer {
return &msgEventer{
MsgReadWriter: rw,
feed: feed,
diff --git a/p2p/peer.go b/p2p/peer.go
index 482e3d506..b61cced54 100644
--- a/p2p/peer.go
+++ b/p2p/peer.go
@@ -28,7 +28,8 @@ import (
"github.com/ethereum/go-ethereum/common/mclock"
"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/rlp"
)
@@ -60,7 +61,7 @@ type protoHandshake struct {
Name string
Caps []Cap
ListenPort uint64
- ID discover.NodeID
+ ID []byte // secp256k1 public key
// Ignore additional fields (for forward compatibility).
Rest []rlp.RawValue `rlp:"tail"`
@@ -90,12 +91,12 @@ const (
// 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 discover.NodeID `json:"peer"`
- Error string `json:"error,omitempty"`
- Protocol string `json:"protocol,omitempty"`
- MsgCode *uint64 `json:"msg_code,omitempty"`
- MsgSize *uint32 `json:"msg_size,omitempty"`
+ 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"`
}
// Peer represents a connected remote node.
@@ -115,17 +116,23 @@ type Peer struct {
}
// NewPeer returns a peer for testing purposes.
-func NewPeer(id discover.NodeID, name string, caps []Cap) *Peer {
+func NewPeer(id enode.ID, name string, caps []Cap) *Peer {
pipe, _ := net.Pipe()
- conn := &conn{fd: pipe, transport: nil, id: id, caps: caps, name: name}
+ node := enode.SignNull(new(enr.Record), id)
+ conn := &conn{fd: pipe, transport: nil, node: node, caps: caps, name: name}
peer := newPeer(conn, nil)
close(peer.closed) // ensures Disconnect doesn't block
return peer
}
// ID returns the node's public key.
-func (p *Peer) ID() discover.NodeID {
- return p.rw.id
+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 the node name that the remote node advertised.
@@ -160,7 +167,8 @@ func (p *Peer) Disconnect(reason DiscReason) {
// String implements fmt.Stringer.
func (p *Peer) String() string {
- return fmt.Sprintf("Peer %x %v", p.rw.id[:8], p.RemoteAddr())
+ id := p.ID()
+ return fmt.Sprintf("Peer %x %v", id[:8], p.RemoteAddr())
}
// Inbound returns true if the peer is an inbound connection
@@ -177,7 +185,7 @@ func newPeer(conn *conn, protocols []Protocol) *Peer {
disc: make(chan DiscReason),
protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop
closed: make(chan struct{}),
- log: log.New("id", conn.id, "conn", conn.flags),
+ log: log.New("id", conn.node.ID(), "conn", conn.flags),
}
return p
}
diff --git a/p2p/peer_test.go b/p2p/peer_test.go
index a3e1c74fd..5aa64a32e 100644
--- a/p2p/peer_test.go
+++ b/p2p/peer_test.go
@@ -45,8 +45,8 @@ var discard = Protocol{
func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) {
fd1, fd2 := net.Pipe()
- c1 := &conn{fd: fd1, transport: newTestTransport(randomID(), fd1)}
- c2 := &conn{fd: fd2, transport: newTestTransport(randomID(), fd2)}
+ c1 := &conn{fd: fd1, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd1)}
+ c2 := &conn{fd: fd2, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd2)}
for _, p := range protos {
c1.caps = append(c1.caps, p.cap())
c2.caps = append(c2.caps, p.cap())
diff --git a/p2p/protocol.go b/p2p/protocol.go
index 948aeb494..4b90a2a70 100644
--- a/p2p/protocol.go
+++ b/p2p/protocol.go
@@ -19,7 +19,7 @@ package p2p
import (
"fmt"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
)
// Protocol represents a P2P subprotocol implementation.
@@ -51,7 +51,7 @@ type Protocol struct {
// 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 discover.NodeID) interface{}
+ PeerInfo func(id enode.ID) interface{}
}
func (p Protocol) cap() Cap {
diff --git a/p2p/protocols/protocol_test.go b/p2p/protocols/protocol_test.go
index 11df8ff39..4755db3e6 100644
--- a/p2p/protocols/protocol_test.go
+++ b/p2p/protocols/protocol_test.go
@@ -24,7 +24,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
)
@@ -36,7 +36,7 @@ type hs0 struct {
// message to kill/drop the peer with nodeID
type kill struct {
- C discover.NodeID
+ C enode.ID
}
// message to drop connection
@@ -144,7 +144,7 @@ func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTes
return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp))
}
-func protoHandshakeExchange(id discover.NodeID, proto *protoHandshake) []p2ptest.Exchange {
+func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchange {
return []p2ptest.Exchange{
{
@@ -172,13 +172,13 @@ func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) {
pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp)
// TODO: make this more than one handshake
- id := s.IDs[0]
- if err := s.TestExchanges(protoHandshakeExchange(id, proto)...); err != nil {
+ node := s.Nodes[0]
+ if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil {
t.Fatal(err)
}
var disconnects []*p2ptest.Disconnect
for i, err := range errs {
- disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
+ disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err})
}
if err := s.TestDisconnected(disconnects...); err != nil {
t.Fatal(err)
@@ -197,7 +197,7 @@ func TestProtoHandshakeSuccess(t *testing.T) {
runProtoHandshake(t, &protoHandshake{42, "420"})
}
-func moduleHandshakeExchange(id discover.NodeID, resp uint) []p2ptest.Exchange {
+func moduleHandshakeExchange(id enode.ID, resp uint) []p2ptest.Exchange {
return []p2ptest.Exchange{
{
@@ -224,16 +224,16 @@ func moduleHandshakeExchange(id discover.NodeID, resp uint) []p2ptest.Exchange {
func runModuleHandshake(t *testing.T, resp uint, errs ...error) {
pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp)
- id := s.IDs[0]
- if err := s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...); err != nil {
+ node := s.Nodes[0]
+ if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil {
t.Fatal(err)
}
- if err := s.TestExchanges(moduleHandshakeExchange(id, resp)...); err != nil {
+ if err := s.TestExchanges(moduleHandshakeExchange(node.ID(), resp)...); err != nil {
t.Fatal(err)
}
var disconnects []*p2ptest.Disconnect
for i, err := range errs {
- disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
+ disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err})
}
if err := s.TestDisconnected(disconnects...); err != nil {
t.Fatal(err)
@@ -249,7 +249,7 @@ func TestModuleHandshakeSuccess(t *testing.T) {
}
// testing complex interactions over multiple peers, relaying, dropping
-func testMultiPeerSetup(a, b discover.NodeID) []p2ptest.Exchange {
+func testMultiPeerSetup(a, b enode.ID) []p2ptest.Exchange {
return []p2ptest.Exchange{
{
@@ -305,7 +305,7 @@ func runMultiplePeers(t *testing.T, peer int, errs ...error) {
pp := p2ptest.NewTestPeerPool()
s := protocolTester(t, pp)
- if err := s.TestExchanges(testMultiPeerSetup(s.IDs[0], s.IDs[1])...); err != nil {
+ if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); err != nil {
t.Fatal(err)
}
// after some exchanges of messages, we can test state changes
@@ -318,15 +318,15 @@ WAIT:
for {
select {
case <-tick.C:
- if pp.Has(s.IDs[0]) {
+ if pp.Has(s.Nodes[0].ID()) {
break WAIT
}
case <-timeout.C:
t.Fatal("timeout")
}
}
- if !pp.Has(s.IDs[1]) {
- t.Fatalf("missing peer test-1: %v (%v)", pp, s.IDs)
+ if !pp.Has(s.Nodes[1].ID()) {
+ t.Fatalf("missing peer test-1: %v (%v)", pp, s.Nodes)
}
// peer 0 sends kill request for peer with index <peer>
@@ -334,8 +334,8 @@ WAIT:
Triggers: []p2ptest.Trigger{
{
Code: 2,
- Msg: &kill{s.IDs[peer]},
- Peer: s.IDs[0],
+ Msg: &kill{s.Nodes[peer].ID()},
+ Peer: s.Nodes[0].ID(),
},
},
})
@@ -350,7 +350,7 @@ WAIT:
{
Code: 3,
Msg: &drop{},
- Peer: s.IDs[(peer+1)%2],
+ Peer: s.Nodes[(peer+1)%2].ID(),
},
},
})
@@ -362,14 +362,14 @@ WAIT:
// check the actual discconnect errors on the individual peers
var disconnects []*p2ptest.Disconnect
for i, err := range errs {
- disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
+ disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err})
}
if err := s.TestDisconnected(disconnects...); err != nil {
t.Fatal(err)
}
// test if disconnected peers have been removed from peerPool
- if pp.Has(s.IDs[peer]) {
- t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.IDs)
+ if pp.Has(s.Nodes[peer].ID()) {
+ t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.Nodes)
}
}
diff --git a/p2p/rlpx.go b/p2p/rlpx.go
index 46b666869..a105720a4 100644
--- a/p2p/rlpx.go
+++ b/p2p/rlpx.go
@@ -35,11 +35,11 @@ import (
"sync"
"time"
+ "github.com/ethereum/go-ethereum/common/bitutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/crypto/sha3"
- "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/rlp"
"github.com/golang/snappy"
)
@@ -165,7 +165,7 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake,
if err := msg.Decode(&hs); err != nil {
return nil, err
}
- if (hs.ID == discover.NodeID{}) {
+ if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) {
return nil, DiscInvalidIdentity
}
return &hs, nil
@@ -175,7 +175,7 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake,
// messages. the protocol handshake is the first authenticated message
// and also verifies whether the encryption handshake 'worked' and the
// remote side actually provided the right public key.
-func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *discover.Node) (discover.NodeID, error) {
+func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *ecdsa.PublicKey) (*ecdsa.PublicKey, error) {
var (
sec secrets
err error
@@ -183,23 +183,21 @@ func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *discover.Node) (disco
if dial == nil {
sec, err = receiverEncHandshake(t.fd, prv)
} else {
- sec, err = initiatorEncHandshake(t.fd, prv, dial.ID)
+ sec, err = initiatorEncHandshake(t.fd, prv, dial)
}
if err != nil {
- return discover.NodeID{}, err
+ return nil, err
}
t.wmu.Lock()
t.rw = newRLPXFrameRW(t.fd, sec)
t.wmu.Unlock()
- return sec.RemoteID, nil
+ return sec.Remote.ExportECDSA(), nil
}
// encHandshake contains the state of the encryption handshake.
type encHandshake struct {
- initiator bool
- remoteID discover.NodeID
-
- remotePub *ecies.PublicKey // remote-pubk
+ initiator bool
+ remote *ecies.PublicKey // remote-pubk
initNonce, respNonce []byte // nonce
randomPrivKey *ecies.PrivateKey // ecdhe-random
remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk
@@ -208,7 +206,7 @@ type encHandshake struct {
// secrets represents the connection secrets
// which are negotiated during the encryption handshake.
type secrets struct {
- RemoteID discover.NodeID
+ Remote *ecies.PublicKey
AES, MAC []byte
EgressMAC, IngressMAC hash.Hash
Token []byte
@@ -249,9 +247,9 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce))
aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret)
s := secrets{
- RemoteID: h.remoteID,
- AES: aesSecret,
- MAC: crypto.Keccak256(ecdheSecret, aesSecret),
+ Remote: h.remote,
+ AES: aesSecret,
+ MAC: crypto.Keccak256(ecdheSecret, aesSecret),
}
// setup sha3 instances for the MACs
@@ -273,15 +271,15 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
// staticSharedSecret returns the static shared secret, the result
// of key agreement between the local and remote static node key.
func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) {
- return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen)
+ return ecies.ImportECDSA(prv).GenerateShared(h.remote, sskLen, sskLen)
}
// initiatorEncHandshake 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 initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID discover.NodeID) (s secrets, err error) {
- h := &encHandshake{initiator: true, remoteID: remoteID}
+func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s secrets, err error) {
+ h := &encHandshake{initiator: true, remote: ecies.ImportECDSAPublic(remote)}
authMsg, err := h.makeAuthMsg(prv)
if err != nil {
return s, err
@@ -307,14 +305,10 @@ func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID d
// makeAuthMsg creates the initiator handshake message.
func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) {
- rpub, err := h.remoteID.Pubkey()
- if err != nil {
- return nil, fmt.Errorf("bad remoteID: %v", err)
- }
- h.remotePub = ecies.ImportECDSAPublic(rpub)
// Generate random initiator nonce.
h.initNonce = make([]byte, shaLen)
- if _, err := rand.Read(h.initNonce); err != nil {
+ _, err := rand.Read(h.initNonce)
+ if err != nil {
return nil, err
}
// Generate random keypair to for ECDH.
@@ -384,13 +378,12 @@ func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s secrets,
func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error {
// Import the remote identity.
- h.initNonce = msg.Nonce[:]
- h.remoteID = msg.InitiatorPubkey
- rpub, err := h.remoteID.Pubkey()
+ rpub, err := importPublicKey(msg.InitiatorPubkey[:])
if err != nil {
- return fmt.Errorf("bad remoteID: %#v", err)
+ return err
}
- h.remotePub = ecies.ImportECDSAPublic(rpub)
+ 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).
@@ -436,7 +429,7 @@ func (msg *authMsgV4) sealPlain(h *encHandshake) ([]byte, error) {
n += copy(buf[n:], msg.InitiatorPubkey[:])
n += copy(buf[n:], msg.Nonce[:])
buf[n] = 0 // token-flag
- return ecies.Encrypt(rand.Reader, h.remotePub, buf, nil, nil)
+ return ecies.Encrypt(rand.Reader, h.remote, buf, nil, nil)
}
func (msg *authMsgV4) decodePlain(input []byte) {
@@ -452,7 +445,7 @@ func (msg *authRespV4) sealPlain(hs *encHandshake) ([]byte, error) {
buf := make([]byte, authRespLen)
n := copy(buf, msg.RandomPubkey[:])
copy(buf[n:], msg.Nonce[:])
- return ecies.Encrypt(rand.Reader, hs.remotePub, buf, nil, nil)
+ return ecies.Encrypt(rand.Reader, hs.remote, buf, nil, nil)
}
func (msg *authRespV4) decodePlain(input []byte) {
@@ -475,7 +468,7 @@ func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) {
prefix := make([]byte, 2)
binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead))
- enc, err := ecies.Encrypt(rand.Reader, h.remotePub, buf.Bytes(), nil, prefix)
+ enc, err := ecies.Encrypt(rand.Reader, h.remote, buf.Bytes(), nil, prefix)
return append(prefix, enc...), err
}
diff --git a/p2p/rlpx_test.go b/p2p/rlpx_test.go
index 7ae800774..64172217b 100644
--- a/p2p/rlpx_test.go
+++ b/p2p/rlpx_test.go
@@ -18,6 +18,7 @@ package p2p
import (
"bytes"
+ "crypto/ecdsa"
"crypto/rand"
"errors"
"fmt"
@@ -34,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/crypto/sha3"
- "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/simulations/pipes"
"github.com/ethereum/go-ethereum/rlp"
)
@@ -80,9 +80,9 @@ func TestEncHandshake(t *testing.T) {
func testEncHandshake(token []byte) error {
type result struct {
- side string
- id discover.NodeID
- err error
+ side string
+ pubkey *ecdsa.PublicKey
+ err error
}
var (
prv0, _ = crypto.GenerateKey()
@@ -97,14 +97,12 @@ func testEncHandshake(token []byte) error {
defer func() { output <- r }()
defer fd0.Close()
- dest := &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey)}
- r.id, r.err = c0.doEncHandshake(prv0, dest)
+ r.pubkey, r.err = c0.doEncHandshake(prv0, &prv1.PublicKey)
if r.err != nil {
return
}
- id1 := discover.PubkeyID(&prv1.PublicKey)
- if r.id != id1 {
- r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.id, id1)
+ if !reflect.DeepEqual(r.pubkey, &prv1.PublicKey) {
+ r.err = fmt.Errorf("remote pubkey mismatch: got %v, want: %v", r.pubkey, &prv1.PublicKey)
}
}()
go func() {
@@ -112,13 +110,12 @@ func testEncHandshake(token []byte) error {
defer func() { output <- r }()
defer fd1.Close()
- r.id, r.err = c1.doEncHandshake(prv1, nil)
+ r.pubkey, r.err = c1.doEncHandshake(prv1, nil)
if r.err != nil {
return
}
- id0 := discover.PubkeyID(&prv0.PublicKey)
- if r.id != id0 {
- r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.id, id0)
+ if !reflect.DeepEqual(r.pubkey, &prv0.PublicKey) {
+ r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.pubkey, &prv0.PublicKey)
}
}()
@@ -150,12 +147,12 @@ func testEncHandshake(token []byte) error {
func TestProtocolHandshake(t *testing.T) {
var (
prv0, _ = crypto.GenerateKey()
- node0 = &discover.Node{ID: discover.PubkeyID(&prv0.PublicKey), IP: net.IP{1, 2, 3, 4}, TCP: 33}
- hs0 = &protoHandshake{Version: 3, ID: node0.ID, Caps: []Cap{{"a", 0}, {"b", 2}}}
+ pub0 = crypto.FromECDSAPub(&prv0.PublicKey)[1:]
+ hs0 = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}}
prv1, _ = crypto.GenerateKey()
- node1 = &discover.Node{ID: discover.PubkeyID(&prv1.PublicKey), IP: net.IP{5, 6, 7, 8}, TCP: 44}
- hs1 = &protoHandshake{Version: 3, ID: node1.ID, Caps: []Cap{{"c", 1}, {"d", 3}}}
+ pub1 = crypto.FromECDSAPub(&prv1.PublicKey)[1:]
+ hs1 = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}}
wg sync.WaitGroup
)
@@ -170,13 +167,13 @@ func TestProtocolHandshake(t *testing.T) {
defer wg.Done()
defer fd0.Close()
rlpx := newRLPX(fd0)
- remid, err := rlpx.doEncHandshake(prv0, node1)
+ rpubkey, err := rlpx.doEncHandshake(prv0, &prv1.PublicKey)
if err != nil {
t.Errorf("dial side enc handshake failed: %v", err)
return
}
- if remid != node1.ID {
- t.Errorf("dial side remote id mismatch: got %v, want %v", remid, node1.ID)
+ if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) {
+ t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey)
return
}
@@ -196,13 +193,13 @@ func TestProtocolHandshake(t *testing.T) {
defer wg.Done()
defer fd1.Close()
rlpx := newRLPX(fd1)
- remid, err := rlpx.doEncHandshake(prv1, nil)
+ rpubkey, err := rlpx.doEncHandshake(prv1, nil)
if err != nil {
t.Errorf("listen side enc handshake failed: %v", err)
return
}
- if remid != node0.ID {
- t.Errorf("listen side remote id mismatch: got %v, want %v", remid, node0.ID)
+ if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) {
+ t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey)
return
}
diff --git a/p2p/server.go b/p2p/server.go
index f9a38fcc6..40db758e2 100644
--- a/p2p/server.go
+++ b/p2p/server.go
@@ -18,6 +18,7 @@
package p2p
import (
+ "bytes"
"crypto/ecdsa"
"errors"
"fmt"
@@ -28,10 +29,12 @@ import (
"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/discv5"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil"
)
@@ -87,7 +90,7 @@ type Config struct {
// BootstrapNodes are used to establish connectivity
// with the rest of the network.
- BootstrapNodes []*discover.Node
+ BootstrapNodes []*enode.Node
// BootstrapNodesV5 are used to establish connectivity
// with the rest of the network using the V5 discovery
@@ -96,11 +99,11 @@ type Config struct {
// Static nodes are used as pre-configured connections which are always
// maintained and re-connected on disconnects.
- StaticNodes []*discover.Node
+ StaticNodes []*enode.Node
// Trusted nodes are used as pre-configured connections which are always
// allowed to connect, even above the peer limit.
- TrustedNodes []*discover.Node
+ 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
@@ -168,10 +171,10 @@ type Server struct {
peerOpDone chan struct{}
quit chan struct{}
- addstatic chan *discover.Node
- removestatic chan *discover.Node
- addtrusted chan *discover.Node
- removetrusted chan *discover.Node
+ addstatic chan *enode.Node
+ removestatic chan *enode.Node
+ addtrusted chan *enode.Node
+ removetrusted chan *enode.Node
posthandshake chan *conn
addpeer chan *conn
delpeer chan peerDrop
@@ -180,7 +183,7 @@ type Server struct {
log log.Logger
}
-type peerOpFunc func(map[discover.NodeID]*Peer)
+type peerOpFunc func(map[enode.ID]*Peer)
type peerDrop struct {
*Peer
@@ -202,16 +205,16 @@ const (
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.
- id discover.NodeID // valid after the encryption handshake
- caps []Cap // valid after the protocol handshake
- name string // valid after the protocol handshake
+ 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, dialDest *discover.Node) (discover.NodeID, error)
+ doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*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
@@ -225,8 +228,8 @@ type transport interface {
func (c *conn) String() string {
s := c.flags.String()
- if (c.id != discover.NodeID{}) {
- s += " " + c.id.String()
+ if (c.node.ID() != enode.ID{}) {
+ s += " " + c.node.ID().String()
}
s += " " + c.fd.RemoteAddr().String()
return s
@@ -279,7 +282,7 @@ func (srv *Server) Peers() []*Peer {
// Note: We'd love to put this function into a variable but
// that seems to cause a weird compiler error in some
// environments.
- case srv.peerOp <- func(peers map[discover.NodeID]*Peer) {
+ case srv.peerOp <- func(peers map[enode.ID]*Peer) {
for _, p := range peers {
ps = append(ps, p)
}
@@ -294,7 +297,7 @@ func (srv *Server) Peers() []*Peer {
func (srv *Server) PeerCount() int {
var count int
select {
- case srv.peerOp <- func(ps map[discover.NodeID]*Peer) { count = len(ps) }:
+ case srv.peerOp <- func(ps map[enode.ID]*Peer) { count = len(ps) }:
<-srv.peerOpDone
case <-srv.quit:
}
@@ -304,7 +307,7 @@ func (srv *Server) PeerCount() int {
// AddPeer connects to the given node and maintains the connection until the
// server is shut down. If the connection fails for any reason, the server will
// attempt to reconnect the peer.
-func (srv *Server) AddPeer(node *discover.Node) {
+func (srv *Server) AddPeer(node *enode.Node) {
select {
case srv.addstatic <- node:
case <-srv.quit:
@@ -312,7 +315,7 @@ func (srv *Server) AddPeer(node *discover.Node) {
}
// RemovePeer disconnects from the given node
-func (srv *Server) RemovePeer(node *discover.Node) {
+func (srv *Server) RemovePeer(node *enode.Node) {
select {
case srv.removestatic <- node:
case <-srv.quit:
@@ -321,7 +324,7 @@ func (srv *Server) RemovePeer(node *discover.Node) {
// AddTrustedPeer adds the given node to a reserved whitelist which allows the
// node to always connect, even if the slot are full.
-func (srv *Server) AddTrustedPeer(node *discover.Node) {
+func (srv *Server) AddTrustedPeer(node *enode.Node) {
select {
case srv.addtrusted <- node:
case <-srv.quit:
@@ -329,7 +332,7 @@ func (srv *Server) AddTrustedPeer(node *discover.Node) {
}
// RemoveTrustedPeer removes the given node from the trusted peer set.
-func (srv *Server) RemoveTrustedPeer(node *discover.Node) {
+func (srv *Server) RemoveTrustedPeer(node *enode.Node) {
select {
case srv.removetrusted <- node:
case <-srv.quit:
@@ -342,36 +345,47 @@ func (srv *Server) SubscribeEvents(ch chan *PeerEvent) event.Subscription {
}
// Self returns the local node's endpoint information.
-func (srv *Server) Self() *discover.Node {
+func (srv *Server) Self() *enode.Node {
srv.lock.Lock()
- defer srv.lock.Unlock()
+ running, listener, ntab := srv.running, srv.listener, srv.ntab
+ srv.lock.Unlock()
- if !srv.running {
- return &discover.Node{IP: net.ParseIP("0.0.0.0")}
+ if !running {
+ return enode.NewV4(&srv.PrivateKey.PublicKey, net.ParseIP("0.0.0.0"), 0, 0)
}
- return srv.makeSelf(srv.listener, srv.ntab)
+ return srv.makeSelf(listener, ntab)
}
-func (srv *Server) makeSelf(listener net.Listener, ntab discoverTable) *discover.Node {
- // If the server's not running, return an empty node.
+func (srv *Server) makeSelf(listener net.Listener, ntab discoverTable) *enode.Node {
// If the node is running but discovery is off, manually assemble the node infos.
if ntab == nil {
- // Inbound connections disabled, use zero address.
- if listener == nil {
- return &discover.Node{IP: net.ParseIP("0.0.0.0"), ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
- }
- // Otherwise inject the listener address too
- addr := listener.Addr().(*net.TCPAddr)
- return &discover.Node{
- ID: discover.PubkeyID(&srv.PrivateKey.PublicKey),
- IP: addr.IP,
- TCP: uint16(addr.Port),
- }
+ addr := srv.tcpAddr(listener)
+ return enode.NewV4(&srv.PrivateKey.PublicKey, addr.IP, addr.Port, 0)
}
// Otherwise return the discovery node.
return ntab.Self()
}
+func (srv *Server) tcpAddr(listener net.Listener) net.TCPAddr {
+ addr := net.TCPAddr{IP: net.IP{0, 0, 0, 0}}
+ if listener == nil {
+ return addr // Inbound connections disabled, use zero address.
+ }
+ // Otherwise inject the listener address too.
+ if a, ok := listener.Addr().(*net.TCPAddr); ok {
+ addr = *a
+ }
+ if srv.NAT != nil {
+ if ip, err := srv.NAT.ExternalIP(); err == nil {
+ addr.IP = ip
+ }
+ }
+ if addr.IP.IsUnspecified() {
+ addr.IP = net.IP{127, 0, 0, 1}
+ }
+ return addr
+}
+
// Stop terminates the server and all active peer connections.
// It blocks until all active connections have been closed.
func (srv *Server) Stop() {
@@ -445,10 +459,10 @@ func (srv *Server) Start() (err error) {
srv.addpeer = make(chan *conn)
srv.delpeer = make(chan peerDrop)
srv.posthandshake = make(chan *conn)
- srv.addstatic = make(chan *discover.Node)
- srv.removestatic = make(chan *discover.Node)
- srv.addtrusted = make(chan *discover.Node)
- srv.removetrusted = make(chan *discover.Node)
+ srv.addstatic = make(chan *enode.Node)
+ srv.removestatic = make(chan *enode.Node)
+ srv.addtrusted = make(chan *enode.Node)
+ srv.removetrusted = make(chan *enode.Node)
srv.peerOp = make(chan peerOpFunc)
srv.peerOpDone = make(chan struct{})
@@ -525,7 +539,8 @@ func (srv *Server) Start() (err error) {
dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict)
// handshake
- srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
+ 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())
}
@@ -541,7 +556,6 @@ func (srv *Server) Start() (err error) {
srv.loopWG.Add(1)
go srv.run(dialer)
- srv.running = true
return nil
}
@@ -568,18 +582,18 @@ func (srv *Server) startListening() error {
}
type dialer interface {
- newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task
+ newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task
taskDone(task, time.Time)
- addStatic(*discover.Node)
- removeStatic(*discover.Node)
+ addStatic(*enode.Node)
+ removeStatic(*enode.Node)
}
func (srv *Server) run(dialstate dialer) {
defer srv.loopWG.Done()
var (
- peers = make(map[discover.NodeID]*Peer)
+ peers = make(map[enode.ID]*Peer)
inboundCount = 0
- trusted = make(map[discover.NodeID]bool, len(srv.TrustedNodes))
+ trusted = make(map[enode.ID]bool, len(srv.TrustedNodes))
taskdone = make(chan task, maxActiveDialTasks)
runningTasks []task
queuedTasks []task // tasks that can't run yet
@@ -587,7 +601,7 @@ func (srv *Server) run(dialstate dialer) {
// 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
+ trusted[n.ID()] = true
}
// removes t from runningTasks
@@ -640,27 +654,27 @@ running:
// stop keeping the node connected.
srv.log.Trace("Removing static node", "node", n)
dialstate.removeStatic(n)
- if p, ok := peers[n.ID]; ok {
+ if p, ok := peers[n.ID()]; ok {
p.Disconnect(DiscRequested)
}
case n := <-srv.addtrusted:
// This channel is used by AddTrustedPeer to add an enode
// to the trusted node set.
srv.log.Trace("Adding trusted node", "node", n)
- trusted[n.ID] = true
+ trusted[n.ID()] = true
// Mark any already-connected peer as trusted
- if p, ok := peers[n.ID]; ok {
+ if p, ok := peers[n.ID()]; ok {
p.rw.set(trustedConn, true)
}
case n := <-srv.removetrusted:
// This channel is used by RemoveTrustedPeer to remove an enode
// from the trusted node set.
srv.log.Trace("Removing trusted node", "node", n)
- if _, ok := trusted[n.ID]; ok {
- delete(trusted, n.ID)
+ if _, ok := trusted[n.ID()]; ok {
+ delete(trusted, n.ID())
}
// Unmark any already-connected peer as trusted
- if p, ok := peers[n.ID]; ok {
+ if p, ok := peers[n.ID()]; ok {
p.rw.set(trustedConn, false)
}
case op := <-srv.peerOp:
@@ -677,7 +691,7 @@ running:
case c := <-srv.posthandshake:
// A connection has passed the encryption handshake so
// the remote identity is known (but hasn't been verified yet).
- if trusted[c.id] {
+ if trusted[c.node.ID()] {
// Ensure that the trusted flag is set before checking against MaxPeers.
c.flags |= trustedConn
}
@@ -702,7 +716,7 @@ running:
name := truncateName(c.name)
srv.log.Debug("Adding p2p peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1)
go srv.runPeer(p)
- peers[c.id] = p
+ peers[c.node.ID()] = p
if p.Inbound() {
inboundCount++
}
@@ -749,7 +763,7 @@ running:
}
}
-func (srv *Server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCount int, c *conn) error {
+func (srv *Server) protoHandshakeChecks(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
@@ -759,15 +773,15 @@ func (srv *Server) protoHandshakeChecks(peers map[discover.NodeID]*Peer, inbound
return srv.encHandshakeChecks(peers, inboundCount, c)
}
-func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCount int, c *conn) error {
+func (srv *Server) encHandshakeChecks(peers map[enode.ID]*Peer, inboundCount int, c *conn) error {
switch {
case !c.is(trustedConn|staticDialedConn) && len(peers) >= srv.MaxPeers:
return DiscTooManyPeers
case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns():
return DiscTooManyPeers
- case peers[c.id] != nil:
+ case peers[c.node.ID()] != nil:
return DiscAlreadyConnected
- case c.id == srv.Self().ID:
+ case c.node.ID() == srv.Self().ID():
return DiscSelf
default:
return nil
@@ -777,7 +791,6 @@ func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCo
func (srv *Server) maxInboundConns() int {
return srv.MaxPeers - srv.maxDialedConns()
}
-
func (srv *Server) maxDialedConns() int {
if srv.NoDiscovery || srv.NoDial {
return 0
@@ -797,7 +810,7 @@ type tempError interface {
// inbound connections.
func (srv *Server) listenLoop() {
defer srv.loopWG.Done()
- srv.log.Info("RLPx listener up", "self", srv.makeSelf(srv.listener, srv.ntab))
+ srv.log.Info("RLPx listener up", "self", srv.Self())
tokens := defaultMaxPendingPeers
if srv.MaxPendingPeers > 0 {
@@ -850,7 +863,7 @@ func (srv *Server) listenLoop() {
// 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 *discover.Node) error {
+func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error {
self := srv.Self()
if self == nil {
return errors.New("shutdown")
@@ -859,12 +872,12 @@ func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *discover.Nod
err := srv.setupConn(c, flags, dialDest)
if err != nil {
c.close(err)
- srv.log.Trace("Setting up connection failed", "id", c.id, "err", err)
+ srv.log.Trace("Setting up connection failed", "addr", fd.RemoteAddr(), "err", err)
}
return err
}
-func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) error {
+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
@@ -872,18 +885,30 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) e
if !running {
return errServerStopped
}
+ // If dialing, figure out the remote public key.
+ var dialPubkey *ecdsa.PublicKey
+ if dialDest != nil {
+ dialPubkey = new(ecdsa.PublicKey)
+ if err := dialDest.Load((*enode.Secp256k1)(dialPubkey)); err != nil {
+ return fmt.Errorf("dial destination doesn't have a secp256k1 public key")
+ }
+ }
// Run the encryption handshake.
- var err error
- if c.id, err = c.doEncHandshake(srv.PrivateKey, dialDest); err != nil {
+ remotePubkey, err := c.doEncHandshake(srv.PrivateKey, dialPubkey)
+ if err != nil {
srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
return err
}
- clog := srv.log.New("id", c.id, "addr", c.fd.RemoteAddr(), "conn", c.flags)
- // For dialed connections, check that the remote public key matches.
- if dialDest != nil && c.id != dialDest.ID {
- clog.Trace("Dialed identity mismatch", "want", c, dialDest.ID)
- return DiscUnexpectedIdentity
+ if dialDest != nil {
+ // For dialed connections, check that the remote public key matches.
+ if dialPubkey.X.Cmp(remotePubkey.X) != 0 || dialPubkey.Y.Cmp(remotePubkey.Y) != 0 {
+ return DiscUnexpectedIdentity
+ }
+ 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.posthandshake)
if err != nil {
clog.Trace("Rejected peer before protocol handshake", "err", err)
@@ -895,8 +920,8 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) e
clog.Trace("Failed proto handshake", "err", err)
return err
}
- if phs.ID != c.id {
- clog.Trace("Wrong devp2p handshake identity", "err", phs.ID)
+ if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) {
+ clog.Trace("Wrong devp2p handshake identity", "phsid", fmt.Sprintf("%x", phs.ID))
return DiscUnexpectedIdentity
}
c.caps, c.name = phs.Caps, phs.Name
@@ -911,6 +936,16 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) e
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)
+}
+
func truncateName(s string) string {
if len(s) > 20 {
return s[:20] + "..."
@@ -985,13 +1020,13 @@ func (srv *Server) NodeInfo() *NodeInfo {
info := &NodeInfo{
Name: srv.Name,
Enode: node.String(),
- ID: node.ID.String(),
- IP: node.IP.String(),
+ ID: node.ID().String(),
+ IP: node.IP().String(),
ListenAddr: srv.ListenAddr,
Protocols: make(map[string]interface{}),
}
- info.Ports.Discovery = int(node.UDP)
- info.Ports.Listener = int(node.TCP)
+ info.Ports.Discovery = node.UDP()
+ info.Ports.Listener = node.TCP()
// Gather all the running protocol infos (only once per protocol type)
for _, proto := range srv.Protocols {
diff --git a/p2p/server_test.go b/p2p/server_test.go
index 3f24a79ba..e0b1fc122 100644
--- a/p2p/server_test.go
+++ b/p2p/server_test.go
@@ -28,21 +28,22 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"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"
)
-func init() {
- // log.Root().SetHandler(log.LvlFilterHandler(log.LvlError, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
-}
+// func init() {
+// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
+// }
type testTransport struct {
- id discover.NodeID
+ rpub *ecdsa.PublicKey
*rlpx
closeErr error
}
-func newTestTransport(id discover.NodeID, fd net.Conn) transport {
+func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn) transport {
wrapped := newRLPX(fd).(*rlpx)
wrapped.rw = newRLPXFrameRW(fd, secrets{
MAC: zero16,
@@ -50,15 +51,16 @@ func newTestTransport(id discover.NodeID, fd net.Conn) transport {
IngressMAC: sha3.NewKeccak256(),
EgressMAC: sha3.NewKeccak256(),
})
- return &testTransport{id: id, rlpx: wrapped}
+ return &testTransport{rpub: rpub, rlpx: wrapped}
}
-func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) {
- return c.id, nil
+func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) {
+ return c.rpub, nil
}
func (c *testTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) {
- return &protoHandshake{ID: c.id, Name: "test"}, nil
+ pubkey := crypto.FromECDSAPub(c.rpub)[1:]
+ return &protoHandshake{ID: pubkey, Name: "test"}, nil
}
func (c *testTransport) close(err error) {
@@ -66,7 +68,7 @@ func (c *testTransport) close(err error) {
c.closeErr = err
}
-func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server {
+func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) *Server {
config := Config{
Name: "test",
MaxPeers: 10,
@@ -76,7 +78,7 @@ func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server {
server := &Server{
Config: config,
newPeerHook: pf,
- newTransport: func(fd net.Conn) transport { return newTestTransport(id, fd) },
+ newTransport: func(fd net.Conn) transport { return newTestTransport(remoteKey, fd) },
}
if err := server.Start(); err != nil {
t.Fatalf("Could not start server: %v", err)
@@ -87,14 +89,11 @@ func startTestServer(t *testing.T, id discover.NodeID, pf func(*Peer)) *Server {
func TestServerListen(t *testing.T) {
// start the test server
connected := make(chan *Peer)
- remid := randomID()
+ remid := &newkey().PublicKey
srv := startTestServer(t, remid, func(p *Peer) {
- if p.ID() != remid {
+ if p.ID() != enode.PubkeyToIDV4(remid) {
t.Error("peer func called with wrong node id")
}
- if p == nil {
- t.Error("peer func called with nil conn")
- }
connected <- p
})
defer close(connected)
@@ -141,14 +140,14 @@ func TestServerDial(t *testing.T) {
// start the server
connected := make(chan *Peer)
- remid := randomID()
+ 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 := &discover.Node{ID: remid, IP: tcpAddr.IP, TCP: uint16(tcpAddr.Port)}
+ node := enode.NewV4(remid, tcpAddr.IP, tcpAddr.Port, 0)
srv.AddPeer(node)
select {
@@ -157,7 +156,7 @@ func TestServerDial(t *testing.T) {
select {
case peer := <-connected:
- if peer.ID() != remid {
+ if peer.ID() != enode.PubkeyToIDV4(remid) {
t.Errorf("peer has wrong id")
}
if peer.Name() != "test" {
@@ -211,7 +210,7 @@ func TestServerTaskScheduling(t *testing.T) {
quit, returned = make(chan struct{}), make(chan struct{})
tc = 0
tg = taskgen{
- newFunc: func(running int, peers map[discover.NodeID]*Peer) []task {
+ newFunc: func(running int, peers map[enode.ID]*Peer) []task {
tc++
return []task{&testTask{index: tc - 1}}
},
@@ -284,7 +283,7 @@ func TestServerManyTasks(t *testing.T) {
defer srv.Stop()
srv.loopWG.Add(1)
go srv.run(taskgen{
- newFunc: func(running int, peers map[discover.NodeID]*Peer) []task {
+ newFunc: func(running int, peers map[enode.ID]*Peer) []task {
start, end = end, end+maxActiveDialTasks+10
if end > len(alltasks) {
end = len(alltasks)
@@ -319,19 +318,19 @@ func TestServerManyTasks(t *testing.T) {
}
type taskgen struct {
- newFunc func(running int, peers map[discover.NodeID]*Peer) []task
+ newFunc func(running int, peers map[enode.ID]*Peer) []task
doneFunc func(task)
}
-func (tg taskgen) newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task {
+func (tg taskgen) newTasks(running int, peers map[enode.ID]*Peer, now time.Time) []task {
return tg.newFunc(running, peers)
}
func (tg taskgen) taskDone(t task, now time.Time) {
tg.doneFunc(t)
}
-func (tg taskgen) addStatic(*discover.Node) {
+func (tg taskgen) addStatic(*enode.Node) {
}
-func (tg taskgen) removeStatic(*discover.Node) {
+func (tg taskgen) removeStatic(*enode.Node) {
}
type testTask struct {
@@ -347,13 +346,14 @@ func (t *testTask) Do(srv *Server) {
// just after the encryption handshake when the server is
// at capacity. Trusted connections should still be accepted.
func TestServerAtCap(t *testing.T) {
- trustedID := randomID()
+ trustedNode := newkey()
+ trustedID := enode.PubkeyToIDV4(&trustedNode.PublicKey)
srv := &Server{
Config: Config{
PrivateKey: newkey(),
MaxPeers: 10,
NoDial: true,
- TrustedNodes: []*discover.Node{{ID: trustedID}},
+ TrustedNodes: []*enode.Node{newNode(trustedID, nil)},
},
}
if err := srv.Start(); err != nil {
@@ -361,10 +361,11 @@ func TestServerAtCap(t *testing.T) {
}
defer srv.Stop()
- newconn := func(id discover.NodeID) *conn {
+ newconn := func(id enode.ID) *conn {
fd, _ := net.Pipe()
- tx := newTestTransport(id, fd)
- return &conn{fd: fd, transport: tx, flags: inboundConn, id: id, cont: make(chan error)}
+ tx := newTestTransport(&trustedNode.PublicKey, fd)
+ 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.
@@ -390,14 +391,14 @@ func TestServerAtCap(t *testing.T) {
}
// Remove from trusted set and try again
- srv.RemoveTrustedPeer(&discover.Node{ID: trustedID})
+ srv.RemoveTrustedPeer(newNode(trustedID, nil))
c = newconn(trustedID)
if err := srv.checkpoint(c, srv.posthandshake); err != DiscTooManyPeers {
t.Error("wrong error for insert:", err)
}
// Add anotherID to trusted set and try again
- srv.AddTrustedPeer(&discover.Node{ID: anotherID})
+ srv.AddTrustedPeer(newNode(anotherID, nil))
c = newconn(anotherID)
if err := srv.checkpoint(c, srv.posthandshake); err != nil {
t.Error("unexpected error for trusted conn @posthandshake:", err)
@@ -409,20 +410,17 @@ func TestServerAtCap(t *testing.T) {
func TestServerPeerLimits(t *testing.T) {
srvkey := newkey()
+ clientkey := newkey()
+ clientnode := enode.NewV4(&clientkey.PublicKey, nil, 0, 0)
- clientid := randomID()
- clientnode := &discover.Node{ID: clientid}
-
- var tp *setupTransport = &setupTransport{
- id: clientid,
- phs: &protoHandshake{
- ID: clientid,
+ var tp = &setupTransport{
+ pubkey: &clientkey.PublicKey,
+ phs: protoHandshake{
+ ID: crypto.FromECDSAPub(&clientkey.PublicKey)[1:],
// Force "DiscUselessPeer" due to unmatching caps
// Caps: []Cap{discard.cap()},
},
}
- var flags connFlag = dynDialedConn
- var dialDest *discover.Node = &discover.Node{ID: clientid}
srv := &Server{
Config: Config{
@@ -440,6 +438,8 @@ func TestServerPeerLimits(t *testing.T) {
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 {
@@ -473,59 +473,61 @@ func TestServerPeerLimits(t *testing.T) {
}
func TestServerSetupConn(t *testing.T) {
- id := randomID()
- srvkey := newkey()
- srvid := discover.PubkeyID(&srvkey.PublicKey)
+ var (
+ clientkey, srvkey = newkey(), newkey()
+ clientpub = &clientkey.PublicKey
+ srvpub = &srvkey.PublicKey
+ )
tests := []struct {
dontstart bool
tt *setupTransport
flags connFlag
- dialDest *discover.Node
+ dialDest *enode.Node
wantCloseErr error
wantCalls string
}{
{
dontstart: true,
- tt: &setupTransport{id: id},
+ tt: &setupTransport{pubkey: clientpub},
wantCalls: "close,",
wantCloseErr: errServerStopped,
},
{
- tt: &setupTransport{id: id, encHandshakeErr: errors.New("read error")},
+ tt: &setupTransport{pubkey: clientpub, encHandshakeErr: errors.New("read error")},
flags: inboundConn,
wantCalls: "doEncHandshake,close,",
wantCloseErr: errors.New("read error"),
},
{
- tt: &setupTransport{id: id},
- dialDest: &discover.Node{ID: randomID()},
+ tt: &setupTransport{pubkey: clientpub},
+ dialDest: enode.NewV4(&newkey().PublicKey, nil, 0, 0),
flags: dynDialedConn,
wantCalls: "doEncHandshake,close,",
wantCloseErr: DiscUnexpectedIdentity,
},
{
- tt: &setupTransport{id: id, phs: &protoHandshake{ID: randomID()}},
- dialDest: &discover.Node{ID: id},
+ 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{id: id, protoHandshakeErr: errors.New("foo")},
- dialDest: &discover.Node{ID: id},
+ tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: errors.New("foo")},
+ dialDest: enode.NewV4(clientpub, nil, 0, 0),
flags: dynDialedConn,
wantCalls: "doEncHandshake,doProtoHandshake,close,",
wantCloseErr: errors.New("foo"),
},
{
- tt: &setupTransport{id: srvid, phs: &protoHandshake{ID: srvid}},
+ tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}},
flags: inboundConn,
wantCalls: "doEncHandshake,close,",
wantCloseErr: DiscSelf,
},
{
- tt: &setupTransport{id: id, phs: &protoHandshake{ID: id}},
+ tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: crypto.FromECDSAPub(clientpub)[1:]}},
flags: inboundConn,
wantCalls: "doEncHandshake,doProtoHandshake,close,",
wantCloseErr: DiscUselessPeer,
@@ -560,26 +562,26 @@ func TestServerSetupConn(t *testing.T) {
}
type setupTransport struct {
- id discover.NodeID
- encHandshakeErr error
-
- phs *protoHandshake
+ pubkey *ecdsa.PublicKey
+ encHandshakeErr error
+ phs protoHandshake
protoHandshakeErr error
calls string
closeErr error
}
-func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) {
+func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) {
c.calls += "doEncHandshake,"
- return c.id, c.encHandshakeErr
+ 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
+ return &c.phs, nil
}
func (c *setupTransport) close(err error) {
c.calls += "close,"
@@ -602,7 +604,7 @@ func newkey() *ecdsa.PrivateKey {
return key
}
-func randomID() (id discover.NodeID) {
+func randomID() (id enode.ID) {
for i := range id {
id[i] = byte(rand.Intn(255))
}
diff --git a/p2p/simulations/adapters/docker.go b/p2p/simulations/adapters/docker.go
index d145c46b3..82eab0e9c 100644
--- a/p2p/simulations/adapters/docker.go
+++ b/p2p/simulations/adapters/docker.go
@@ -29,7 +29,7 @@ import (
"github.com/docker/docker/pkg/reexec"
"github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
)
var (
@@ -64,7 +64,7 @@ func NewDockerAdapter() (*DockerAdapter, error) {
return &DockerAdapter{
ExecAdapter{
- nodes: make(map[discover.NodeID]*ExecNode),
+ nodes: make(map[enode.ID]*ExecNode),
},
}, nil
}
diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go
index e64cebc2a..dc7d277ca 100644
--- a/p2p/simulations/adapters/exec.go
+++ b/p2p/simulations/adapters/exec.go
@@ -38,7 +38,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rpc"
"golang.org/x/net/websocket"
)
@@ -54,7 +54,7 @@ type ExecAdapter struct {
// simulation node are created.
BaseDir string
- nodes map[discover.NodeID]*ExecNode
+ nodes map[enode.ID]*ExecNode
}
// NewExecAdapter returns an ExecAdapter which stores node data in
@@ -62,7 +62,7 @@ type ExecAdapter struct {
func NewExecAdapter(baseDir string) *ExecAdapter {
return &ExecAdapter{
BaseDir: baseDir,
- nodes: make(map[discover.NodeID]*ExecNode),
+ nodes: make(map[enode.ID]*ExecNode),
}
}
@@ -122,7 +122,7 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
// ExecNode starts a simulation node by exec'ing the current binary and
// running the configured services
type ExecNode struct {
- ID discover.NodeID
+ ID enode.ID
Dir string
Config *execNodeConfig
Cmd *exec.Cmd
@@ -492,7 +492,7 @@ type wsRPCDialer struct {
// DialRPC implements the RPCDialer interface by creating a WebSocket RPC
// client of the given node
-func (w *wsRPCDialer) DialRPC(id discover.NodeID) (*rpc.Client, error) {
+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)
diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go
index b0fdf49b9..52a662be6 100644
--- a/p2p/simulations/adapters/inproc.go
+++ b/p2p/simulations/adapters/inproc.go
@@ -27,7 +27,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/pipes"
"github.com/ethereum/go-ethereum/rpc"
)
@@ -37,7 +37,7 @@ import (
type SimAdapter struct {
pipe func() (net.Conn, net.Conn, error)
mtx sync.RWMutex
- nodes map[discover.NodeID]*SimNode
+ nodes map[enode.ID]*SimNode
services map[string]ServiceFunc
}
@@ -48,7 +48,7 @@ type SimAdapter struct {
func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter {
return &SimAdapter{
pipe: pipes.NetPipe,
- nodes: make(map[discover.NodeID]*SimNode),
+ nodes: make(map[enode.ID]*SimNode),
services: services,
}
}
@@ -56,7 +56,7 @@ func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter {
func NewTCPAdapter(services map[string]ServiceFunc) *SimAdapter {
return &SimAdapter{
pipe: pipes.TCPPipe,
- nodes: make(map[discover.NodeID]*SimNode),
+ nodes: make(map[enode.ID]*SimNode),
services: services,
}
}
@@ -115,14 +115,14 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
// Dial implements the p2p.NodeDialer interface by connecting to the node using
// an in-memory net.Pipe
-func (s *SimAdapter) Dial(dest *discover.Node) (conn net.Conn, err error) {
- node, ok := s.GetNode(dest.ID)
+func (s *SimAdapter) Dial(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)
+ 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)
+ return nil, fmt.Errorf("node not running: %s", dest.ID())
}
// SimAdapter.pipe is net.Pipe (NewSimAdapter)
pipe1, pipe2, err := s.pipe()
@@ -138,7 +138,7 @@ func (s *SimAdapter) Dial(dest *discover.Node) (conn net.Conn, err error) {
// DialRPC implements the RPCDialer interface by creating an in-memory RPC
// client of the given node
-func (s *SimAdapter) DialRPC(id discover.NodeID) (*rpc.Client, error) {
+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)
@@ -151,7 +151,7 @@ func (s *SimAdapter) DialRPC(id discover.NodeID) (*rpc.Client, error) {
}
// GetNode returns the node with the given ID if it exists
-func (s *SimAdapter) GetNode(id discover.NodeID) (*SimNode, bool) {
+func (s *SimAdapter) GetNode(id enode.ID) (*SimNode, bool) {
s.mtx.RLock()
defer s.mtx.RUnlock()
node, ok := s.nodes[id]
@@ -163,7 +163,7 @@ func (s *SimAdapter) GetNode(id discover.NodeID) (*SimNode, bool) {
// pipe
type SimNode struct {
lock sync.RWMutex
- ID discover.NodeID
+ ID enode.ID
config *NodeConfig
adapter *SimAdapter
node *node.Node
@@ -177,9 +177,9 @@ func (sn *SimNode) Addr() []byte {
return []byte(sn.Node().String())
}
-// Node returns a discover.Node representing the SimNode
-func (sn *SimNode) Node() *discover.Node {
- return discover.NewNode(sn.ID, net.IP{127, 0, 0, 1}, 30303, 30303)
+// 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
diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go
index 2c4b9dd8f..6681726e4 100644
--- a/p2p/simulations/adapters/types.go
+++ b/p2p/simulations/adapters/types.go
@@ -29,7 +29,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rpc"
)
@@ -78,7 +78,7 @@ type NodeAdapter interface {
type NodeConfig struct {
// ID is the node's ID which is used to identify the node in the
// simulation network
- ID discover.NodeID
+ ID enode.ID
// PrivateKey is the node's private key which is used by the devp2p
// stack to encrypt communications
@@ -97,7 +97,7 @@ type NodeConfig struct {
Services []string
// function to sanction or prevent suggesting a peer
- Reachable func(id discover.NodeID) bool
+ Reachable func(id enode.ID) bool
Port uint16
}
@@ -138,11 +138,9 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
}
if confJSON.ID != "" {
- nodeID, err := discover.HexID(confJSON.ID)
- if err != nil {
+ if err := n.ID.UnmarshalText([]byte(confJSON.ID)); err != nil {
return err
}
- n.ID = nodeID
}
if confJSON.PrivateKey != "" {
@@ -165,6 +163,11 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
return nil
}
+// Node returns the node descriptor represented by the config.
+func (n *NodeConfig) Node() *enode.Node {
+ return enode.NewV4(&n.PrivateKey.PublicKey, net.IP{127, 0, 0, 1}, int(n.Port), int(n.Port))
+}
+
// RandomNodeConfig returns node configuration with a randomly generated ID and
// PrivateKey
func RandomNodeConfig() *NodeConfig {
@@ -173,7 +176,7 @@ func RandomNodeConfig() *NodeConfig {
panic("unable to generate key")
}
- id := discover.PubkeyID(&key.PublicKey)
+ id := enode.PubkeyToIDV4(&key.PublicKey)
port, err := assignTCPPort()
if err != nil {
panic("unable to assign tcp port")
@@ -218,7 +221,7 @@ type ServiceContext struct {
// 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 discover.NodeID) (*rpc.Client, error)
+ DialRPC(id enode.ID) (*rpc.Client, error)
}
// Services is a collection of services which can be run in a simulation
diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go
index 6a0ead53a..597cc950c 100644
--- a/p2p/simulations/examples/ping-pong.go
+++ b/p2p/simulations/examples/ping-pong.go
@@ -28,7 +28,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/rpc"
@@ -96,12 +96,12 @@ func main() {
// sends a ping to all its connected peers every 10s and receives a pong in
// return
type pingPongService struct {
- id discover.NodeID
+ id enode.ID
log log.Logger
received int64
}
-func newPingPongService(id discover.NodeID) *pingPongService {
+func newPingPongService(id enode.ID) *pingPongService {
return &pingPongService{
id: id,
log: log.New("node.id", id),
diff --git a/p2p/simulations/http.go b/p2p/simulations/http.go
index 24001f194..1f44cc667 100644
--- a/p2p/simulations/http.go
+++ b/p2p/simulations/http.go
@@ -31,7 +31,7 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/rpc"
"github.com/julienschmidt/httprouter"
@@ -709,8 +709,9 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
ctx := context.Background()
if id := params.ByName("nodeid"); id != "" {
+ var nodeID enode.ID
var node *Node
- if nodeID, err := discover.HexID(id); err == nil {
+ if nodeID.UnmarshalText([]byte(id)) == nil {
node = s.network.GetNode(nodeID)
} else {
node = s.network.GetNodeByName(id)
@@ -723,8 +724,9 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
}
if id := params.ByName("peerid"); id != "" {
+ var peerID enode.ID
var peer *Node
- if peerID, err := discover.HexID(id); err == nil {
+ if peerID.UnmarshalText([]byte(id)) == nil {
peer = s.network.GetNode(peerID)
} else {
peer = s.network.GetNodeByName(id)
diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go
index 732d49f54..7bdbad1b5 100644
--- a/p2p/simulations/http_test.go
+++ b/p2p/simulations/http_test.go
@@ -30,7 +30,7 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/rpc"
)
@@ -38,12 +38,12 @@ import (
// 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 discover.NodeID
+ id enode.ID
// peerCount is incremented once a peer handshake has been performed
peerCount int64
- peers map[discover.NodeID]*testPeer
+ peers map[enode.ID]*testPeer
peersMtx sync.Mutex
// state stores []byte which is used to test creating and loading
@@ -54,7 +54,7 @@ type testService struct {
func newTestService(ctx *adapters.ServiceContext) (node.Service, error) {
svc := &testService{
id: ctx.Config.ID,
- peers: make(map[discover.NodeID]*testPeer),
+ peers: make(map[enode.ID]*testPeer),
}
svc.state.Store(ctx.Snapshot)
return svc, nil
@@ -65,7 +65,7 @@ type testPeer struct {
dumReady chan struct{}
}
-func (t *testService) peer(id discover.NodeID) *testPeer {
+func (t *testService) peer(id enode.ID) *testPeer {
t.peersMtx.Lock()
defer t.peersMtx.Unlock()
if peer, ok := t.peers[id]; ok {
@@ -410,7 +410,7 @@ func (t *expectEvents) nodeEvent(id string, up bool) *Event {
Type: EventTypeNode,
Node: &Node{
Config: &adapters.NodeConfig{
- ID: discover.MustHexID(id),
+ ID: enode.HexID(id),
},
Up: up,
},
@@ -421,8 +421,8 @@ func (t *expectEvents) connEvent(one, other string, up bool) *Event {
return &Event{
Type: EventTypeConn,
Conn: &Conn{
- One: discover.MustHexID(one),
- Other: discover.MustHexID(other),
+ One: enode.HexID(one),
+ Other: enode.HexID(other),
Up: up,
},
}
diff --git a/p2p/simulations/mocker.go b/p2p/simulations/mocker.go
index 389b1e3ec..8ce777a01 100644
--- a/p2p/simulations/mocker.go
+++ b/p2p/simulations/mocker.go
@@ -25,7 +25,7 @@ import (
"time"
"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/simulations/adapters"
)
@@ -154,7 +154,7 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) {
wg.Done()
continue
}
- go func(id discover.NodeID) {
+ go func(id enode.ID) {
time.Sleep(randWait)
err := net.Start(id)
if err != nil {
@@ -169,8 +169,8 @@ func probabilistic(net *Network, quit chan struct{}, nodeCount int) {
}
//connect nodeCount number of nodes in a ring
-func connectNodesInRing(net *Network, nodeCount int) ([]discover.NodeID, error) {
- ids := make([]discover.NodeID, nodeCount)
+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)
diff --git a/p2p/simulations/mocker_test.go b/p2p/simulations/mocker_test.go
index de8ec0b33..7c7016a5e 100644
--- a/p2p/simulations/mocker_test.go
+++ b/p2p/simulations/mocker_test.go
@@ -27,7 +27,7 @@ import (
"testing"
"time"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
)
func TestMocker(t *testing.T) {
@@ -82,7 +82,7 @@ func TestMocker(t *testing.T) {
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[discover.NodeID]bool)
+ nodemap := make(map[enode.ID]bool)
wg.Add(1)
nodesComplete := false
connCount := 0
diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go
index 0fb7485ad..101ac09f8 100644
--- a/p2p/simulations/network.go
+++ b/p2p/simulations/network.go
@@ -27,7 +27,7 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
)
@@ -51,7 +51,7 @@ type Network struct {
NetworkConfig
Nodes []*Node `json:"nodes"`
- nodeMap map[discover.NodeID]int
+ nodeMap map[enode.ID]int
Conns []*Conn `json:"conns"`
connMap map[string]int
@@ -67,7 +67,7 @@ func NewNetwork(nodeAdapter adapters.NodeAdapter, conf *NetworkConfig) *Network
return &Network{
NetworkConfig: *conf,
nodeAdapter: nodeAdapter,
- nodeMap: make(map[discover.NodeID]int),
+ nodeMap: make(map[enode.ID]int),
connMap: make(map[string]int),
quitc: make(chan struct{}),
}
@@ -85,7 +85,7 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error)
defer net.lock.Unlock()
if conf.Reachable == nil {
- conf.Reachable = func(otherID discover.NodeID) bool {
+ 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
@@ -158,13 +158,13 @@ func (net *Network) StopAll() error {
}
// Start starts the node with the given ID
-func (net *Network) Start(id discover.NodeID) error {
+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 discover.NodeID, snapshots map[string][]byte) error {
+func (net *Network) startWithSnapshots(id enode.ID, snapshots map[string][]byte) error {
net.lock.Lock()
defer net.lock.Unlock()
node := net.getNode(id)
@@ -200,7 +200,7 @@ func (net *Network) startWithSnapshots(id discover.NodeID, snapshots map[string]
// watchPeerEvents reads peer events from the given channel and emits
// corresponding network events
-func (net *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEvent, sub event.Subscription) {
+func (net *Network) watchPeerEvents(id enode.ID, events chan *p2p.PeerEvent, sub event.Subscription) {
defer func() {
sub.Unsubscribe()
@@ -248,7 +248,7 @@ func (net *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEve
}
// Stop stops the node with the given ID
-func (net *Network) Stop(id discover.NodeID) error {
+func (net *Network) Stop(id enode.ID) error {
net.lock.Lock()
defer net.lock.Unlock()
node := net.getNode(id)
@@ -270,7 +270,7 @@ func (net *Network) Stop(id discover.NodeID) error {
// 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 discover.NodeID) error {
+func (net *Network) Connect(oneID, otherID enode.ID) error {
log.Debug(fmt.Sprintf("connecting %s to %s", oneID, otherID))
conn, err := net.InitConn(oneID, otherID)
if err != nil {
@@ -286,7 +286,7 @@ func (net *Network) Connect(oneID, otherID discover.NodeID) error {
// 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 discover.NodeID) error {
+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)
@@ -303,7 +303,7 @@ func (net *Network) Disconnect(oneID, otherID discover.NodeID) error {
}
// DidConnect tracks the fact that the "one" node connected to the "other" node
-func (net *Network) DidConnect(one, other discover.NodeID) error {
+func (net *Network) DidConnect(one, other enode.ID) error {
net.lock.Lock()
defer net.lock.Unlock()
conn, err := net.getOrCreateConn(one, other)
@@ -320,7 +320,7 @@ func (net *Network) DidConnect(one, other discover.NodeID) error {
// DidDisconnect tracks the fact that the "one" node disconnected from the
// "other" node
-func (net *Network) DidDisconnect(one, other discover.NodeID) error {
+func (net *Network) DidDisconnect(one, other enode.ID) error {
net.lock.Lock()
defer net.lock.Unlock()
conn := net.getConn(one, other)
@@ -337,7 +337,7 @@ func (net *Network) DidDisconnect(one, other discover.NodeID) error {
}
// DidSend tracks the fact that "sender" sent a message to "receiver"
-func (net *Network) DidSend(sender, receiver discover.NodeID, proto string, code uint64) error {
+func (net *Network) DidSend(sender, receiver enode.ID, proto string, code uint64) error {
msg := &Msg{
One: sender,
Other: receiver,
@@ -350,7 +350,7 @@ func (net *Network) DidSend(sender, receiver discover.NodeID, proto string, code
}
// DidReceive tracks the fact that "receiver" received a message from "sender"
-func (net *Network) DidReceive(sender, receiver discover.NodeID, proto string, code uint64) error {
+func (net *Network) DidReceive(sender, receiver enode.ID, proto string, code uint64) error {
msg := &Msg{
One: sender,
Other: receiver,
@@ -364,7 +364,7 @@ func (net *Network) DidReceive(sender, receiver discover.NodeID, proto string, c
// GetNode gets the node with the given ID, returning nil if the node does not
// exist
-func (net *Network) GetNode(id discover.NodeID) *Node {
+func (net *Network) GetNode(id enode.ID) *Node {
net.lock.Lock()
defer net.lock.Unlock()
return net.getNode(id)
@@ -387,7 +387,7 @@ func (net *Network) GetNodes() (nodes []*Node) {
return nodes
}
-func (net *Network) getNode(id discover.NodeID) *Node {
+func (net *Network) getNode(id enode.ID) *Node {
i, found := net.nodeMap[id]
if !found {
return nil
@@ -406,7 +406,7 @@ func (net *Network) getNodeByName(name string) *Node {
// GetConn returns the connection which exists between "one" and "other"
// regardless of which node initiated the connection
-func (net *Network) GetConn(oneID, otherID discover.NodeID) *Conn {
+func (net *Network) GetConn(oneID, otherID enode.ID) *Conn {
net.lock.Lock()
defer net.lock.Unlock()
return net.getConn(oneID, otherID)
@@ -414,13 +414,13 @@ func (net *Network) GetConn(oneID, otherID discover.NodeID) *Conn {
// GetOrCreateConn is like GetConn but creates the connection if it doesn't
// already exist
-func (net *Network) GetOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) {
+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 discover.NodeID) (*Conn, error) {
+func (net *Network) getOrCreateConn(oneID, otherID enode.ID) (*Conn, error) {
if conn := net.getConn(oneID, otherID); conn != nil {
return conn, nil
}
@@ -445,7 +445,7 @@ func (net *Network) getOrCreateConn(oneID, otherID discover.NodeID) (*Conn, erro
return conn, nil
}
-func (net *Network) getConn(oneID, otherID discover.NodeID) *Conn {
+func (net *Network) getConn(oneID, otherID enode.ID) *Conn {
label := ConnLabel(oneID, otherID)
i, found := net.connMap[label]
if !found {
@@ -462,7 +462,7 @@ func (net *Network) getConn(oneID, otherID discover.NodeID) *Conn {
// 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 discover.NodeID) (*Conn, error) {
+func (net *Network) InitConn(oneID, otherID enode.ID) (*Conn, error) {
net.lock.Lock()
defer net.lock.Unlock()
if oneID == otherID {
@@ -508,7 +508,7 @@ func (net *Network) Reset() {
//re-initialize the maps
net.connMap = make(map[string]int)
- net.nodeMap = make(map[discover.NodeID]int)
+ net.nodeMap = make(map[enode.ID]int)
net.Nodes = nil
net.Conns = nil
@@ -527,7 +527,7 @@ type Node struct {
}
// ID returns the ID of the node
-func (n *Node) ID() discover.NodeID {
+func (n *Node) ID() enode.ID {
return n.Config.ID
}
@@ -564,10 +564,10 @@ func (n *Node) MarshalJSON() ([]byte, error) {
// Conn represents a connection between two nodes in the network
type Conn struct {
// One is the node which initiated the connection
- One discover.NodeID `json:"one"`
+ One enode.ID `json:"one"`
// Other is the node which the connection was made to
- Other discover.NodeID `json:"other"`
+ Other enode.ID `json:"other"`
// Up tracks whether or not the connection is active
Up bool `json:"up"`
@@ -596,11 +596,11 @@ func (c *Conn) String() string {
// Msg represents a p2p message sent between two nodes in the network
type Msg struct {
- One discover.NodeID `json:"one"`
- Other discover.NodeID `json:"other"`
- Protocol string `json:"protocol"`
- Code uint64 `json:"code"`
- Received bool `json:"received"`
+ 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
@@ -611,8 +611,8 @@ func (m *Msg) String() string {
// 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 discover.NodeID) string {
- var first, second discover.NodeID
+func ConnLabel(source, target enode.ID) string {
+ var first, second enode.ID
if bytes.Compare(source.Bytes(), target.Bytes()) > 0 {
first = target
second = source
diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go
index f178bac50..f34935265 100644
--- a/p2p/simulations/network_test.go
+++ b/p2p/simulations/network_test.go
@@ -22,7 +22,7 @@ import (
"testing"
"time"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
)
@@ -39,7 +39,7 @@ func TestNetworkSimulation(t *testing.T) {
})
defer network.Shutdown()
nodeCount := 20
- ids := make([]discover.NodeID, nodeCount)
+ ids := make([]enode.ID, nodeCount)
for i := 0; i < nodeCount; i++ {
conf := adapters.RandomNodeConfig()
node, err := network.NewNodeWithConfig(conf)
@@ -64,7 +64,7 @@ func TestNetworkSimulation(t *testing.T) {
}
return nil
}
- check := func(ctx context.Context, id discover.NodeID) (bool, error) {
+ check := func(ctx context.Context, id enode.ID) (bool, error) {
// check we haven't run out of time
select {
case <-ctx.Done():
@@ -102,7 +102,7 @@ func TestNetworkSimulation(t *testing.T) {
defer cancel()
// trigger a check every 100ms
- trigger := make(chan discover.NodeID)
+ trigger := make(chan enode.ID)
go triggerChecks(ctx, ids, trigger, 100*time.Millisecond)
result := NewSimulation(network).Run(ctx, &Step{
@@ -140,7 +140,7 @@ func TestNetworkSimulation(t *testing.T) {
}
}
-func triggerChecks(ctx context.Context, ids []discover.NodeID, trigger chan discover.NodeID, interval time.Duration) {
+func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) {
tick := time.NewTicker(interval)
defer tick.Stop()
for {
diff --git a/p2p/simulations/simulation.go b/p2p/simulations/simulation.go
index 28886e924..ae62c42b9 100644
--- a/p2p/simulations/simulation.go
+++ b/p2p/simulations/simulation.go
@@ -20,7 +20,7 @@ import (
"context"
"time"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
)
// Simulation provides a framework for running actions in a simulated network
@@ -55,7 +55,7 @@ func (s *Simulation) Run(ctx context.Context, step *Step) (result *StepResult) {
}
// wait for all node expectations to either pass, error or timeout
- nodes := make(map[discover.NodeID]struct{}, len(step.Expect.Nodes))
+ nodes := make(map[enode.ID]struct{}, len(step.Expect.Nodes))
for _, id := range step.Expect.Nodes {
nodes[id] = struct{}{}
}
@@ -119,7 +119,7 @@ type Step struct {
// Trigger is a channel which receives node ids and triggers an
// expectation check for that node
- Trigger chan discover.NodeID
+ Trigger chan enode.ID
// Expect is the expectation to wait for when performing this step
Expect *Expectation
@@ -127,15 +127,15 @@ type Step struct {
type Expectation struct {
// Nodes is a list of nodes to check
- Nodes []discover.NodeID
+ Nodes []enode.ID
// Check checks whether a given node meets the expectation
- Check func(context.Context, discover.NodeID) (bool, error)
+ Check func(context.Context, enode.ID) (bool, error)
}
func newStepResult() *StepResult {
return &StepResult{
- Passes: make(map[discover.NodeID]time.Time),
+ Passes: make(map[enode.ID]time.Time),
}
}
@@ -150,7 +150,7 @@ type StepResult struct {
FinishedAt time.Time
// Passes are the timestamps of the successful node expectations
- Passes map[discover.NodeID]time.Time
+ Passes map[enode.ID]time.Time
// NetworkEvents are the network events which occurred during the step
NetworkEvents []*Event
diff --git a/p2p/testing/peerpool.go b/p2p/testing/peerpool.go
index ed00396e2..01ccce67e 100644
--- a/p2p/testing/peerpool.go
+++ b/p2p/testing/peerpool.go
@@ -21,22 +21,22 @@ import (
"sync"
"github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
)
type TestPeer interface {
- ID() discover.NodeID
+ ID() enode.ID
Drop(error)
}
// TestPeerPool is an example peerPool to demonstrate registration of peer connections
type TestPeerPool struct {
lock sync.Mutex
- peers map[discover.NodeID]TestPeer
+ peers map[enode.ID]TestPeer
}
func NewTestPeerPool() *TestPeerPool {
- return &TestPeerPool{peers: make(map[discover.NodeID]TestPeer)}
+ return &TestPeerPool{peers: make(map[enode.ID]TestPeer)}
}
func (p *TestPeerPool) Add(peer TestPeer) {
@@ -53,14 +53,14 @@ func (p *TestPeerPool) Remove(peer TestPeer) {
delete(p.peers, peer.ID())
}
-func (p *TestPeerPool) Has(id discover.NodeID) bool {
+func (p *TestPeerPool) Has(id enode.ID) bool {
p.lock.Lock()
defer p.lock.Unlock()
_, ok := p.peers[id]
return ok
}
-func (p *TestPeerPool) Get(id discover.NodeID) TestPeer {
+func (p *TestPeerPool) Get(id enode.ID) TestPeer {
p.lock.Lock()
defer p.lock.Unlock()
return p.peers[id]
diff --git a/p2p/testing/protocolsession.go b/p2p/testing/protocolsession.go
index e3ec41ad6..476c2a984 100644
--- a/p2p/testing/protocolsession.go
+++ b/p2p/testing/protocolsession.go
@@ -24,7 +24,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
)
@@ -35,7 +35,7 @@ var errTimedOut = errors.New("timed out")
// receive (expect) messages
type ProtocolSession struct {
Server *p2p.Server
- IDs []discover.NodeID
+ Nodes []*enode.Node
adapter *adapters.SimAdapter
events chan *p2p.PeerEvent
}
@@ -56,32 +56,32 @@ type Exchange struct {
// Trigger is part of the exchange, incoming message for the pivot node
// sent by a peer
type Trigger struct {
- Msg interface{} // type of message to be sent
- Code uint64 // code of message is given
- Peer discover.NodeID // the peer to send the message to
- Timeout time.Duration // timeout duration for the sending
+ Msg interface{} // type of message to be sent
+ Code uint64 // code of message is given
+ Peer enode.ID // the peer to send the message to
+ Timeout time.Duration // timeout duration for the sending
}
// Expect is part of an exchange, outgoing message from the pivot node
// received by a peer
type Expect struct {
- Msg interface{} // type of message to expect
- Code uint64 // code of message is now given
- Peer discover.NodeID // the peer that expects the message
- Timeout time.Duration // timeout duration for receiving
+ Msg interface{} // type of message to expect
+ Code uint64 // code of message is now given
+ Peer enode.ID // the peer that expects the message
+ Timeout time.Duration // timeout duration for receiving
}
// Disconnect represents a disconnect event, used and checked by TestDisconnected
type Disconnect struct {
- Peer discover.NodeID // discconnected peer
- Error error // disconnect reason
+ Peer enode.ID // discconnected peer
+ Error error // disconnect reason
}
// trigger sends messages from peers
func (s *ProtocolSession) trigger(trig Trigger) error {
simNode, ok := s.adapter.GetNode(trig.Peer)
if !ok {
- return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.IDs))
+ return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.Nodes))
}
mockNode, ok := simNode.Services()[0].(*mockNode)
if !ok {
@@ -111,7 +111,7 @@ func (s *ProtocolSession) trigger(trig Trigger) error {
// expect checks an expectation of a message sent out by the pivot node
func (s *ProtocolSession) expect(exps []Expect) error {
// construct a map of expectations for each node
- peerExpects := make(map[discover.NodeID][]Expect)
+ peerExpects := make(map[enode.ID][]Expect)
for _, exp := range exps {
if exp.Msg == nil {
return errors.New("no message to expect")
@@ -120,11 +120,11 @@ func (s *ProtocolSession) expect(exps []Expect) error {
}
// construct a map of mockNodes for each node
- mockNodes := make(map[discover.NodeID]*mockNode)
+ mockNodes := make(map[enode.ID]*mockNode)
for nodeID := range peerExpects {
simNode, ok := s.adapter.GetNode(nodeID)
if !ok {
- return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.IDs))
+ return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.Nodes))
}
mockNode, ok := simNode.Services()[0].(*mockNode)
if !ok {
@@ -253,7 +253,7 @@ func (s *ProtocolSession) testExchange(e Exchange) error {
// TestDisconnected tests the disconnections given as arguments
// the disconnect structs describe what disconnect error is expected on which peer
func (s *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
- expects := make(map[discover.NodeID]error)
+ expects := make(map[enode.ID]error)
for _, disconnect := range disconnects {
expects[disconnect.Peer] = disconnect.Error
}
diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go
index c99578fe0..afc03b009 100644
--- a/p2p/testing/protocoltester.go
+++ b/p2p/testing/protocoltester.go
@@ -35,7 +35,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/rlp"
@@ -52,7 +52,7 @@ type ProtocolTester struct {
// NewProtocolTester constructs a new ProtocolTester
// it takes as argument the pivot node id, the number of dummy peers and the
// protocol run function called on a peer connection by the p2p server
-func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
+func NewProtocolTester(t *testing.T, id enode.ID, n int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
services := adapters.Services{
"test": func(ctx *adapters.ServiceContext) (node.Service, error) {
return &testNode{run}, nil
@@ -76,17 +76,17 @@ func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Pe
node := net.GetNode(id).Node.(*adapters.SimNode)
peers := make([]*adapters.NodeConfig, n)
- peerIDs := make([]discover.NodeID, n)
+ nodes := make([]*enode.Node, n)
for i := 0; i < n; i++ {
peers[i] = adapters.RandomNodeConfig()
peers[i].Services = []string{"mock"}
- peerIDs[i] = peers[i].ID
+ nodes[i] = peers[i].Node()
}
events := make(chan *p2p.PeerEvent, 1000)
node.SubscribeEvents(events)
ps := &ProtocolSession{
Server: node.Server(),
- IDs: peerIDs,
+ Nodes: nodes,
adapter: adapter,
events: events,
}
@@ -108,7 +108,7 @@ func (t *ProtocolTester) Stop() error {
// Connect brings up the remote peer node and connects it using the
// p2p/simulations network connection with the in memory network adapter
-func (t *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) {
+func (t *ProtocolTester) Connect(selfID enode.ID, peers ...*adapters.NodeConfig) {
for _, peer := range peers {
log.Trace(fmt.Sprintf("start node %v", peer.ID))
if _, err := t.network.NewNodeWithConfig(peer); err != nil {