skyfffire 2 жил өмнө
parent
commit
2e5f94cd56

+ 2 - 2
event/subscription.go

@@ -21,8 +21,8 @@ import (
 	"sync"
 	"time"
 
-	"github.com/ethereum/go-ethereum/common/gopool"
-	"github.com/ethereum/go-ethereum/common/mclock"
+	"blockchain-go/common/gopool"
+	"blockchain-go/common/mclock"
 )
 
 // Subscription represents a stream of events. The carrier of the events is typically a

+ 9 - 1
go.mod

@@ -3,9 +3,17 @@ module blockchain-go
 go 1.18
 
 require (
+	github.com/ethereum/go-ethereum v1.11.5
 	github.com/go-stack/stack v1.8.1
 	github.com/panjf2000/ants/v2 v2.7.2
+	github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
 	golang.org/x/crypto v0.7.0
 )
 
-require golang.org/x/sys v0.6.0 // indirect
+require (
+	github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
+	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
+	github.com/holiman/uint256 v1.2.0 // indirect
+	golang.org/x/sys v0.6.0 // indirect
+)

+ 160 - 0
p2p/enode/idscheme.go

@@ -0,0 +1,160 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package enode
+
+import (
+	"crypto/ecdsa"
+	"fmt"
+	"io"
+
+	"blockchain-go/common/math"
+	"blockchain-go/p2p/enr"
+	"blockchain-go/rlp"
+	"github.com/ethereum/go-ethereum/crypto"
+	"golang.org/x/crypto/sha3"
+)
+
+// List of known secure identity schemes.
+var ValidSchemes = enr.SchemeMap{
+	"v4": V4ID{},
+}
+
+var ValidSchemesForTesting = enr.SchemeMap{
+	"v4":   V4ID{},
+	"null": NullID{},
+}
+
+// v4ID is the "v4" identity scheme.
+type V4ID struct{}
+
+// SignV4 signs a record using the v4 scheme.
+func SignV4(r *enr.Record, privkey *ecdsa.PrivateKey) error {
+	// Copy r to avoid modifying it if signing fails.
+	cpy := *r
+	cpy.Set(enr.ID("v4"))
+	cpy.Set(Secp256k1(privkey.PublicKey))
+
+	h := sha3.NewLegacyKeccak256()
+	rlp.Encode(h, cpy.AppendElements(nil))
+	sig, err := crypto.Sign(h.Sum(nil), privkey)
+	if err != nil {
+		return err
+	}
+	sig = sig[:len(sig)-1] // remove v
+	if err = cpy.SetSig(V4ID{}, sig); err == nil {
+		*r = cpy
+	}
+	return err
+}
+
+func (V4ID) Verify(r *enr.Record, sig []byte) error {
+	var entry s256raw
+	if err := r.Load(&entry); err != nil {
+		return err
+	} else if len(entry) != 33 {
+		return fmt.Errorf("invalid public key")
+	}
+
+	h := sha3.NewLegacyKeccak256()
+	rlp.Encode(h, r.AppendElements(nil))
+	if !crypto.VerifySignature(entry, h.Sum(nil), sig) {
+		return enr.ErrInvalidSig
+	}
+	return nil
+}
+
+func (V4ID) NodeAddr(r *enr.Record) []byte {
+	var pubkey Secp256k1
+	err := r.Load(&pubkey)
+	if err != nil {
+		return nil
+	}
+	buf := make([]byte, 64)
+	math.ReadBits(pubkey.X, buf[:32])
+	math.ReadBits(pubkey.Y, buf[32:])
+	return crypto.Keccak256(buf)
+}
+
+// Secp256k1 is the "secp256k1" key, which holds a public key.
+type Secp256k1 ecdsa.PublicKey
+
+func (v Secp256k1) ENRKey() string { return "secp256k1" }
+
+// EncodeRLP implements rlp.Encoder.
+func (v Secp256k1) EncodeRLP(w io.Writer) error {
+	return rlp.Encode(w, crypto.CompressPubkey((*ecdsa.PublicKey)(&v)))
+}
+
+// DecodeRLP implements rlp.Decoder.
+func (v *Secp256k1) DecodeRLP(s *rlp.Stream) error {
+	buf, err := s.Bytes()
+	if err != nil {
+		return err
+	}
+	pk, err := crypto.DecompressPubkey(buf)
+	if err != nil {
+		return err
+	}
+	*v = (Secp256k1)(*pk)
+	return nil
+}
+
+// s256raw is an unparsed secp256k1 public key entry.
+type s256raw []byte
+
+func (s256raw) ENRKey() string { return "secp256k1" }
+
+// v4CompatID is a weaker and insecure version of the "v4" scheme which only checks for the
+// presence of a secp256k1 public key, but doesn't verify the signature.
+type v4CompatID struct {
+	V4ID
+}
+
+func (v4CompatID) Verify(r *enr.Record, sig []byte) error {
+	var pubkey Secp256k1
+	return r.Load(&pubkey)
+}
+
+func signV4Compat(r *enr.Record, pubkey *ecdsa.PublicKey) {
+	r.Set((*Secp256k1)(pubkey))
+	if err := r.SetSig(v4CompatID{}, []byte{}); err != nil {
+		panic(err)
+	}
+}
+
+// NullID is the "null" ENR identity scheme. This scheme stores the node
+// ID in the record without any signature.
+type NullID struct{}
+
+func (NullID) Verify(r *enr.Record, sig []byte) error {
+	return nil
+}
+
+func (NullID) NodeAddr(r *enr.Record) []byte {
+	var id ID
+	r.Load(enr.WithEntry("nulladdr", &id))
+	return id[:]
+}
+
+func SignNull(r *enr.Record, id ID) *Node {
+	r.Set(enr.ID("null"))
+	r.Set(enr.WithEntry("nulladdr", id))
+	if err := r.SetSig(NullID{}, []byte{}); err != nil {
+		panic(err)
+	}
+	return &Node{r: *r, id: id}
+}

+ 292 - 0
p2p/enode/iter.go

