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.
This commit is contained in:
parent
896322bf88
commit
e83c3ccc47
@ -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.")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -51,9 +51,13 @@ type LocalNode struct {
|
||||
mu sync.Mutex
|
||||
seq uint64
|
||||
entries map[string]enr.Entry
|
||||
udpTrack *netutil.IPTracker // predicts external UDP endpoint
|
||||
staticIP net.IP
|
||||
fallbackIP net.IP
|
||||
endpoint4 lnEndpoint
|
||||
endpoint6 lnEndpoint
|
||||
}
|
||||
|
||||
type lnEndpoint struct {
|
||||
track *netutil.IPTracker
|
||||
staticIP, fallbackIP net.IP
|
||||
fallbackUDP int
|
||||
}
|
||||
|
||||
@ -63,8 +67,13 @@ func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode {
|
||||
id: PubkeyToIDV4(&key.PublicKey),
|
||||
db: db,
|
||||
key: key,
|
||||
udpTrack: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
|
||||
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,34 +199,52 @@ 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))
|
||||
if ip4 != nil && !ip4.IsUnspecified() {
|
||||
ln.set(enr.IPv4(ip4))
|
||||
} else {
|
||||
ln.delete(enr.IPv4{})
|
||||
}
|
||||
if ip6 != nil && !ip6.IsUnspecified() {
|
||||
ln.set(enr.IPv6(ip6))
|
||||
} else {
|
||||
ln.delete(enr.IPv6{})
|
||||
}
|
||||
if udp4 != 0 {
|
||||
ln.set(enr.UDP(udp4))
|
||||
} else {
|
||||
ln.delete(enr.UDP(0))
|
||||
}
|
||||
if udp6 != 0 && udp6 != udp4 {
|
||||
ln.set(enr.UDP6(udp6))
|
||||
} else {
|
||||
ln.delete(enr.IP{})
|
||||
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
|
||||
// endpoint representation to IP and port types.
|
||||
func predictAddr(t *netutil.IPTracker) (net.IP, int) {
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
rawurl: "http://foobar",
|
||||
wantError: `invalid URL scheme, want "enode"`,
|
||||
input: "enr:-IS4QGrdq0ugARp5T2BZ41TrZOqLc_oKvZoPuZP5--anqWE_J-Tucc1xgkOL7qXl0puJgT7qc2KSvcupc4NCb0nr4tdjgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQM6UUF2Rm-oFe1IH_rQkRCi00T2ybeMHRSvw1HDpRvjPYN1ZHCCdl8",
|
||||
wantResult: func() *Node {
|
||||
testKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
|
||||
var r enr.Record
|
||||
r.Set(enr.IP{127, 0, 0, 1})
|
||||
r.Set(enr.UDP(30303))
|
||||
r.SetSeq(99)
|
||||
SignV4(&r, testKey)
|
||||
n, _ := New(ValidSchemes, &r)
|
||||
return n
|
||||
}(),
|
||||
},
|
||||
// Invalid Records
|
||||
{
|
||||
input: "enr:",
|
||||
wantError: "EOF", // could be nicer
|
||||
},
|
||||
{
|
||||
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,16 +108,9 @@ var parseNodeTests = []struct {
|
||||
22334,
|
||||
),
|
||||
},
|
||||
// Incomplete nodes with no address.
|
||||
// Incomplete node URLs with no address
|
||||
{
|
||||
rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
|
||||
wantResult: NewV4(
|
||||
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
||||
nil, 0, 0,
|
||||
),
|
||||
},
|
||||
{
|
||||
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
|
||||
input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
|
||||
wantResult: NewV4(
|
||||
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
||||
nil, 0, 0,
|
||||
@ -106,17 +118,32 @@ var parseNodeTests = []struct {
|
||||
},
|
||||
// Invalid URLs
|
||||
{
|
||||
rawurl: "01010101",
|
||||
wantError: `invalid node ID (wrong length, want 128 hex chars)`,
|
||||
input: "",
|
||||
wantError: errMissingPrefix.Error(),
|
||||
},
|
||||
{
|
||||
rawurl: "enode://01010101",
|
||||
wantError: `invalid node ID (wrong length, want 128 hex chars)`,
|
||||
input: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
|
||||
wantError: errMissingPrefix.Error(),
|
||||
},
|
||||
{
|
||||
// This test checks that errors from url.Parse are handled.
|
||||
rawurl: "://foo",
|
||||
wantError: `parse ://foo: missing protocol scheme`,
|
||||
input: "01010101",
|
||||
wantError: errMissingPrefix.Error(),
|
||||
},
|
||||
{
|
||||
input: "enode://01010101@123.124.125.126:3",
|
||||
wantError: `invalid public key (wrong length, want 128 hex chars)`,
|
||||
},
|
||||
{
|
||||
input: "enode://01010101",
|
||||
wantError: `invalid public key (wrong length, want 128 hex chars)`,
|
||||
},
|
||||
{
|
||||
input: "http://foobar",
|
||||
wantError: errMissingPrefix.Error(),
|
||||
},
|
||||
{
|
||||
input: "://foo",
|
||||
wantError: errMissingPrefix.Error(),
|
||||
},
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user