aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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)
}