@@ -0,0 +1,292 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package enode
+
+import (
+	"sync"
+	"time"
+
+	"blockchain-go/common/gopool"
+)
+
+// Iterator represents a sequence of nodes. The Next method moves to the next node in the
+// sequence. It returns false when the sequence has ended or the iterator is closed. Close
+// may be called concurrently with Next and Node, and interrupts Next if it is blocked.
+type Iterator interface {
+	Next() bool  // moves to next node
+	Node() *Node // returns current node
+	Close()      // ends the iterator
+}
+
+// ReadNodes reads at most n nodes from the given iterator. The return value contains no
+// duplicates and no nil values. To prevent looping indefinitely for small repeating node
+// sequences, this function calls Next at most n times.
+func ReadNodes(it Iterator, n int) []*Node {
+	seen := make(map[ID]*Node, n)
+	for i := 0; i < n && it.Next(); i++ {
+		// Remove duplicates, keeping the node with higher seq.
+		node := it.Node()
+		prevNode, ok := seen[node.ID()]
+		if ok && prevNode.Seq() > node.Seq() {
+			continue
+		}
+		seen[node.ID()] = node
+	}
+	result := make([]*Node, 0, len(seen))
+	for _, node := range seen {
+		result = append(result, node)
+	}
+	return result
+}
+
+// IterNodes makes an iterator which runs through the given nodes once.
+func IterNodes(nodes []*Node) Iterator {
+	return &sliceIter{nodes: nodes, index: -1}
+}
+
+// CycleNodes makes an iterator which cycles through the given nodes indefinitely.
+func CycleNodes(nodes []*Node) Iterator {
+	return &sliceIter{nodes: nodes, index: -1, cycle: true}
+}
+
+type sliceIter struct {
+	mu    sync.Mutex
+	nodes []*Node
+	index int
+	cycle bool
+}
+
+func (it *sliceIter) Next() bool {
+	it.mu.Lock()
+	defer it.mu.Unlock()
+
+	if len(it.nodes) == 0 {
+		return false
+	}
+	it.index++
+	if it.index == len(it.nodes) {
+		if it.cycle {
+			it.index = 0
+		} else {
+			it.nodes = nil
+			return false
+		}
+	}
+	return true
+}
+
+func (it *sliceIter) Node() *Node {
+	it.mu.Lock()
+	defer it.mu.Unlock()
+	if len(it.nodes) == 0 {
+		return nil
+	}
+	return it.nodes[it.index]
+}
+
+func (it *sliceIter) Close() {
+	it.mu.Lock()
+	defer it.mu.Unlock()
+
+	it.nodes = nil
+}
+
+// Filter wraps an iterator such that Next only returns nodes for which
+// the 'check' function returns true.
+func Filter(it Iterator, check func(*Node) bool) Iterator {
+	return &filterIter{it, check}
+}
+
+type filterIter struct {
+	Iterator
+	check func(*Node) bool
+}
+
+func (f *filterIter) Next() bool {
+	for f.Iterator.Next() {
+		if f.check(f.Node()) {
+			return true
+		}
+	}
+	return false
+}
+
+// FairMix aggregates multiple node iterators. The mixer itself is an iterator which ends
+// only when Close is called. Source iterators added via AddSource are removed from the
+// mix when they end.
+//
+// The distribution of nodes returned by Next is approximately fair, i.e. FairMix
+// attempts to draw from all sources equally often. However, if a certain source is slow
+// and doesn't return a node within the configured timeout, a node from any other source
+// will be returned.
+//
+// It's safe to call AddSource and Close concurrently with Next.
+type FairMix struct {
+	wg      sync.WaitGroup
+	fromAny chan *Node
+	timeout time.Duration
+	cur     *Node
+
+	mu      sync.Mutex
+	closed  chan struct{}
+	sources []*mixSource
+	last    int
+}
+
+type mixSource struct {
+	it      Iterator
+	next    chan *Node
+	timeout time.Duration
+}
+
+// NewFairMix creates a mixer.
+//
+// The timeout specifies how long the mixer will wait for the next fairly-chosen source
+// before giving up and taking a node from any other source. A good way to set the timeout
+// is deciding how long you'd want to wait for a node on average. Passing a negative
+// timeout makes the mixer completely fair.
+func NewFairMix(timeout time.Duration) *FairMix {
+	m := &FairMix{
+		fromAny: make(chan *Node),
+		closed:  make(chan struct{}),
+		timeout: timeout,
+	}
+	return m
+}
+
+// AddSource adds a source of nodes.
+func (m *FairMix) AddSource(it Iterator) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	if m.closed == nil {
+		return
+	}
+	m.wg.Add(1)
+	source := &mixSource{it, make(chan *Node), m.timeout}
+	m.sources = append(m.sources, source)
+	gopool.Submit(func() {
+		m.runSource(m.closed, source)
+	})
+}
+
+// Close shuts down the mixer and all current sources.
+// Calling this is required to release resources associated with the mixer.
+func (m *FairMix) Close() {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	if m.closed == nil {
+		return
+	}
+	for _, s := range m.sources {
+		s.it.Close()
+	}
+	close(m.closed)
+	m.wg.Wait()
+	close(m.fromAny)
+	m.sources = nil
+	m.closed = nil
+}
+
+// Next returns a node from a random source.
+func (m *FairMix) Next() bool {
+	m.cur = nil
+
+	var timeout <-chan time.Time
+	if m.timeout >= 0 {
+		timer := time.NewTimer(m.timeout)
+		timeout = timer.C
+		defer timer.Stop()
+	}
+	for {
+		source := m.pickSource()
+		if source == nil {
+			return m.nextFromAny()
+		}
+		select {
+		case n, ok := <-source.next:
+			if ok {
+				m.cur = n
+				source.timeout = m.timeout
+				return true
+			}
+			// This source has ended.
+			m.deleteSource(source)
+		case <-timeout:
+			source.timeout /= 2
+			return m.nextFromAny()
+		}
+	}
+}
+
+// Node returns the current node.
+func (m *FairMix) Node() *Node {
+	return m.cur
+}
+
+// nextFromAny is used when there are no sources or when the 'fair' choice
+// doesn't turn up a node quickly enough.
+func (m *FairMix) nextFromAny() bool {
+	n, ok := <-m.fromAny
+	if ok {
+		m.cur = n
+	}
+	return ok
+}
+
+// pickSource chooses the next source to read from, cycling through them in order.
+func (m *FairMix) pickSource() *mixSource {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	if len(m.sources) == 0 {
+		return nil
+	}
+	m.last = (m.last + 1) % len(m.sources)
+	return m.sources[m.last]
+}
+
+// deleteSource deletes a source.
+func (m *FairMix) deleteSource(s *mixSource) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	for i := range m.sources {
+		if m.sources[i] == s {
+			copy(m.sources[i:], m.sources[i+1:])
+			m.sources[len(m.sources)-1] = nil
+			m.sources = m.sources[:len(m.sources)-1]
+			break
+		}
+	}
+}
+
+// runSource reads a single source in a loop.
+func (m *FairMix) runSource(closed chan struct{}, s *mixSource) {
+	defer m.wg.Done()
+	defer close(s.next)
+	for s.it.Next() {
+		n := s.it.Node()
+		select {
+		case s.next <- n:
+		case m.fromAny <- n:
+		case <-closed:
+			return
+		}
+	}
+}

+ 290 - 0
p2p/enode/localnode.go

@@ -0,0 +1,290 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package enode
+
+import (
+	"crypto/ecdsa"
+	"fmt"
+	"net"
+	"reflect"
+	"strconv"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"blockchain-go/log"
+	"blockchain-go/p2p/enr"
+	"blockchain-go/p2p/netutil"
+)
+
+const (
+	// IP tracker configuration
+	iptrackMinStatements = 10
+	iptrackWindow        = 5 * time.Minute
+	iptrackContactWindow = 10 * time.Minute
+)
+
+// LocalNode produces the signed node record of a local node, i.e. a node run in the
+// current process. Setting ENR entries via the Set method updates the record. A new version
+// of the record is signed on demand when the Node method is called.
+type LocalNode struct {
+	cur atomic.Value // holds a non-nil node pointer while the record is up-to-date.
+	id  ID
+	key *ecdsa.PrivateKey
+	db  *DB
+
+	// everything below is protected by a lock
+	mu        sync.Mutex
+	seq       uint64
+	entries   map[string]enr.Entry
+	endpoint4 lnEndpoint
+	endpoint6 lnEndpoint
+}
+
+type lnEndpoint struct {
+	track                *netutil.IPTracker
+	staticIP, fallbackIP net.IP
+	fallbackUDP          int
+}
+
+// NewLocalNode creates a local node.
+func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode {
+	ln := &LocalNode{
+		id:      PubkeyToIDV4(&key.PublicKey),
+		db:      db,
+		key:     key,
+		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()
+	return ln
+}
+
+// Database returns the node database associated with the local node.
+func (ln *LocalNode) Database() *DB {
+	return ln.db
+}
+
+// Node returns the current version of the local node record.
+func (ln *LocalNode) Node() *Node {
+	n := ln.cur.Load().(*Node)
+	if n != nil {
+		return n
+	}
+	// Record was invalidated, sign a new copy.
+	ln.mu.Lock()
+	defer ln.mu.Unlock()
+	ln.sign()
+	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.
+// 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()
+
+	ln.set(e)
+}
+
+func (ln *LocalNode) set(e enr.Entry) {
+	val, exists := ln.entries[e.ENRKey()]
+	if !exists || !reflect.DeepEqual(val, e) {
+		ln.entries[e.ENRKey()] = e
+		ln.invalidate()
+	}
+}
+
+// Delete removes the given entry from the local record.
+func (ln *LocalNode) Delete(e enr.Entry) {
+	ln.mu.Lock()
+	defer ln.mu.Unlock()
+
+	ln.delete(e)
+}
+
+func (ln *LocalNode) delete(e enr.Entry) {
+	_, exists := ln.entries[e.ENRKey()]
+	if exists {
+		delete(ln.entries, e.ENRKey())
+		ln.invalidate()
+	}
+}
+
+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.endpointForIP(ip).staticIP = ip
+	ln.updateEndpoints()
+}
+
+// SetFallbackIP sets the last-resort IP address. This address is used
+// if no endpoint prediction can be made and no static IP is set.
+func (ln *LocalNode) SetFallbackIP(ip net.IP) {
+	ln.mu.Lock()
+	defer ln.mu.Unlock()
+
+	ln.endpointForIP(ip).fallbackIP = ip
+	ln.updateEndpoints()
+}
+
+// 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.endpoint4.fallbackUDP = port
+	ln.endpoint6.fallbackUDP = port
+	ln.updateEndpoints()
+}
+
+// UDPEndpointStatement should be called whenever a statement about the local node's
+// UDP endpoint is received. It feeds the local endpoint predictor.
+func (ln *LocalNode) UDPEndpointStatement(fromaddr, endpoint *net.UDPAddr) {
+	ln.mu.Lock()
+	defer ln.mu.Unlock()
+
+	ln.endpointForIP(endpoint.IP).track.AddStatement(fromaddr.String(), endpoint.String())
+	ln.updateEndpoints()
+}
+
+// UDPContact should be called whenever the local node has announced itself to another node
+// via UDP. It feeds the local endpoint predictor.
+func (ln *LocalNode) UDPContact(toaddr *net.UDPAddr) {
+	ln.mu.Lock()
+	defer ln.mu.Unlock()
+
+	ln.endpointForIP(toaddr.IP).track.AddContact(toaddr.String())
+	ln.updateEndpoints()
+}
+
+// updateEndpoints updates the record with predicted endpoints.
+func (ln *LocalNode) updateEndpoints() {
+	ip4, udp4 := ln.endpoint4.get()
+	ip6, udp6 := ln.endpoint6.get()
+
+	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.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) {
+	ep := t.PredictEndpoint()
+	if ep == "" {
+		return nil, 0
+	}
+	ipString, portString, _ := net.SplitHostPort(ep)
+	ip := net.ParseIP(ipString)
+	port, _ := strconv.Atoi(portString)
+	return ip, port
+}
+
+func (ln *LocalNode) invalidate() {
+	ln.cur.Store((*Node)(nil))
+}
+
+func (ln *LocalNode) sign() {
+	if n := ln.cur.Load().(*Node); n != nil {
+		return // no changes
+	}
+
+	var r enr.Record
+	for _, e := range ln.entries {
+		r.Set(e)
+	}
+	ln.bumpSeq()
+	r.SetSeq(ln.seq)
+	if err := SignV4(&r, ln.key); err != nil {
+		panic(fmt.Errorf("enode: can't sign record: %v", err))
+	}
+	n, err := New(ValidSchemes, &r)
+	if err != nil {
+		panic(fmt.Errorf("enode: can't verify local record: %v", err))
+	}
+	ln.cur.Store(n)
+	log.Info("New local node record", "seq", ln.seq, "id", n.ID(), "ip", n.IP(), "udp", n.UDP(), "tcp", n.TCP())
+}
+
+func (ln *LocalNode) bumpSeq() {
+	ln.seq++
+	ln.db.storeLocalSeq(ln.id, ln.seq)
+}

