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}
|
addr.IP = net.IP{127, 0, 0, 1}
|
||||||
}
|
}
|
||||||
n := enode.NewV4(nodeKey, addr.IP, 0, addr.Port)
|
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("Note: you're using cmd/bootnode, a developer tool.")
|
||||||
fmt.Println("We recommend using a regular node as bootstrap node for production deployments.")
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, boot := range enodes {
|
for _, boot := range enodes {
|
||||||
old, err := enode.ParseV4(boot.String())
|
old, err := enode.Parse(enode.ValidSchemes, boot.String())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
stack.Server().AddPeer(old)
|
stack.Server().AddPeer(old)
|
||||||
}
|
}
|
||||||
|
@ -794,7 +794,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
|
|||||||
cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls))
|
cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls))
|
||||||
for _, url := range urls {
|
for _, url := range urls {
|
||||||
if url != "" {
|
if url != "" {
|
||||||
node, err := enode.ParseV4(url)
|
node, err := enode.Parse(enode.ValidSchemes, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Crit("Bootstrap URL invalid", "enode", url, "err", err)
|
log.Crit("Bootstrap URL invalid", "enode", url, "err", err)
|
||||||
continue
|
continue
|
||||||
|
@ -203,7 +203,7 @@ func initialize() {
|
|||||||
if len(*argEnode) == 0 {
|
if len(*argEnode) == 0 {
|
||||||
argEnode = scanLineA("Please enter the peer's enode: ")
|
argEnode = scanLineA("Please enter the peer's enode: ")
|
||||||
}
|
}
|
||||||
peer := enode.MustParseV4(*argEnode)
|
peer := enode.MustParse(*argEnode)
|
||||||
peers = append(peers, peer)
|
peers = append(peers, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,9 +747,9 @@ func requestExpiredMessagesLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractIDFromEnode(s string) []byte {
|
func extractIDFromEnode(s string) []byte {
|
||||||
n, err := enode.ParseV4(s)
|
n, err := enode.Parse(enode.ValidSchemes, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to parse enode: %s", err)
|
utils.Fatalf("Failed to parse node: %s", err)
|
||||||
}
|
}
|
||||||
return n.ID().Bytes()
|
return n.ID().Bytes()
|
||||||
}
|
}
|
||||||
|
@ -505,7 +505,7 @@ func parseTrustedNodes(trustedNodes []string) map[enode.ID]*enode.Node {
|
|||||||
nodes := make(map[enode.ID]*enode.Node)
|
nodes := make(map[enode.ID]*enode.Node)
|
||||||
|
|
||||||
for _, node := range trustedNodes {
|
for _, node := range trustedNodes {
|
||||||
node, err := enode.ParseV4(node)
|
node, err := enode.Parse(enode.ValidSchemes, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Trusted node URL invalid", "enode", node, "err", err)
|
log.Warn("Trusted node URL invalid", "enode", node, "err", err)
|
||||||
continue
|
continue
|
||||||
|
@ -34,7 +34,7 @@ func newULC(ulcConfig *eth.ULCConfig) *ulc {
|
|||||||
}
|
}
|
||||||
m := make(map[string]struct{}, len(ulcConfig.TrustedServers))
|
m := make(map[string]struct{}, len(ulcConfig.TrustedServers))
|
||||||
for _, id := range ulcConfig.TrustedServers {
|
for _, id := range ulcConfig.TrustedServers {
|
||||||
node, err := enode.ParseV4(id)
|
node, err := enode.Parse(enode.ValidSchemes, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to parse trusted server", "id", id, "err", err)
|
log.Debug("Failed to parse trusted server", "id", id, "err", err)
|
||||||
continue
|
continue
|
||||||
|
@ -49,7 +49,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
|
|||||||
return false, ErrNodeStopped
|
return false, ErrNodeStopped
|
||||||
}
|
}
|
||||||
// Try to add the url as a static peer and return
|
// 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 {
|
if err != nil {
|
||||||
return false, fmt.Errorf("invalid enode: %v", err)
|
return false, fmt.Errorf("invalid enode: %v", err)
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
|
|||||||
return false, ErrNodeStopped
|
return false, ErrNodeStopped
|
||||||
}
|
}
|
||||||
// Try to remove the url as a static peer and return
|
// 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 {
|
if err != nil {
|
||||||
return false, fmt.Errorf("invalid enode: %v", err)
|
return false, fmt.Errorf("invalid enode: %v", err)
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) {
|
|||||||
if server == nil {
|
if server == nil {
|
||||||
return false, ErrNodeStopped
|
return false, ErrNodeStopped
|
||||||
}
|
}
|
||||||
node, err := enode.ParseV4(url)
|
node, err := enode.Parse(enode.ValidSchemes, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("invalid enode: %v", err)
|
return false, fmt.Errorf("invalid enode: %v", err)
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
|
|||||||
if server == nil {
|
if server == nil {
|
||||||
return false, ErrNodeStopped
|
return false, ErrNodeStopped
|
||||||
}
|
}
|
||||||
node, err := enode.ParseV4(url)
|
node, err := enode.Parse(enode.ValidSchemes, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("invalid enode: %v", err)
|
return false, fmt.Errorf("invalid enode: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -425,7 +425,7 @@ func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node {
|
|||||||
if url == "" {
|
if url == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
node, err := enode.ParseV4(url)
|
node, err := enode.Parse(enode.ValidSchemes, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err))
|
log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err))
|
||||||
continue
|
continue
|
||||||
|
@ -342,10 +342,10 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
|
|||||||
|
|
||||||
// send the reply as two packets.
|
// send the reply as two packets.
|
||||||
list := []*node{
|
list := []*node{
|
||||||
wrapNode(enode.MustParseV4("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")),
|
wrapNode(enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")),
|
||||||
wrapNode(enode.MustParseV4("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")),
|
wrapNode(enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")),
|
||||||
wrapNode(enode.MustParseV4("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")),
|
wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")),
|
||||||
wrapNode(enode.MustParseV4("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")),
|
wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")),
|
||||||
}
|
}
|
||||||
rpclist := make([]rpcNode, len(list))
|
rpclist := make([]rpcNode, len(list))
|
||||||
for i := range list {
|
for i := range list {
|
||||||
|
@ -48,23 +48,32 @@ type LocalNode struct {
|
|||||||
db *DB
|
db *DB
|
||||||
|
|
||||||
// everything below is protected by a lock
|
// everything below is protected by a lock
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
seq uint64
|
seq uint64
|
||||||
entries map[string]enr.Entry
|
entries map[string]enr.Entry
|
||||||
udpTrack *netutil.IPTracker // predicts external UDP endpoint
|
endpoint4 lnEndpoint
|
||||||
staticIP net.IP
|
endpoint6 lnEndpoint
|
||||||
fallbackIP net.IP
|
}
|
||||||
fallbackUDP int
|
|
||||||
|
type lnEndpoint struct {
|
||||||
|
track *netutil.IPTracker
|
||||||
|
staticIP, fallbackIP net.IP
|
||||||
|
fallbackUDP int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLocalNode creates a local node.
|
// NewLocalNode creates a local node.
|
||||||
func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode {
|
func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode {
|
||||||
ln := &LocalNode{
|
ln := &LocalNode{
|
||||||
id: PubkeyToIDV4(&key.PublicKey),
|
id: PubkeyToIDV4(&key.PublicKey),
|
||||||
db: db,
|
db: db,
|
||||||
key: key,
|
key: key,
|
||||||
udpTrack: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
|
entries: make(map[string]enr.Entry),
|
||||||
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.seq = db.localSeq(ln.id)
|
||||||
ln.invalidate()
|
ln.invalidate()
|
||||||
@ -89,13 +98,22 @@ func (ln *LocalNode) Node() *Node {
|
|||||||
return ln.cur.Load().(*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.
|
// ID returns the local node ID.
|
||||||
func (ln *LocalNode) ID() ID {
|
func (ln *LocalNode) ID() ID {
|
||||||
return ln.id
|
return ln.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set puts the given entry into the local record, overwriting
|
// Set puts the given entry into the local record, overwriting any existing value.
|
||||||
// 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) {
|
func (ln *LocalNode) Set(e enr.Entry) {
|
||||||
ln.mu.Lock()
|
ln.mu.Lock()
|
||||||
defer ln.mu.Unlock()
|
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.
|
// SetStaticIP sets the local IP to the given one unconditionally.
|
||||||
// This disables endpoint prediction.
|
// This disables endpoint prediction.
|
||||||
func (ln *LocalNode) SetStaticIP(ip net.IP) {
|
func (ln *LocalNode) SetStaticIP(ip net.IP) {
|
||||||
ln.mu.Lock()
|
ln.mu.Lock()
|
||||||
defer ln.mu.Unlock()
|
defer ln.mu.Unlock()
|
||||||
|
|
||||||
ln.staticIP = ip
|
ln.endpointForIP(ip).staticIP = ip
|
||||||
ln.updateEndpoints()
|
ln.updateEndpoints()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,17 +168,18 @@ func (ln *LocalNode) SetFallbackIP(ip net.IP) {
|
|||||||
ln.mu.Lock()
|
ln.mu.Lock()
|
||||||
defer ln.mu.Unlock()
|
defer ln.mu.Unlock()
|
||||||
|
|
||||||
ln.fallbackIP = ip
|
ln.endpointForIP(ip).fallbackIP = ip
|
||||||
ln.updateEndpoints()
|
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.
|
// if no endpoint prediction can be made.
|
||||||
func (ln *LocalNode) SetFallbackUDP(port int) {
|
func (ln *LocalNode) SetFallbackUDP(port int) {
|
||||||
ln.mu.Lock()
|
ln.mu.Lock()
|
||||||
defer ln.mu.Unlock()
|
defer ln.mu.Unlock()
|
||||||
|
|
||||||
ln.fallbackUDP = port
|
ln.endpoint4.fallbackUDP = port
|
||||||
|
ln.endpoint6.fallbackUDP = port
|
||||||
ln.updateEndpoints()
|
ln.updateEndpoints()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +189,7 @@ func (ln *LocalNode) UDPEndpointStatement(fromaddr, endpoint *net.UDPAddr) {
|
|||||||
ln.mu.Lock()
|
ln.mu.Lock()
|
||||||
defer ln.mu.Unlock()
|
defer ln.mu.Unlock()
|
||||||
|
|
||||||
ln.udpTrack.AddStatement(fromaddr.String(), endpoint.String())
|
ln.endpointForIP(endpoint.IP).track.AddStatement(fromaddr.String(), endpoint.String())
|
||||||
ln.updateEndpoints()
|
ln.updateEndpoints()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,32 +199,50 @@ func (ln *LocalNode) UDPContact(toaddr *net.UDPAddr) {
|
|||||||
ln.mu.Lock()
|
ln.mu.Lock()
|
||||||
defer ln.mu.Unlock()
|
defer ln.mu.Unlock()
|
||||||
|
|
||||||
ln.udpTrack.AddContact(toaddr.String())
|
ln.endpointForIP(toaddr.IP).track.AddContact(toaddr.String())
|
||||||
ln.updateEndpoints()
|
ln.updateEndpoints()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateEndpoints updates the record with predicted endpoints.
|
||||||
func (ln *LocalNode) updateEndpoints() {
|
func (ln *LocalNode) updateEndpoints() {
|
||||||
// Determine the endpoints.
|
ip4, udp4 := ln.endpoint4.get()
|
||||||
newIP := ln.fallbackIP
|
ip6, udp6 := ln.endpoint6.get()
|
||||||
newUDP := ln.fallbackUDP
|
|
||||||
if ln.staticIP != nil {
|
|
||||||
newIP = ln.staticIP
|
|
||||||
} else if ip, port := predictAddr(ln.udpTrack); ip != nil {
|
|
||||||
newIP = ip
|
|
||||||
newUDP = port
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the record.
|
if ip4 != nil && !ip4.IsUnspecified() {
|
||||||
if newIP != nil && !newIP.IsUnspecified() {
|
ln.set(enr.IPv4(ip4))
|
||||||
ln.set(enr.IP(newIP))
|
|
||||||
if newUDP != 0 {
|
|
||||||
ln.set(enr.UDP(newUDP))
|
|
||||||
} else {
|
|
||||||
ln.delete(enr.UDP(0))
|
|
||||||
}
|
|
||||||
} else {
|
} 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
|
// predictAddr wraps IPTracker.PredictEndpoint, converting from its string-based
|
||||||
|
@ -17,10 +17,13 @@
|
|||||||
package enode
|
package enode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newLocalNodeForTesting() (*LocalNode, *DB) {
|
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)
|
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 (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -27,8 +28,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
"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.
|
// Node represents a host on the network.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
r enr.Record
|
r enr.Record
|
||||||
@ -48,6 +52,34 @@ func New(validSchemes enr.IdentityScheme, r *enr.Record) (*Node, error) {
|
|||||||
return node, nil
|
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.
|
// ID returns the node identifier.
|
||||||
func (n *Node) ID() ID {
|
func (n *Node) ID() ID {
|
||||||
return n.id
|
return n.id
|
||||||
@ -68,11 +100,19 @@ func (n *Node) Load(k enr.Entry) error {
|
|||||||
return n.r.Load(k)
|
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 {
|
func (n *Node) IP() net.IP {
|
||||||
var ip net.IP
|
var (
|
||||||
n.Load((*enr.IP)(&ip))
|
ip4 enr.IPv4
|
||||||
return ip
|
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.
|
// UDP returns the UDP port of the node.
|
||||||
@ -105,10 +145,11 @@ func (n *Node) Record() *enr.Record {
|
|||||||
return &cpy
|
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 {
|
func (n *Node) ValidateComplete() error {
|
||||||
if n.Incomplete() {
|
if n.Incomplete() {
|
||||||
return errors.New("incomplete node")
|
return errors.New("missing IP address")
|
||||||
}
|
}
|
||||||
if n.UDP() == 0 {
|
if n.UDP() == 0 {
|
||||||
return errors.New("missing UDP port")
|
return errors.New("missing UDP port")
|
||||||
@ -122,20 +163,24 @@ func (n *Node) ValidateComplete() error {
|
|||||||
return n.Load(&key)
|
return n.Load(&key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The string representation of a Node is a URL.
|
// String returns the text representation of the record.
|
||||||
// Please see ParseNode for a description of the format.
|
|
||||||
func (n *Node) String() string {
|
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.
|
// MarshalText implements encoding.TextMarshaler.
|
||||||
func (n *Node) MarshalText() ([]byte, error) {
|
func (n *Node) MarshalText() ([]byte, error) {
|
||||||
return []byte(n.v4URL()), nil
|
return []byte(n.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
func (n *Node) UnmarshalText(text []byte) error {
|
func (n *Node) UnmarshalText(text []byte) error {
|
||||||
dec, err := ParseV4(string(text))
|
dec, err := Parse(ValidSchemes, string(text))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
*n = *dec
|
*n = *dec
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,12 @@
|
|||||||
package enode
|
package enode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
@ -43,7 +46,7 @@ func TestPythonInterop(t *testing.T) {
|
|||||||
var (
|
var (
|
||||||
wantID = HexID("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
|
wantID = HexID("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
|
||||||
wantSeq = uint64(1)
|
wantSeq = uint64(1)
|
||||||
wantIP = enr.IP{127, 0, 0, 1}
|
wantIP = enr.IPv4{127, 0, 0, 1}
|
||||||
wantUDP = enr.UDP(30303)
|
wantUDP = enr.UDP(30303)
|
||||||
)
|
)
|
||||||
if n.Seq() != wantSeq {
|
if n.Seq() != wantSeq {
|
||||||
@ -52,7 +55,7 @@ func TestPythonInterop(t *testing.T) {
|
|||||||
if n.ID() != wantID {
|
if n.ID() != wantID {
|
||||||
t.Errorf("wrong id: got %x, want %x", 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 {
|
for k, v := range want {
|
||||||
desc := fmt.Sprintf("loading key %q", k.ENRKey())
|
desc := fmt.Sprintf("loading key %q", k.ENRKey())
|
||||||
if assert.NoError(t, n.Load(k), desc) {
|
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 {
|
if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
|
||||||
id, err := parsePubkey(m[1])
|
id, err := parsePubkey(m[1])
|
||||||
if err != nil {
|
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
|
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.
|
// contained in the node has a zero-length signature.
|
||||||
func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node {
|
func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node {
|
||||||
var r enr.Record
|
var r enr.Record
|
||||||
if ip != nil {
|
if len(ip) > 0 {
|
||||||
r.Set(enr.IP(ip))
|
r.Set(enr.IP(ip))
|
||||||
}
|
}
|
||||||
if udp != 0 {
|
if udp != 0 {
|
||||||
@ -98,6 +98,12 @@ func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node {
|
|||||||
return n
|
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) {
|
func parseComplete(rawurl string) (*Node, error) {
|
||||||
var (
|
var (
|
||||||
id *ecdsa.PublicKey
|
id *ecdsa.PublicKey
|
||||||
@ -116,7 +122,7 @@ func parseComplete(rawurl string) (*Node, error) {
|
|||||||
return nil, errors.New("does not contain node ID")
|
return nil, errors.New("does not contain node ID")
|
||||||
}
|
}
|
||||||
if id, err = parsePubkey(u.User.String()); err != nil {
|
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.
|
// Parse the IP address.
|
||||||
host, port, err := net.SplitHostPort(u.Host)
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
@ -126,10 +132,6 @@ func parseComplete(rawurl string) (*Node, error) {
|
|||||||
if ip = net.ParseIP(host); ip == nil {
|
if ip = net.ParseIP(host); ip == nil {
|
||||||
return nil, errors.New("invalid IP address")
|
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.
|
// Parse the port numbers.
|
||||||
if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil {
|
if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil {
|
||||||
return nil, errors.New("invalid port")
|
return nil, errors.New("invalid port")
|
||||||
@ -157,7 +159,7 @@ func parsePubkey(in string) (*ecdsa.PublicKey, error) {
|
|||||||
return crypto.UnmarshalPubkey(b)
|
return crypto.UnmarshalPubkey(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) v4URL() string {
|
func (n *Node) URLv4() string {
|
||||||
var (
|
var (
|
||||||
scheme enr.ID
|
scheme enr.ID
|
||||||
nodeid string
|
nodeid string
|
||||||
|
@ -17,44 +17,63 @@
|
|||||||
package enode
|
package enode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"math/big"
|
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/quick"
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var parseNodeTests = []struct {
|
var parseNodeTests = []struct {
|
||||||
rawurl string
|
input string
|
||||||
wantError string
|
wantError string
|
||||||
wantResult *Node
|
wantResult *Node
|
||||||
}{
|
}{
|
||||||
|
// Records
|
||||||
{
|
{
|
||||||
rawurl: "http://foobar",
|
input: "enr:-IS4QGrdq0ugARp5T2BZ41TrZOqLc_oKvZoPuZP5--anqWE_J-Tucc1xgkOL7qXl0puJgT7qc2KSvcupc4NCb0nr4tdjgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQM6UUF2Rm-oFe1IH_rQkRCi00T2ybeMHRSvw1HDpRvjPYN1ZHCCdl8",
|
||||||
wantError: `invalid URL scheme, want "enode"`,
|
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",
|
input: "enr:x",
|
||||||
wantError: `invalid node ID (wrong length, want 128 hex chars)`,
|
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`,
|
wantError: `invalid IP address`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo",
|
input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo",
|
||||||
wantError: `invalid port`,
|
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`,
|
wantError: `invalid discport in query`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
|
input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
|
||||||
wantResult: NewV4(
|
wantResult: NewV4(
|
||||||
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
||||||
net.IP{0x7f, 0x0, 0x0, 0x1},
|
net.IP{0x7f, 0x0, 0x0, 0x1},
|
||||||
@ -63,7 +82,7 @@ var parseNodeTests = []struct {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
|
input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
|
||||||
wantResult: NewV4(
|
wantResult: NewV4(
|
||||||
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
||||||
net.ParseIP("::"),
|
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(
|
wantResult: NewV4(
|
||||||
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
||||||
net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
|
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(
|
wantResult: NewV4(
|
||||||
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
||||||
net.IP{0x7f, 0x0, 0x0, 0x1},
|
net.IP{0x7f, 0x0, 0x0, 0x1},
|
||||||
@ -89,16 +108,9 @@ var parseNodeTests = []struct {
|
|||||||
22334,
|
22334,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// Incomplete nodes with no address.
|
// Incomplete node URLs with no address
|
||||||
{
|
{
|
||||||
rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
|
input: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
|
||||||
wantResult: NewV4(
|
|
||||||
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
|
||||||
nil, 0, 0,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
|
|
||||||
wantResult: NewV4(
|
wantResult: NewV4(
|
||||||
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
hexPubkey("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
|
||||||
nil, 0, 0,
|
nil, 0, 0,
|
||||||
@ -106,17 +118,32 @@ var parseNodeTests = []struct {
|
|||||||
},
|
},
|
||||||
// Invalid URLs
|
// Invalid URLs
|
||||||
{
|
{
|
||||||
rawurl: "01010101",
|
input: "",
|
||||||
wantError: `invalid node ID (wrong length, want 128 hex chars)`,
|
wantError: errMissingPrefix.Error(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rawurl: "enode://01010101",
|
input: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
|
||||||
wantError: `invalid node ID (wrong length, want 128 hex chars)`,
|
wantError: errMissingPrefix.Error(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// This test checks that errors from url.Parse are handled.
|
input: "01010101",
|
||||||
rawurl: "://foo",
|
wantError: errMissingPrefix.Error(),
|
||||||
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) {
|
func TestParseNode(t *testing.T) {
|
||||||
for _, test := range parseNodeTests {
|
for _, test := range parseNodeTests {
|
||||||
n, err := ParseV4(test.rawurl)
|
n, err := Parse(ValidSchemes, test.input)
|
||||||
if test.wantError != "" {
|
if test.wantError != "" {
|
||||||
if err == nil {
|
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
|
continue
|
||||||
} else if err.Error() != test.wantError {
|
} 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
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(n, test.wantResult) {
|
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) {
|
func TestNodeString(t *testing.T) {
|
||||||
for i, test := range parseNodeTests {
|
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()
|
str := test.wantResult.String()
|
||||||
if str != test.rawurl {
|
if str != test.input {
|
||||||
t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl)
|
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
|
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
|
// EncodeRLP implements rlp.Encoder. Encoding fails if
|
||||||
// the record is unsigned.
|
// the record is unsigned.
|
||||||
func (r Record) EncodeRLP(w io.Writer) error {
|
func (r Record) EncodeRLP(w io.Writer) error {
|
||||||
@ -173,7 +183,7 @@ func (r Record) EncodeRLP(w io.Writer) error {
|
|||||||
return err
|
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 {
|
func (r *Record) DecodeRLP(s *rlp.Stream) error {
|
||||||
dec, raw, err := decodeRecord(s)
|
dec, raw, err := decodeRecord(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -49,23 +49,23 @@ func TestGetSetID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestGetSetIP4 tests encoding/decoding and setting/getting of the IP key.
|
// TestGetSetIP4 tests encoding/decoding and setting/getting of the IP key.
|
||||||
func TestGetSetIP4(t *testing.T) {
|
func TestGetSetIPv4(t *testing.T) {
|
||||||
ip := IP{192, 168, 0, 3}
|
ip := IPv4{192, 168, 0, 3}
|
||||||
var r Record
|
var r Record
|
||||||
r.Set(ip)
|
r.Set(ip)
|
||||||
|
|
||||||
var ip2 IP
|
var ip2 IPv4
|
||||||
require.NoError(t, r.Load(&ip2))
|
require.NoError(t, r.Load(&ip2))
|
||||||
assert.Equal(t, ip, ip2)
|
assert.Equal(t, ip, ip2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP key.
|
// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP6 key.
|
||||||
func TestGetSetIP6(t *testing.T) {
|
func TestGetSetIPv6(t *testing.T) {
|
||||||
ip := IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}
|
ip := IPv6{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}
|
||||||
var r Record
|
var r Record
|
||||||
r.Set(ip)
|
r.Set(ip)
|
||||||
|
|
||||||
var ip2 IP
|
var ip2 IPv6
|
||||||
require.NoError(t, r.Load(&ip2))
|
require.NoError(t, r.Load(&ip2))
|
||||||
assert.Equal(t, ip, ip2)
|
assert.Equal(t, ip, ip2)
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ func TestGetSetUDP(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoadErrors(t *testing.T) {
|
func TestLoadErrors(t *testing.T) {
|
||||||
var r Record
|
var r Record
|
||||||
ip4 := IP{127, 0, 0, 1}
|
ip4 := IPv4{127, 0, 0, 1}
|
||||||
r.Set(ip4)
|
r.Set(ip4)
|
||||||
|
|
||||||
// Check error for missing keys.
|
// Check error for missing keys.
|
||||||
@ -185,13 +185,13 @@ func TestSeq(t *testing.T) {
|
|||||||
func TestGetSetOverwrite(t *testing.T) {
|
func TestGetSetOverwrite(t *testing.T) {
|
||||||
var r Record
|
var r Record
|
||||||
|
|
||||||
ip := IP{192, 168, 0, 3}
|
ip := IPv4{192, 168, 0, 3}
|
||||||
r.Set(ip)
|
r.Set(ip)
|
||||||
|
|
||||||
ip2 := IP{192, 168, 0, 4}
|
ip2 := IPv4{192, 168, 0, 4}
|
||||||
r.Set(ip2)
|
r.Set(ip2)
|
||||||
|
|
||||||
var ip3 IP
|
var ip3 IPv4
|
||||||
require.NoError(t, r.Load(&ip3))
|
require.NoError(t, r.Load(&ip3))
|
||||||
assert.Equal(t, ip2, ip3)
|
assert.Equal(t, ip2, ip3)
|
||||||
}
|
}
|
||||||
@ -200,7 +200,7 @@ func TestGetSetOverwrite(t *testing.T) {
|
|||||||
func TestSignEncodeAndDecode(t *testing.T) {
|
func TestSignEncodeAndDecode(t *testing.T) {
|
||||||
var r Record
|
var r Record
|
||||||
r.Set(UDP(30303))
|
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))
|
require.NoError(t, signTest([]byte{5}, &r))
|
||||||
|
|
||||||
blob, err := rlp.EncodeToBytes(r)
|
blob, err := rlp.EncodeToBytes(r)
|
||||||
|
@ -60,11 +60,21 @@ type TCP uint16
|
|||||||
|
|
||||||
func (v TCP) ENRKey() string { return "tcp" }
|
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.
|
// UDP is the "udp" key, which holds the UDP port of the node.
|
||||||
type UDP uint16
|
type UDP uint16
|
||||||
|
|
||||||
func (v UDP) ENRKey() string { return "udp" }
|
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.
|
// ID is the "id" key, which holds the name of the identity scheme.
|
||||||
type ID string
|
type ID string
|
||||||
|
|
||||||
@ -72,17 +82,27 @@ const IDv4 = ID("v4") // the default identity scheme
|
|||||||
|
|
||||||
func (v ID) ENRKey() string { return "id" }
|
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
|
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.
|
// EncodeRLP implements rlp.Encoder.
|
||||||
func (v IP) EncodeRLP(w io.Writer) error {
|
func (v IP) EncodeRLP(w io.Writer) error {
|
||||||
if ip4 := net.IP(v).To4(); ip4 != nil {
|
if ip4 := net.IP(v).To4(); ip4 != nil {
|
||||||
return rlp.Encode(w, ip4)
|
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.
|
// DecodeRLP implements rlp.Decoder.
|
||||||
@ -96,6 +116,56 @@ func (v *IP) DecodeRLP(s *rlp.Stream) error {
|
|||||||
return nil
|
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.
|
// KeyError is an error related to a key.
|
||||||
type KeyError struct {
|
type KeyError struct {
|
||||||
Key string
|
Key string
|
||||||
|
@ -39,7 +39,6 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -602,7 +601,7 @@ type dialer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) run(dialstate dialer) {
|
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.loopWG.Done()
|
||||||
defer srv.nodedb.Close()
|
defer srv.nodedb.Close()
|
||||||
|
|
||||||
@ -1034,7 +1033,7 @@ func (srv *Server) NodeInfo() *NodeInfo {
|
|||||||
node := srv.Self()
|
node := srv.Self()
|
||||||
info := &NodeInfo{
|
info := &NodeInfo{
|
||||||
Name: srv.Name,
|
Name: srv.Name,
|
||||||
Enode: node.String(),
|
Enode: node.URLv4(),
|
||||||
ID: node.ID().String(),
|
ID: node.ID().String(),
|
||||||
IP: node.IP().String(),
|
IP: node.IP().String(),
|
||||||
ListenAddr: srv.ListenAddr,
|
ListenAddr: srv.ListenAddr,
|
||||||
@ -1042,9 +1041,7 @@ func (srv *Server) NodeInfo() *NodeInfo {
|
|||||||
}
|
}
|
||||||
info.Ports.Discovery = node.UDP()
|
info.Ports.Discovery = node.UDP()
|
||||||
info.Ports.Listener = node.TCP()
|
info.Ports.Listener = node.TCP()
|
||||||
if enc, err := rlp.EncodeToBytes(node.Record()); err == nil {
|
info.ENR = node.String()
|
||||||
info.ENR = "0x" + hex.EncodeToString(enc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather all the running protocol infos (only once per protocol type)
|
// Gather all the running protocol infos (only once per protocol type)
|
||||||
for _, proto := range srv.Protocols {
|
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.
|
// 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.
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -291,7 +291,7 @@ func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil.
|
|||||||
|
|
||||||
// send to specific node (skip PoW check)
|
// send to specific node (skip PoW check)
|
||||||
if len(req.TargetPeer) > 0 {
|
if len(req.TargetPeer) > 0 {
|
||||||
n, err := enode.ParseV4(req.TargetPeer)
|
n, err := enode.Parse(enode.ValidSchemes, req.TargetPeer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse target peer: %s", err)
|
return nil, fmt.Errorf("failed to parse target peer: %s", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user