aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix Lange <fjl@twurst.com>2019-06-07 21:31:00 +0800
committerGitHub <noreply@github.com>2019-06-07 21:31:00 +0800
commite83c3ccc47b2001b7871b60084d10c5f861c9c93 (patch)
tree54014c38b9cab361d2194304b19d2f51c11d7cda
parent896322bf88f40329b400d691cbdce9275739310e (diff)
downloadgo-tangerine-e83c3ccc47b2001b7871b60084d10c5f861c9c93.tar
go-tangerine-e83c3ccc47b2001b7871b60084d10c5f861c9c93.tar.gz
go-tangerine-e83c3ccc47b2001b7871b60084d10c5f861c9c93.tar.bz2
go-tangerine-e83c3ccc47b2001b7871b60084d10c5f861c9c93.tar.lz
go-tangerine-e83c3ccc47b2001b7871b60084d10c5f861c9c93.tar.xz
go-tangerine-e83c3ccc47b2001b7871b60084d10c5f861c9c93.tar.zst
go-tangerine-e83c3ccc47b2001b7871b60084d10c5f861c9c93.zip
p2p/enode: improve IPv6 support, add ENR text representation (#19663)
* p2p/enr: add entries for for IPv4/IPv6 separation This adds entry types for "ip6", "udp6", "tcp6" keys. The IP type stays around because removing it would break a lot of code and force everyone to care about the distinction. * p2p/enode: track IPv4 and IPv6 address separately LocalNode predicts the local node's UDP endpoint and updates the record. This change makes it predict IPv4 and IPv6 endpoints separately since they can now be in the record at the same time. * p2p/enode: implement base64 text format * all: switch to enode.Parse(...) This allows passing base64-encoded node records to all the places that previously accepted enode:// URLs. The URL format is still supported. * cmd/bootnode, p2p: log node URL instead of ENR ...and return the base64 record in NodeInfo.
-rw-r--r--cmd/bootnode/main.go2
-rw-r--r--cmd/faucet/faucet.go2
-rw-r--r--cmd/utils/flags.go2
-rw-r--r--cmd/wnode/main.go6
-rw-r--r--les/serverpool.go2
-rw-r--r--les/ulc.go2
-rw-r--r--node/api.go8
-rw-r--r--node/config.go2
-rw-r--r--p2p/discover/v4_udp_test.go8
-rw-r--r--p2p/enode/localnode.go120
-rw-r--r--p2p/enode/localnode_test.go46
-rw-r--r--p2p/enode/node.go67
-rw-r--r--p2p/enode/node_test.go87
-rw-r--r--p2p/enode/urlv4.go18
-rw-r--r--p2p/enode/urlv4_test.go185
-rw-r--r--p2p/enr/enr.go12
-rw-r--r--p2p/enr/enr_test.go24
-rw-r--r--p2p/enr/entries.go76
-rw-r--r--p2p/server.go9
-rw-r--r--whisper/whisperv6/api.go4
20 files changed, 463 insertions, 219 deletions
diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go
index a40b32b60..2f9bba111 100644
--- a/cmd/bootnode/main.go
+++ b/cmd/bootnode/main.go
@@ -143,7 +143,7 @@ func printNotice(nodeKey *ecdsa.PublicKey, addr net.UDPAddr) {
addr.IP = net.IP{127, 0, 0, 1}
}
n := enode.NewV4(nodeKey, addr.IP, 0, addr.Port)
- fmt.Println(n.String())
+ fmt.Println(n.URLv4())
fmt.Println("Note: you're using cmd/bootnode, a developer tool.")
fmt.Println("We recommend using a regular node as bootstrap node for production deployments.")
}
diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go
index 08b23d46c..f8092084a 100644
--- a/cmd/faucet/faucet.go
+++ b/cmd/faucet/faucet.go
@@ -260,7 +260,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
return nil, err
}
for _, boot := range enodes {
- old, err := enode.ParseV4(boot.String())
+ old, err := enode.Parse(enode.ValidSchemes, boot.String())
if err == nil {
stack.Server().AddPeer(old)
}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 002d37f16..973e47ea0 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -794,7 +794,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls))
for _, url := range urls {
if url != "" {
- node, err := enode.ParseV4(url)
+ node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
log.Crit("Bootstrap URL invalid", "enode", url, "err", err)
continue
diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go
index 97e585201..99cf84ec1 100644
--- a/cmd/wnode/main.go
+++ b/cmd/wnode/main.go
@@ -203,7 +203,7 @@ func initialize() {
if len(*argEnode) == 0 {
argEnode = scanLineA("Please enter the peer's enode: ")
}
- peer := enode.MustParseV4(*argEnode)
+ peer := enode.MustParse(*argEnode)
peers = append(peers, peer)
}
@@ -747,9 +747,9 @@ func requestExpiredMessagesLoop() {
}
func extractIDFromEnode(s string) []byte {
- n, err := enode.ParseV4(s)
+ n, err := enode.Parse(enode.ValidSchemes, s)
if err != nil {
- utils.Fatalf("Failed to parse enode: %s", err)
+ utils.Fatalf("Failed to parse node: %s", err)
}
return n.ID().Bytes()
}
diff --git a/les/serverpool.go b/les/serverpool.go
index 668f39c56..3e8cdee41 100644
--- a/les/serverpool.go
+++ b/les/serverpool.go
@@ -505,7 +505,7 @@ func parseTrustedNodes(trustedNodes []string) map[enode.ID]*enode.Node {
nodes := make(map[enode.ID]*enode.Node)
for _, node := range trustedNodes {
- node, err := enode.ParseV4(node)
+ node, err := enode.Parse(enode.ValidSchemes, node)
if err != nil {
log.Warn("Trusted node URL invalid", "enode", node, "err", err)
continue
diff --git a/les/ulc.go b/les/ulc.go
index c6d415552..8792f60d3 100644
--- a/les/ulc.go
+++ b/les/ulc.go
@@ -34,7 +34,7 @@ func newULC(ulcConfig *eth.ULCConfig) *ulc {
}
m := make(map[string]struct{}, len(ulcConfig.TrustedServers))
for _, id := range ulcConfig.TrustedServers {
- node, err := enode.ParseV4(id)
+ node, err := enode.Parse(enode.ValidSchemes, id)
if err != nil {
log.Debug("Failed to parse trusted server", "id", id, "err", err)
continue
diff --git a/node/api.go b/node/api.go
index cc80efabe..66cd1dde3 100644
--- a/node/api.go
+++ b/node/api.go
@@ -49,7 +49,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
return false, ErrNodeStopped
}
// Try to add the url as a static peer and return
- node, err := enode.ParseV4(url)
+ node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
return false, fmt.Errorf("invalid enode: %v", err)
}
@@ -65,7 +65,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
return false, ErrNodeStopped
}
// Try to remove the url as a static peer and return
- node, err := enode.ParseV4(url)
+ node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
return false, fmt.Errorf("invalid enode: %v", err)
}
@@ -80,7 +80,7 @@ func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) {
if server == nil {
return false, ErrNodeStopped
}
- node, err := enode.ParseV4(url)
+ node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
return false, fmt.Errorf("invalid enode: %v", err)
}
@@ -96,7 +96,7 @@ func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
if server == nil {
return false, ErrNodeStopped
}
- node, err := enode.ParseV4(url)
+ node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
return false, fmt.Errorf("invalid enode: %v", err)
}
diff --git a/node/config.go b/node/config.go
index 31d36f203..1905ac7fa 100644
--- a/node/config.go
+++ b/node/config.go
@@ -425,7 +425,7 @@ func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node {
if url == "" {
continue
}
- node, err := enode.ParseV4(url)
+ node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err))
continue
diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go
index 9d7badea1..c4f5b5de0 100644
--- a/p2p/discover/v4_udp_test.go
+++ b/p2p/discover/v4_udp_test.go
@@ -342,10 +342,10 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
// send the reply as two packets.
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")),
+ wrapNode(enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")),
+ wrapNode(enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")),
+ wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")),
+ wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")),
}
rpclist := make([]rpcNode, len(list))
for i := range list {
diff --git a/p2p/enode/localnode.go b/p2p/enode/localnode.go
index 623f8eae1..d8aa02a77 100644
--- a/p2p/enode/localnode.go
+++ b/p2p/enode/localnode.go
@@ -48,23 +48,32 @@ type LocalNode struct {
db *DB
// everything below is protected by a lock
- mu sync.Mutex
- seq uint64
- entries map[string]enr.Entry
- udpTrack *netutil.IPTracker // predicts external UDP endpoint
- staticIP net.IP
- fallbackIP net.IP
- fallbackUDP int
+ mu sync.Mutex
+ seq uint64
+ entries map[string]enr.Entry
+ endpoint4 lnEndpoint
+ endpoint6 lnEndpoint
+}
+
+type lnEndpoint struct {
+ track *netutil.IPTracker
+ staticIP, fallbackIP net.IP
+ fallbackUDP int
}
// NewLocalNode creates a local node.
func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode {
ln := &LocalNode{
- id: PubkeyToIDV4(&key.PublicKey),
- db: db,
- key: key,
- udpTrack: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
- entries: make(map[string]enr.Entry),
+ id: PubkeyToIDV4(&key.PublicKey),
+ db: db,
+ key: key,
+ entries: make(map[string]enr.Entry),
+ endpoint4: lnEndpoint{
+ track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
+ },
+ endpoint6: lnEndpoint{
+ track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
+ },
}
ln.seq = db.localSeq(ln.id)
ln.invalidate()
@@ -89,13 +98,22 @@ func (ln *LocalNode) Node() *Node {
return ln.cur.Load().(*Node)
}
+// Seq returns the current sequence number of the local node record.
+func (ln *LocalNode) Seq() uint64 {
+ ln.mu.Lock()
+ defer ln.mu.Unlock()
+
+ return ln.seq
+}
+
// ID returns the local node ID.
func (ln *LocalNode) ID() ID {
return ln.id
}
-// Set puts the given entry into the local record, overwriting
-// any existing value.
+// Set puts the given entry into the local record, overwriting any existing value.
+// Use Set*IP and SetFallbackUDP to set IP addresses and UDP port, otherwise they'll
+// be overwritten by the endpoint predictor.
func (ln *LocalNode) Set(e enr.Entry) {
ln.mu.Lock()
defer ln.mu.Unlock()
@@ -127,13 +145,20 @@ func (ln *LocalNode) delete(e enr.Entry) {
}
}
+func (ln *LocalNode) endpointForIP(ip net.IP) *lnEndpoint {
+ if ip.To4() != nil {
+ return &ln.endpoint4
+ }
+ return &ln.endpoint6
+}
+
// SetStaticIP sets the local IP to the given one unconditionally.
// This disables endpoint prediction.
func (ln *LocalNode) SetStaticIP(ip net.IP) {
ln.mu.Lock()
defer ln.mu.Unlock()
- ln.staticIP = ip
+ ln.endpointForIP(ip).staticIP = ip
ln.updateEndpoints()
}
@@ -143,17 +168,18 @@ func (ln *LocalNode) SetFallbackIP(ip net.IP) {
ln.mu.Lock()
defer ln.mu.Unlock()
- ln.fallbackIP = ip
+ ln.endpointForIP(ip).fallbackIP = ip
ln.updateEndpoints()
}
-// SetFallbackUDP sets the last-resort UDP port. This port is used
+// SetFallbackUDP sets the last-resort UDP-on-IPv4 port. This port is used
// if no endpoint prediction can be made.
func (ln *LocalNode) SetFallbackUDP(port int) {
ln.mu.Lock()
defer ln.mu.Unlock()
- ln.fallbackUDP = port
+ ln.endpoint4.fallbackUDP = port
+ ln.endpoint6.fallbackUDP = port
ln.updateEndpoints()
}
@@ -163,7 +189,7 @@ func (ln *LocalNode) UDPEndpointStatement(fromaddr, endpoint *net.UDPAddr) {
ln.mu.Lock()
defer ln.mu.Unlock()
- ln.udpTrack.AddStatement(fromaddr.String(), endpoint.String())
+ ln.endpointForIP(endpoint.IP).track.AddStatement(fromaddr.String(), endpoint.String())
ln.updateEndpoints()
}
@@ -173,32 +199,50 @@ func (ln *LocalNode) UDPContact(toaddr *net.UDPAddr) {
ln.mu.Lock()
defer ln.mu.Unlock()
- ln.udpTrack.AddContact(toaddr.String())
+ ln.endpointForIP(toaddr.IP).track.AddContact(toaddr.String())
ln.updateEndpoints()
}
+// updateEndpoints updates the record with predicted endpoints.
func (ln *LocalNode) updateEndpoints() {
- // Determine the endpoints.
- newIP := ln.fallbackIP
- newUDP := ln.fallbackUDP
- if ln.staticIP != nil {
- newIP = ln.staticIP
- } else if ip, port := predictAddr(ln.udpTrack); ip != nil {
- newIP = ip
- newUDP = port
- }
+ ip4, udp4 := ln.endpoint4.get()
+ ip6, udp6 := ln.endpoint6.get()
- // Update the record.
- if newIP != nil && !newIP.IsUnspecified() {
- ln.set(enr.IP(newIP))
- if newUDP != 0 {
- ln.set(enr.UDP(newUDP))
- } else {
- ln.delete(enr.UDP(0))
- }
+ if ip4 != nil && !ip4.IsUnspecified() {
+ ln.set(enr.IPv4(ip4))
} else {
- ln.delete(enr.IP{})
+ ln.delete(enr.IPv4{})
+ }
+ if ip6 != nil && !ip6.IsUnspecified() {
+ ln.set(enr.IPv6(ip6))
+ } else {
+ ln.delete(enr.IPv6{})
+ }
+ if udp4 != 0 {
+ ln.set(enr.UDP(udp4))
+ } else {
+ ln.delete(enr.UDP(0))
+ }
+ if udp6 != 0 && udp6 != udp4 {
+ ln.set(enr.UDP6(udp6))
+ } else {
+ ln.delete(enr.UDP6(0))
+ }
+}
+
+// get returns the endpoint with highest precedence.
+func (e *lnEndpoint) get() (newIP net.IP, newPort int) {
+ newPort = e.fallbackUDP
+ if e.fallbackIP != nil {
+ newIP = e.fallbackIP
+ }
+ if e.staticIP != nil {
+ newIP = e.staticIP
+ } else if ip, port := predictAddr(e.track); ip != nil {
+ newIP = ip
+ newPort = port
}
+ return newIP, newPort
}
// predictAddr wraps IPTracker.PredictEndpoint, converting from its string-based
diff --git a/p2p/enode/localnode_test.go b/p2p/enode/localnode_test.go
index f5e3496d6..00746a8d2 100644
--- a/p2p/enode/localnode_test.go
+++ b/p2p/enode/localnode_test.go
@@ -17,10 +17,13 @@
package enode
import (
+ "math/rand"
+ "net"
"testing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enr"
+ "github.com/stretchr/testify/assert"
)
func newLocalNodeForTesting() (*LocalNode, *DB) {
@@ -74,3 +77,46 @@ func TestLocalNodeSeqPersist(t *testing.T) {
t.Fatalf("wrong seq %d on instance with changed key, want 1", s)
}
}
+
+// This test checks behavior of the endpoint predictor.
+func TestLocalNodeEndpoint(t *testing.T) {
+ var (
+ fallback = &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 80}
+ predicted = &net.UDPAddr{IP: net.IP{127, 0, 1, 2}, Port: 81}
+ staticIP = net.IP{127, 0, 1, 2}
+ )
+ ln, db := newLocalNodeForTesting()
+ defer db.Close()
+
+ // Nothing is set initially.
+ assert.Equal(t, net.IP(nil), ln.Node().IP())
+ assert.Equal(t, 0, ln.Node().UDP())
+ assert.Equal(t, uint64(1), ln.Node().Seq())
+
+ // Set up fallback address.
+ ln.SetFallbackIP(fallback.IP)
+ ln.SetFallbackUDP(fallback.Port)
+ assert.Equal(t, fallback.IP, ln.Node().IP())
+ assert.Equal(t, fallback.Port, ln.Node().UDP())
+ assert.Equal(t, uint64(2), ln.Node().Seq())
+
+ // Add endpoint statements from random hosts.
+ for i := 0; i < iptrackMinStatements; i++ {
+ assert.Equal(t, fallback.IP, ln.Node().IP())
+ assert.Equal(t, fallback.Port, ln.Node().UDP())
+ assert.Equal(t, uint64(2), ln.Node().Seq())
+
+ from := &net.UDPAddr{IP: make(net.IP, 4), Port: 90}
+ rand.Read(from.IP)
+ ln.UDPEndpointStatement(from, predicted)
+ }
+ assert.Equal(t, predicted.IP, ln.Node().IP())
+ assert.Equal(t, predicted.Port, ln.Node().UDP())
+ assert.Equal(t, uint64(3), ln.Node().Seq())
+
+ // Static IP overrides prediction.
+ ln.SetStaticIP(staticIP)
+ assert.Equal(t, staticIP, ln.Node().IP())
+ assert.Equal(t, fallback.Port, ln.Node().UDP())
+ assert.Equal(t, uint64(4), ln.Node().Seq())
+}
diff --git a/p2p/enode/node.go b/p2p/enode/node.go
index b454ab255..9eb2544ff 100644
--- a/p2p/enode/node.go
+++ b/p2p/enode/node.go
@@ -18,6 +18,7 @@ package enode
import (
"crypto/ecdsa"
+ "encoding/base64"
"encoding/hex"
"errors"
"fmt"
@@ -27,8 +28,11 @@ import (
"strings"
"github.com/ethereum/go-ethereum/p2p/enr"
+ "github.com/ethereum/go-ethereum/rlp"
)
+var errMissingPrefix = errors.New("missing 'enr:' prefix for base64-encoded record")
+
// Node represents a host on the network.
type Node struct {
r enr.Record
@@ -48,6 +52,34 @@ func New(validSchemes enr.IdentityScheme, r *enr.Record) (*Node, error) {
return node, nil
}
+// MustParse parses a node record or enode:// URL. It panics if the input is invalid.
+func MustParse(rawurl string) *Node {
+ n, err := Parse(ValidSchemes, rawurl)
+ if err != nil {
+ panic("invalid node: " + err.Error())
+ }
+ return n
+}
+
+// Parse decodes and verifies a base64-encoded node record.
+func Parse(validSchemes enr.IdentityScheme, input string) (*Node, error) {
+ if strings.HasPrefix(input, "enode://") {
+ return ParseV4(input)
+ }
+ if !strings.HasPrefix(input, "enr:") {
+ return nil, errMissingPrefix
+ }
+ bin, err := base64.RawURLEncoding.DecodeString(input[4:])
+ if err != nil {
+ return nil, err
+ }
+ var r enr.Record
+ if err := rlp.DecodeBytes(bin, &r); err != nil {
+ return nil, err
+ }
+ return New(validSchemes, &r)
+}
+
// ID returns the node identifier.
func (n *Node) ID() ID {
return n.id
@@ -68,11 +100,19 @@ func (n *Node) Load(k enr.Entry) error {
return n.r.Load(k)
}
-// IP returns the IP address of the node.
+// IP returns the IP address of the node. This prefers IPv4 addresses.
func (n *Node) IP() net.IP {
- var ip net.IP
- n.Load((*enr.IP)(&ip))
- return ip
+ var (
+ ip4 enr.IPv4
+ ip6 enr.IPv6
+ )
+ if n.Load(&ip4) == nil {
+ return net.IP(ip4)
+ }
+ if n.Load(&ip6) == nil {
+ return net.IP(ip6)
+ }
+ return nil
}
// UDP returns the UDP port of the node.
@@ -105,10 +145,11 @@ func (n *Node) Record() *enr.Record {
return &cpy
}
-// checks whether n is a valid complete node.
+// ValidateComplete checks whether n has a valid IP and UDP port.
+// Deprecated: don't use this method.
func (n *Node) ValidateComplete() error {
if n.Incomplete() {
- return errors.New("incomplete node")
+ return errors.New("missing IP address")
}
if n.UDP() == 0 {
return errors.New("missing UDP port")
@@ -122,20 +163,24 @@ func (n *Node) ValidateComplete() error {
return n.Load(&key)
}
-// The string representation of a Node is a URL.
-// Please see ParseNode for a description of the format.
+// String returns the text representation of the record.
func (n *Node) String() string {
- return n.v4URL()
+ if isNewV4(n) {
+ return n.URLv4() // backwards-compatibility glue for NewV4 nodes
+ }
+ enc, _ := rlp.EncodeToBytes(&n.r) // always succeeds because record is valid
+ b64 := base64.RawURLEncoding.EncodeToString(enc)
+ return "enr:" + b64
}
// MarshalText implements encoding.TextMarshaler.
func (n *Node) MarshalText() ([]byte, error) {
- return []byte(n.v4URL()), nil
+ return []byte(n.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (n *Node) UnmarshalText(text []byte) error {
- dec, err := ParseV4(string(text))
+ dec, err := Parse(ValidSchemes, string(text))
if err == nil {
*n = *dec
}
diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go
index 861a70bd6..d15859c47 100644
--- a/p2p/enode/node_test.go
+++ b/p2p/enode/node_test.go
@@ -17,9 +17,12 @@
package enode
import (
+ "bytes"
"encoding/hex"
"fmt"
+ "math/big"
"testing"
+ "testing/quick"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/rlp"
@@ -43,7 +46,7 @@ func TestPythonInterop(t *testing.T) {
var (
wantID = HexID("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
wantSeq = uint64(1)
- wantIP = enr.IP{127, 0, 0, 1}
+ wantIP = enr.IPv4{127, 0, 0, 1}
wantUDP = enr.UDP(30303)
)
if n.Seq() != wantSeq {
@@ -52,7 +55,7 @@ func TestPythonInterop(t *testing.T) {
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}
+ want := map[enr.Entry]interface{}{new(enr.IPv4): &wantIP, new(enr.UDP): &wantUDP}
for k, v := range want {
desc := fmt.Sprintf("loading key %q", k.ENRKey())
if assert.NoError(t, n.Load(k), desc) {
@@ -60,3 +63,83 @@ func TestPythonInterop(t *testing.T) {
}
}
}
+
+func TestHexID(t *testing.T) {
+ ref := ID{0, 0, 0, 0, 0, 0, 0, 128, 106, 217, 182, 31, 165, 174, 1, 67, 7, 235, 220, 150, 66, 83, 173, 205, 159, 44, 10, 57, 42, 161, 26, 188}
+ id1 := HexID("0x00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
+ id2 := HexID("00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
+
+ if id1 != ref {
+ t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:])
+ }
+ if id2 != ref {
+ t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:])
+ }
+}
+
+func TestID_textEncoding(t *testing.T) {
+ ref := ID{
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30,
+ 0x31, 0x32,
+ }
+ hex := "0102030405060708091011121314151617181920212223242526272829303132"
+
+ text, err := ref.MarshalText()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(text, []byte(hex)) {
+ t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text)
+ }
+
+ id := new(ID)
+ if err := id.UnmarshalText(text); err != nil {
+ t.Fatal(err)
+ }
+ if *id != ref {
+ t.Fatalf("text decoding did not match\nexpected: %s\ngot: %s", ref, id)
+ }
+}
+
+func TestID_distcmp(t *testing.T) {
+ distcmpBig := func(target, a, b ID) int {
+ tbig := new(big.Int).SetBytes(target[:])
+ abig := new(big.Int).SetBytes(a[:])
+ bbig := new(big.Int).SetBytes(b[:])
+ return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig))
+ }
+ if err := quick.CheckEqual(DistCmp, distcmpBig, nil); err != nil {
+ t.Error(err)
+ }
+}
+
+// The random tests is likely to miss the case where a and b are equal,
+// this test checks it explicitly.
+func TestID_distcmpEqual(t *testing.T) {
+ base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+ x := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
+ if DistCmp(base, x, x) != 0 {
+ t.Errorf("DistCmp(base, x, x) != 0")
+ }
+}
+
+func TestID_logdist(t *testing.T) {
+ logdistBig := func(a, b ID) int {
+ abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:])
+ return new(big.Int).Xor(abig, bbig).BitLen()
+ }
+ if err := quick.CheckEqual(LogDist, logdistBig, nil); err != nil {
+ t.Error(err)
+ }
+}
+
+// The random tests is likely to miss the case where a and b are equal,
+// this test checks it explicitly.
+func TestID_logdistEqual(t *testing.T) {
+ x := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+ if LogDist(x, x) != 0 {
+ t.Errorf("LogDist(x, x) != 0")
+ }
+}
diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go
index 50e9485d0..2372d4820 100644
--- a/p2p/enode/urlv4.go
+++ b/p2p/enode/urlv4.go
@@ -70,7 +70,7 @@ 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 nil, fmt.Errorf("invalid public key (%v)", err)
}
return NewV4(id, nil, 0, 0), nil
}
@@ -81,7 +81,7 @@ func ParseV4(rawurl string) (*Node, error) {
// 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 {
+ if len(ip) > 0 {
r.Set(enr.IP(ip))
}
if udp != 0 {
@@ -98,6 +98,12 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node {
return n
}
+// isNewV4 returns true for nodes created by NewV4.
+func isNewV4(n *Node) bool {
+ var k s256raw
+ return n.r.IdentityScheme() == "" && n.r.Load(&k) == nil && len(n.r.Signature()) == 0
+}
+
func parseComplete(rawurl string) (*Node, error) {
var (
id *ecdsa.PublicKey
@@ -116,7 +122,7 @@ func parseComplete(rawurl string) (*Node, error) {
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)
+ return nil, fmt.Errorf("invalid public key (%v)", err)
}
// Parse the IP address.
host, port, err := net.SplitHostPort(u.Host)
@@ -126,10 +132,6 @@ func parseComplete(rawurl string) (*Node, error) {
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")
@@ -157,7 +159,7 @@ func parsePubkey(in string) (*ecdsa.PublicKey, error) {
return crypto.UnmarshalPubkey(b)
}
-func (n *Node) v4URL() string {
+func (n *Node) URLv4() string {
var (
scheme enr.ID
nodeid string
diff --git a/p2p/enode/urlv4_test.go b/p2p/enode/urlv4_test.go
index 3680ab6b7..69ed11102 100644
--- a/p2p/enode/urlv4_test.go
+++ b/p2p/enode/urlv4_test.go
@@ -17,44 +17,63 @@
package enode
import (
- "bytes"
"crypto/ecdsa"
- "math/big"
"net"
"reflect"
"strings"
"testing"
- "testing/quick"
+
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/p2p/enr"
)
var parseNodeTests = []struct {
- rawurl string
+ input string
wantError string
wantResult *Node
}{
+ // Records
+ {
+ input: "enr:-IS4QGrdq0ugARp5T2BZ41TrZOqLc_oKvZoPuZP5--anqWE_J-Tucc1xgkOL7qXl0puJgT7qc2KSvcupc4NCb0nr4tdjgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQM6UUF2Rm-oFe1IH_rQkRCi00T2ybeMHRSvw1HDpRvjPYN1ZHCCdl8",
+ wantResult: func() *Node {
+ testKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
+ var r enr.Record
+ r.Set(enr.IP{127, 0, 0, 1})
+ r.Set(enr.UDP(30303))
+ r.SetSeq(99)
+ SignV4(&r, testKey)
+ n, _ := New(ValidSchemes, &r)
+ return n
+ }(),
+ },
+ // Invalid Records
{
- rawurl: "http://foobar",
- wantError: `invalid URL scheme, want "enode"`,
+ input: "enr:",
+ wantError: "EOF", // could be nicer
},
{
- rawurl: "enode://01010101@123.124.125.126:3",
- wantError: `invalid node ID (wrong length, want 128 hex chars)`,
+ input: "enr:x",
+ wantError: "illegal base64 data at input byte 0",
},
- // Complete nodes with IP address.
{
- rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3",
+ input: "enr:-EmGZm9vYmFyY4JpZIJ2NIJpcIR_AAABiXNlY3AyNTZrMaEDOlFBdkZvqBXtSB_60JEQotNE9sm3jB0Ur8NRw6Ub4z2DdWRwgnZf",
+ wantError: enr.ErrInvalidSig.Error(),
+ },
+ // Complete node URLs with IP address and ports
+ {
+ input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3",
wantError: `invalid IP address`,
},
{
- rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo",
+ input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo",
wantError: `invalid port`,
},
{
- rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo",
+ input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo",
wantError: `invalid discport in query`,
},
{
- rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
+ input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
wantResult: NewV4(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{0x7f, 0x0, 0x0, 0x1},
@@ -63,7 +82,7 @@ var parseNodeTests = []struct {
),
},
{
- rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
+ input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
wantResult: NewV4(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.ParseIP("::"),
@@ -72,7 +91,7 @@ var parseNodeTests = []struct {
),
},
{
- rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150",
+ input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150",
wantResult: NewV4(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
@@ -81,7 +100,7 @@ var parseNodeTests = []struct {
),
},
{
- rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334",
+ input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334",
wantResult: NewV4(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
net.IP{0x7f, 0x0, 0x0, 0x1},
@@ -89,34 +108,42 @@ var parseNodeTests = []struct {
22334,
),
},
- // Incomplete nodes with no address.
+ // Incomplete node URLs with no address
{
- rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
+ input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
wantResult: NewV4(
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
nil, 0, 0,
),
},
+ // Invalid URLs
{
- rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
- wantResult: NewV4(
- hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
- nil, 0, 0,
- ),
+ input: "",
+ wantError: errMissingPrefix.Error(),
},
- // Invalid URLs
{
- rawurl: "01010101",
- wantError: `invalid node ID (wrong length, want 128 hex chars)`,
+ input: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
+ wantError: errMissingPrefix.Error(),
},
{
- rawurl: "enode://01010101",
- wantError: `invalid node ID (wrong length, want 128 hex chars)`,
+ input: "01010101",
+ wantError: errMissingPrefix.Error(),
},
{
- // This test checks that errors from url.Parse are handled.
- rawurl: "://foo",
- wantError: `parse ://foo: missing protocol scheme`,
+ input: "enode://01010101@123.124.125.126:3",
+ wantError: `invalid public key (wrong length, want 128 hex chars)`,
+ },
+ {
+ input: "enode://01010101",
+ wantError: `invalid public key (wrong length, want 128 hex chars)`,
+ },
+ {
+ input: "http://foobar",
+ wantError: errMissingPrefix.Error(),
+ },
+ {
+ input: "://foo",
+ wantError: errMissingPrefix.Error(),
},
}
@@ -130,22 +157,22 @@ func hexPubkey(h string) *ecdsa.PublicKey {
func TestParseNode(t *testing.T) {
for _, test := range parseNodeTests {
- n, err := ParseV4(test.rawurl)
+ n, err := Parse(ValidSchemes, test.input)
if test.wantError != "" {
if err == nil {
- t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError)
+ t.Errorf("test %q:\n got nil error, expected %#q", test.input, test.wantError)
continue
} else if err.Error() != test.wantError {
- t.Errorf("test %q:\n got error %#q, expected %#q", test.rawurl, err.Error(), test.wantError)
+ t.Errorf("test %q:\n got error %#q, expected %#q", test.input, err.Error(), test.wantError)
continue
}
} else {
if err != nil {
- t.Errorf("test %q:\n unexpected error: %v", test.rawurl, err)
+ t.Errorf("test %q:\n unexpected error: %v", test.input, err)
continue
}
if !reflect.DeepEqual(n, test.wantResult) {
- t.Errorf("test %q:\n result mismatch:\ngot: %#v\nwant: %#v", test.rawurl, n, test.wantResult)
+ t.Errorf("test %q:\n result mismatch:\ngot: %#v\nwant: %#v", test.input, n, test.wantResult)
}
}
}
@@ -153,91 +180,11 @@ func TestParseNode(t *testing.T) {
func TestNodeString(t *testing.T) {
for i, test := range parseNodeTests {
- if test.wantError == "" && strings.HasPrefix(test.rawurl, "enode://") {
+ if test.wantError == "" && strings.HasPrefix(test.input, "enode://") {
str := test.wantResult.String()
- if str != test.rawurl {
- t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl)
+ if str != test.input {
+ t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.input)
}
}
}
}
-
-func TestHexID(t *testing.T) {
- ref := ID{0, 0, 0, 0, 0, 0, 0, 128, 106, 217, 182, 31, 165, 174, 1, 67, 7, 235, 220, 150, 66, 83, 173, 205, 159, 44, 10, 57, 42, 161, 26, 188}
- id1 := HexID("0x00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
- id2 := HexID("00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
-
- if id1 != ref {
- t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:])
- }
- if id2 != ref {
- t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:])
- }
-}
-
-func TestID_textEncoding(t *testing.T) {
- ref := ID{
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
- 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20,
- 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30,
- 0x31, 0x32,
- }
- hex := "0102030405060708091011121314151617181920212223242526272829303132"
-
- text, err := ref.MarshalText()
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(text, []byte(hex)) {
- t.Fatalf("text encoding did not match\nexpected: %s\ngot: %s", hex, text)
- }
-
- id := new(ID)
- if err := id.UnmarshalText(text); err != nil {
- t.Fatal(err)
- }
- if *id != ref {
- t.Fatalf("text decoding did not match\nexpected: %s\ngot: %s", ref, id)
- }
-}
-
-func TestNodeID_distcmp(t *testing.T) {
- distcmpBig := func(target, a, b ID) int {
- tbig := new(big.Int).SetBytes(target[:])
- abig := new(big.Int).SetBytes(a[:])
- bbig := new(big.Int).SetBytes(b[:])
- return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig))
- }
- if err := quick.CheckEqual(DistCmp, distcmpBig, nil); err != nil {
- t.Error(err)
- }
-}
-
-// The random tests is likely to miss the case where a and b are equal,
-// this test checks it explicitly.
-func TestNodeID_distcmpEqual(t *testing.T) {
- base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
- x := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
- if DistCmp(base, x, x) != 0 {
- t.Errorf("DistCmp(base, x, x) != 0")
- }
-}
-
-func TestNodeID_logdist(t *testing.T) {
- logdistBig := func(a, b ID) int {
- abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:])
- return new(big.Int).Xor(abig, bbig).BitLen()
- }
- if err := quick.CheckEqual(LogDist, logdistBig, nil); err != nil {
- t.Error(err)
- }
-}
-
-// The random tests is likely to miss the case where a and b are equal,
-// this test checks it explicitly.
-func TestNodeID_logdistEqual(t *testing.T) {
- x := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
- if LogDist(x, x) != 0 {
- t.Errorf("LogDist(x, x) != 0")
- }
-}
diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go
index 444820c15..c36ae9e3e 100644
--- a/p2p/enr/enr.go
+++ b/p2p/enr/enr.go
@@ -163,6 +163,16 @@ func (r *Record) invalidate() {
r.raw = nil
}
+// Signature returns the signature of the record.
+func (r *Record) Signature() []byte {
+ if r.signature == nil {
+ return nil
+ }
+ cpy := make([]byte, len(r.signature))
+ copy(cpy, r.signature)
+ return cpy
+}
+
// EncodeRLP implements rlp.Encoder. Encoding fails if
// the record is unsigned.
func (r Record) EncodeRLP(w io.Writer) error {
@@ -173,7 +183,7 @@ func (r Record) EncodeRLP(w io.Writer) error {
return err
}
-// DecodeRLP implements rlp.Decoder. Decoding verifies the signature.
+// DecodeRLP implements rlp.Decoder. Decoding doesn't verify the signature.
func (r *Record) DecodeRLP(s *rlp.Stream) error {
dec, raw, err := decodeRecord(s)
if err != nil {
diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go
index 449c898a8..434685e0b 100644
--- a/p2p/enr/enr_test.go
+++ b/p2p/enr/enr_test.go
@@ -49,23 +49,23 @@ func TestGetSetID(t *testing.T) {
}
// TestGetSetIP4 tests encoding/decoding and setting/getting of the IP key.
-func TestGetSetIP4(t *testing.T) {
- ip := IP{192, 168, 0, 3}
+func TestGetSetIPv4(t *testing.T) {
+ ip := IPv4{192, 168, 0, 3}
var r Record
r.Set(ip)
- var ip2 IP
+ var ip2 IPv4
require.NoError(t, r.Load(&ip2))
assert.Equal(t, ip, ip2)
}
-// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP key.
-func TestGetSetIP6(t *testing.T) {
- ip := IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}
+// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP6 key.
+func TestGetSetIPv6(t *testing.T) {
+ ip := IPv6{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}
var r Record
r.Set(ip)
- var ip2 IP
+ var ip2 IPv6
require.NoError(t, r.Load(&ip2))
assert.Equal(t, ip, ip2)
}
@@ -83,7 +83,7 @@ func TestGetSetUDP(t *testing.T) {
func TestLoadErrors(t *testing.T) {
var r Record
- ip4 := IP{127, 0, 0, 1}
+ ip4 := IPv4{127, 0, 0, 1}
r.Set(ip4)
// Check error for missing keys.
@@ -185,13 +185,13 @@ func TestSeq(t *testing.T) {
func TestGetSetOverwrite(t *testing.T) {
var r Record
- ip := IP{192, 168, 0, 3}
+ ip := IPv4{192, 168, 0, 3}
r.Set(ip)
- ip2 := IP{192, 168, 0, 4}
+ ip2 := IPv4{192, 168, 0, 4}
r.Set(ip2)
- var ip3 IP
+ var ip3 IPv4
require.NoError(t, r.Load(&ip3))
assert.Equal(t, ip2, ip3)
}
@@ -200,7 +200,7 @@ func TestGetSetOverwrite(t *testing.T) {
func TestSignEncodeAndDecode(t *testing.T) {
var r Record
r.Set(UDP(30303))
- r.Set(IP{127, 0, 0, 1})
+ r.Set(IPv4{127, 0, 0, 1})
require.NoError(t, signTest([]byte{5}, &r))
blob, err := rlp.EncodeToBytes(r)
diff --git a/p2p/enr/entries.go b/p2p/enr/entries.go
index 347990ab6..f2118401a 100644
--- a/p2p/enr/entries.go
+++ b/p2p/enr/entries.go
@@ -60,11 +60,21 @@ type TCP uint16
func (v TCP) ENRKey() string { return "tcp" }
+// UDP is the "udp" key, which holds the IPv6-specific UDP port of the node.
+type TCP6 uint16
+
+func (v TCP6) ENRKey() string { return "tcp6" }
+
// UDP is the "udp" key, which holds the UDP port of the node.
type UDP uint16
func (v UDP) ENRKey() string { return "udp" }
+// UDP is the "udp" key, which holds the IPv6-specific UDP port of the node.
+type UDP6 uint16
+
+func (v UDP6) ENRKey() string { return "udp6" }
+
// ID is the "id" key, which holds the name of the identity scheme.
type ID string
@@ -72,17 +82,27 @@ const IDv4 = ID("v4") // the default identity scheme
func (v ID) ENRKey() string { return "id" }
-// IP is the "ip" key, which holds the IP address of the node.
+// IP is either the "ip" or "ip6" key, depending on the value.
+// Use this value to encode IP addresses that can be either v4 or v6.
+// To load an address from a record use the IPv4 or IPv6 types.
type IP net.IP
-func (v IP) ENRKey() string { return "ip" }
+func (v IP) ENRKey() string {
+ if net.IP(v).To4() == nil {
+ return "ip6"
+ }
+ return "ip"
+}
// EncodeRLP implements rlp.Encoder.
func (v IP) EncodeRLP(w io.Writer) error {
if ip4 := net.IP(v).To4(); ip4 != nil {
return rlp.Encode(w, ip4)
}
- return rlp.Encode(w, net.IP(v))
+ if ip6 := net.IP(v).To16(); ip6 != nil {
+ return rlp.Encode(w, ip6)
+ }
+ return fmt.Errorf("invalid IP address: %v", net.IP(v))
}
// DecodeRLP implements rlp.Decoder.
@@ -96,6 +116,56 @@ func (v *IP) DecodeRLP(s *rlp.Stream) error {
return nil
}
+// IPv4 is the "ip" key, which holds the IP address of the node.
+type IPv4 net.IP
+
+func (v IPv4) ENRKey() string { return "ip" }
+
+// EncodeRLP implements rlp.Encoder.
+func (v IPv4) EncodeRLP(w io.Writer) error {
+ ip4 := net.IP(v).To4()
+ if ip4 == nil {
+ return fmt.Errorf("invalid IPv4 address: %v", net.IP(v))
+ }
+ return rlp.Encode(w, ip4)
+}
+
+// DecodeRLP implements rlp.Decoder.
+func (v *IPv4) DecodeRLP(s *rlp.Stream) error {
+ if err := s.Decode((*net.IP)(v)); err != nil {
+ return err
+ }
+ if len(*v) != 4 {
+ return fmt.Errorf("invalid IPv4 address, want 4 bytes: %v", *v)
+ }
+ return nil
+}
+
+// IPv6 is the "ip6" key, which holds the IP address of the node.
+type IPv6 net.IP
+
+func (v IPv6) ENRKey() string { return "ip6" }
+
+// EncodeRLP implements rlp.Encoder.
+func (v IPv6) EncodeRLP(w io.Writer) error {
+ ip6 := net.IP(v).To16()
+ if ip6 == nil {
+ return fmt.Errorf("invalid IPv6 address: %v", net.IP(v))
+ }
+ return rlp.Encode(w, ip6)
+}
+
+// DecodeRLP implements rlp.Decoder.
+func (v *IPv6) DecodeRLP(s *rlp.Stream) error {
+ if err := s.Decode((*net.IP)(v)); err != nil {
+ return err
+ }
+ if len(*v) != 16 {
+ return fmt.Errorf("invalid IPv6 address, want 16 bytes: %v", *v)
+ }
+ return nil
+}
+
// KeyError is an error related to a key.
type KeyError struct {
Key string
diff --git a/p2p/server.go b/p2p/server.go
index 566f01ffc..f17ef2c2b 100644
--- a/p2p/server.go
+++ b/p2p/server.go
@@ -39,7 +39,6 @@ import (
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil"
- "github.com/ethereum/go-ethereum/rlp"
)
const (
@@ -602,7 +601,7 @@ type dialer interface {
}
func (srv *Server) run(dialstate dialer) {
- srv.log.Info("Started P2P networking", "self", srv.localnode.Node())
+ srv.log.Info("Started P2P networking", "self", srv.localnode.Node().URLv4())
defer srv.loopWG.Done()
defer srv.nodedb.Close()
@@ -1034,7 +1033,7 @@ func (srv *Server) NodeInfo() *NodeInfo {
node := srv.Self()
info := &NodeInfo{
Name: srv.Name,
- Enode: node.String(),
+ Enode: node.URLv4(),
ID: node.ID().String(),
IP: node.IP().String(),
ListenAddr: srv.ListenAddr,
@@ -1042,9 +1041,7 @@ func (srv *Server) NodeInfo() *NodeInfo {
}
info.Ports.Discovery = node.UDP()
info.Ports.Listener = node.TCP()
- if enc, err := rlp.EncodeToBytes(node.Record()); err == nil {
- info.ENR = "0x" + hex.EncodeToString(enc)
- }
+ info.ENR = node.String()
// Gather all the running protocol infos (only once per protocol type)
for _, proto := range srv.Protocols {
diff --git a/whisper/whisperv6/api.go b/whisper/whisperv6/api.go
index 7609a03c2..d6d4c8d3d 100644
--- a/whisper/whisperv6/api.go
+++ b/whisper/whisperv6/api.go
@@ -103,7 +103,7 @@ func (api *PublicWhisperAPI) SetBloomFilter(ctx context.Context, bloom hexutil.B
// MarkTrustedPeer marks a peer trusted, which will allow it to send historic (expired) messages.
// Note: This function is not adding new nodes, the node needs to exists as a peer.
func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, url string) (bool, error) {
- n, err := enode.ParseV4(url)
+ n, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
return false, err
}
@@ -291,7 +291,7 @@ func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil.
// send to specific node (skip PoW check)
if len(req.TargetPeer) > 0 {
- n, err := enode.ParseV4(req.TargetPeer)
+ n, err := enode.Parse(enode.ValidSchemes, req.TargetPeer)
if err != nil {
return nil, fmt.Errorf("failed to parse target peer: %s", err)
}