+ 279 - 0
p2p/enode/node.go

@@ -0,0 +1,279 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package enode
+
+import (
+	"crypto/ecdsa"
+	"encoding/base64"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"math/bits"
+	"net"
+	"strings"
+
+	"blockchain-go/p2p/enr"
+	"blockchain-go/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
+	id ID
+}
+
+// New wraps a node record. The record must be valid according to the given
+// identity scheme.
+func New(validSchemes enr.IdentityScheme, r *enr.Record) (*Node, error) {
+	if err := r.VerifySignature(validSchemes); err != nil {
+		return nil, err
+	}
+	node := &Node{r: *r}
+	if n := copy(node.id[:], validSchemes.NodeAddr(&node.r)); n != len(ID{}) {
+		return nil, fmt.Errorf("invalid node ID length %d, need %d", n, len(ID{}))
+	}
+	return node, nil
+}
+
+// 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
+}
+
+// Seq returns the sequence number of the underlying record.
+func (n *Node) Seq() uint64 {
+	return n.r.Seq()
+}
+
+// Incomplete returns true for nodes with no IP address.
+func (n *Node) Incomplete() bool {
+	return n.IP() == nil
+}
+
+// Load retrieves an entry from the underlying record.
+func (n *Node) Load(k enr.Entry) error {
+	return n.r.Load(k)
+}
+
+// IP returns the IP address of the node. This prefers IPv4 addresses.
+func (n *Node) IP() net.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.
+func (n *Node) UDP() int {
+	var port enr.UDP
+	n.Load(&port)
+	return int(port)
+}
+
+// UDP returns the TCP port of the node.
+func (n *Node) TCP() int {
+	var port enr.TCP
+	n.Load(&port)
+	return int(port)
+}
+
+// Pubkey returns the secp256k1 public key of the node, if present.
+func (n *Node) Pubkey() *ecdsa.PublicKey {
+	var key ecdsa.PublicKey
+	if n.Load((*Secp256k1)(&key)) != nil {
+		return nil
+	}
+	return &key
+}
+
+// Record returns the node's record. The return value is a copy and may
+// be modified by the caller.
+func (n *Node) Record() *enr.Record {
+	cpy := n.r
+	return &cpy
+}
+
+// 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("missing IP address")
+	}
+	if n.UDP() == 0 {
+		return errors.New("missing UDP port")
+	}
+	ip := n.IP()
+	if ip.IsMulticast() || ip.IsUnspecified() {
+		return errors.New("invalid IP (multicast/unspecified)")
+	}
+	// Validate the node key (on curve, etc.).
+	var key Secp256k1
+	return n.Load(&key)
+}
+
+// String returns the text representation of the record.
+func (n *Node) String() string {
+	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.String()), nil
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler.
+func (n *Node) UnmarshalText(text []byte) error {
+	dec, err := Parse(ValidSchemes, string(text))
+	if err == nil {
+		*n = *dec
+	}
+	return err
+}
+
+// ID is a unique identifier for each node.
+type ID [32]byte
+
+// Bytes returns a byte slice representation of the ID
+func (n ID) Bytes() []byte {
+	return n[:]
+}
+
+// ID prints as a long hexadecimal number.
+func (n ID) String() string {
+	return fmt.Sprintf("%x", n[:])
+}
+
+// The Go syntax representation of a ID is a call to HexID.
+func (n ID) GoString() string {
+	return fmt.Sprintf("enode.HexID(\"%x\")", n[:])
+}
+
+// TerminalString returns a shortened hex string for terminal logging.
+func (n ID) TerminalString() string {
+	return hex.EncodeToString(n[:8])
+}
+
+// MarshalText implements the encoding.TextMarshaler interface.
+func (n ID) MarshalText() ([]byte, error) {
+	return []byte(hex.EncodeToString(n[:])), nil
+}
+
+// UnmarshalText implements the encoding.TextUnmarshaler interface.
+func (n *ID) UnmarshalText(text []byte) error {
+	id, err := ParseID(string(text))
+	if err != nil {
+		return err
+	}
+	*n = id
+	return nil
+}
+
+// HexID converts a hex string to an ID.
+// The string may be prefixed with 0x.
+// It panics if the string is not a valid ID.
+func HexID(in string) ID {
+	id, err := ParseID(in)
+	if err != nil {
+		panic(err)
+	}
+	return id
+}
+
+func ParseID(in string) (ID, error) {
+	var id ID
+	b, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
+	if err != nil {
+		return id, err
+	} else if len(b) != len(id) {
+		return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2)
+	}
+	copy(id[:], b)
+	return id, nil
+}
+
+// DistCmp compares the distances a->target and b->target.
+// Returns -1 if a is closer to target, 1 if b is closer to target
+// and 0 if they are equal.
+func DistCmp(target, a, b ID) int {
+	for i := range target {
+		da := a[i] ^ target[i]
+		db := b[i] ^ target[i]
+		if da > db {
+			return 1
+		} else if da < db {
+			return -1
+		}
+	}
+	return 0
+}
+
+// LogDist returns the logarithmic distance between a and b, log2(a ^ b).
+func LogDist(a, b ID) int {
+	lz := 0
+	for i := range a {
+		x := a[i] ^ b[i]
+		if x == 0 {
+			lz += 8
+		} else {
+			lz += bits.LeadingZeros8(x)
+			break
+		}
+	}
+	return len(a)*8 - lz
+}

+ 501 - 0
p2p/enode/nodedb.go

@@ -0,0 +1,501 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package enode
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/binary"
+	"fmt"
+	"net"
+	"os"
+	"sync"
+	"time"
+
+	"blockchain-go/common/gopool"
+	"blockchain-go/rlp"
+	"github.com/syndtr/goleveldb/leveldb"
+	"github.com/syndtr/goleveldb/leveldb/errors"
+	"github.com/syndtr/goleveldb/leveldb/iterator"
+	"github.com/syndtr/goleveldb/leveldb/opt"
+	"github.com/syndtr/goleveldb/leveldb/storage"
+	"github.com/syndtr/goleveldb/leveldb/util"
+)
+
+// Keys in the node database.
+const (
+	dbVersionKey   = "version" // Version of the database to flush if changes
+	dbNodePrefix   = "n:"      // Identifier to prefix node entries with
+	dbLocalPrefix  = "local:"
+	dbDiscoverRoot = "v4"
+	dbDiscv5Root   = "v5"
+
+	// These fields are stored per ID and IP, the full key is "n:<ID>:v4:<IP>:findfail".
+	// Use nodeItemKey to create those keys.
+	dbNodeFindFails = "findfail"
+	dbNodePing      = "lastping"
+	dbNodePong      = "lastpong"
+	dbNodeSeq       = "seq"
+
+	// Local information is keyed by ID only, the full key is "local:<ID>:seq".
+	// Use localItemKey to create those keys.
+	dbLocalSeq = "seq"
+)
+
+const (
+	dbNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
+	dbCleanupCycle   = time.Hour      // Time period for running the expiration task.
+	dbVersion        = 9
+)
+
+var (
+	errInvalidIP = errors.New("invalid IP")
+)
+
+var zeroIP = make(net.IP, 16)
+
+// DB is the node database, storing previously seen nodes and any collected metadata about
+// them for QoS purposes.
+type DB struct {
+	lvl    *leveldb.DB   // Interface to the database itself
+	runner sync.Once     // Ensures we can start at most one expirer
+	quit   chan struct{} // Channel to signal the expiring thread to stop
+}
+
+// OpenDB opens a node database for storing and retrieving infos about known peers in the
+// network. If no path is given an in-memory, temporary database is constructed.
+func OpenDB(path string) (*DB, error) {
+	if path == "" {
+		return newMemoryDB()
+	}
+	return newPersistentDB(path)
+}
+
+// newMemoryNodeDB creates a new in-memory node database without a persistent backend.
+func newMemoryDB() (*DB, error) {
+	db, err := leveldb.Open(storage.NewMemStorage(), nil)
+	if err != nil {
+		return nil, err
+	}
+	return &DB{lvl: db, quit: make(chan struct{})}, nil
+}
+
+// newPersistentNodeDB creates/opens a leveldb backed persistent node database,
+// also flushing its contents in case of a version mismatch.
+func newPersistentDB(path string) (*DB, error) {
+	opts := &opt.Options{OpenFilesCacheCapacity: 5}
+	db, err := leveldb.OpenFile(path, opts)
+	if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
+		db, err = leveldb.RecoverFile(path, nil)
+	}
+	if err != nil {
+		return nil, err
+	}
+	// The nodes contained in the cache correspond to a certain protocol version.
+	// Flush all nodes if the version doesn't match.
+	currentVer := make([]byte, binary.MaxVarintLen64)
+	currentVer = currentVer[:binary.PutVarint(currentVer, int64(dbVersion))]
+
+	blob, err := db.Get([]byte(dbVersionKey), nil)
+	switch err {
+	case leveldb.ErrNotFound:
+		// Version not found (i.e. empty cache), insert it
+		if err := db.Put([]byte(dbVersionKey), currentVer, nil); err != nil {
+			db.Close()
+			return nil, err
+		}
+
+	case nil:
+		// Version present, flush if different
+		if !bytes.Equal(blob, currentVer) {
+			db.Close()
+			if err = os.RemoveAll(path); err != nil {
+				return nil, err
+			}
+			return newPersistentDB(path)
+		}
+	}
+	return &DB{lvl: db, quit: make(chan struct{})}, nil
+}
+
+// nodeKey returns the database key for a node record.
+func nodeKey(id ID) []byte {
+	key := append([]byte(dbNodePrefix), id[:]...)
+	key = append(key, ':')
+	key = append(key, dbDiscoverRoot...)
+	return key
+}
+
+// splitNodeKey returns the node ID of a key created by nodeKey.
+func splitNodeKey(key []byte) (id ID, rest []byte) {
+	if !bytes.HasPrefix(key, []byte(dbNodePrefix)) {
+		return ID{}, nil
+	}
+	item := key[len(dbNodePrefix):]
+	copy(id[:], item[:len(id)])
+	return id, item[len(id)+1:]
+}
+
+// nodeItemKey returns the database key for a node metadata field.
+func nodeItemKey(id ID, ip net.IP, field string) []byte {
+	ip16 := ip.To16()
+	if ip16 == nil {
+		panic(fmt.Errorf("invalid IP (length %d)", len(ip)))
+	}
+	return bytes.Join([][]byte{nodeKey(id), ip16, []byte(field)}, []byte{':'})
+}
+
+// splitNodeItemKey returns the components of a key created by nodeItemKey.
+func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) {
+	id, key = splitNodeKey(key)
+	// Skip discover root.
+	if string(key) == dbDiscoverRoot {
+		return id, nil, ""
+	}
+	key = key[len(dbDiscoverRoot)+1:]
+	// Split out the IP.
+	ip = key[:16]
+	if ip4 := ip.To4(); ip4 != nil {
+		ip = ip4
+	}
+	key = key[16+1:]
+	// Field is the remainder of key.
+	field = string(key)
+	return id, ip, field
+}
+
+func v5Key(id ID, ip net.IP, field string) []byte {
+	return bytes.Join([][]byte{
+		[]byte(dbNodePrefix),
+		id[:],
+		[]byte(dbDiscv5Root),
+		ip.To16(),
+		[]byte(field),
+	}, []byte{':'})
+}
+
+// localItemKey returns the key of a local node item.
+func localItemKey(id ID, field string) []byte {
+	key := append([]byte(dbLocalPrefix), id[:]...)
+	key = append(key, ':')
+	key = append(key, field...)
+	return key
+}
+
+// fetchInt64 retrieves an integer associated with a particular key.
+func (db *DB) fetchInt64(key []byte) int64 {
+	blob, err := db.lvl.Get(key, nil)
+	if err != nil {
+		return 0
+	}
+	val, read := binary.Varint(blob)
+	if read <= 0 {
+		return 0
+	}
+	return val
+}
+
+// storeInt64 stores an integer in the given key.
+func (db *DB) storeInt64(key []byte, n int64) error {
+	blob := make([]byte, binary.MaxVarintLen64)
+	blob = blob[:binary.PutVarint(blob, n)]
+	return db.lvl.Put(key, blob, nil)
+}
+
+// fetchUint64 retrieves an integer associated with a particular key.
+func (db *DB) fetchUint64(key []byte) uint64 {
+	blob, err := db.lvl.Get(key, nil)
+	if err != nil {
+		return 0
+	}
+	val, _ := binary.Uvarint(blob)
+	return val
+}
+
+// storeUint64 stores an integer in the given key.
+func (db *DB) storeUint64(key []byte, n uint64) error {
+	blob := make([]byte, binary.MaxVarintLen64)
+	blob = blob[:binary.PutUvarint(blob, n)]
+	return db.lvl.Put(key, blob, nil)
+}
+
+// Node retrieves a node with a given id from the database.
+func (db *DB) Node(id ID) *Node {
+	blob, err := db.lvl.Get(nodeKey(id), nil)
+	if err != nil {
+		return nil
+	}
+	return mustDecodeNode(id[:], blob)
+}
+
+func mustDecodeNode(id, data []byte) *Node {
+	node := new(Node)
+	if err := rlp.DecodeBytes(data, &node.r); err != nil {
+		panic(fmt.Errorf("p2p/enode: can't decode node %x in DB: %v", id, err))
+	}
+	// Restore node id cache.
+	copy(node.id[:], id)
+	return node
+}
+
+// UpdateNode inserts - potentially overwriting - a node into the peer database.
+func (db *DB) UpdateNode(node *Node) error {
+	if node.Seq() < db.NodeSeq(node.ID()) {
+		return nil
+	}
+	blob, err := rlp.EncodeToBytes(&node.r)
+	if err != nil {
+		return err
+	}
+	if err := db.lvl.Put(nodeKey(node.ID()), blob, nil); err != nil {
+		return err
+	}
+	return db.storeUint64(nodeItemKey(node.ID(), zeroIP, dbNodeSeq), node.Seq())
+}
+
+// NodeSeq returns the stored record sequence number of the given node.
+func (db *DB) NodeSeq(id ID) uint64 {
+	return db.fetchUint64(nodeItemKey(id, zeroIP, dbNodeSeq))
+}
+
+// Resolve returns the stored record of the node if it has a larger sequence
+// number than n.
+func (db *DB) Resolve(n *Node) *Node {
+	if n.Seq() > db.NodeSeq(n.ID()) {
+		return n
+	}
+	return db.Node(n.ID())
+}
+
+// DeleteNode deletes all information associated with a node.
+func (db *DB) DeleteNode(id ID) {
+	deleteRange(db.lvl, nodeKey(id))
+}
+
+func deleteRange(db *leveldb.DB, prefix []byte) {
+	it := db.NewIterator(util.BytesPrefix(prefix), nil)
+	defer it.Release()
+	for it.Next() {
+		db.Delete(it.Key(), nil)
+	}
+}
+
+// ensureExpirer is a small helper method ensuring that the data expiration
+// mechanism is running. If the expiration goroutine is already running, this
+// method simply returns.
+//
+// The goal is to start the data evacuation only after the network successfully
+// bootstrapped itself (to prevent dumping potentially useful seed nodes). Since
+// it would require significant overhead to exactly trace the first successful
+// convergence, it's simpler to "ensure" the correct state when an appropriate
+// condition occurs (i.e. a successful bonding), and discard further events.
+func (db *DB) ensureExpirer() {
+	db.runner.Do(func() {
+		gopool.Submit(func() {
+			db.expirer()
+		})
+	})
+}
+
+// expirer should be started in a go routine, and is responsible for looping ad
+// infinitum and dropping stale data from the database.
+func (db *DB) expirer() {
+	tick := time.NewTicker(dbCleanupCycle)
+	defer tick.Stop()
+	for {
+		select {
+		case <-tick.C:
+			db.expireNodes()
+		case <-db.quit:
+			return
+		}
+	}
+}
+
+// expireNodes iterates over the database and deletes all nodes that have not
+// been seen (i.e. received a pong from) for some time.
+func (db *DB) expireNodes() {
+	it := db.lvl.NewIterator(util.BytesPrefix([]byte(dbNodePrefix)), nil)
+	defer it.Release()
+	if !it.Next() {
+		return
+	}
+
+	var (
+		threshold    = time.Now().Add(-dbNodeExpiration).Unix()
+		youngestPong int64
+		atEnd        = false
+	)
+	for !atEnd {
+		id, ip, field := splitNodeItemKey(it.Key())
+		if field == dbNodePong {
+			time, _ := binary.Varint(it.Value())
+			if time > youngestPong {
+				youngestPong = time
+			}
+			if time < threshold {
+				// Last pong from this IP older than threshold, remove fields belonging to it.
+				deleteRange(db.lvl, nodeItemKey(id, ip, ""))
+			}
+		}
+		atEnd = !it.Next()
+		nextID, _ := splitNodeKey(it.Key())
+		if atEnd || nextID != id {
+			// We've moved beyond the last entry of the current ID.
+			// Remove everything if there was no recent enough pong.
+			if youngestPong > 0 && youngestPong < threshold {
+				deleteRange(db.lvl, nodeKey(id))
+			}
+			youngestPong = 0
+		}
+	}
+}
+
+// LastPingReceived retrieves the time of the last ping packet received from
+// a remote node.
+func (db *DB) LastPingReceived(id ID, ip net.IP) time.Time {
+	if ip = ip.To16(); ip == nil {
+		return time.Time{}
+	}
+	return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePing)), 0)
+}
+
+// UpdateLastPingReceived updates the last time we tried contacting a remote node.
+func (db *DB) UpdateLastPingReceived(id ID, ip net.IP, instance time.Time) error {
+	if ip = ip.To16(); ip == nil {
+		return errInvalidIP
+	}
+	return db.storeInt64(nodeItemKey(id, ip, dbNodePing), instance.Unix())
+}
+
+// LastPongReceived retrieves the time of the last successful pong from remote node.
+func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time {
+	if ip = ip.To16(); ip == nil {
+		return time.Time{}
+	}
+	// Launch expirer
+	db.ensureExpirer()
+	return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePong)), 0)
+}
+
+// UpdateLastPongReceived updates the last pong time of a node.
+func (db *DB) UpdateLastPongReceived(id ID, ip net.IP, instance time.Time) error {
+	if ip = ip.To16(); ip == nil {
+		return errInvalidIP
+	}
+	return db.storeInt64(nodeItemKey(id, ip, dbNodePong), instance.Unix())
+}
+
+// FindFails retrieves the number of findnode failures since bonding.
+func (db *DB) FindFails(id ID, ip net.IP) int {
+	if ip = ip.To16(); ip == nil {
+		return 0
+	}
+	return int(db.fetchInt64(nodeItemKey(id, ip, dbNodeFindFails)))
+}
+
+// UpdateFindFails updates the number of findnode failures since bonding.
+func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error {
+	if ip = ip.To16(); ip == nil {
+		return errInvalidIP
+	}
+	return db.storeInt64(nodeItemKey(id, ip, dbNodeFindFails), int64(fails))
+}
+
+// FindFailsV5 retrieves the discv5 findnode failure counter.
+func (db *DB) FindFailsV5(id ID, ip net.IP) int {
+	if ip = ip.To16(); ip == nil {
+		return 0
+	}
+	return int(db.fetchInt64(v5Key(id, ip, dbNodeFindFails)))
+}
+
+// UpdateFindFailsV5 stores the discv5 findnode failure counter.
+func (db *DB) UpdateFindFailsV5(id ID, ip net.IP, fails int) error {
+	if ip = ip.To16(); ip == nil {
+		return errInvalidIP
+	}
+	return db.storeInt64(v5Key(id, ip, dbNodeFindFails), int64(fails))
+}
+
+// LocalSeq retrieves the local record sequence counter.
+func (db *DB) localSeq(id ID) uint64 {
+	return db.fetchUint64(localItemKey(id, dbLocalSeq))
+}
+
+// storeLocalSeq stores the local record sequence counter.
+func (db *DB) storeLocalSeq(id ID, n uint64) {
+	db.storeUint64(localItemKey(id, dbLocalSeq), n)
+}
+
+// QuerySeeds retrieves random nodes to be used as potential seed nodes
+// for bootstrapping.
+func (db *DB) QuerySeeds(n int, maxAge time.Duration) []*Node {
+	var (
+		now   = time.Now()
+		nodes = make([]*Node, 0, n)
+		it    = db.lvl.NewIterator(nil, nil)
+		id    ID
+	)
+	defer it.Release()
+
+seek:
+	for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ {
+		// Seek to a random entry. The first byte is incremented by a
+		// random amount each time in order to increase the likelihood
+		// of hitting all existing nodes in very small databases.
+		ctr := id[0]
+		rand.Read(id[:])
+		id[0] = ctr + id[0]%16
+		it.Seek(nodeKey(id))
+
+		n := nextNode(it)
+		if n == nil {
+			id[0] = 0
+			continue seek // iterator exhausted
+		}
+		if now.Sub(db.LastPongReceived(n.ID(), n.IP())) > maxAge {
+			continue seek
+		}
+		for i := range nodes {
+			if nodes[i].ID() == n.ID() {
+				continue seek // duplicate
+			}
+		}
+		nodes = append(nodes, n)
+	}
+	return nodes
+}
+
+// reads the next node record from the iterator, skipping over other
+// database entries.
+func nextNode(it iterator.Iterator) *Node {
+	for end := false; !end; end = !it.Next() {
+		id, rest := splitNodeKey(it.Key())
+		if string(rest) != dbDiscoverRoot {
+			continue
+		}
+		return mustDecodeNode(id[:], it.Value())
+	}
+	return nil
+}
+
+// close flushes and closes the database files.
+func (db *DB) Close() {
+	close(db.quit)
+	db.lvl.Close()
+}

+ 203 - 0
p2p/enode/urlv4.go

@@ -0,0 +1,203 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package enode
+
+import (
+	"crypto/ecdsa"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"net"
+	"net/url"
+	"regexp"
+	"strconv"
+
+	"blockchain-go/common/math"
+	"blockchain-go/p2p/enr"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+var (
+	incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
+	lookupIPFunc      = net.LookupIP
+)
+
+// MustParseV4 parses a node URL. It panics if the URL is not valid.
+func MustParseV4(rawurl string) *Node {
+	n, err := ParseV4(rawurl)
+	if err != nil {
+		panic("invalid node URL: " + err.Error())
+	}
+	return n
+}
+
+// ParseV4 parses a node URL.
+//
+// There are two basic forms of node URLs:
+//
+//   - incomplete nodes, which only have the public key (node ID)
+//   - complete nodes, which contain the public key and IP/Port information
+//
+// For incomplete nodes, the designator must look like one of these
+//
+//    enode://<hex node id>
+//    <hex node id>
+//
+// For complete nodes, the node ID is encoded in the username portion
+// of the URL, separated from the host by an @ sign. The hostname can
+// only be given as an IP address or using DNS domain name.
+// The port in the host name section is the TCP listening port. If the
+// TCP and UDP (discovery) ports differ, the UDP port is specified as
+// query parameter "discport".
+//
+// In the following example, the node URL describes
+// a node with IP address 10.3.58.6, TCP listening port 30303
+// and UDP discovery port 30301.
+//
+//    enode://<hex node id>@10.3.58.6:30303?discport=30301
+func ParseV4(rawurl string) (*Node, error) {
+	if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
+		id, err := parsePubkey(m[1])
+		if err != nil {
+			return nil, fmt.Errorf("invalid public key (%v)", err)
+		}
+		return NewV4(id, nil, 0, 0), nil
+	}
+	return parseComplete(rawurl)
+}
+
+// NewV4 creates a node from discovery v4 node information. The record
+// contained in the node has a zero-length signature.
+func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node {
+	var r enr.Record
+	if len(ip) > 0 {
+		r.Set(enr.IP(ip))
+	}
+	if udp != 0 {
+		r.Set(enr.UDP(udp))
+	}
+	if tcp != 0 {
+		r.Set(enr.TCP(tcp))
+	}
+	signV4Compat(&r, pubkey)
+	n, err := New(v4CompatID{}, &r)
+	if err != nil {
+		panic(err)
+	}
+	return n
+}
+
+// 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
+		tcpPort, udpPort uint64
+	)
+	u, err := url.Parse(rawurl)
+	if err != nil {
+		return nil, err
+	}
+	if u.Scheme != "enode" {
+		return nil, errors.New("invalid URL scheme, want \"enode\"")
+	}
+	// Parse the Node ID from the user portion.
+	if u.User == nil {
+		return nil, errors.New("does not contain node ID")
+	}
+	if id, err = parsePubkey(u.User.String()); err != nil {
+		return nil, fmt.Errorf("invalid public key (%v)", err)
+	}
+	// Parse the IP address.
+	ip := net.ParseIP(u.Hostname())
+	if ip == nil {
+		ips, err := lookupIPFunc(u.Hostname())
+		if err != nil {
+			return nil, err
+		}
+		ip = ips[0]
+	}
+	// 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(u.Port(), 10, 16); err != nil {
+		return nil, errors.New("invalid port")
+	}
+	udpPort = tcpPort
+	qv := u.Query()
+	if qv.Get("discport") != "" {
+		udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16)
+		if err != nil {
+			return nil, errors.New("invalid discport in query")
+		}
+	}
+	return NewV4(id, ip, int(tcpPort), int(udpPort)), nil
+}
+
+// parsePubkey parses a hex-encoded secp256k1 public key.
+func parsePubkey(in string) (*ecdsa.PublicKey, error) {
+	b, err := hex.DecodeString(in)
+	if err != nil {
+		return nil, err
+	} else if len(b) != 64 {
+		return nil, fmt.Errorf("wrong length, want %d hex chars", 128)
+	}
+	b = append([]byte{0x4}, b...)
+	return crypto.UnmarshalPubkey(b)
+}
+
+func (n *Node) URLv4() string {
+	var (
+		scheme enr.ID
+		nodeid string
+		key    ecdsa.PublicKey
+	)
+	n.Load(&scheme)
+	n.Load((*Secp256k1)(&key))
+	switch {
+	case scheme == "v4" || key != ecdsa.PublicKey{}:
+		nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:])
+	default:
+		nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:])
+	}
+	u := url.URL{Scheme: "enode"}
+	if n.Incomplete() {
+		u.Host = nodeid
+	} else {
+		addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()}
+		u.User = url.User(nodeid)
+		u.Host = addr.String()
+		if n.UDP() != n.TCP() {
+			u.RawQuery = "discport=" + strconv.Itoa(n.UDP())
+		}
+	}
+	return u.String()
+}
+
+// PubkeyToIDV4 derives the v4 node address from the given public key.
+func PubkeyToIDV4(key *ecdsa.PublicKey) ID {
+	e := make([]byte, 64)
+	math.ReadBits(key.X, e[:len(e)/2])
+	math.ReadBits(key.Y, e[len(e)/2:])
+	return ID(crypto.Keccak256Hash(e))
+}

+ 317 - 0
p2p/enr/enr.go

@@ -0,0 +1,317 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Package enr implements Ethereum Node Records as defined in EIP-778. A node record holds
+// arbitrary information about a node on the peer-to-peer network. Node information is
+// stored in key/value pairs. To store and retrieve key/values in a record, use the Entry
+// interface.
+//
+// Signature Handling
+//
+// Records must be signed before transmitting them to another node.
+//
+// Decoding a record doesn't check its signature. Code working with records from an
+// untrusted source must always verify two things: that the record uses an identity scheme
+// deemed secure, and that the signature is valid according to the declared scheme.
+//
+// When creating a record, set the entries you want and use a signing function provided by
+// the identity scheme to add the signature. Modifying a record invalidates the signature.
+//
+// Package enr supports the "secp256k1-keccak" identity scheme.
+package enr
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"sort"
+
+	"blockchain-go/rlp"
+)
+
+const SizeLimit = 300 // maximum encoded size of a node record in bytes
+
+var (
+	ErrInvalidSig     = errors.New("invalid signature on node record")
+	errNotSorted      = errors.New("record key/value pairs are not sorted by key")
+	errDuplicateKey   = errors.New("record contains duplicate key")
+	errIncompletePair = errors.New("record contains incomplete k/v pair")
+	errIncompleteList = errors.New("record contains less than two list elements")
+	errTooBig         = fmt.Errorf("record bigger than %d bytes", SizeLimit)
+	errEncodeUnsigned = errors.New("can't encode unsigned record")
+	errNotFound       = errors.New("no such key in record")
+)
+
+// An IdentityScheme is capable of verifying record signatures and
+// deriving node addresses.
+type IdentityScheme interface {
+	Verify(r *Record, sig []byte) error
+	NodeAddr(r *Record) []byte
+}
+
+// SchemeMap is a registry of named identity schemes.
+type SchemeMap map[string]IdentityScheme
+
+func (m SchemeMap) Verify(r *Record, sig []byte) error {
+	s := m[r.IdentityScheme()]
+	if s == nil {
+		return ErrInvalidSig
+	}
+	return s.Verify(r, sig)
+}
+
+func (m SchemeMap) NodeAddr(r *Record) []byte {
+	s := m[r.IdentityScheme()]
+	if s == nil {
+		return nil
+	}
+	return s.NodeAddr(r)
+}
+
+// Record represents a node record. The zero value is an empty record.
+type Record struct {
+	seq       uint64 // sequence number
+	signature []byte // the signature
+	raw       []byte // RLP encoded record
+	pairs     []pair // sorted list of all key/value pairs
+}
+
+// pair is a key/value pair in a record.
+type pair struct {
+	k string
+	v rlp.RawValue
+}
+
+// Seq returns the sequence number.
+func (r *Record) Seq() uint64 {
+	return r.seq
+}
+
+// SetSeq updates the record sequence number. This invalidates any signature on the record.
+// Calling SetSeq is usually not required because setting any key in a signed record
+// increments the sequence number.
+func (r *Record) SetSeq(s uint64) {
+	r.signature = nil
+	r.raw = nil
+	r.seq = s
+}
+
+// Load retrieves the value of a key/value pair. The given Entry must be a pointer and will
+// be set to the value of the entry in the record.
+//
+// Errors returned by Load are wrapped in KeyError. You can distinguish decoding errors
+// from missing keys using the IsNotFound function.
+func (r *Record) Load(e Entry) error {
+	i := sort.Search(len(r.pairs), func(i int) bool { return r.pairs[i].k >= e.ENRKey() })
+	if i < len(r.pairs) && r.pairs[i].k == e.ENRKey() {
+		if err := rlp.DecodeBytes(r.pairs[i].v, e); err != nil {
+			return &KeyError{Key: e.ENRKey(), Err: err}
+		}
+		return nil
+	}
+	return &KeyError{Key: e.ENRKey(), Err: errNotFound}
+}
+
+// Set adds or updates the given entry in the record. It panics if the value can't be
+// encoded. If the record is signed, Set increments the sequence number and invalidates
+// the sequence number.
+func (r *Record) Set(e Entry) {
+	blob, err := rlp.EncodeToBytes(e)
+	if err != nil {
+		panic(fmt.Errorf("enr: can't encode %s: %v", e.ENRKey(), err))
+	}
+	r.invalidate()
+
+	pairs := make([]pair, len(r.pairs))
+	copy(pairs, r.pairs)
+	i := sort.Search(len(pairs), func(i int) bool { return pairs[i].k >= e.ENRKey() })
+	switch {
+	case i < len(pairs) && pairs[i].k == e.ENRKey():
+		// element is present at r.pairs[i]
+		pairs[i].v = blob
+	case i < len(r.pairs):
+		// insert pair before i-th elem
+		el := pair{e.ENRKey(), blob}
+		pairs = append(pairs, pair{})
+		copy(pairs[i+1:], pairs[i:])
+		pairs[i] = el
+	default:
+		// element should be placed at the end of r.pairs
+		pairs = append(pairs, pair{e.ENRKey(), blob})
+	}
+	r.pairs = pairs
+}
+
+func (r *Record) invalidate() {
+	if r.signature != nil {
+		r.seq++
+	}
+	r.signature = 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
+// the record is unsigned.
+func (r Record) EncodeRLP(w io.Writer) error {
+	if r.signature == nil {
+		return errEncodeUnsigned
+	}
+	_, err := w.Write(r.raw)
+	return err
+}
+
+// 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 {
+		return err
+	}
+	*r = dec
+	r.raw = raw
+	return nil
+}
+
+func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) {
+	raw, err = s.Raw()
+	if err != nil {
+		return dec, raw, err
+	}
+	if len(raw) > SizeLimit {
+		return dec, raw, errTooBig
+	}
+
+	// Decode the RLP container.
+	s = rlp.NewStream(bytes.NewReader(raw), 0)
+	if _, err := s.List(); err != nil {
+		return dec, raw, err
+	}
+	if err = s.Decode(&dec.signature); err != nil {
+		if err == rlp.EOL {
+			err = errIncompleteList
+		}
+		return dec, raw, err
+	}
+	if err = s.Decode(&dec.seq); err != nil {
+		if err == rlp.EOL {
+			err = errIncompleteList
+		}
+		return dec, raw, err
+	}
+	// The rest of the record contains sorted k/v pairs.
+	var prevkey string
+	for i := 0; ; i++ {
+		var kv pair
+		if err := s.Decode(&kv.k); err != nil {
+			if err == rlp.EOL {
+				break
+			}
+			return dec, raw, err
+		}
+		if err := s.Decode(&kv.v); err != nil {
+			if err == rlp.EOL {
+				return dec, raw, errIncompletePair
+			}
+			return dec, raw, err
+		}
+		if i > 0 {
+			if kv.k == prevkey {
+				return dec, raw, errDuplicateKey
+			}
+			if kv.k < prevkey {
+				return dec, raw, errNotSorted
+			}
+		}
+		dec.pairs = append(dec.pairs, kv)
+		prevkey = kv.k
+	}
+	return dec, raw, s.ListEnd()
+}
+
+// IdentityScheme returns the name of the identity scheme in the record.
+func (r *Record) IdentityScheme() string {
+	var id ID
+	r.Load(&id)
+	return string(id)
+}
+
+// VerifySignature checks whether the record is signed using the given identity scheme.
+func (r *Record) VerifySignature(s IdentityScheme) error {
+	return s.Verify(r, r.signature)
+}
+
+// SetSig sets the record signature. It returns an error if the encoded record is larger
+// than the size limit or if the signature is invalid according to the passed scheme.
+//
+// You can also use SetSig to remove the signature explicitly by passing a nil scheme
+// and signature.
+//
+// SetSig panics when either the scheme or the signature (but not both) are nil.
+func (r *Record) SetSig(s IdentityScheme, sig []byte) error {
+	switch {
+	// Prevent storing invalid data.
+	case s == nil && sig != nil:
+		panic("enr: invalid call to SetSig with non-nil signature but nil scheme")
+	case s != nil && sig == nil:
+		panic("enr: invalid call to SetSig with nil signature but non-nil scheme")
+	// Verify if we have a scheme.
+	case s != nil:
+		if err := s.Verify(r, sig); err != nil {
+			return err
+		}
+		raw, err := r.encode(sig)
+		if err != nil {
+			return err
+		}
+		r.signature, r.raw = sig, raw
+	// Reset otherwise.
+	default:
+		r.signature, r.raw = nil, nil
+	}
+	return nil
+}
+
+// AppendElements appends the sequence number and entries to the given slice.
+func (r *Record) AppendElements(list []interface{}) []interface{} {
+	list = append(list, r.seq)
+	for _, p := range r.pairs {
+		list = append(list, p.k, p.v)
+	}
+	return list
+}
+
+func (r *Record) encode(sig []byte) (raw []byte, err error) {
+	list := make([]interface{}, 1, 2*len(r.pairs)+1)
+	list[0] = sig
+	list = r.AppendElements(list)
+	if raw, err = rlp.EncodeToBytes(list); err != nil {
+		return nil, err
+	}
+	if len(raw) > SizeLimit {
+		return nil, errTooBig
+	}
+	return raw, nil
+}

+ 188 - 0
p2p/enr/entries.go

@@ -0,0 +1,188 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package enr
+
+import (
+	"fmt"
+	"io"
+	"net"
+
+	"blockchain-go/rlp"
+)
+
+// Entry is implemented by known node record entry types.
+//
+// To define a new entry that is to be included in a node record,
+// create a Go type that satisfies this interface. The type should
+// also implement rlp.Decoder if additional checks are needed on the value.
+type Entry interface {
+	ENRKey() string
+}
+
+type generic struct {
+	key   string
+	value interface{}
+}
+
+func (g generic) ENRKey() string { return g.key }
+
+func (g generic) EncodeRLP(w io.Writer) error {
+	return rlp.Encode(w, g.value)
+}
+
+func (g *generic) DecodeRLP(s *rlp.Stream) error {
+	return s.Decode(g.value)
+}
+
+// WithEntry wraps any value with a key name. It can be used to set and load arbitrary values
+// in a record. The value v must be supported by rlp. To use WithEntry with Load, the value
+// must be a pointer.
+func WithEntry(k string, v interface{}) Entry {
+	return &generic{key: k, value: v}
+}
+
+// TCP is the "tcp" key, which holds the TCP port of the node.
+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
+
+const IDv4 = ID("v4") // the default identity scheme
+
+func (v ID) ENRKey() string { return "id" }
+
+// 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 {
+	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)
+	}
+	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.
+func (v *IP) DecodeRLP(s *rlp.Stream) error {
+	if err := s.Decode((*net.IP)(v)); err != nil {
+		return err
+	}
+	if len(*v) != 4 && len(*v) != 16 {
+		return fmt.Errorf("invalid IP address, want 4 or 16 bytes: %v", *v)
+	}
+	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
+	Err error
+}
+
+// Error implements error.
+func (err *KeyError) Error() string {
+	if err.Err == errNotFound {
+		return fmt.Sprintf("missing ENR key %q", err.Key)
+	}
+	return fmt.Sprintf("ENR key %q: %v", err.Key, err.Err)
+}
+
+// IsNotFound reports whether the given error means that a key/value pair is
+// missing from a record.
+func IsNotFound(err error) bool {
+	kerr, ok := err.(*KeyError)
+	return ok && kerr.Err == errNotFound
+}

+ 33 - 0
p2p/netutil/addrutil.go

@@ -0,0 +1,33 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package netutil
+
+import "net"
+
+// AddrIP gets the IP address contained in addr. It returns nil if no address is present.
+func AddrIP(addr net.Addr) net.IP {
+	switch a := addr.(type) {
+	case *net.IPAddr:
+		return a.IP
+	case *net.TCPAddr:
+		return a.IP
+	case *net.UDPAddr:
+		return a.IP
+	default:
+		return nil
+	}
+}

+ 33 - 0
p2p/netutil/error.go

@@ -0,0 +1,33 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package netutil
+
+// IsTemporaryError checks whether the given error should be considered temporary.
+func IsTemporaryError(err error) bool {
+	tempErr, ok := err.(interface {
+		Temporary() bool
+	})
+	return ok && tempErr.Temporary() || isPacketTooBig(err)
+}
+
+// IsTimeout checks whether the given error is a timeout.
+func IsTimeout(err error) bool {
+	timeoutErr, ok := err.(interface {
+		Timeout() bool
+	})
+	return ok && timeoutErr.Timeout()
+}

+ 130 - 0
p2p/netutil/iptrack.go

@@ -0,0 +1,130 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package netutil
+
+import (
+	"time"
+
+	"blockchain-go/common/mclock"
+)
+
+// IPTracker predicts the external endpoint, i.e. IP address and port, of the local host
+// based on statements made by other hosts.
+type IPTracker struct {
+	window          time.Duration
+	contactWindow   time.Duration
+	minStatements   int
+	clock           mclock.Clock
+	statements      map[string]ipStatement
+	contact         map[string]mclock.AbsTime
+	lastStatementGC mclock.AbsTime
+	lastContactGC   mclock.AbsTime
+}
+
+type ipStatement struct {
+	endpoint string
+	time     mclock.AbsTime
+}
+
+// NewIPTracker creates an IP tracker.
+//
+// The window parameters configure the amount of past network events which are kept. The
+// minStatements parameter enforces a minimum number of statements which must be recorded
+// before any prediction is made. Higher values for these parameters decrease 'flapping' of
+// predictions as network conditions change. Window duration values should typically be in
+// the range of minutes.
+func NewIPTracker(window, contactWindow time.Duration, minStatements int) *IPTracker {
+	return &IPTracker{
+		window:        window,
+		contactWindow: contactWindow,
+		statements:    make(map[string]ipStatement),
+		minStatements: minStatements,
+		contact:       make(map[string]mclock.AbsTime),
+		clock:         mclock.System{},
+	}
+}
+
+// PredictFullConeNAT checks whether the local host is behind full cone NAT. It predicts by
+// checking whether any statement has been received from a node we didn't contact before
+// the statement was made.
+func (it *IPTracker) PredictFullConeNAT() bool {
+	now := it.clock.Now()
+	it.gcContact(now)
+	it.gcStatements(now)
+	for host, st := range it.statements {
+		if c, ok := it.contact[host]; !ok || c > st.time {
+			return true
+		}
+	}
+	return false
+}
+
+// PredictEndpoint returns the current prediction of the external endpoint.
+func (it *IPTracker) PredictEndpoint() string {
+	it.gcStatements(it.clock.Now())
+
+	// The current strategy is simple: find the endpoint with most statements.
+	counts := make(map[string]int)
+	maxcount, max := 0, ""
+	for _, s := range it.statements {
+		c := counts[s.endpoint] + 1
+		counts[s.endpoint] = c
+		if c > maxcount && c >= it.minStatements {
+			maxcount, max = c, s.endpoint
+		}
+	}
+	return max
+}
+
+// AddStatement records that a certain host thinks our external endpoint is the one given.
+func (it *IPTracker) AddStatement(host, endpoint string) {
+	now := it.clock.Now()
+	it.statements[host] = ipStatement{endpoint, now}
+	if time.Duration(now-it.lastStatementGC) >= it.window {
+		it.gcStatements(now)
+	}
+}
+
+// AddContact records that a packet containing our endpoint information has been sent to a
+// certain host.
+func (it *IPTracker) AddContact(host string) {
+	now := it.clock.Now()
+	it.contact[host] = now
+	if time.Duration(now-it.lastContactGC) >= it.contactWindow {
+		it.gcContact(now)
+	}
+}
+
+func (it *IPTracker) gcStatements(now mclock.AbsTime) {
+	it.lastStatementGC = now
+	cutoff := now.Add(-it.window)
+	for host, s := range it.statements {
+		if s.time < cutoff {
+			delete(it.statements, host)
+		}
+	}
+}
+
+func (it *IPTracker) gcContact(now mclock.AbsTime) {
+	it.lastContactGC = now
+	cutoff := now.Add(-it.contactWindow)
+	for host, ct := range it.contact {
+		if ct < cutoff {
+			delete(it.contact, host)
+		}
+	}
+}

+ 322 - 0
p2p/netutil/net.go

@@ -0,0 +1,322 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// Package netutil contains extensions to the net package.
+package netutil
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"net"
+	"sort"
+	"strings"
+)
+
+var lan4, lan6, special4, special6 Netlist
+
+func init() {
+	// Lists from RFC 5735, RFC 5156,
+	// https://www.iana.org/assignments/iana-ipv4-special-registry/
+	lan4.Add("0.0.0.0/8")              // "This" network
+	lan4.Add("10.0.0.0/8")             // Private Use
+	lan4.Add("172.16.0.0/12")          // Private Use
+	lan4.Add("192.168.0.0/16")         // Private Use
+	lan6.Add("fe80::/10")              // Link-Local
+	lan6.Add("fc00::/7")               // Unique-Local
+	special4.Add("192.0.0.0/29")       // IPv4 Service Continuity
+	special4.Add("192.0.0.9/32")       // PCP Anycast
+	special4.Add("192.0.0.170/32")     // NAT64/DNS64 Discovery
+	special4.Add("192.0.0.171/32")     // NAT64/DNS64 Discovery
+	special4.Add("192.0.2.0/24")       // TEST-NET-1
+	special4.Add("192.31.196.0/24")    // AS112
+	special4.Add("192.52.193.0/24")    // AMT
+	special4.Add("192.88.99.0/24")     // 6to4 Relay Anycast
+	special4.Add("192.175.48.0/24")    // AS112
+	special4.Add("198.18.0.0/15")      // Device Benchmark Testing
+	special4.Add("198.51.100.0/24")    // TEST-NET-2
+	special4.Add("203.0.113.0/24")     // TEST-NET-3
+	special4.Add("255.255.255.255/32") // Limited Broadcast
+
+	// http://www.iana.org/assignments/iana-ipv6-special-registry/
+	special6.Add("100::/64")
+	special6.Add("2001::/32")
+	special6.Add("2001:1::1/128")
+	special6.Add("2001:2::/48")
+	special6.Add("2001:3::/32")
+	special6.Add("2001:4:112::/48")
+	special6.Add("2001:5::/32")
+	special6.Add("2001:10::/28")
+	special6.Add("2001:20::/28")
+	special6.Add("2001:db8::/32")
+	special6.Add("2002::/16")
+}
+
+// Netlist is a list of IP networks.
+type Netlist []net.IPNet
+
+// ParseNetlist parses a comma-separated list of CIDR masks.
+// Whitespace and extra commas are ignored.
+func ParseNetlist(s string) (*Netlist, error) {
+	ws := strings.NewReplacer(" ", "", "\n", "", "\t", "")
+	masks := strings.Split(ws.Replace(s), ",")
+	l := make(Netlist, 0)
+	for _, mask := range masks {
+		if mask == "" {
+			continue
+		}
+		_, n, err := net.ParseCIDR(mask)
+		if err != nil {
+			return nil, err
+		}
+		l = append(l, *n)
+	}
+	return &l, nil
+}
+
+// MarshalTOML implements toml.MarshalerRec.
+func (l Netlist) MarshalTOML() interface{} {
+	list := make([]string, 0, len(l))
+	for _, net := range l {
+		list = append(list, net.String())
+	}
+	return list
+}
+
+// UnmarshalTOML implements toml.UnmarshalerRec.
+func (l *Netlist) UnmarshalTOML(fn func(interface{}) error) error {
+	var masks []string
+	if err := fn(&masks); err != nil {
+		return err
+	}
+	for _, mask := range masks {
+		_, n, err := net.ParseCIDR(mask)
+		if err != nil {
+			return err
+		}
+		*l = append(*l, *n)
+	}
+	return nil
+}
+
+// Add parses a CIDR mask and appends it to the list. It panics for invalid masks and is
+// intended to be used for setting up static lists.
+func (l *Netlist) Add(cidr string) {
+	_, n, err := net.ParseCIDR(cidr)
+	if err != nil {
+		panic(err)
+	}
+	*l = append(*l, *n)
+}
+
+// Contains reports whether the given IP is contained in the list.
+func (l *Netlist) Contains(ip net.IP) bool {
+	if l == nil {
+		return false
+	}
+	for _, net := range *l {
+		if net.Contains(ip) {
+			return true
+		}
+	}
+	return false
+}
+
+// IsLAN reports whether an IP is a local network address.
+func IsLAN(ip net.IP) bool {
+	if ip.IsLoopback() {
+		return true
+	}
+	if v4 := ip.To4(); v4 != nil {
+		return lan4.Contains(v4)
+	}
+	return lan6.Contains(ip)
+}
+
+// IsSpecialNetwork reports whether an IP is located in a special-use network range
+// This includes broadcast, multicast and documentation addresses.
+func IsSpecialNetwork(ip net.IP) bool {
+	if ip.IsMulticast() {
+		return true
+	}
+	if v4 := ip.To4(); v4 != nil {
+		return special4.Contains(v4)
+	}
+	return special6.Contains(ip)
+}
+
+var (
+	errInvalid     = errors.New("invalid IP")
+	errUnspecified = errors.New("zero address")
+	errSpecial     = errors.New("special network")
+	errLoopback    = errors.New("loopback address from non-loopback host")
+	errLAN         = errors.New("LAN address from WAN host")
+)
+
+// CheckRelayIP reports whether an IP relayed from the given sender IP
+// is a valid connection target.
+//
+// There are four rules:
+//   - Special network addresses are never valid.
+//   - Loopback addresses are OK if relayed by a loopback host.
+//   - LAN addresses are OK if relayed by a LAN host.
+//   - All other addresses are always acceptable.
+func CheckRelayIP(sender, addr net.IP) error {
+	if len(addr) != net.IPv4len && len(addr) != net.IPv6len {
+		return errInvalid
+	}
+	if addr.IsUnspecified() {
+		return errUnspecified
+	}
+	if IsSpecialNetwork(addr) {
+		return errSpecial
+	}
+	if addr.IsLoopback() && !sender.IsLoopback() {
+		return errLoopback
+	}
+	if IsLAN(addr) && !IsLAN(sender) {
+		return errLAN
+	}
+	return nil
+}
+
+// SameNet reports whether two IP addresses have an equal prefix of the given bit length.
+func SameNet(bits uint, ip, other net.IP) bool {
+	ip4, other4 := ip.To4(), other.To4()
+	switch {
+	case (ip4 == nil) != (other4 == nil):
+		return false
+	case ip4 != nil:
+		return sameNet(bits, ip4, other4)
+	default:
+		return sameNet(bits, ip.To16(), other.To16())
+	}
+}
+
+func sameNet(bits uint, ip, other net.IP) bool {
+	nb := int(bits / 8)
+	mask := ^byte(0xFF >> (bits % 8))
+	if mask != 0 && nb < len(ip) && ip[nb]&mask != other[nb]&mask {
+		return false
+	}
+	return nb <= len(ip) && ip[:nb].Equal(other[:nb])
+}
+
+// DistinctNetSet tracks IPs, ensuring that at most N of them
+// fall into the same network range.
+type DistinctNetSet struct {
+	Subnet uint // number of common prefix bits
+	Limit  uint // maximum number of IPs in each subnet
+
+	members map[string]uint
+	buf     net.IP
+}
+
+// Add adds an IP address to the set. It returns false (and doesn't add the IP) if the
+// number of existing IPs in the defined range exceeds the limit.
+func (s *DistinctNetSet) Add(ip net.IP) bool {
+	key := s.key(ip)
+	n := s.members[string(key)]
+	if n < s.Limit {
+		s.members[string(key)] = n + 1
+		return true
+	}
+	return false
+}
+
+// Remove removes an IP from the set.
+func (s *DistinctNetSet) Remove(ip net.IP) {
+	key := s.key(ip)
+	if n, ok := s.members[string(key)]; ok {
+		if n == 1 {
+			delete(s.members, string(key))
+		} else {
+			s.members[string(key)] = n - 1
+		}
+	}
+}
+
+// Contains whether the given IP is contained in the set.
+func (s DistinctNetSet) Contains(ip net.IP) bool {
+	key := s.key(ip)
+	_, ok := s.members[string(key)]
+	return ok
+}
+
+// Len returns the number of tracked IPs.
+func (s DistinctNetSet) Len() int {
+	n := uint(0)
+	for _, i := range s.members {
+		n += i
+	}
+	return int(n)
+}
+
+// key encodes the map key for an address into a temporary buffer.
+//
+// The first byte of key is '4' or '6' to distinguish IPv4/IPv6 address types.
+// The remainder of the key is the IP, truncated to the number of bits.
+func (s *DistinctNetSet) key(ip net.IP) net.IP {
+	// Lazily initialize storage.
+	if s.members == nil {
+		s.members = make(map[string]uint)
+		s.buf = make(net.IP, 17)
+	}
+	// Canonicalize ip and bits.
+	typ := byte('6')
+	if ip4 := ip.To4(); ip4 != nil {
+		typ, ip = '4', ip4
+	}
+	bits := s.Subnet
+	if bits > uint(len(ip)*8) {
+		bits = uint(len(ip) * 8)
+	}
+	// Encode the prefix into s.buf.
+	nb := int(bits / 8)
+	mask := ^byte(0xFF >> (bits % 8))
+	s.buf[0] = typ
+	buf := append(s.buf[:1], ip[:nb]...)
+	if nb < len(ip) && mask != 0 {
+		buf = append(buf, ip[nb]&mask)
+	}
+	return buf
+}
+
+// String implements fmt.Stringer
+func (s DistinctNetSet) String() string {
+	var buf bytes.Buffer
+	buf.WriteString("{")
+	keys := make([]string, 0, len(s.members))
+	for k := range s.members {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	for i, k := range keys {
+		var ip net.IP
+		if k[0] == '4' {
+			ip = make(net.IP, 4)
+		} else {
+			ip = make(net.IP, 16)
+		}
+		copy(ip, k[1:])
+		fmt.Fprintf(&buf, "%v×%d", ip, s.members[k])
+		if i != len(keys)-1 {
+			buf.WriteString(" ")
+		}
+	}
+	buf.WriteString("}")
+	return buf.String()
+}

+ 27 - 0
p2p/netutil/toobig_notwindows.go

@@ -0,0 +1,27 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+//go:build !windows
+// +build !windows
+
+package netutil
+
+// isPacketTooBig reports whether err indicates that a UDP packet didn't
+// fit the receive buffer. There is no such error on
+// non-Windows platforms.
+func isPacketTooBig(err error) bool {
+	return false
+}

+ 41 - 0
p2p/netutil/toobig_windows.go

@@ -0,0 +1,41 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+//go:build windows
+// +build windows
+
+package netutil
+
+import (
+	"net"
+	"os"
+	"syscall"
+)
+
+const _WSAEMSGSIZE = syscall.Errno(10040)
+
+// isPacketTooBig reports whether err indicates that a UDP packet didn't
+// fit the receive buffer. On Windows, WSARecvFrom returns
+// code WSAEMSGSIZE and no data if this happens.
+func isPacketTooBig(err error) bool {
+	if opErr, ok := err.(*net.OpError); ok {
+		if scErr, ok := opErr.Err.(*os.SyscallError); ok {
+			return scErr.Err == _WSAEMSGSIZE
+		}
+		return opErr.Err == _WSAEMSGSIZE
+	}
+	return false
+}