Explorar o código

discover模块加入

skyfffire %!s(int64=2) %!d(string=hai) anos
pai
achega
9db109b889

+ 75 - 0
go.sum

@@ -1,8 +1,44 @@
+github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
+github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
+github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
+github.com/ethereum/go-ethereum v1.11.5 h1:3M1uan+LAUvdn+7wCEFrcMM4LJTeuxDrPTg/f31a5QQ=
+github.com/ethereum/go-ethereum v1.11.5/go.mod h1:it7x0DWnTDMfVFdXcU6Ti4KEFQynLHVRarcSlPr0HBo=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
 github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
 github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
+github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/panjf2000/ants/v2 v2.7.2 h1:2NUt9BaZFO5kQzrieOmK/wdb/tQ/K+QHaxN8sOgD63U=
 github.com/panjf2000/ants/v2 v2.7.2/go.mod h1:KIBmYG9QQX5U2qzFP/yQJaq/nSb6rahS9iEHkrCMgM8=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -14,13 +50,52 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 82 - 0
p2p/discover/common.go

@@ -0,0 +1,82 @@
+// 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 discover
+
+import (
+	"crypto/ecdsa"
+	"net"
+
+	"blockchain-go/common/mclock"
+	"blockchain-go/log"
+	"blockchain-go/p2p/enode"
+	"blockchain-go/p2p/enr"
+	"blockchain-go/p2p/netutil"
+)
+
+// UDPConn is a network connection on which discovery can operate.
+type UDPConn interface {
+	ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
+	WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error)
+	Close() error
+	LocalAddr() net.Addr
+}
+
+// Config holds settings for the discovery listener.
+type Config struct {
+	// These settings are required and configure the UDP listener:
+	PrivateKey *ecdsa.PrivateKey
+
+	// These settings are optional:
+	NetRestrict  *netutil.Netlist   // network whitelist
+	Bootnodes    []*enode.Node      // list of bootstrap nodes
+	Unhandled    chan<- ReadPacket  // unhandled packets are sent on this channel
+	Log          log.Logger         // if set, log messages go here
+	ValidSchemes enr.IdentityScheme // allowed identity schemes
+	Clock        mclock.Clock
+}
+
+func (cfg Config) withDefaults() Config {
+	if cfg.Log == nil {
+		cfg.Log = log.Root()
+	}
+	if cfg.ValidSchemes == nil {
+		cfg.ValidSchemes = enode.ValidSchemes
+	}
+	if cfg.Clock == nil {
+		cfg.Clock = mclock.System{}
+	}
+	return cfg
+}
+
+// ListenUDP starts listening for discovery packets on the given UDP socket.
+func ListenUDP(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
+	return ListenV4(c, ln, cfg)
+}
+
+// ReadPacket is a packet that couldn't be handled. Those packets are sent to the unhandled
+// channel if configured.
+type ReadPacket struct {
+	Data []byte
+	Addr *net.UDPAddr
+}
+
+func min(x, y int) int {
+	if x > y {
+		return y
+	}
+	return x
+}

+ 229 - 0
p2p/discover/lookup.go

@@ -0,0 +1,229 @@
+// 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 discover
+
+import (
+	"context"
+	"time"
+
+	"blockchain-go/common/gopool"
+	"blockchain-go/p2p/enode"
+)
+
+// lookup performs a network search for nodes close to the given target. It approaches the
+// target by querying nodes that are closer to it on each iteration. The given target does
+// not need to be an actual node identifier.
+type lookup struct {
+	tab         *Table
+	queryfunc   func(*node) ([]*node, error)
+	replyCh     chan []*node
+	cancelCh    <-chan struct{}
+	asked, seen map[enode.ID]bool
+	result      nodesByDistance
+	replyBuffer []*node
+	queries     int
+}
+
+type queryFunc func(*node) ([]*node, error)
+
+func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup {
+	it := &lookup{
+		tab:       tab,
+		queryfunc: q,
+		asked:     make(map[enode.ID]bool),
+		seen:      make(map[enode.ID]bool),
+		result:    nodesByDistance{target: target},
+		replyCh:   make(chan []*node, alpha),
+		cancelCh:  ctx.Done(),
+		queries:   -1,
+	}
+	// Don't query further if we hit ourself.
+	// Unlikely to happen often in practice.
+	it.asked[tab.self().ID()] = true
+	return it
+}
+
+// run runs the lookup to completion and returns the closest nodes found.
+func (it *lookup) run() []*enode.Node {
+	for it.advance() {
+	}
+	return unwrapNodes(it.result.entries)
+}
+
+// advance advances the lookup until any new nodes have been found.
+// It returns false when the lookup has ended.
+func (it *lookup) advance() bool {
+	for it.startQueries() {
+		select {
+		case nodes := <-it.replyCh:
+			it.replyBuffer = it.replyBuffer[:0]
+			for _, n := range nodes {
+				if n != nil && !it.seen[n.ID()] {
+					it.seen[n.ID()] = true
+					it.result.push(n, bucketSize)
+					it.replyBuffer = append(it.replyBuffer, n)
+				}
+			}
+			it.queries--
+			if len(it.replyBuffer) > 0 {
+				return true
+			}
+		case <-it.cancelCh:
+			it.shutdown()
+		}
+	}
+	return false
+}
+
+func (it *lookup) shutdown() {
+	for it.queries > 0 {
+		<-it.replyCh
+		it.queries--
+	}
+	it.queryfunc = nil
+	it.replyBuffer = nil
+}
+
+func (it *lookup) startQueries() bool {
+	if it.queryfunc == nil {
+		return false
+	}
+
+	// The first query returns nodes from the local table.
+	if it.queries == -1 {
+		closest := it.tab.findnodeByID(it.result.target, bucketSize, false)
+		// Avoid finishing the lookup too quickly if table is empty. It'd be better to wait
+		// for the table to fill in this case, but there is no good mechanism for that
+		// yet.
+		if len(closest.entries) == 0 {
+			it.slowdown()
+		}
+		it.queries = 1
+		it.replyCh <- closest.entries
+		return true
+	}
+
+	// Ask the closest nodes that we haven't asked yet.
+	for i := 0; i < len(it.result.entries) && it.queries < alpha; i++ {
+		n := it.result.entries[i]
+		if !it.asked[n.ID()] {
+			it.asked[n.ID()] = true
+			it.queries++
+			gopool.Submit(func() {
+				it.query(n, it.replyCh)
+			})
+		}
+	}
+	// The lookup ends when no more nodes can be asked.
+	return it.queries > 0
+}
+
+func (it *lookup) slowdown() {
+	sleep := time.NewTimer(1 * time.Second)
+	defer sleep.Stop()
+	select {
+	case <-sleep.C:
+	case <-it.tab.closeReq:
+	}
+}
+
+func (it *lookup) query(n *node, reply chan<- []*node) {
+	fails := it.tab.db.FindFails(n.ID(), n.IP())
+	r, err := it.queryfunc(n)
+	if err == errClosed {
+		// Avoid recording failures on shutdown.
+		reply <- nil
+		return
+	} else if len(r) == 0 {
+		fails++
+		it.tab.db.UpdateFindFails(n.ID(), n.IP(), fails)
+		// Remove the node from the local table if it fails to return anything useful too
+		// many times, but only if there are enough other nodes in the bucket.
+		dropped := false
+		if fails >= maxFindnodeFailures && it.tab.bucketLen(n.ID()) >= bucketSize/2 {
+			dropped = true
+			it.tab.delete(n)
+		}
+		it.tab.log.Trace("FINDNODE failed", "id", n.ID(), "failcount", fails, "dropped", dropped, "err", err)
+	} else if fails > 0 {
+		// Reset failure counter because it counts _consecutive_ failures.
+		it.tab.db.UpdateFindFails(n.ID(), n.IP(), 0)
+	}
+
+	// Grab as many nodes as possible. Some of them might not be alive anymore, but we'll
+	// just remove those again during revalidation.
+	for _, n := range r {
+		it.tab.addSeenNode(n)
+	}
+	reply <- r
+}
+
+// lookupIterator performs lookup operations and iterates over all seen nodes.
+// When a lookup finishes, a new one is created through nextLookup.
+type lookupIterator struct {
+	buffer     []*node
+	nextLookup lookupFunc
+	ctx        context.Context
+	cancel     func()
+	lookup     *lookup
+}
+
+type lookupFunc func(ctx context.Context) *lookup
+
+func newLookupIterator(ctx context.Context, next lookupFunc) *lookupIterator {
+	ctx, cancel := context.WithCancel(ctx)
+	return &lookupIterator{ctx: ctx, cancel: cancel, nextLookup: next}
+}
+
+// Node returns the current node.
+func (it *lookupIterator) Node() *enode.Node {
+	if len(it.buffer) == 0 {
+		return nil
+	}
+	return unwrapNode(it.buffer[0])
+}
+
+// Next moves to the next node.
+func (it *lookupIterator) Next() bool {
+	// Consume next node in buffer.
+	if len(it.buffer) > 0 {
+		it.buffer = it.buffer[1:]
+	}
+	// Advance the lookup to refill the buffer.
+	for len(it.buffer) == 0 {
+		if it.ctx.Err() != nil {
+			it.lookup = nil
+			it.buffer = nil
+			return false
+		}
+		if it.lookup == nil {
+			it.lookup = it.nextLookup(it.ctx)
+			continue
+		}
+		if !it.lookup.advance() {
+			it.lookup = nil
+			continue
+		}
+		it.buffer = it.lookup.replyBuffer
+	}
+	return true
+}
+
+// Close ends the iterator.
+func (it *lookupIterator) Close() {
+	it.cancel()
+}

+ 97 - 0
p2p/discover/node.go

@@ -0,0 +1,97 @@
+// Copyright 2015 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 discover
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"errors"
+	"math/big"
+	"net"
+	"time"
+
+	"blockchain-go/common/math"
+	"blockchain-go/p2p/enode"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+// node represents a host on the network.
+// The fields of Node may not be modified.
+type node struct {
+	enode.Node
+	addedAt        time.Time // time when the node was added to the table
+	livenessChecks uint      // how often liveness was checked
+}
+
+type encPubkey [64]byte
+
+func encodePubkey(key *ecdsa.PublicKey) encPubkey {
+	var e encPubkey
+	math.ReadBits(key.X, e[:len(e)/2])
+	math.ReadBits(key.Y, e[len(e)/2:])
+	return e
+}
+
+func decodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) {
+	if len(e) != len(encPubkey{}) {
+		return nil, errors.New("wrong size public key data")
+	}
+	p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}
+	half := len(e) / 2
+	p.X.SetBytes(e[:half])
+	p.Y.SetBytes(e[half:])
+	if !p.Curve.IsOnCurve(p.X, p.Y) {
+		return nil, errors.New("invalid curve point")
+	}
+	return p, nil
+}
+
+func (e encPubkey) id() enode.ID {
+	return enode.ID(crypto.Keccak256Hash(e[:]))
+}
+
+func wrapNode(n *enode.Node) *node {
+	return &node{Node: *n}
+}
+
+func wrapNodes(ns []*enode.Node) []*node {
+	result := make([]*node, len(ns))
+	for i, n := range ns {
+		result[i] = wrapNode(n)
+	}
+	return result
+}
+
+func unwrapNode(n *node) *enode.Node {
+	return &n.Node
+}
+
+func unwrapNodes(ns []*node) []*enode.Node {
+	result := make([]*enode.Node, len(ns))
+	for i, n := range ns {
+		result[i] = unwrapNode(n)
+	}
+	return result
+}
+
+func (n *node) addr() *net.UDPAddr {
+	return &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
+}
+
+func (n *node) String() string {
+	return n.Node.String()
+}

+ 119 - 0
p2p/discover/ntp.go

@@ -0,0 +1,119 @@
+// 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/>.
+
+// Contains the NTP time drift detection via the SNTP protocol:
+//   https://tools.ietf.org/html/rfc4330
+
+package discover
+
+import (
+	"fmt"
+	"net"
+	"sort"
+	"time"
+
+	"blockchain-go/log"
+)
+
+const (
+	ntpPool   = "pool.ntp.org" // ntpPool is the NTP server to query for the current time
+	ntpChecks = 3              // Number of measurements to do against the NTP server
+)
+
+// durationSlice attaches the methods of sort.Interface to []time.Duration,
+// sorting in increasing order.
+type durationSlice []time.Duration
+
+func (s durationSlice) Len() int           { return len(s) }
+func (s durationSlice) Less(i, j int) bool { return s[i] < s[j] }
+func (s durationSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+
+// checkClockDrift queries an NTP server for clock drifts and warns the user if
+// one large enough is detected.
+func checkClockDrift() {
+	drift, err := sntpDrift(ntpChecks)
+	if err != nil {
+		return
+	}
+	if drift < -driftThreshold || drift > driftThreshold {
+		log.Warn(fmt.Sprintf("System clock seems off by %v, which can prevent network connectivity", drift))
+		log.Warn("Please enable network time synchronisation in system settings.")
+	} else {
+		log.Debug("NTP sanity check done", "drift", drift)
+	}
+}
+
+// sntpDrift does a naive time resolution against an NTP server and returns the
+// measured drift. This method uses the simple version of NTP. It's not precise
+// but should be fine for these purposes.
+//
+// Note, it executes two extra measurements compared to the number of requested
+// ones to be able to discard the two extremes as outliers.
+func sntpDrift(measurements int) (time.Duration, error) {
+	// Resolve the address of the NTP server
+	addr, err := net.ResolveUDPAddr("udp", ntpPool+":123")
+	if err != nil {
+		return 0, err
+	}
+	// Construct the time request (empty package with only 2 fields set):
+	//   Bits 3-5: Protocol version, 3
+	//   Bits 6-8: Mode of operation, client, 3
+	request := make([]byte, 48)
+	request[0] = 3<<3 | 3
+
+	// Execute each of the measurements
+	drifts := []time.Duration{}
+	for i := 0; i < measurements+2; i++ {
+		// Dial the NTP server and send the time retrieval request
+		conn, err := net.DialUDP("udp", nil, addr)
+		if err != nil {
+			return 0, err
+		}
+		defer conn.Close()
+
+		sent := time.Now()
+		if _, err = conn.Write(request); err != nil {
+			return 0, err
+		}
+		// Retrieve the reply and calculate the elapsed time
+		conn.SetDeadline(time.Now().Add(5 * time.Second))
+
+		reply := make([]byte, 48)
+		if _, err = conn.Read(reply); err != nil {
+			return 0, err
+		}
+		elapsed := time.Since(sent)
+
+		// Reconstruct the time from the reply data
+		sec := uint64(reply[43]) | uint64(reply[42])<<8 | uint64(reply[41])<<16 | uint64(reply[40])<<24
+		frac := uint64(reply[47]) | uint64(reply[46])<<8 | uint64(reply[45])<<16 | uint64(reply[44])<<24
+
+		nanosec := sec*1e9 + (frac*1e9)>>32
+
+		t := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nanosec)).Local()
+
+		// Calculate the drift based on an assumed answer time of RRT/2
+		drifts = append(drifts, sent.Sub(t)+elapsed/2)
+	}
+	// Calculate average drif (drop two extremities to avoid outliers)
+	sort.Sort(durationSlice(drifts))
+
+	drift := time.Duration(0)
+	for i := 1; i < len(drifts)-1; i++ {
+		drift += drifts[i]
+	}
+	return drift / time.Duration(measurements), nil
+}

+ 699 - 0
p2p/discover/table.go

@@ -0,0 +1,699 @@
+// Copyright 2015 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 discover implements the Node Discovery Protocol.
+//
+// The Node Discovery protocol provides a way to find RLPx nodes that
+// can be connected to. It uses a Kademlia-like protocol to maintain a
+// distributed database of the IDs and endpoints of all listening
+// nodes.
+package discover
+
+import (
+	crand "crypto/rand"
+	"encoding/binary"
+	"fmt"
+	mrand "math/rand"
+	"net"
+	"sort"
+	"sync"
+	"time"
+
+	"blockchain-go/common"
+	"blockchain-go/common/gopool"
+	"blockchain-go/log"
+	"blockchain-go/p2p/enode"
+	"blockchain-go/p2p/netutil"
+)
+
+const (
+	alpha           = 3  // Kademlia concurrency factor
+	bucketSize      = 16 // Kademlia bucket size
+	maxReplacements = 10 // Size of per-bucket replacement list
+
+	// We keep buckets for the upper 1/15 of distances because
+	// it's very unlikely we'll ever encounter a node that's closer.
+	hashBits          = len(common.Hash{}) * 8
+	nBuckets          = hashBits / 15       // Number of buckets
+	bucketMinDistance = hashBits - nBuckets // Log distance of closest bucket
+
+	// IP address limits.
+	bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
+	tableIPLimit, tableSubnet   = 10, 24
+
+	refreshInterval    = 30 * time.Minute
+	revalidateInterval = 10 * time.Second
+	copyNodesInterval  = 30 * time.Second
+	seedMinTableTime   = 5 * time.Minute
+	seedCount          = 30
+	seedMaxAge         = 5 * 24 * time.Hour
+)
+
+// Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps
+// itself up-to-date by verifying the liveness of neighbors and requesting their node
+// records when announcements of a new record version are received.
+type Table struct {
+	mutex   sync.Mutex        // protects buckets, bucket content, nursery, rand
+	buckets [nBuckets]*bucket // index of known nodes by distance
+	nursery []*node           // bootstrap nodes
+	rand    *mrand.Rand       // source of randomness, periodically reseeded
+	ips     netutil.DistinctNetSet
+
+	log        log.Logger
+	db         *enode.DB // database of known nodes
+	net        transport
+	refreshReq chan chan struct{}
+	initDone   chan struct{}
+	closeReq   chan struct{}
+	closed     chan struct{}
+
+	nodeAddedHook func(*node) // for testing
+}
+
+// transport is implemented by the UDP transports.
+type transport interface {
+	Self() *enode.Node
+	RequestENR(*enode.Node) (*enode.Node, error)
+	lookupRandom() []*enode.Node
+	lookupSelf() []*enode.Node
+	ping(*enode.Node) (seq uint64, err error)
+}
+
+// bucket contains nodes, ordered by their last activity. the entry
+// that was most recently active is the first element in entries.
+type bucket struct {
+	entries      []*node // live entries, sorted by time of last contact
+	replacements []*node // recently seen nodes to be used if revalidation fails
+	ips          netutil.DistinctNetSet
+}
+
+func newTable(t transport, db *enode.DB, bootnodes []*enode.Node, log log.Logger) (*Table, error) {
+	tab := &Table{
+		net:        t,
+		db:         db,
+		refreshReq: make(chan chan struct{}),
+		initDone:   make(chan struct{}),
+		closeReq:   make(chan struct{}),
+		closed:     make(chan struct{}),
+		rand:       mrand.New(mrand.NewSource(0)),
+		ips:        netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit},
+		log:        log,
+	}
+	if err := tab.setFallbackNodes(bootnodes); err != nil {
+		return nil, err
+	}
+	for i := range tab.buckets {
+		tab.buckets[i] = &bucket{
+			ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
+		}
+	}
+	tab.seedRand()
+	tab.loadSeedNodes()
+
+	return tab, nil
+}
+
+func (tab *Table) self() *enode.Node {
+	return tab.net.Self()
+}
+
+func (tab *Table) seedRand() {
+	var b [8]byte
+	crand.Read(b[:])
+
+	tab.mutex.Lock()
+	tab.rand.Seed(int64(binary.BigEndian.Uint64(b[:])))
+	tab.mutex.Unlock()
+}
+
+// ReadRandomNodes fills the given slice with random nodes from the table. The results
+// are guaranteed to be unique for a single invocation, no node will appear twice.
+func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) {
+	if !tab.isInitDone() {
+		return 0
+	}
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	var nodes []*enode.Node
+	for _, b := range &tab.buckets {
+		for _, n := range b.entries {
+			nodes = append(nodes, unwrapNode(n))
+		}
+	}
+	// Shuffle.
+	for i := 0; i < len(nodes); i++ {
+		j := tab.rand.Intn(len(nodes))
+		nodes[i], nodes[j] = nodes[j], nodes[i]
+	}
+	return copy(buf, nodes)
+}
+
+// getNode returns the node with the given ID or nil if it isn't in the table.
+func (tab *Table) getNode(id enode.ID) *enode.Node {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	b := tab.bucket(id)
+	for _, e := range b.entries {
+		if e.ID() == id {
+			return unwrapNode(e)
+		}
+	}
+	return nil
+}
+
+// close terminates the network listener and flushes the node database.
+func (tab *Table) close() {
+	close(tab.closeReq)
+	<-tab.closed
+}
+
+// setFallbackNodes sets the initial points of contact. These nodes
+// are used to connect to the network if the table is empty and there
+// are no known nodes in the database.
+func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
+	for _, n := range nodes {
+		if err := n.ValidateComplete(); err != nil {
+			return fmt.Errorf("bad bootstrap node %q: %v", n, err)
+		}
+	}
+	tab.nursery = wrapNodes(nodes)
+	return nil
+}
+
+// isInitDone returns whether the table's initial seeding procedure has completed.
+func (tab *Table) isInitDone() bool {
+	select {
+	case <-tab.initDone:
+		return true
+	default:
+		return false
+	}
+}
+
+func (tab *Table) refresh() <-chan struct{} {
+	done := make(chan struct{})
+	select {
+	case tab.refreshReq <- done:
+	case <-tab.closeReq:
+		close(done)
+	}
+	return done
+}
+
+// loop schedules runs of doRefresh, doRevalidate and copyLiveNodes.
+func (tab *Table) loop() {
+	var (
+		revalidate     = time.NewTimer(tab.nextRevalidateTime())
+		refresh        = time.NewTicker(refreshInterval)
+		copyNodes      = time.NewTicker(copyNodesInterval)
+		refreshDone    = make(chan struct{})           // where doRefresh reports completion
+		revalidateDone chan struct{}                   // where doRevalidate reports completion
+		waiting        = []chan struct{}{tab.initDone} // holds waiting callers while doRefresh runs
+	)
+	defer refresh.Stop()
+	defer revalidate.Stop()
+	defer copyNodes.Stop()
+
+	// Start initial refresh.
+	gopool.Submit(func() {
+		tab.doRefresh(refreshDone)
+	})
+loop:
+	for {
+		select {
+		case <-refresh.C:
+			tab.seedRand()
+			if refreshDone == nil {
+				refreshDone = make(chan struct{})
+				gopool.Submit(func() {
+					tab.doRefresh(refreshDone)
+				})
+			}
+		case req := <-tab.refreshReq:
+			waiting = append(waiting, req)
+			if refreshDone == nil {
+				refreshDone = make(chan struct{})
+				gopool.Submit(
+					func() {
+						tab.doRefresh(refreshDone)
+					})
+			}
+		case <-refreshDone:
+			for _, ch := range waiting {
+				close(ch)
+			}
+			waiting, refreshDone = nil, nil
+		case <-revalidate.C:
+			revalidateDone = make(chan struct{})
+			gopool.Submit(func() {
+				tab.doRevalidate(revalidateDone)
+			})
+		case <-revalidateDone:
+			revalidate.Reset(tab.nextRevalidateTime())
+			revalidateDone = nil
+		case <-copyNodes.C:
+			gopool.Submit(func() {
+				tab.copyLiveNodes()
+			})
+
+		case <-tab.closeReq:
+			break loop
+		}
+	}
+
+	if refreshDone != nil {
+		<-refreshDone
+	}
+	for _, ch := range waiting {
+		close(ch)
+	}
+	if revalidateDone != nil {
+		<-revalidateDone
+	}
+	close(tab.closed)
+}
+
+// doRefresh performs a lookup for a random target to keep buckets full. seed nodes are
+// inserted if the table is empty (initial bootstrap or discarded faulty peers).
+func (tab *Table) doRefresh(done chan struct{}) {
+	defer close(done)
+
+	// Load nodes from the database and insert
+	// them. This should yield a few previously seen nodes that are
+	// (hopefully) still alive.
+	tab.loadSeedNodes()
+
+	// Run self lookup to discover new neighbor nodes.
+	tab.net.lookupSelf()
+
+	// The Kademlia paper specifies that the bucket refresh should
+	// perform a lookup in the least recently used bucket. We cannot
+	// adhere to this because the findnode target is a 512bit value
+	// (not hash-sized) and it is not easily possible to generate a
+	// sha3 preimage that falls into a chosen bucket.
+	// We perform a few lookups with a random target instead.
+	for i := 0; i < 3; i++ {
+		tab.net.lookupRandom()
+	}
+}
+
+func (tab *Table) loadSeedNodes() {
+	seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge))
+	seeds = append(seeds, tab.nursery...)
+	for i := range seeds {
+		seed := seeds[i]
+		age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) }}
+		tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
+		tab.addSeenNode(seed)
+	}
+}
+
+// doRevalidate checks that the last node in a random bucket is still live and replaces or
+// deletes the node if it isn't.
+func (tab *Table) doRevalidate(done chan<- struct{}) {
+	defer func() { done <- struct{}{} }()
+
+	last, bi := tab.nodeToRevalidate()
+	if last == nil {
+		// No non-empty bucket found.
+		return
+	}
+
+	// Ping the selected node and wait for a pong.
+	remoteSeq, err := tab.net.ping(unwrapNode(last))
+
+	// Also fetch record if the node replied and returned a higher sequence number.
+	if last.Seq() < remoteSeq {
+		n, err := tab.net.RequestENR(unwrapNode(last))
+		if err != nil {
+			tab.log.Debug("ENR request failed", "id", last.ID(), "addr", last.addr(), "err", err)
+		} else {
+			last = &node{Node: *n, addedAt: last.addedAt, livenessChecks: last.livenessChecks}
+		}
+	}
+
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+	b := tab.buckets[bi]
+	if err == nil {
+		// The node responded, move it to the front.
+		last.livenessChecks++
+		tab.log.Debug("Revalidated node", "b", bi, "id", last.ID(), "checks", last.livenessChecks)
+		tab.bumpInBucket(b, last)
+		return
+	}
+	// No reply received, pick a replacement or delete the node if there aren't
+	// any replacements.
+	if r := tab.replace(b, last); r != nil {
+		tab.log.Debug("Replaced dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks, "r", r.ID(), "rip", r.IP())
+	} else {
+		tab.log.Debug("Removed dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks)
+	}
+}
+
+// nodeToRevalidate returns the last node in a random, non-empty bucket.
+func (tab *Table) nodeToRevalidate() (n *node, bi int) {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	for _, bi = range tab.rand.Perm(len(tab.buckets)) {
+		b := tab.buckets[bi]
+		if len(b.entries) > 0 {
+			last := b.entries[len(b.entries)-1]
+			return last, bi
+		}
+	}
+	return nil, 0
+}
+
+func (tab *Table) nextRevalidateTime() time.Duration {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	return time.Duration(tab.rand.Int63n(int64(revalidateInterval)))
+}
+
+// copyLiveNodes adds nodes from the table to the database if they have been in the table
+// longer than seedMinTableTime.
+func (tab *Table) copyLiveNodes() {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	now := time.Now()
+	for _, b := range &tab.buckets {
+		for _, n := range b.entries {
+			if n.livenessChecks > 0 && now.Sub(n.addedAt) >= seedMinTableTime {
+				tab.db.UpdateNode(unwrapNode(n))
+			}
+		}
+	}
+}
+
+// findnodeByID returns the n nodes in the table that are closest to the given id.
+// This is used by the FINDNODE/v4 handler.
+//
+// The preferLive parameter says whether the caller wants liveness-checked results. If
+// preferLive is true and the table contains any verified nodes, the result will not
+// contain unverified nodes. However, if there are no verified nodes at all, the result
+// will contain unverified nodes.
+func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *nodesByDistance {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	// Scan all buckets. There might be a better way to do this, but there aren't that many
+	// buckets, so this solution should be fine. The worst-case complexity of this loop
+	// is O(tab.len() * nresults).
+	nodes := &nodesByDistance{target: target}
+	liveNodes := &nodesByDistance{target: target}
+	for _, b := range &tab.buckets {
+		for _, n := range b.entries {
+			nodes.push(n, nresults)
+			if preferLive && n.livenessChecks > 0 {
+				liveNodes.push(n, nresults)
+			}
+		}
+	}
+
+	if preferLive && len(liveNodes.entries) > 0 {
+		return liveNodes
+	}
+	return nodes
+}
+
+// len returns the number of nodes in the table.
+func (tab *Table) len() (n int) {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	for _, b := range &tab.buckets {
+		n += len(b.entries)
+	}
+	return n
+}
+
+// bucketLen returns the number of nodes in the bucket for the given ID.
+func (tab *Table) bucketLen(id enode.ID) int {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	return len(tab.bucket(id).entries)
+}
+
+// bucket returns the bucket for the given node ID hash.
+func (tab *Table) bucket(id enode.ID) *bucket {
+	d := enode.LogDist(tab.self().ID(), id)
+	return tab.bucketAtDistance(d)
+}
+
+func (tab *Table) bucketAtDistance(d int) *bucket {
+	if d <= bucketMinDistance {
+		return tab.buckets[0]
+	}
+	return tab.buckets[d-bucketMinDistance-1]
+}
+
+// addSeenNode adds a node which may or may not be live to the end of a bucket. If the
+// bucket has space available, adding the node succeeds immediately. Otherwise, the node is
+// added to the replacements list.
+//
+// The caller must not hold tab.mutex.
+func (tab *Table) addSeenNode(n *node) {
+	if n.ID() == tab.self().ID() {
+		return
+	}
+
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+	b := tab.bucket(n.ID())
+	if contains(b.entries, n.ID()) {
+		// Already in bucket, don't add.
+		return
+	}
+	if len(b.entries) >= bucketSize {
+		// Bucket full, maybe add as replacement.
+		tab.addReplacement(b, n)
+		return
+	}
+	if !tab.addIP(b, n.IP()) {
+		// Can't add: IP limit reached.
+		return
+	}
+	// Add to end of bucket:
+	b.entries = append(b.entries, n)
+	b.replacements = deleteNode(b.replacements, n)
+	n.addedAt = time.Now()
+	if tab.nodeAddedHook != nil {
+		tab.nodeAddedHook(n)
+	}
+}
+
+// addVerifiedNode adds a node whose existence has been verified recently to the front of a
+// bucket. If the node is already in the bucket, it is moved to the front. If the bucket
+// has no space, the node is added to the replacements list.
+//
+// There is an additional safety measure: if the table is still initializing the node
+// is not added. This prevents an attack where the table could be filled by just sending
+// ping repeatedly.
+//
+// The caller must not hold tab.mutex.
+func (tab *Table) addVerifiedNode(n *node) {
+	if !tab.isInitDone() {
+		return
+	}
+	if n.ID() == tab.self().ID() {
+		return
+	}
+
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+	b := tab.bucket(n.ID())
+	if tab.bumpInBucket(b, n) {
+		// Already in bucket, moved to front.
+		return
+	}
+	if len(b.entries) >= bucketSize {
+		// Bucket full, maybe add as replacement.
+		tab.addReplacement(b, n)
+		return
+	}
+	if !tab.addIP(b, n.IP()) {
+		// Can't add: IP limit reached.
+		return
+	}
+	// Add to front of bucket.
+	b.entries, _ = pushNode(b.entries, n, bucketSize)
+	b.replacements = deleteNode(b.replacements, n)
+	n.addedAt = time.Now()
+	if tab.nodeAddedHook != nil {
+		tab.nodeAddedHook(n)
+	}
+}
+
+// delete removes an entry from the node table. It is used to evacuate dead nodes.
+func (tab *Table) delete(node *node) {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	tab.deleteInBucket(tab.bucket(node.ID()), node)
+}
+
+func (tab *Table) addIP(b *bucket, ip net.IP) bool {
+	if len(ip) == 0 {
+		return false // Nodes without IP cannot be added.
+	}
+	if netutil.IsLAN(ip) {
+		return true
+	}
+	if !tab.ips.Add(ip) {
+		tab.log.Debug("IP exceeds table limit", "ip", ip)
+		return false
+	}
+	if !b.ips.Add(ip) {
+		tab.log.Debug("IP exceeds bucket limit", "ip", ip)
+		tab.ips.Remove(ip)
+		return false
+	}
+	return true
+}
+
+func (tab *Table) removeIP(b *bucket, ip net.IP) {
+	if netutil.IsLAN(ip) {
+		return
+	}
+	tab.ips.Remove(ip)
+	b.ips.Remove(ip)
+}
+
+func (tab *Table) addReplacement(b *bucket, n *node) {
+	for _, e := range b.replacements {
+		if e.ID() == n.ID() {
+			return // already in list
+		}
+	}
+	if !tab.addIP(b, n.IP()) {
+		return
+	}
+	var removed *node
+	b.replacements, removed = pushNode(b.replacements, n, maxReplacements)
+	if removed != nil {
+		tab.removeIP(b, removed.IP())
+	}
+}
+
+// replace removes n from the replacement list and replaces 'last' with it if it is the
+// last entry in the bucket. If 'last' isn't the last entry, it has either been replaced
+// with someone else or became active.
+func (tab *Table) replace(b *bucket, last *node) *node {
+	if len(b.entries) == 0 || b.entries[len(b.entries)-1].ID() != last.ID() {
+		// Entry has moved, don't replace it.
+		return nil
+	}
+	// Still the last entry.
+	if len(b.replacements) == 0 {
+		tab.deleteInBucket(b, last)
+		return nil
+	}
+	r := b.replacements[tab.rand.Intn(len(b.replacements))]
+	b.replacements = deleteNode(b.replacements, r)
+	b.entries[len(b.entries)-1] = r
+	tab.removeIP(b, last.IP())
+	return r
+}
+
+// bumpInBucket moves the given node to the front of the bucket entry list
+// if it is contained in that list.
+func (tab *Table) bumpInBucket(b *bucket, n *node) bool {
+	for i := range b.entries {
+		if b.entries[i].ID() == n.ID() {
+			if !n.IP().Equal(b.entries[i].IP()) {
+				// Endpoint has changed, ensure that the new IP fits into table limits.
+				tab.removeIP(b, b.entries[i].IP())
+				if !tab.addIP(b, n.IP()) {
+					// It doesn't, put the previous one back.
+					tab.addIP(b, b.entries[i].IP())
+					return false
+				}
+			}
+			// Move it to the front.
+			copy(b.entries[1:], b.entries[:i])
+			b.entries[0] = n
+			return true
+		}
+	}
+	return false
+}
+
+func (tab *Table) deleteInBucket(b *bucket, n *node) {
+	b.entries = deleteNode(b.entries, n)
+	tab.removeIP(b, n.IP())
+}
+
+func contains(ns []*node, id enode.ID) bool {
+	for _, n := range ns {
+		if n.ID() == id {
+			return true
+		}
+	}
+	return false
+}
+
+// pushNode adds n to the front of list, keeping at most max items.
+func pushNode(list []*node, n *node, max int) ([]*node, *node) {
+	if len(list) < max {
+		list = append(list, nil)
+	}
+	removed := list[len(list)-1]
+	copy(list[1:], list)
+	list[0] = n
+	return list, removed
+}
+
+// deleteNode removes n from list.
+func deleteNode(list []*node, n *node) []*node {
+	for i := range list {
+		if list[i].ID() == n.ID() {
+			return append(list[:i], list[i+1:]...)
+		}
+	}
+	return list
+}
+
+// nodesByDistance is a list of nodes, ordered by distance to target.
+type nodesByDistance struct {
+	entries []*node
+	target  enode.ID
+}
+
+// push adds the given node to the list, keeping the total size below maxElems.
+func (h *nodesByDistance) push(n *node, maxElems int) {
+	ix := sort.Search(len(h.entries), func(i int) bool {
+		return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
+	})
+	if len(h.entries) < maxElems {
+		h.entries = append(h.entries, n)
+	}
+	if ix == len(h.entries) {
+		// farther away than all nodes we already have.
+		// if there was room for it, the node is now the last element.
+	} else {
+		// slide existing entries down to make room
+		// this will overwrite the entry we just appended.
+		copy(h.entries[ix+1:], h.entries[ix:])
+		h.entries[ix] = n
+	}
+}

+ 793 - 0
p2p/discover/v4_udp.go

@@ -0,0 +1,793 @@
+// 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 discover
+
+import (
+	"bytes"
+	"container/list"
+	"context"
+	"crypto/ecdsa"
+	crand "crypto/rand"
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"time"
+
+	"blockchain-go/common/gopool"
+	"blockchain-go/log"
+	"blockchain-go/p2p/discover/v4wire"
+	"blockchain-go/p2p/enode"
+	"blockchain-go/p2p/netutil"
+	"blockchain-go/rlp"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+// Errors
+var (
+	errExpired          = errors.New("expired")
+	errUnsolicitedReply = errors.New("unsolicited reply")
+	errUnknownNode      = errors.New("unknown node")
+	errTimeout          = errors.New("RPC timeout")
+	errClockWarp        = errors.New("reply deadline too far in the future")
+	errClosed           = errors.New("socket closed")
+	errLowPort          = errors.New("low port")
+)
+
+const (
+	respTimeout    = 500 * time.Millisecond
+	expiration     = 20 * time.Second
+	bondExpiration = 24 * time.Hour
+
+	maxFindnodeFailures = 5                // nodes exceeding this limit are dropped
+	ntpFailureThreshold = 32               // Continuous timeouts after which to check NTP
+	ntpWarningCooldown  = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning
+	driftThreshold      = 10 * time.Second // Allowed clock drift before warning user
+
+	// Discovery packets are defined to be no larger than 1280 bytes.
+	// Packets larger than this size will be cut at the end and treated
+	// as invalid because their hash won't match.
+	maxPacketSize = 1280
+)
+
+// UDPv4 implements the v4 wire protocol.
+type UDPv4 struct {
+	conn        UDPConn
+	log         log.Logger
+	netrestrict *netutil.Netlist
+	priv        *ecdsa.PrivateKey
+	localNode   *enode.LocalNode
+	db          *enode.DB
+	tab         *Table
+	closeOnce   sync.Once
+	wg          sync.WaitGroup
+
+	addReplyMatcher chan *replyMatcher
+	gotreply        chan reply
+	closeCtx        context.Context
+	cancelCloseCtx  context.CancelFunc
+}
+
+// replyMatcher represents a pending reply.
+//
+// Some implementations of the protocol wish to send more than one
+// reply packet to findnode. In general, any neighbors packet cannot
+// be matched up with a specific findnode packet.
+//
+// Our implementation handles this by storing a callback function for
+// each pending reply. Incoming packets from a node are dispatched
+// to all callback functions for that node.
+type replyMatcher struct {
+	// these fields must match in the reply.
+	from  enode.ID
+	ip    net.IP
+	ptype byte
+
+	// time when the request must complete
+	deadline time.Time
+
+	// callback is called when a matching reply arrives. If it returns matched == true, the
+	// reply was acceptable. The second return value indicates whether the callback should
+	// be removed from the pending reply queue. If it returns false, the reply is considered
+	// incomplete and the callback will be invoked again for the next matching reply.
+	callback replyMatchFunc
+
+	// errc receives nil when the callback indicates completion or an
+	// error if no further reply is received within the timeout.
+	errc chan error
+
+	// reply contains the most recent reply. This field is safe for reading after errc has
+	// received a value.
+	reply v4wire.Packet
+}
+
+type replyMatchFunc func(v4wire.Packet) (matched bool, requestDone bool)
+
+// reply is a reply packet from a certain node.
+type reply struct {
+	from enode.ID
+	ip   net.IP
+	data v4wire.Packet
+	// loop indicates whether there was
+	// a matching request by sending on this channel.
+	matched chan<- bool
+}
+
+func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
+	cfg = cfg.withDefaults()
+	closeCtx, cancel := context.WithCancel(context.Background())
+	t := &UDPv4{
+		conn:            c,
+		priv:            cfg.PrivateKey,
+		netrestrict:     cfg.NetRestrict,
+		localNode:       ln,
+		db:              ln.Database(),
+		gotreply:        make(chan reply),
+		addReplyMatcher: make(chan *replyMatcher),
+		closeCtx:        closeCtx,
+		cancelCloseCtx:  cancel,
+		log:             cfg.Log,
+	}
+
+	tab, err := newTable(t, ln.Database(), cfg.Bootnodes, t.log)
+	if err != nil {
+		return nil, err
+	}
+	t.tab = tab
+	go tab.loop()
+
+	t.wg.Add(2)
+	go t.loop()
+	go t.readLoop(cfg.Unhandled)
+	return t, nil
+}
+
+// Self returns the local node.
+func (t *UDPv4) Self() *enode.Node {
+	return t.localNode.Node()
+}
+
+// Close shuts down the socket and aborts any running queries.
+func (t *UDPv4) Close() {
+	t.closeOnce.Do(func() {
+		t.cancelCloseCtx()
+		t.conn.Close()
+		t.wg.Wait()
+		t.tab.close()
+	})
+}
+
+// Resolve searches for a specific node with the given ID and tries to get the most recent
+// version of the node record for it. It returns n if the node could not be resolved.
+func (t *UDPv4) Resolve(n *enode.Node) *enode.Node {
+	// Try asking directly. This works if the node is still responding on the endpoint we have.
+	if rn, err := t.RequestENR(n); err == nil {
+		return rn
+	}
+	// Check table for the ID, we might have a newer version there.
+	if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
+		n = intable
+		if rn, err := t.RequestENR(n); err == nil {
+			return rn
+		}
+	}
+	// Otherwise perform a network lookup.
+	var key enode.Secp256k1
+	if n.Load(&key) != nil {
+		return n // no secp256k1 key
+	}
+	result := t.LookupPubkey((*ecdsa.PublicKey)(&key))
+	for _, rn := range result {
+		if rn.ID() == n.ID() {
+			if rn, err := t.RequestENR(rn); err == nil {
+				return rn
+			}
+		}
+	}
+	return n
+}
+
+func (t *UDPv4) ourEndpoint() v4wire.Endpoint {
+	n := t.Self()
+	a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
+	return v4wire.NewEndpoint(a, uint16(n.TCP()))
+}
+
+// Ping sends a ping message to the given node.
+func (t *UDPv4) Ping(n *enode.Node) error {
+	_, err := t.ping(n)
+	return err
+}
+
+// ping sends a ping message to the given node and waits for a reply.
+func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
+	rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil)
+	if err = <-rm.errc; err == nil {
+		seq = rm.reply.(*v4wire.Pong).ENRSeq()
+	}
+	return seq, err
+}
+
+// sendPing sends a ping message to the given node and invokes the callback
+// when the reply arrives.
+func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher {
+	req := t.makePing(toaddr)
+	packet, hash, err := v4wire.Encode(t.priv, req)
+	if err != nil {
+		errc := make(chan error, 1)
+		errc <- err
+		return &replyMatcher{errc: errc}
+	}
+	// Add a matcher for the reply to the pending reply queue. Pongs are matched if they
+	// reference the ping we're about to send.
+	rm := t.pending(toid, toaddr.IP, v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
+		matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash)
+		if matched && callback != nil {
+			callback()
+		}
+		return matched, matched
+	})
+	// Send the packet.
+	t.localNode.UDPContact(toaddr)
+	t.write(toaddr, toid, req.Name(), packet)
+	return rm
+}
+
+func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping {
+	seq, _ := rlp.EncodeToBytes(t.localNode.Node().Seq())
+	return &v4wire.Ping{
+		Version:    4,
+		From:       t.ourEndpoint(),
+		To:         v4wire.NewEndpoint(toaddr, 0),
+		Expiration: uint64(time.Now().Add(expiration).Unix()),
+		Rest:       []rlp.RawValue{seq},
+	}
+}
+
+// LookupPubkey finds the closest nodes to the given public key.
+func (t *UDPv4) LookupPubkey(key *ecdsa.PublicKey) []*enode.Node {
+	if t.tab.len() == 0 {
+		// All nodes were dropped, refresh. The very first query will hit this
+		// case and run the bootstrapping logic.
+		<-t.tab.refresh()
+	}
+	return t.newLookup(t.closeCtx, encodePubkey(key)).run()
+}
+
+// RandomNodes is an iterator yielding nodes from a random walk of the DHT.
+func (t *UDPv4) RandomNodes() enode.Iterator {
+	return newLookupIterator(t.closeCtx, t.newRandomLookup)
+}
+
+// lookupRandom implements transport.
+func (t *UDPv4) lookupRandom() []*enode.Node {
+	return t.newRandomLookup(t.closeCtx).run()
+}
+
+// lookupSelf implements transport.
+func (t *UDPv4) lookupSelf() []*enode.Node {
+	return t.newLookup(t.closeCtx, encodePubkey(&t.priv.PublicKey)).run()
+}
+
+func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup {
+	var target encPubkey
+	crand.Read(target[:])
+	return t.newLookup(ctx, target)
+}
+
+func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup {
+	target := enode.ID(crypto.Keccak256Hash(targetKey[:]))
+	ekey := v4wire.Pubkey(targetKey)
+	it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
+		return t.findnode(n.ID(), n.addr(), ekey)
+	})
+	return it
+}
+
+// findnode sends a findnode request to the given node and waits until
+// the node has sent up to k neighbors.
+func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubkey) ([]*node, error) {
+	t.ensureBond(toid, toaddr)
+
+	// Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is
+	// active until enough nodes have been received.
+	nodes := make([]*node, 0, bucketSize)
+	nreceived := 0
+	rm := t.pending(toid, toaddr.IP, v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
+		reply := r.(*v4wire.Neighbors)
+		for _, rn := range reply.Nodes {
+			nreceived++
+			n, err := t.nodeFromRPC(toaddr, rn)
+			if err != nil {
+				t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err)
+				continue
+			}
+			nodes = append(nodes, n)
+		}
+		return true, nreceived >= bucketSize
+	})
+	t.send(toaddr, toid, &v4wire.Findnode{
+		Target:     target,
+		Expiration: uint64(time.Now().Add(expiration).Unix()),
+	})
+	// Ensure that callers don't see a timeout if the node actually responded. Since
+	// findnode can receive more than one neighbors response, the reply matcher will be
+	// active until the remote node sends enough nodes. If the remote end doesn't have
+	// enough nodes the reply matcher will time out waiting for the second reply, but
+	// there's no need for an error in that case.
+	err := <-rm.errc
+	if err == errTimeout && rm.reply != nil {
+		err = nil
+	}
+	return nodes, err
+}
+
+// RequestENR sends enrRequest to the given node and waits for a response.
+func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
+	addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
+	t.ensureBond(n.ID(), addr)
+
+	req := &v4wire.ENRRequest{
+		Expiration: uint64(time.Now().Add(expiration).Unix()),
+	}
+	packet, hash, err := v4wire.Encode(t.priv, req)
+	if err != nil {
+		return nil, err
+	}
+
+	// Add a matcher for the reply to the pending reply queue. Responses are matched if
+	// they reference the request we're about to send.
+	rm := t.pending(n.ID(), addr.IP, v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
+		matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash)
+		return matched, matched
+	})
+	// Send the packet and wait for the reply.
+	t.write(addr, n.ID(), req.Name(), packet)
+	if err := <-rm.errc; err != nil {
+		return nil, err
+	}
+	// Verify the response record.
+	respN, err := enode.New(enode.ValidSchemes, &rm.reply.(*v4wire.ENRResponse).Record)
+	if err != nil {
+		return nil, err
+	}
+	if respN.ID() != n.ID() {
+		return nil, fmt.Errorf("invalid ID in response record")
+	}
+	if respN.Seq() < n.Seq() {
+		return n, nil // response record is older
+	}
+	if err := netutil.CheckRelayIP(addr.IP, respN.IP()); err != nil {
+		return nil, fmt.Errorf("invalid IP in response record: %v", err)
+	}
+	return respN, nil
+}
+
+// pending adds a reply matcher to the pending reply queue.
+// see the documentation of type replyMatcher for a detailed explanation.
+func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) *replyMatcher {
+	ch := make(chan error, 1)
+	p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch}
+	select {
+	case t.addReplyMatcher <- p:
+		// loop will handle it
+	case <-t.closeCtx.Done():
+		ch <- errClosed
+	}
+	return p
+}
+
+// handleReply dispatches a reply packet, invoking reply matchers. It returns
+// whether any matcher considered the packet acceptable.
+func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req v4wire.Packet) bool {
+	matched := make(chan bool, 1)
+	select {
+	case t.gotreply <- reply{from, fromIP, req, matched}:
+		// loop will handle it
+		return <-matched
+	case <-t.closeCtx.Done():
+		return false
+	}
+}
+
+// loop runs in its own goroutine. it keeps track of
+// the refresh timer and the pending reply queue.
+func (t *UDPv4) loop() {
+	defer t.wg.Done()
+
+	var (
+		plist        = list.New()
+		timeout      = time.NewTimer(0)
+		nextTimeout  *replyMatcher // head of plist when timeout was last reset
+		contTimeouts = 0           // number of continuous timeouts to do NTP checks
+		ntpWarnTime  = time.Unix(0, 0)
+	)
+	<-timeout.C // ignore first timeout
+	defer timeout.Stop()
+
+	resetTimeout := func() {
+		if plist.Front() == nil || nextTimeout == plist.Front().Value {
+			return
+		}
+		// Start the timer so it fires when the next pending reply has expired.
+		now := time.Now()
+		for el := plist.Front(); el != nil; el = el.Next() {
+			nextTimeout = el.Value.(*replyMatcher)
+			if dist := nextTimeout.deadline.Sub(now); dist < 2*respTimeout {
+				timeout.Reset(dist)
+				return
+			}
+			// Remove pending replies whose deadline is too far in the
+			// future. These can occur if the system clock jumped
+			// backwards after the deadline was assigned.
+			nextTimeout.errc <- errClockWarp
+			plist.Remove(el)
+		}
+		nextTimeout = nil
+		timeout.Stop()
+	}
+
+	for {
+		resetTimeout()
+
+		select {
+		case <-t.closeCtx.Done():
+			for el := plist.Front(); el != nil; el = el.Next() {
+				el.Value.(*replyMatcher).errc <- errClosed
+			}
+			return
+
+		case p := <-t.addReplyMatcher:
+			p.deadline = time.Now().Add(respTimeout)
+			plist.PushBack(p)
+
+		case r := <-t.gotreply:
+			var matched bool // whether any replyMatcher considered the reply acceptable.
+			for el := plist.Front(); el != nil; el = el.Next() {
+				p := el.Value.(*replyMatcher)
+				if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) {
+					ok, requestDone := p.callback(r.data)
+					matched = matched || ok
+					p.reply = r.data
+					// Remove the matcher if callback indicates that all replies have been received.
+					if requestDone {
+						p.errc <- nil
+						plist.Remove(el)
+					}
+					// Reset the continuous timeout counter (time drift detection)
+					contTimeouts = 0
+				}
+			}
+			r.matched <- matched
+
+		case now := <-timeout.C:
+			nextTimeout = nil
+
+			// Notify and remove callbacks whose deadline is in the past.
+			for el := plist.Front(); el != nil; el = el.Next() {
+				p := el.Value.(*replyMatcher)
+				if now.After(p.deadline) || now.Equal(p.deadline) {
+					p.errc <- errTimeout
+					plist.Remove(el)
+					contTimeouts++
+				}
+			}
+			// If we've accumulated too many timeouts, do an NTP time sync check
+			if contTimeouts > ntpFailureThreshold {
+				if time.Since(ntpWarnTime) >= ntpWarningCooldown {
+					ntpWarnTime = time.Now()
+					gopool.Submit(func() {
+						checkClockDrift()
+					})
+				}
+				contTimeouts = 0
+			}
+		}
+	}
+}
+
+func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]byte, error) {
+	packet, hash, err := v4wire.Encode(t.priv, req)
+	if err != nil {
+		return hash, err
+	}
+	return hash, t.write(toaddr, toid, req.Name(), packet)
+}
+
+func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error {
+	_, err := t.conn.WriteToUDP(packet, toaddr)
+	t.log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err)
+	return err
+}
+
+// readLoop runs in its own goroutine. it handles incoming UDP packets.
+func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
+	defer t.wg.Done()
+	if unhandled != nil {
+		defer close(unhandled)
+	}
+
+	buf := make([]byte, maxPacketSize)
+	for {
+		nbytes, from, err := t.conn.ReadFromUDP(buf)
+		if netutil.IsTemporaryError(err) {
+			// Ignore temporary read errors.
+			t.log.Debug("Temporary UDP read error", "err", err)
+			continue
+		} else if err != nil {
+			// Shut down the loop for permament errors.
+			if err != io.EOF {
+				t.log.Debug("UDP read error", "err", err)
+			}
+			return
+		}
+		if t.handlePacket(from, buf[:nbytes]) != nil && unhandled != nil {
+			select {
+			case unhandled <- ReadPacket{buf[:nbytes], from}:
+			default:
+			}
+		}
+	}
+}
+
+func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
+	rawpacket, fromKey, hash, err := v4wire.Decode(buf)
+	if err != nil {
+		t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
+		return err
+	}
+	packet := t.wrapPacket(rawpacket)
+	fromID := fromKey.ID()
+	if err == nil && packet.preverify != nil {
+		err = packet.preverify(packet, from, fromID, fromKey)
+	}
+	t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", from, "err", err)
+	if err == nil && packet.handle != nil {
+		packet.handle(packet, from, fromID, hash)
+	}
+	return err
+}
+
+// checkBond checks if the given node has a recent enough endpoint proof.
+func (t *UDPv4) checkBond(id enode.ID, ip net.IP) bool {
+	return time.Since(t.db.LastPongReceived(id, ip)) < bondExpiration
+}
+
+// ensureBond solicits a ping from a node if we haven't seen a ping from it for a while.
+// This ensures there is a valid endpoint proof on the remote end.
+func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) {
+	tooOld := time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration
+	if tooOld || t.db.FindFails(toid, toaddr.IP) > maxFindnodeFailures {
+		rm := t.sendPing(toid, toaddr, nil)
+		<-rm.errc
+		// Wait for them to ping back and process our pong.
+		time.Sleep(respTimeout)
+	}
+}
+
+func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) {
+	if rn.UDP <= 1024 {
+		return nil, errLowPort
+	}
+	if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
+		return nil, err
+	}
+	if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
+		return nil, errors.New("not contained in netrestrict whitelist")
+	}
+	key, err := v4wire.DecodePubkey(crypto.S256(), rn.ID)
+	if err != nil {
+		return nil, err
+	}
+	n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP)))
+	err = n.ValidateComplete()
+	return n, err
+}
+
+func nodeToRPC(n *node) v4wire.Node {
+	var key ecdsa.PublicKey
+	var ekey v4wire.Pubkey
+	if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
+		ekey = v4wire.EncodePubkey(&key)
+	}
+	return v4wire.Node{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())}
+}
+
+// wrapPacket returns the handler functions applicable to a packet.
+func (t *UDPv4) wrapPacket(p v4wire.Packet) *packetHandlerV4 {
+	var h packetHandlerV4
+	h.Packet = p
+	switch p.(type) {
+	case *v4wire.Ping:
+		h.preverify = t.verifyPing
+		h.handle = t.handlePing
+	case *v4wire.Pong:
+		h.preverify = t.verifyPong
+	case *v4wire.Findnode:
+		h.preverify = t.verifyFindnode
+		h.handle = t.handleFindnode
+	case *v4wire.Neighbors:
+		h.preverify = t.verifyNeighbors
+	case *v4wire.ENRRequest:
+		h.preverify = t.verifyENRRequest
+		h.handle = t.handleENRRequest
+	case *v4wire.ENRResponse:
+		h.preverify = t.verifyENRResponse
+	}
+	return &h
+}
+
+// packetHandlerV4 wraps a packet with handler functions.
+type packetHandlerV4 struct {
+	v4wire.Packet
+	senderKey *ecdsa.PublicKey // used for ping
+
+	// preverify checks whether the packet is valid and should be handled at all.
+	preverify func(p *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error
+	// handle handles the packet.
+	handle func(req *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte)
+}
+
+// PING/v4
+
+func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.Ping)
+
+	senderKey, err := v4wire.DecodePubkey(crypto.S256(), fromKey)
+	if err != nil {
+		return err
+	}
+	if v4wire.Expired(req.Expiration) {
+		return errExpired
+	}
+	h.senderKey = senderKey
+	return nil
+}
+
+func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+	req := h.Packet.(*v4wire.Ping)
+
+	// Reply.
+	seq, _ := rlp.EncodeToBytes(t.localNode.Node().Seq())
+	t.send(from, fromID, &v4wire.Pong{
+		To:         v4wire.NewEndpoint(from, req.From.TCP),
+		ReplyTok:   mac,
+		Expiration: uint64(time.Now().Add(expiration).Unix()),
+		Rest:       []rlp.RawValue{seq},
+	})
+
+	// Ping back if our last pong on file is too far in the past.
+	n := wrapNode(enode.NewV4(h.senderKey, from.IP, int(req.From.TCP), from.Port))
+	if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration {
+		t.sendPing(fromID, from, func() {
+			t.tab.addVerifiedNode(n)
+		})
+	} else {
+		t.tab.addVerifiedNode(n)
+	}
+
+	// Update node database and endpoint predictor.
+	t.db.UpdateLastPingReceived(n.ID(), from.IP, time.Now())
+	t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
+}
+
+// PONG/v4
+
+func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.Pong)
+
+	if v4wire.Expired(req.Expiration) {
+		return errExpired
+	}
+	if !t.handleReply(fromID, from.IP, req) {
+		return errUnsolicitedReply
+	}
+	t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
+	t.db.UpdateLastPongReceived(fromID, from.IP, time.Now())
+	return nil
+}
+
+// FINDNODE/v4
+
+func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.Findnode)
+
+	if v4wire.Expired(req.Expiration) {
+		return errExpired
+	}
+	if !t.checkBond(fromID, from.IP) {
+		// No endpoint proof pong exists, we don't process the packet. This prevents an
+		// attack vector where the discovery protocol could be used to amplify traffic in a
+		// DDOS attack. A malicious actor would send a findnode request with the IP address
+		// and UDP port of the target as the source address. The recipient of the findnode
+		// packet would then send a neighbors packet (which is a much bigger packet than
+		// findnode) to the victim.
+		return errUnknownNode
+	}
+	return nil
+}
+
+func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+	req := h.Packet.(*v4wire.Findnode)
+
+	// Determine closest nodes.
+	target := enode.ID(crypto.Keccak256Hash(req.Target[:]))
+	closest := t.tab.findnodeByID(target, bucketSize, true).entries
+
+	// Send neighbors in chunks with at most maxNeighbors per packet
+	// to stay below the packet size limit.
+	p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
+	var sent bool
+	for _, n := range closest {
+		if netutil.CheckRelayIP(from.IP, n.IP()) == nil {
+			p.Nodes = append(p.Nodes, nodeToRPC(n))
+		}
+		if len(p.Nodes) == v4wire.MaxNeighbors {
+			t.send(from, fromID, &p)
+			p.Nodes = p.Nodes[:0]
+			sent = true
+		}
+	}
+	if len(p.Nodes) > 0 || !sent {
+		t.send(from, fromID, &p)
+	}
+}
+
+// NEIGHBORS/v4
+
+func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.Neighbors)
+
+	if v4wire.Expired(req.Expiration) {
+		return errExpired
+	}
+	if !t.handleReply(fromID, from.IP, h.Packet) {
+		return errUnsolicitedReply
+	}
+	return nil
+}
+
+// ENRREQUEST/v4
+
+func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.ENRRequest)
+
+	if v4wire.Expired(req.Expiration) {
+		return errExpired
+	}
+	if !t.checkBond(fromID, from.IP) {
+		return errUnknownNode
+	}
+	return nil
+}
+
+func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+	t.send(from, fromID, &v4wire.ENRResponse{
+		ReplyTok: mac,
+		Record:   *t.localNode.Node().Record(),
+	})
+}
+
+// ENRRESPONSE/v4
+
+func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	if !t.handleReply(fromID, from.IP, h.Packet) {
+		return errUnsolicitedReply
+	}
+	return nil
+}

+ 300 - 0
p2p/discover/v4wire/v4wire.go

@@ -0,0 +1,300 @@
+// 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 v4wire implements the Discovery v4 Wire Protocol.
+package v4wire
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"errors"
+	"fmt"
+	"math/big"
+	"net"
+	"time"
+
+	"blockchain-go/common/math"
+	"blockchain-go/p2p/enode"
+	"blockchain-go/p2p/enr"
+	"blockchain-go/rlp"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+// RPC packet types
+const (
+	PingPacket = iota + 1 // zero is 'reserved'
+	PongPacket
+	FindnodePacket
+	NeighborsPacket
+	ENRRequestPacket
+	ENRResponsePacket
+)
+
+// RPC request structures
+type (
+	Ping struct {
+		Version    uint
+		From, To   Endpoint
+		Expiration uint64
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// Pong is the reply to ping.
+	Pong struct {
+		// This field should mirror the UDP envelope address
+		// of the ping packet, which provides a way to discover the
+		// the external address (after NAT).
+		To         Endpoint
+		ReplyTok   []byte // This contains the hash of the ping packet.
+		Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// Findnode is a query for nodes close to the given target.
+	Findnode struct {
+		Target     Pubkey
+		Expiration uint64
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// Neighbors is the reply to findnode.
+	Neighbors struct {
+		Nodes      []Node
+		Expiration uint64
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// enrRequest queries for the remote node's record.
+	ENRRequest struct {
+		Expiration uint64
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// enrResponse is the reply to enrRequest.
+	ENRResponse struct {
+		ReplyTok []byte // Hash of the enrRequest packet.
+		Record   enr.Record
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+)
+
+// This number is the maximum number of neighbor nodes in a Neigbors packet.
+const MaxNeighbors = 12
+
+// This code computes the MaxNeighbors constant value.
+
+// func init() {
+// 	var maxNeighbors int
+// 	p := Neighbors{Expiration: ^uint64(0)}
+// 	maxSizeNode := Node{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
+// 	for n := 0; ; n++ {
+// 		p.Nodes = append(p.Nodes, maxSizeNode)
+// 		size, _, err := rlp.EncodeToReader(p)
+// 		if err != nil {
+// 			// If this ever happens, it will be caught by the unit tests.
+// 			panic("cannot encode: " + err.Error())
+// 		}
+// 		if headSize+size+1 >= 1280 {
+// 			maxNeighbors = n
+// 			break
+// 		}
+// 	}
+// 	fmt.Println("maxNeighbors", maxNeighbors)
+// }
+
+// Pubkey represents an encoded 64-byte secp256k1 public key.
+type Pubkey [64]byte
+
+// ID returns the node ID corresponding to the public key.
+func (e Pubkey) ID() enode.ID {
+	return enode.ID(crypto.Keccak256Hash(e[:]))
+}
+
+// Node represents information about a node.
+type Node struct {
+	IP  net.IP // len 4 for IPv4 or 16 for IPv6
+	UDP uint16 // for discovery protocol
+	TCP uint16 // for RLPx protocol
+	ID  Pubkey
+}
+
+// Endpoint represents a network endpoint.
+type Endpoint struct {
+	IP  net.IP // len 4 for IPv4 or 16 for IPv6
+	UDP uint16 // for discovery protocol
+	TCP uint16 // for RLPx protocol
+}
+
+// NewEndpoint creates an endpoint.
+func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint {
+	ip := net.IP{}
+	if ip4 := addr.IP.To4(); ip4 != nil {
+		ip = ip4
+	} else if ip6 := addr.IP.To16(); ip6 != nil {
+		ip = ip6
+	}
+	return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
+}
+
+type Packet interface {
+	// packet name and type for logging purposes.
+	Name() string
+	Kind() byte
+}
+
+func (req *Ping) Name() string   { return "PING/v4" }
+func (req *Ping) Kind() byte     { return PingPacket }
+func (req *Ping) ENRSeq() uint64 { return seqFromTail(req.Rest) }
+
+func (req *Pong) Name() string   { return "PONG/v4" }
+func (req *Pong) Kind() byte     { return PongPacket }
+func (req *Pong) ENRSeq() uint64 { return seqFromTail(req.Rest) }
+
+func (req *Findnode) Name() string { return "FINDNODE/v4" }
+func (req *Findnode) Kind() byte   { return FindnodePacket }
+
+func (req *Neighbors) Name() string { return "NEIGHBORS/v4" }
+func (req *Neighbors) Kind() byte   { return NeighborsPacket }
+
+func (req *ENRRequest) Name() string { return "ENRREQUEST/v4" }
+func (req *ENRRequest) Kind() byte   { return ENRRequestPacket }
+
+func (req *ENRResponse) Name() string { return "ENRRESPONSE/v4" }
+func (req *ENRResponse) Kind() byte   { return ENRResponsePacket }
+
+// Expired checks whether the given UNIX time stamp is in the past.
+func Expired(ts uint64) bool {
+	return time.Unix(int64(ts), 0).Before(time.Now())
+}
+
+func seqFromTail(tail []rlp.RawValue) uint64 {
+	if len(tail) == 0 {
+		return 0
+	}
+	var seq uint64
+	rlp.DecodeBytes(tail[0], &seq)
+	return seq
+}
+
+// Encoder/decoder.
+
+const (
+	macSize  = 32
+	sigSize  = crypto.SignatureLength
+	headSize = macSize + sigSize // space of packet frame data
+)
+
+var (
+	ErrPacketTooSmall = errors.New("too small")
+	ErrBadHash        = errors.New("bad hash")
+	ErrBadPoint       = errors.New("invalid curve point")
+)
+
+var headSpace = make([]byte, headSize)
+
+// Decode reads a discovery v4 packet.
+func Decode(input []byte) (Packet, Pubkey, []byte, error) {
+	if len(input) < headSize+1 {
+		return nil, Pubkey{}, nil, ErrPacketTooSmall
+	}
+	hash, sig, sigdata := input[:macSize], input[macSize:headSize], input[headSize:]
+	shouldhash := crypto.Keccak256(input[macSize:])
+	if !bytes.Equal(hash, shouldhash) {
+		return nil, Pubkey{}, nil, ErrBadHash
+	}
+	fromKey, err := recoverNodeKey(crypto.Keccak256(input[headSize:]), sig)
+	if err != nil {
+		return nil, fromKey, hash, err
+	}
+
+	var req Packet
+	switch ptype := sigdata[0]; ptype {
+	case PingPacket:
+		req = new(Ping)
+	case PongPacket:
+		req = new(Pong)
+	case FindnodePacket:
+		req = new(Findnode)
+	case NeighborsPacket:
+		req = new(Neighbors)
+	case ENRRequestPacket:
+		req = new(ENRRequest)
+	case ENRResponsePacket:
+		req = new(ENRResponse)
+	default:
+		return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype)
+	}
+	s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
+	err = s.Decode(req)
+	return req, fromKey, hash, err
+}
+
+// Encode encodes a discovery packet.
+func Encode(priv *ecdsa.PrivateKey, req Packet) (packet, hash []byte, err error) {
+	b := new(bytes.Buffer)
+	b.Write(headSpace)
+	b.WriteByte(req.Kind())
+	if err := rlp.Encode(b, req); err != nil {
+		return nil, nil, err
+	}
+	packet = b.Bytes()
+	sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv)
+	if err != nil {
+		return nil, nil, err
+	}
+	copy(packet[macSize:], sig)
+	// Add the hash to the front. Note: this doesn't protect the packet in any way.
+	hash = crypto.Keccak256(packet[macSize:])
+	copy(packet, hash)
+	return packet, hash, nil
+}
+
+// recoverNodeKey computes the public key used to sign the given hash from the signature.
+func recoverNodeKey(hash, sig []byte) (key Pubkey, err error) {
+	pubkey, err := crypto.Ecrecover(hash, sig)
+	if err != nil {
+		return key, err
+	}
+	copy(key[:], pubkey[1:])
+	return key, nil
+}
+
+// EncodePubkey encodes a secp256k1 public key.
+func EncodePubkey(key *ecdsa.PublicKey) Pubkey {
+	var e Pubkey
+	math.ReadBits(key.X, e[:len(e)/2])
+	math.ReadBits(key.Y, e[len(e)/2:])
+	return e
+}
+
+// DecodePubkey reads an encoded secp256k1 public key.
+func DecodePubkey(curve elliptic.Curve, e Pubkey) (*ecdsa.PublicKey, error) {
+	p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}
+	half := len(e) / 2
+	p.X.SetBytes(e[:half])
+	p.Y.SetBytes(e[half:])
+	if !p.Curve.IsOnCurve(p.X, p.Y) {
+		return nil, ErrBadPoint
+	}
+	return p, nil
+}

+ 858 - 0
p2p/discover/v5_udp.go

@@ -0,0 +1,858 @@
+// 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 discover
+
+import (
+	"bytes"
+	"context"
+	"crypto/ecdsa"
+	crand "crypto/rand"
+	"errors"
+	"fmt"
+	"io"
+	"math"
+	"net"
+	"sync"
+	"time"
+
+	"blockchain-go/common/mclock"
+	"blockchain-go/log"
+	"blockchain-go/p2p/discover/v5wire"
+	"blockchain-go/p2p/enode"
+	"blockchain-go/p2p/enr"
+	"blockchain-go/p2p/netutil"
+)
+
+const (
+	lookupRequestLimit      = 3  // max requests against a single node during lookup
+	findnodeResultLimit     = 16 // applies in FINDNODE handler
+	totalNodesResponseLimit = 5  // applies in waitForNodes
+	nodesResponseItemLimit  = 3  // applies in sendNodes
+
+	respTimeoutV5 = 700 * time.Millisecond
+)
+
+// codecV5 is implemented by v5wire.Codec (and testCodec).
+//
+// The UDPv5 transport is split into two objects: the codec object deals with
+// encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns.
+type codecV5 interface {
+	// Encode encodes a packet.
+	Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error)
+
+	// decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails.
+	// The *enode.Node return value is non-nil when the input contains a handshake response.
+	Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error)
+}
+
+// UDPv5 is the implementation of protocol version 5.
+type UDPv5 struct {
+	// static fields
+	conn         UDPConn
+	tab          *Table
+	netrestrict  *netutil.Netlist
+	priv         *ecdsa.PrivateKey
+	localNode    *enode.LocalNode
+	db           *enode.DB
+	log          log.Logger
+	clock        mclock.Clock
+	validSchemes enr.IdentityScheme
+
+	// talkreq handler registry
+	trlock     sync.Mutex
+	trhandlers map[string]TalkRequestHandler
+
+	// channels into dispatch
+	packetInCh    chan ReadPacket
+	readNextCh    chan struct{}
+	callCh        chan *callV5
+	callDoneCh    chan *callV5
+	respTimeoutCh chan *callTimeout
+
+	// state of dispatch
+	codec            codecV5
+	activeCallByNode map[enode.ID]*callV5
+	activeCallByAuth map[v5wire.Nonce]*callV5
+	callQueue        map[enode.ID][]*callV5
+
+	// shutdown stuff
+	closeOnce      sync.Once
+	closeCtx       context.Context
+	cancelCloseCtx context.CancelFunc
+	wg             sync.WaitGroup
+}
+
+// TalkRequestHandler callback processes a talk request and optionally returns a reply
+type TalkRequestHandler func(enode.ID, *net.UDPAddr, []byte) []byte
+
+// callV5 represents a remote procedure call against another node.
+type callV5 struct {
+	node         *enode.Node
+	packet       v5wire.Packet
+	responseType byte // expected packet type of response
+	reqid        []byte
+	ch           chan v5wire.Packet // responses sent here
+	err          chan error         // errors sent here
+
+	// Valid for active calls only:
+	nonce          v5wire.Nonce      // nonce of request packet
+	handshakeCount int               // # times we attempted handshake for this call
+	challenge      *v5wire.Whoareyou // last sent handshake challenge
+	timeout        mclock.Timer
+}
+
+// callTimeout is the response timeout event of a call.
+type callTimeout struct {
+	c     *callV5
+	timer mclock.Timer
+}
+
+// ListenV5 listens on the given connection.
+func ListenV5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
+	t, err := newUDPv5(conn, ln, cfg)
+	if err != nil {
+		return nil, err
+	}
+	go t.tab.loop()
+	t.wg.Add(2)
+	go t.readLoop()
+	go t.dispatch()
+	return t, nil
+}
+
+// newUDPv5 creates a UDPv5 transport, but doesn't start any goroutines.
+func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
+	closeCtx, cancelCloseCtx := context.WithCancel(context.Background())
+	cfg = cfg.withDefaults()
+	t := &UDPv5{
+		// static fields
+		conn:         conn,
+		localNode:    ln,
+		db:           ln.Database(),
+		netrestrict:  cfg.NetRestrict,
+		priv:         cfg.PrivateKey,
+		log:          cfg.Log,
+		validSchemes: cfg.ValidSchemes,
+		clock:        cfg.Clock,
+		trhandlers:   make(map[string]TalkRequestHandler),
+		// channels into dispatch
+		packetInCh:    make(chan ReadPacket, 1),
+		readNextCh:    make(chan struct{}, 1),
+		callCh:        make(chan *callV5),
+		callDoneCh:    make(chan *callV5),
+		respTimeoutCh: make(chan *callTimeout),
+		// state of dispatch
+		codec:            v5wire.NewCodec(ln, cfg.PrivateKey, cfg.Clock),
+		activeCallByNode: make(map[enode.ID]*callV5),
+		activeCallByAuth: make(map[v5wire.Nonce]*callV5),
+		callQueue:        make(map[enode.ID][]*callV5),
+		// shutdown
+		closeCtx:       closeCtx,
+		cancelCloseCtx: cancelCloseCtx,
+	}
+	tab, err := newTable(t, t.db, cfg.Bootnodes, cfg.Log)
+	if err != nil {
+		return nil, err
+	}
+	t.tab = tab
+	return t, nil
+}
+
+// Self returns the local node record.
+func (t *UDPv5) Self() *enode.Node {
+	return t.localNode.Node()
+}
+
+// Close shuts down packet processing.
+func (t *UDPv5) Close() {
+	t.closeOnce.Do(func() {
+		t.cancelCloseCtx()
+		t.conn.Close()
+		t.wg.Wait()
+		t.tab.close()
+	})
+}
+
+// Ping sends a ping message to the given node.
+func (t *UDPv5) Ping(n *enode.Node) error {
+	_, err := t.ping(n)
+	return err
+}
+
+// Resolve searches for a specific node with the given ID and tries to get the most recent
+// version of the node record for it. It returns n if the node could not be resolved.
+func (t *UDPv5) Resolve(n *enode.Node) *enode.Node {
+	if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
+		n = intable
+	}
+	// Try asking directly. This works if the node is still responding on the endpoint we have.
+	if resp, err := t.RequestENR(n); err == nil {
+		return resp
+	}
+	// Otherwise do a network lookup.
+	result := t.Lookup(n.ID())
+	for _, rn := range result {
+		if rn.ID() == n.ID() && rn.Seq() > n.Seq() {
+			return rn
+		}
+	}
+	return n
+}
+
+// AllNodes returns all the nodes stored in the local table.
+func (t *UDPv5) AllNodes() []*enode.Node {
+	t.tab.mutex.Lock()
+	defer t.tab.mutex.Unlock()
+	nodes := make([]*enode.Node, 0)
+
+	for _, b := range &t.tab.buckets {
+		for _, n := range b.entries {
+			nodes = append(nodes, unwrapNode(n))
+		}
+	}
+	return nodes
+}
+
+// LocalNode returns the current local node running the
+// protocol.
+func (t *UDPv5) LocalNode() *enode.LocalNode {
+	return t.localNode
+}
+
+// RegisterTalkHandler adds a handler for 'talk requests'. The handler function is called
+// whenever a request for the given protocol is received and should return the response
+// data or nil.
+func (t *UDPv5) RegisterTalkHandler(protocol string, handler TalkRequestHandler) {
+	t.trlock.Lock()
+	defer t.trlock.Unlock()
+	t.trhandlers[protocol] = handler
+}
+
+// TalkRequest sends a talk request to n and waits for a response.
+func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]byte, error) {
+	req := &v5wire.TalkRequest{Protocol: protocol, Message: request}
+	resp := t.call(n, v5wire.TalkResponseMsg, req)
+	defer t.callDone(resp)
+	select {
+	case respMsg := <-resp.ch:
+		return respMsg.(*v5wire.TalkResponse).Message, nil
+	case err := <-resp.err:
+		return nil, err
+	}
+}
+
+// RandomNodes returns an iterator that finds random nodes in the DHT.
+func (t *UDPv5) RandomNodes() enode.Iterator {
+	if t.tab.len() == 0 {
+		// All nodes were dropped, refresh. The very first query will hit this
+		// case and run the bootstrapping logic.
+		<-t.tab.refresh()
+	}
+
+	return newLookupIterator(t.closeCtx, t.newRandomLookup)
+}
+
+// Lookup performs a recursive lookup for the given target.
+// It returns the closest nodes to target.
+func (t *UDPv5) Lookup(target enode.ID) []*enode.Node {
+	return t.newLookup(t.closeCtx, target).run()
+}
+
+// lookupRandom looks up a random target.
+// This is needed to satisfy the transport interface.
+func (t *UDPv5) lookupRandom() []*enode.Node {
+	return t.newRandomLookup(t.closeCtx).run()
+}
+
+// lookupSelf looks up our own node ID.
+// This is needed to satisfy the transport interface.
+func (t *UDPv5) lookupSelf() []*enode.Node {
+	return t.newLookup(t.closeCtx, t.Self().ID()).run()
+}
+
+func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup {
+	var target enode.ID
+	crand.Read(target[:])
+	return t.newLookup(ctx, target)
+}
+
+func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
+	return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
+		return t.lookupWorker(n, target)
+	})
+}
+
+// lookupWorker performs FINDNODE calls against a single node during lookup.
+func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
+	var (
+		dists = lookupDistances(target, destNode.ID())
+		nodes = nodesByDistance{target: target}
+		err   error
+	)
+	var r []*enode.Node
+	r, err = t.findnode(unwrapNode(destNode), dists)
+	if err == errClosed {
+		return nil, err
+	}
+	for _, n := range r {
+		if n.ID() != t.Self().ID() {
+			nodes.push(wrapNode(n), findnodeResultLimit)
+		}
+	}
+	return nodes.entries, err
+}
+
+// lookupDistances computes the distance parameter for FINDNODE calls to dest.
+// It chooses distances adjacent to logdist(target, dest), e.g. for a target
+// with logdist(target, dest) = 255 the result is [255, 256, 254].
+func lookupDistances(target, dest enode.ID) (dists []uint) {
+	td := enode.LogDist(target, dest)
+	dists = append(dists, uint(td))
+	for i := 1; len(dists) < lookupRequestLimit; i++ {
+		if td+i < 256 {
+			dists = append(dists, uint(td+i))
+		}
+		if td-i > 0 {
+			dists = append(dists, uint(td-i))
+		}
+	}
+	return dists
+}
+
+// ping calls PING on a node and waits for a PONG response.
+func (t *UDPv5) ping(n *enode.Node) (uint64, error) {
+	req := &v5wire.Ping{ENRSeq: t.localNode.Node().Seq()}
+	resp := t.call(n, v5wire.PongMsg, req)
+	defer t.callDone(resp)
+
+	select {
+	case pong := <-resp.ch:
+		return pong.(*v5wire.Pong).ENRSeq, nil
+	case err := <-resp.err:
+		return 0, err
+	}
+}
+
+// requestENR requests n's record.
+func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) {
+	nodes, err := t.findnode(n, []uint{0})
+	if err != nil {
+		return nil, err
+	}
+	if len(nodes) != 1 {
+		return nil, fmt.Errorf("%d nodes in response for distance zero", len(nodes))
+	}
+	return nodes[0], nil
+}
+
+// findnode calls FINDNODE on a node and waits for responses.
+func (t *UDPv5) findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) {
+	resp := t.call(n, v5wire.NodesMsg, &v5wire.Findnode{Distances: distances})
+	return t.waitForNodes(resp, distances)
+}
+
+// waitForNodes waits for NODES responses to the given call.
+func (t *UDPv5) waitForNodes(c *callV5, distances []uint) ([]*enode.Node, error) {
+	defer t.callDone(c)
+
+	var (
+		nodes           []*enode.Node
+		seen            = make(map[enode.ID]struct{})
+		received, total = 0, -1
+	)
+	for {
+		select {
+		case responseP := <-c.ch:
+			response := responseP.(*v5wire.Nodes)
+			for _, record := range response.Nodes {
+				node, err := t.verifyResponseNode(c, record, distances, seen)
+				if err != nil {
+					t.log.Debug("Invalid record in "+response.Name(), "id", c.node.ID(), "err", err)
+					continue
+				}
+				nodes = append(nodes, node)
+			}
+			if total == -1 {
+				total = min(int(response.Total), totalNodesResponseLimit)
+			}
+			if received++; received == total {
+				return nodes, nil
+			}
+		case err := <-c.err:
+			return nodes, err
+		}
+	}
+}
+
+// verifyResponseNode checks validity of a record in a NODES response.
+func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, seen map[enode.ID]struct{}) (*enode.Node, error) {
+	node, err := enode.New(t.validSchemes, r)
+	if err != nil {
+		return nil, err
+	}
+	if err := netutil.CheckRelayIP(c.node.IP(), node.IP()); err != nil {
+		return nil, err
+	}
+	if c.node.UDP() <= 1024 {
+		return nil, errLowPort
+	}
+	if distances != nil {
+		nd := enode.LogDist(c.node.ID(), node.ID())
+		if !containsUint(uint(nd), distances) {
+			return nil, errors.New("does not match any requested distance")
+		}
+	}
+	if _, ok := seen[node.ID()]; ok {
+		return nil, fmt.Errorf("duplicate record")
+	}
+	seen[node.ID()] = struct{}{}
+	return node, nil
+}
+
+func containsUint(x uint, xs []uint) bool {
+	for _, v := range xs {
+		if x == v {
+			return true
+		}
+	}
+	return false
+}
+
+// call sends the given call and sets up a handler for response packets (of message type
+// responseType). Responses are dispatched to the call's response channel.
+func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) *callV5 {
+	c := &callV5{
+		node:         node,
+		packet:       packet,
+		responseType: responseType,
+		reqid:        make([]byte, 8),
+		ch:           make(chan v5wire.Packet, 1),
+		err:          make(chan error, 1),
+	}
+	// Assign request ID.
+	crand.Read(c.reqid)
+	packet.SetRequestID(c.reqid)
+	// Send call to dispatch.
+	select {
+	case t.callCh <- c:
+	case <-t.closeCtx.Done():
+		c.err <- errClosed
+	}
+	return c
+}
+
+// callDone tells dispatch that the active call is done.
+func (t *UDPv5) callDone(c *callV5) {
+	// This needs a loop because further responses may be incoming until the
+	// send to callDoneCh has completed. Such responses need to be discarded
+	// in order to avoid blocking the dispatch loop.
+	for {
+		select {
+		case <-c.ch:
+			// late response, discard.
+		case <-c.err:
+			// late error, discard.
+		case t.callDoneCh <- c:
+			return
+		case <-t.closeCtx.Done():
+			return
+		}
+	}
+}
+
+// dispatch runs in its own goroutine, handles incoming packets and deals with calls.
+//
+// For any destination node there is at most one 'active call', stored in the t.activeCall*
+// maps. A call is made active when it is sent. The active call can be answered by a
+// matching response, in which case c.ch receives the response; or by timing out, in which case
+// c.err receives the error. When the function that created the call signals the active
+// call is done through callDone, the next call from the call queue is started.
+//
+// Calls may also be answered by a WHOAREYOU packet referencing the call packet's authTag.
+// When that happens the call is simply re-sent to complete the handshake. We allow one
+// handshake attempt per call.
+func (t *UDPv5) dispatch() {
+	defer t.wg.Done()
+
+	// Arm first read.
+	t.readNextCh <- struct{}{}
+
+	for {
+		select {
+		case c := <-t.callCh:
+			id := c.node.ID()
+			t.callQueue[id] = append(t.callQueue[id], c)
+			t.sendNextCall(id)
+
+		case ct := <-t.respTimeoutCh:
+			active := t.activeCallByNode[ct.c.node.ID()]
+			if ct.c == active && ct.timer == active.timeout {
+				ct.c.err <- errTimeout
+			}
+
+		case c := <-t.callDoneCh:
+			id := c.node.ID()
+			active := t.activeCallByNode[id]
+			if active != c {
+				panic("BUG: callDone for inactive call")
+			}
+			c.timeout.Stop()
+			delete(t.activeCallByAuth, c.nonce)
+			delete(t.activeCallByNode, id)
+			t.sendNextCall(id)
+
+		case p := <-t.packetInCh:
+			t.handlePacket(p.Data, p.Addr)
+			// Arm next read.
+			t.readNextCh <- struct{}{}
+
+		case <-t.closeCtx.Done():
+			close(t.readNextCh)
+			for id, queue := range t.callQueue {
+				for _, c := range queue {
+					c.err <- errClosed
+				}
+				delete(t.callQueue, id)
+			}
+			for id, c := range t.activeCallByNode {
+				c.err <- errClosed
+				delete(t.activeCallByNode, id)
+				delete(t.activeCallByAuth, c.nonce)
+			}
+			return
+		}
+	}
+}
+
+// startResponseTimeout sets the response timer for a call.
+func (t *UDPv5) startResponseTimeout(c *callV5) {
+	if c.timeout != nil {
+		c.timeout.Stop()
+	}
+	var (
+		timer mclock.Timer
+		done  = make(chan struct{})
+	)
+	timer = t.clock.AfterFunc(respTimeoutV5, func() {
+		<-done
+		select {
+		case t.respTimeoutCh <- &callTimeout{c, timer}:
+		case <-t.closeCtx.Done():
+		}
+	})
+	c.timeout = timer
+	close(done)
+}
+
+// sendNextCall sends the next call in the call queue if there is no active call.
+func (t *UDPv5) sendNextCall(id enode.ID) {
+	queue := t.callQueue[id]
+	if len(queue) == 0 || t.activeCallByNode[id] != nil {
+		return
+	}
+	t.activeCallByNode[id] = queue[0]
+	t.sendCall(t.activeCallByNode[id])
+	if len(queue) == 1 {
+		delete(t.callQueue, id)
+	} else {
+		copy(queue, queue[1:])
+		t.callQueue[id] = queue[:len(queue)-1]
+	}
+}
+
+// sendCall encodes and sends a request packet to the call's recipient node.
+// This performs a handshake if needed.
+func (t *UDPv5) sendCall(c *callV5) {
+	// The call might have a nonce from a previous handshake attempt. Remove the entry for
+	// the old nonce because we're about to generate a new nonce for this call.
+	if c.nonce != (v5wire.Nonce{}) {
+		delete(t.activeCallByAuth, c.nonce)
+	}
+
+	addr := &net.UDPAddr{IP: c.node.IP(), Port: c.node.UDP()}
+	newNonce, _ := t.send(c.node.ID(), addr, c.packet, c.challenge)
+	c.nonce = newNonce
+	t.activeCallByAuth[newNonce] = c
+	t.startResponseTimeout(c)
+}
+
+// sendResponse sends a response packet to the given node.
+// This doesn't trigger a handshake even if no keys are available.
+func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error {
+	_, err := t.send(toID, toAddr, packet, nil)
+	return err
+}
+
+// send sends a packet to the given node.
+func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
+	addr := toAddr.String()
+	enc, nonce, err := t.codec.Encode(toID, addr, packet, c)
+	if err != nil {
+		t.log.Warn(">> "+packet.Name(), "id", toID, "addr", addr, "err", err)
+		return nonce, err
+	}
+	_, err = t.conn.WriteToUDP(enc, toAddr)
+	t.log.Trace(">> "+packet.Name(), "id", toID, "addr", addr)
+	return nonce, err
+}
+
+// readLoop runs in its own goroutine and reads packets from the network.
+func (t *UDPv5) readLoop() {
+	defer t.wg.Done()
+
+	buf := make([]byte, maxPacketSize)
+	for range t.readNextCh {
+		nbytes, from, err := t.conn.ReadFromUDP(buf)
+		if netutil.IsTemporaryError(err) {
+			// Ignore temporary read errors.
+			t.log.Debug("Temporary UDP read error", "err", err)
+			continue
+		} else if err != nil {
+			// Shut down the loop for permament errors.
+			if err != io.EOF {
+				t.log.Debug("UDP read error", "err", err)
+			}
+			return
+		}
+		t.dispatchReadPacket(from, buf[:nbytes])
+	}
+}
+
+// dispatchReadPacket sends a packet into the dispatch loop.
+func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
+	select {
+	case t.packetInCh <- ReadPacket{content, from}:
+		return true
+	case <-t.closeCtx.Done():
+		return false
+	}
+}
+
+// handlePacket decodes and processes an incoming packet from the network.
+func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
+	addr := fromAddr.String()
+	fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr)
+	if err != nil {
+		t.log.Debug("Bad discv5 packet", "id", fromID, "addr", addr, "err", err)
+		return err
+	}
+	if fromNode != nil {
+		// Handshake succeeded, add to table.
+		t.tab.addSeenNode(wrapNode(fromNode))
+	}
+	if packet.Kind() != v5wire.WhoareyouPacket {
+		// WHOAREYOU logged separately to report errors.
+		t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", addr)
+	}
+	t.handle(packet, fromID, fromAddr)
+	return nil
+}
+
+// handleCallResponse dispatches a response packet to the call waiting for it.
+func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool {
+	ac := t.activeCallByNode[fromID]
+	if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) {
+		t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr)
+		return false
+	}
+	if !fromAddr.IP.Equal(ac.node.IP()) || fromAddr.Port != ac.node.UDP() {
+		t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr)
+		return false
+	}
+	if p.Kind() != ac.responseType {
+		t.log.Debug(fmt.Sprintf("Wrong discv5 response type %s", p.Name()), "id", fromID, "addr", fromAddr)
+		return false
+	}
+	t.startResponseTimeout(ac)
+	ac.ch <- p
+	return true
+}
+
+// getNode looks for a node record in table and database.
+func (t *UDPv5) getNode(id enode.ID) *enode.Node {
+	if n := t.tab.getNode(id); n != nil {
+		return n
+	}
+	if n := t.localNode.Database().Node(id); n != nil {
+		return n
+	}
+	return nil
+}
+
+// handle processes incoming packets according to their message type.
+func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) {
+	switch p := p.(type) {
+	case *v5wire.Unknown:
+		t.handleUnknown(p, fromID, fromAddr)
+	case *v5wire.Whoareyou:
+		t.handleWhoareyou(p, fromID, fromAddr)
+	case *v5wire.Ping:
+		t.handlePing(p, fromID, fromAddr)
+	case *v5wire.Pong:
+		if t.handleCallResponse(fromID, fromAddr, p) {
+			t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)})
+		}
+	case *v5wire.Findnode:
+		t.handleFindnode(p, fromID, fromAddr)
+	case *v5wire.Nodes:
+		t.handleCallResponse(fromID, fromAddr, p)
+	case *v5wire.TalkRequest:
+		t.handleTalkRequest(p, fromID, fromAddr)
+	case *v5wire.TalkResponse:
+		t.handleCallResponse(fromID, fromAddr, p)
+	}
+}
+
+// handleUnknown initiates a handshake by responding with WHOAREYOU.
+func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) {
+	challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
+	crand.Read(challenge.IDNonce[:])
+	if n := t.getNode(fromID); n != nil {
+		challenge.Node = n
+		challenge.RecordSeq = n.Seq()
+	}
+	t.sendResponse(fromID, fromAddr, challenge)
+}
+
+var (
+	errChallengeNoCall = errors.New("no matching call")
+	errChallengeTwice  = errors.New("second handshake")
+)
+
+// handleWhoareyou resends the active call as a handshake packet.
+func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) {
+	c, err := t.matchWithCall(fromID, p.Nonce)
+	if err != nil {
+		t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err)
+		return
+	}
+
+	// Resend the call that was answered by WHOAREYOU.
+	t.log.Trace("<< "+p.Name(), "id", c.node.ID(), "addr", fromAddr)
+	c.handshakeCount++
+	c.challenge = p
+	p.Node = c.node
+	t.sendCall(c)
+}
+
+// matchWithCall checks whether a handshake attempt matches the active call.
+func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, error) {
+	c := t.activeCallByAuth[nonce]
+	if c == nil {
+		return nil, errChallengeNoCall
+	}
+	if c.handshakeCount > 0 {
+		return nil, errChallengeTwice
+	}
+	return c, nil
+}
+
+// handlePing sends a PONG response.
+func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) {
+	remoteIP := fromAddr.IP
+	// Handle IPv4 mapped IPv6 addresses in the
+	// event the local node is binded to an
+	// ipv6 interface.
+	if remoteIP.To4() != nil {
+		remoteIP = remoteIP.To4()
+	}
+	t.sendResponse(fromID, fromAddr, &v5wire.Pong{
+		ReqID:  p.ReqID,
+		ToIP:   remoteIP,
+		ToPort: uint16(fromAddr.Port),
+		ENRSeq: t.localNode.Node().Seq(),
+	})
+}
+
+// handleFindnode returns nodes to the requester.
+func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) {
+	nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit)
+	for _, resp := range packNodes(p.ReqID, nodes) {
+		t.sendResponse(fromID, fromAddr, resp)
+	}
+}
+
+// collectTableNodes creates a FINDNODE result set for the given distances.
+func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node {
+	var nodes []*enode.Node
+	var processed = make(map[uint]struct{})
+	for _, dist := range distances {
+		// Reject duplicate / invalid distances.
+		_, seen := processed[dist]
+		if seen || dist > 256 {
+			continue
+		}
+
+		// Get the nodes.
+		var bn []*enode.Node
+		if dist == 0 {
+			bn = []*enode.Node{t.Self()}
+		} else if dist <= 256 {
+			t.tab.mutex.Lock()
+			bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries)
+			t.tab.mutex.Unlock()
+		}
+		processed[dist] = struct{}{}
+
+		// Apply some pre-checks to avoid sending invalid nodes.
+		for _, n := range bn {
+			// TODO livenessChecks > 1
+			if netutil.CheckRelayIP(rip, n.IP()) != nil {
+				continue
+			}
+			nodes = append(nodes, n)
+			if len(nodes) >= limit {
+				return nodes
+			}
+		}
+	}
+	return nodes
+}
+
+// packNodes creates NODES response packets for the given node list.
+func packNodes(reqid []byte, nodes []*enode.Node) []*v5wire.Nodes {
+	if len(nodes) == 0 {
+		return []*v5wire.Nodes{{ReqID: reqid, Total: 1}}
+	}
+
+	total := uint8(math.Ceil(float64(len(nodes)) / 3))
+	var resp []*v5wire.Nodes
+	for len(nodes) > 0 {
+		p := &v5wire.Nodes{ReqID: reqid, Total: total}
+		items := min(nodesResponseItemLimit, len(nodes))
+		for i := 0; i < items; i++ {
+			p.Nodes = append(p.Nodes, nodes[i].Record())
+		}
+		nodes = nodes[items:]
+		resp = append(resp, p)
+	}
+	return resp
+}
+
+// handleTalkRequest runs the talk request handler of the requested protocol.
+func (t *UDPv5) handleTalkRequest(p *v5wire.TalkRequest, fromID enode.ID, fromAddr *net.UDPAddr) {
+	t.trlock.Lock()
+	handler := t.trhandlers[p.Protocol]
+	t.trlock.Unlock()
+
+	var response []byte
+	if handler != nil {
+		response = handler(fromID, fromAddr, p.Message)
+	}
+	resp := &v5wire.TalkResponse{ReqID: p.ReqID, Message: response}
+	t.sendResponse(fromID, fromAddr, resp)
+}

+ 180 - 0
p2p/discover/v5wire/crypto.go

@@ -0,0 +1,180 @@
+// Copyright 2020 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 v5wire
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"errors"
+	"fmt"
+	"hash"
+
+	"blockchain-go/common/math"
+	"blockchain-go/p2p/enode"
+	"github.com/ethereum/go-ethereum/crypto"
+	"golang.org/x/crypto/hkdf"
+)
+
+const (
+	// Encryption/authentication parameters.
+	aesKeySize   = 16
+	gcmNonceSize = 12
+)
+
+// Nonce represents a nonce used for AES/GCM.
+type Nonce [gcmNonceSize]byte
+
+// EncodePubkey encodes a public key.
+func EncodePubkey(key *ecdsa.PublicKey) []byte {
+	switch key.Curve {
+	case crypto.S256():
+		return crypto.CompressPubkey(key)
+	default:
+		panic("unsupported curve " + key.Curve.Params().Name + " in EncodePubkey")
+	}
+}
+
+// DecodePubkey decodes a public key in compressed format.
+func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) {
+	switch curve {
+	case crypto.S256():
+		if len(e) != 33 {
+			return nil, errors.New("wrong size public key data")
+		}
+		return crypto.DecompressPubkey(e)
+	default:
+		return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name)
+	}
+}
+
+// idNonceHash computes the ID signature hash used in the handshake.
+func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte {
+	h.Reset()
+	h.Write([]byte("discovery v5 identity proof"))
+	h.Write(challenge)
+	h.Write(ephkey)
+	h.Write(destID[:])
+	return h.Sum(nil)
+}
+
+// makeIDSignature creates the ID nonce signature.
+func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) {
+	input := idNonceHash(hash, challenge, ephkey, destID)
+	switch key.Curve {
+	case crypto.S256():
+		idsig, err := crypto.Sign(input, key)
+		if err != nil {
+			return nil, err
+		}
+		return idsig[:len(idsig)-1], nil // remove recovery ID
+	default:
+		return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name)
+	}
+}
+
+// s256raw is an unparsed secp256k1 public key ENR entry.
+type s256raw []byte
+
+func (s256raw) ENRKey() string { return "secp256k1" }
+
+// verifyIDSignature checks that signature over idnonce was made by the given node.
+func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, ephkey []byte, destID enode.ID) error {
+	switch idscheme := n.Record().IdentityScheme(); idscheme {
+	case "v4":
+		var pubkey s256raw
+		if n.Load(&pubkey) != nil {
+			return errors.New("no secp256k1 public key in record")
+		}
+		input := idNonceHash(hash, challenge, ephkey, destID)
+		if !crypto.VerifySignature(pubkey, input, sig) {
+			return errInvalidNonceSig
+		}
+		return nil
+	default:
+		return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme)
+	}
+}
+
+type hashFn func() hash.Hash
+
+// deriveKeys creates the session keys.
+func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session {
+	const text = "discovery v5 key agreement"
+	var info = make([]byte, 0, len(text)+len(n1)+len(n2))
+	info = append(info, text...)
+	info = append(info, n1[:]...)
+	info = append(info, n2[:]...)
+
+	eph := ecdh(priv, pub)
+	if eph == nil {
+		return nil
+	}
+	kdf := hkdf.New(hash, eph, challenge, info)
+	sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)}
+	kdf.Read(sec.writeKey)
+	kdf.Read(sec.readKey)
+	for i := range eph {
+		eph[i] = 0
+	}
+	return &sec
+}
+
+// ecdh creates a shared secret.
+func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte {
+	secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
+	if secX == nil {
+		return nil
+	}
+	sec := make([]byte, 33)
+	sec[0] = 0x02 | byte(secY.Bit(0))
+	math.ReadBits(secX, sec[1:])
+	return sec
+}
+
+// encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is
+// appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16
+// bytes longer than plaintext because it contains an authentication tag.
+func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		panic(fmt.Errorf("can't create block cipher: %v", err))
+	}
+	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
+	if err != nil {
+		panic(fmt.Errorf("can't create GCM: %v", err))
+	}
+	return aesgcm.Seal(dest, nonce, plaintext, authData), nil
+}
+
+// decryptGCM decrypts ct using AES-GCM with the given key and nonce.
+func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, fmt.Errorf("can't create block cipher: %v", err)
+	}
+	if len(nonce) != gcmNonceSize {
+		return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce))
+	}
+	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
+	if err != nil {
+		return nil, fmt.Errorf("can't create GCM: %v", err)
+	}
+	pt := make([]byte, 0, len(ct))
+	return aesgcm.Open(pt, nonce, ct, authData)
+}

+ 648 - 0
p2p/discover/v5wire/encoding.go

@@ -0,0 +1,648 @@
+// 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 v5wire
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/ecdsa"
+	crand "crypto/rand"
+	"crypto/sha256"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"hash"
+
+	"blockchain-go/common/mclock"
+	"blockchain-go/p2p/enode"
+	"blockchain-go/p2p/enr"
+	"blockchain-go/rlp"
+)
+
+// TODO concurrent WHOAREYOU tie-breaker
+// TODO rehandshake after X packets
+
+// Header represents a packet header.
+type Header struct {
+	IV [sizeofMaskingIV]byte
+	StaticHeader
+	AuthData []byte
+
+	src enode.ID // used by decoder
+}
+
+// StaticHeader contains the static fields of a packet header.
+type StaticHeader struct {
+	ProtocolID [6]byte
+	Version    uint16
+	Flag       byte
+	Nonce      Nonce
+	AuthSize   uint16
+}
+
+// Authdata layouts.
+type (
+	whoareyouAuthData struct {
+		IDNonce   [16]byte // ID proof data
+		RecordSeq uint64   // highest known ENR sequence of requester
+	}
+
+	handshakeAuthData struct {
+		h struct {
+			SrcID      enode.ID
+			SigSize    byte // ignature data
+			PubkeySize byte // offset of
+		}
+		// Trailing variable-size data.
+		signature, pubkey, record []byte
+	}
+
+	messageAuthData struct {
+		SrcID enode.ID
+	}
+)
+
+// Packet header flag values.
+const (
+	flagMessage = iota
+	flagWhoareyou
+	flagHandshake
+)
+
+// Protocol constants.
+const (
+	version         = 1
+	minVersion      = 1
+	sizeofMaskingIV = 16
+
+	minMessageSize      = 48 // this refers to data after static headers
+	randomPacketMsgSize = 20
+)
+
+var protocolID = [6]byte{'d', 'i', 's', 'c', 'v', '5'}
+
+// Errors.
+var (
+	errTooShort            = errors.New("packet too short")
+	errInvalidHeader       = errors.New("invalid packet header")
+	errInvalidFlag         = errors.New("invalid flag value in header")
+	errMinVersion          = errors.New("version of packet header below minimum")
+	errMsgTooShort         = errors.New("message/handshake packet below minimum size")
+	errAuthSize            = errors.New("declared auth size is beyond packet length")
+	errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake")
+	errInvalidAuthKey      = errors.New("invalid ephemeral pubkey")
+	errNoRecord            = errors.New("expected ENR in handshake but none sent")
+	errInvalidNonceSig     = errors.New("invalid ID nonce signature")
+	errMessageTooShort     = errors.New("message contains no data")
+	errMessageDecrypt      = errors.New("cannot decrypt message")
+)
+
+// Public errors.
+var (
+	ErrInvalidReqID = errors.New("request ID larger than 8 bytes")
+)
+
+// Packet sizes.
+var (
+	sizeofStaticHeader      = binary.Size(StaticHeader{})
+	sizeofWhoareyouAuthData = binary.Size(whoareyouAuthData{})
+	sizeofHandshakeAuthData = binary.Size(handshakeAuthData{}.h)
+	sizeofMessageAuthData   = binary.Size(messageAuthData{})
+	sizeofStaticPacketData  = sizeofMaskingIV + sizeofStaticHeader
+)
+
+// Codec encodes and decodes Discovery v5 packets.
+// This type is not safe for concurrent use.
+type Codec struct {
+	sha256    hash.Hash
+	localnode *enode.LocalNode
+	privkey   *ecdsa.PrivateKey
+	sc        *SessionCache
+
+	// encoder buffers
+	buf      bytes.Buffer // whole packet
+	headbuf  bytes.Buffer // packet header
+	msgbuf   bytes.Buffer // message RLP plaintext
+	msgctbuf []byte       // message data ciphertext
+
+	// decoder buffer
+	reader bytes.Reader
+}
+
+// NewCodec creates a wire codec.
+func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *Codec {
+	c := &Codec{
+		sha256:    sha256.New(),
+		localnode: ln,
+		privkey:   key,
+		sc:        NewSessionCache(1024, clock),
+	}
+	return c
+}
+
+// Encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The
+// 'challenge' parameter should be the most recently received WHOAREYOU packet from that
+// node.
+func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoareyou) ([]byte, Nonce, error) {
+	// Create the packet header.
+	var (
+		head    Header
+		session *session
+		msgData []byte
+		err     error
+	)
+	switch {
+	case packet.Kind() == WhoareyouPacket:
+		head, err = c.encodeWhoareyou(id, packet.(*Whoareyou))
+	case challenge != nil:
+		// We have an unanswered challenge, send handshake.
+		head, session, err = c.encodeHandshakeHeader(id, addr, challenge)
+	default:
+		session = c.sc.session(id, addr)
+		if session != nil {
+			// There is a session, use it.
+			head, err = c.encodeMessageHeader(id, session)
+		} else {
+			// No keys, send random data to kick off the handshake.
+			head, msgData, err = c.encodeRandom(id)
+		}
+	}
+	if err != nil {
+		return nil, Nonce{}, err
+	}
+
+	// Generate masking IV.
+	if err := c.sc.maskingIVGen(head.IV[:]); err != nil {
+		return nil, Nonce{}, fmt.Errorf("can't generate masking IV: %v", err)
+	}
+
+	// Encode header data.
+	c.writeHeaders(&head)
+
+	// Store sent WHOAREYOU challenges.
+	if challenge, ok := packet.(*Whoareyou); ok {
+		challenge.ChallengeData = bytesCopy(&c.buf)
+		c.sc.storeSentHandshake(id, addr, challenge)
+	} else if msgData == nil {
+		headerData := c.buf.Bytes()
+		msgData, err = c.encryptMessage(session, packet, &head, headerData)
+		if err != nil {
+			return nil, Nonce{}, err
+		}
+	}
+
+	enc, err := c.EncodeRaw(id, head, msgData)
+	return enc, head.Nonce, err
+}
+
+// EncodeRaw encodes a packet with the given header.
+func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, error) {
+	c.writeHeaders(&head)
+
+	// Apply masking.
+	masked := c.buf.Bytes()[sizeofMaskingIV:]
+	mask := head.mask(id)
+	mask.XORKeyStream(masked[:], masked[:])
+
+	// Write message data.
+	c.buf.Write(msgdata)
+	return c.buf.Bytes(), nil
+}
+
+func (c *Codec) writeHeaders(head *Header) {
+	c.buf.Reset()
+	c.buf.Write(head.IV[:])
+	binary.Write(&c.buf, binary.BigEndian, &head.StaticHeader)
+	c.buf.Write(head.AuthData)
+}
+
+// makeHeader creates a packet header.
+func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header {
+	var authsize int
+	switch flag {
+	case flagMessage:
+		authsize = sizeofMessageAuthData
+	case flagWhoareyou:
+		authsize = sizeofWhoareyouAuthData
+	case flagHandshake:
+		authsize = sizeofHandshakeAuthData
+	default:
+		panic(fmt.Errorf("BUG: invalid packet header flag %x", flag))
+	}
+	authsize += authsizeExtra
+	if authsize > int(^uint16(0)) {
+		panic(fmt.Errorf("BUG: auth size %d overflows uint16", authsize))
+	}
+	return Header{
+		StaticHeader: StaticHeader{
+			ProtocolID: protocolID,
+			Version:    version,
+			Flag:       flag,
+			AuthSize:   uint16(authsize),
+		},
+	}
+}
+
+// encodeRandom encodes a packet with random content.
+func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) {
+	head := c.makeHeader(toID, flagMessage, 0)
+
+	// Encode auth data.
+	auth := messageAuthData{SrcID: c.localnode.ID()}
+	if _, err := crand.Read(head.Nonce[:]); err != nil {
+		return head, nil, fmt.Errorf("can't get random data: %v", err)
+	}
+	c.headbuf.Reset()
+	binary.Write(&c.headbuf, binary.BigEndian, auth)
+	head.AuthData = c.headbuf.Bytes()
+
+	// Fill message ciphertext buffer with random bytes.
+	c.msgctbuf = append(c.msgctbuf[:0], make([]byte, randomPacketMsgSize)...)
+	crand.Read(c.msgctbuf)
+	return head, c.msgctbuf, nil
+}
+
+// encodeWhoareyou encodes a WHOAREYOU packet.
+func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error) {
+	// Sanity check node field to catch misbehaving callers.
+	if packet.RecordSeq > 0 && packet.Node == nil {
+		panic("BUG: missing node in whoareyou with non-zero seq")
+	}
+
+	// Create header.
+	head := c.makeHeader(toID, flagWhoareyou, 0)
+	head.AuthData = bytesCopy(&c.buf)
+	head.Nonce = packet.Nonce
+
+	// Encode auth data.
+	auth := &whoareyouAuthData{
+		IDNonce:   packet.IDNonce,
+		RecordSeq: packet.RecordSeq,
+	}
+	c.headbuf.Reset()
+	binary.Write(&c.headbuf, binary.BigEndian, auth)
+	head.AuthData = c.headbuf.Bytes()
+	return head, nil
+}
+
+// encodeHandshakeMessage encodes the handshake message packet header.
+func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Whoareyou) (Header, *session, error) {
+	// Ensure calling code sets challenge.node.
+	if challenge.Node == nil {
+		panic("BUG: missing challenge.Node in encode")
+	}
+
+	// Generate new secrets.
+	auth, session, err := c.makeHandshakeAuth(toID, addr, challenge)
+	if err != nil {
+		return Header{}, nil, err
+	}
+
+	// Generate nonce for message.
+	nonce, err := c.sc.nextNonce(session)
+	if err != nil {
+		return Header{}, nil, fmt.Errorf("can't generate nonce: %v", err)
+	}
+
+	// TODO: this should happen when the first authenticated message is received
+	c.sc.storeNewSession(toID, addr, session)
+
+	// Encode the auth header.
+	var (
+		authsizeExtra = len(auth.pubkey) + len(auth.signature) + len(auth.record)
+		head          = c.makeHeader(toID, flagHandshake, authsizeExtra)
+	)
+	c.headbuf.Reset()
+	binary.Write(&c.headbuf, binary.BigEndian, &auth.h)
+	c.headbuf.Write(auth.signature)
+	c.headbuf.Write(auth.pubkey)
+	c.headbuf.Write(auth.record)
+	head.AuthData = c.headbuf.Bytes()
+	head.Nonce = nonce
+	return head, session, err
+}
+
+// encodeAuthHeader creates the auth header on a request packet following WHOAREYOU.
+func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoareyou) (*handshakeAuthData, *session, error) {
+	auth := new(handshakeAuthData)
+	auth.h.SrcID = c.localnode.ID()
+
+	// Create the ephemeral key. This needs to be first because the
+	// key is part of the ID nonce signature.
+	var remotePubkey = new(ecdsa.PublicKey)
+	if err := challenge.Node.Load((*enode.Secp256k1)(remotePubkey)); err != nil {
+		return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient")
+	}
+	ephkey, err := c.sc.ephemeralKeyGen()
+	if err != nil {
+		return nil, nil, fmt.Errorf("can't generate ephemeral key")
+	}
+	ephpubkey := EncodePubkey(&ephkey.PublicKey)
+	auth.pubkey = ephpubkey[:]
+	auth.h.PubkeySize = byte(len(auth.pubkey))
+
+	// Add ID nonce signature to response.
+	cdata := challenge.ChallengeData
+	idsig, err := makeIDSignature(c.sha256, c.privkey, cdata, ephpubkey[:], toID)
+	if err != nil {
+		return nil, nil, fmt.Errorf("can't sign: %v", err)
+	}
+	auth.signature = idsig
+	auth.h.SigSize = byte(len(auth.signature))
+
+	// Add our record to response if it's newer than what remote side has.
+	ln := c.localnode.Node()
+	if challenge.RecordSeq < ln.Seq() {
+		auth.record, _ = rlp.EncodeToBytes(ln.Record())
+	}
+
+	// Create session keys.
+	sec := deriveKeys(sha256.New, ephkey, remotePubkey, c.localnode.ID(), challenge.Node.ID(), cdata)
+	if sec == nil {
+		return nil, nil, fmt.Errorf("key derivation failed")
+	}
+	return auth, sec, err
+}
+
+// encodeMessage encodes an encrypted message packet.
+func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) {
+	head := c.makeHeader(toID, flagMessage, 0)
+
+	// Create the header.
+	nonce, err := c.sc.nextNonce(s)
+	if err != nil {
+		return Header{}, fmt.Errorf("can't generate nonce: %v", err)
+	}
+	auth := messageAuthData{SrcID: c.localnode.ID()}
+	c.buf.Reset()
+	binary.Write(&c.buf, binary.BigEndian, &auth)
+	head.AuthData = bytesCopy(&c.buf)
+	head.Nonce = nonce
+	return head, err
+}
+
+func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData []byte) ([]byte, error) {
+	// Encode message plaintext.
+	c.msgbuf.Reset()
+	c.msgbuf.WriteByte(p.Kind())
+	if err := rlp.Encode(&c.msgbuf, p); err != nil {
+		return nil, err
+	}
+	messagePT := c.msgbuf.Bytes()
+
+	// Encrypt into message ciphertext buffer.
+	messageCT, err := encryptGCM(c.msgctbuf[:0], s.writeKey, head.Nonce[:], messagePT, headerData)
+	if err == nil {
+		c.msgctbuf = messageCT
+	}
+	return messageCT, err
+}
+
+// Decode decodes a discovery packet.
+func (c *Codec) Decode(input []byte, addr string) (src enode.ID, n *enode.Node, p Packet, err error) {
+	// Unmask the static header.
+	if len(input) < sizeofStaticPacketData {
+		return enode.ID{}, nil, nil, errTooShort
+	}
+	var head Header
+	copy(head.IV[:], input[:sizeofMaskingIV])
+	mask := head.mask(c.localnode.ID())
+	staticHeader := input[sizeofMaskingIV:sizeofStaticPacketData]
+	mask.XORKeyStream(staticHeader, staticHeader)
+
+	// Decode and verify the static header.
+	c.reader.Reset(staticHeader)
+	binary.Read(&c.reader, binary.BigEndian, &head.StaticHeader)
+	remainingInput := len(input) - sizeofStaticPacketData
+	if err := head.checkValid(remainingInput); err != nil {
+		return enode.ID{}, nil, nil, err
+	}
+
+	// Unmask auth data.
+	authDataEnd := sizeofStaticPacketData + int(head.AuthSize)
+	authData := input[sizeofStaticPacketData:authDataEnd]
+	mask.XORKeyStream(authData, authData)
+	head.AuthData = authData
+
+	// Delete timed-out handshakes. This must happen before decoding to avoid
+	// processing the same handshake twice.
+	c.sc.handshakeGC()
+
+	// Decode auth part and message.
+	headerData := input[:authDataEnd]
+	msgData := input[authDataEnd:]
+	switch head.Flag {
+	case flagWhoareyou:
+		p, err = c.decodeWhoareyou(&head, headerData)
+	case flagHandshake:
+		n, p, err = c.decodeHandshakeMessage(addr, &head, headerData, msgData)
+	case flagMessage:
+		p, err = c.decodeMessage(addr, &head, headerData, msgData)
+	default:
+		err = errInvalidFlag
+	}
+	return head.src, n, p, err
+}
+
+// decodeWhoareyou reads packet data after the header as a WHOAREYOU packet.
+func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) {
+	if len(head.AuthData) != sizeofWhoareyouAuthData {
+		return nil, fmt.Errorf("invalid auth size %d for WHOAREYOU", len(head.AuthData))
+	}
+	var auth whoareyouAuthData
+	c.reader.Reset(head.AuthData)
+	binary.Read(&c.reader, binary.BigEndian, &auth)
+	p := &Whoareyou{
+		Nonce:         head.Nonce,
+		IDNonce:       auth.IDNonce,
+		RecordSeq:     auth.RecordSeq,
+		ChallengeData: make([]byte, len(headerData)),
+	}
+	copy(p.ChallengeData, headerData)
+	return p, nil
+}
+
+func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData, msgData []byte) (n *enode.Node, p Packet, err error) {
+	node, auth, session, err := c.decodeHandshake(fromAddr, head)
+	if err != nil {
+		c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
+		return nil, nil, err
+	}
+
+	// Decrypt the message using the new session keys.
+	msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, session.readKey)
+	if err != nil {
+		c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
+		return node, msg, err
+	}
+
+	// Handshake OK, drop the challenge and store the new session keys.
+	c.sc.storeNewSession(auth.h.SrcID, fromAddr, session)
+	c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
+	return node, msg, nil
+}
+
+func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, auth handshakeAuthData, s *session, err error) {
+	if auth, err = c.decodeHandshakeAuthData(head); err != nil {
+		return nil, auth, nil, err
+	}
+
+	// Verify against our last WHOAREYOU.
+	challenge := c.sc.getHandshake(auth.h.SrcID, fromAddr)
+	if challenge == nil {
+		return nil, auth, nil, errUnexpectedHandshake
+	}
+	// Get node record.
+	n, err = c.decodeHandshakeRecord(challenge.Node, auth.h.SrcID, auth.record)
+	if err != nil {
+		return nil, auth, nil, err
+	}
+	// Verify ID nonce signature.
+	sig := auth.signature
+	cdata := challenge.ChallengeData
+	err = verifyIDSignature(c.sha256, sig, n, cdata, auth.pubkey, c.localnode.ID())
+	if err != nil {
+		return nil, auth, nil, err
+	}
+	// Verify ephemeral key is on curve.
+	ephkey, err := DecodePubkey(c.privkey.Curve, auth.pubkey)
+	if err != nil {
+		return nil, auth, nil, errInvalidAuthKey
+	}
+	// Derive sesssion keys.
+	session := deriveKeys(sha256.New, c.privkey, ephkey, auth.h.SrcID, c.localnode.ID(), cdata)
+	session = session.keysFlipped()
+	return n, auth, session, nil
+}
+
+// decodeHandshakeAuthData reads the authdata section of a handshake packet.
+func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, err error) {
+	// Decode fixed size part.
+	if len(head.AuthData) < sizeofHandshakeAuthData {
+		return auth, fmt.Errorf("header authsize %d too low for handshake", head.AuthSize)
+	}
+	c.reader.Reset(head.AuthData)
+	binary.Read(&c.reader, binary.BigEndian, &auth.h)
+	head.src = auth.h.SrcID
+
+	// Decode variable-size part.
+	var (
+		vardata       = head.AuthData[sizeofHandshakeAuthData:]
+		sigAndKeySize = int(auth.h.SigSize) + int(auth.h.PubkeySize)
+		keyOffset     = int(auth.h.SigSize)
+		recOffset     = keyOffset + int(auth.h.PubkeySize)
+	)
+	if len(vardata) < sigAndKeySize {
+		return auth, errTooShort
+	}
+	auth.signature = vardata[:keyOffset]
+	auth.pubkey = vardata[keyOffset:recOffset]
+	auth.record = vardata[recOffset:]
+	return auth, nil
+}
+
+// decodeHandshakeRecord verifies the node record contained in a handshake packet. The
+// remote node should include the record if we don't have one or if ours is older than the
+// latest sequence number.
+func (c *Codec) decodeHandshakeRecord(local *enode.Node, wantID enode.ID, remote []byte) (*enode.Node, error) {
+	node := local
+	if len(remote) > 0 {
+		var record enr.Record
+		if err := rlp.DecodeBytes(remote, &record); err != nil {
+			return nil, err
+		}
+		if local == nil || local.Seq() < record.Seq() {
+			n, err := enode.New(enode.ValidSchemes, &record)
+			if err != nil {
+				return nil, fmt.Errorf("invalid node record: %v", err)
+			}
+			if n.ID() != wantID {
+				return nil, fmt.Errorf("record in handshake has wrong ID: %v", n.ID())
+			}
+			node = n
+		}
+	}
+	if node == nil {
+		return nil, errNoRecord
+	}
+	return node, nil
+}
+
+// decodeMessage reads packet data following the header as an ordinary message packet.
+func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData []byte) (Packet, error) {
+	if len(head.AuthData) != sizeofMessageAuthData {
+		return nil, fmt.Errorf("invalid auth size %d for message packet", len(head.AuthData))
+	}
+	var auth messageAuthData
+	c.reader.Reset(head.AuthData)
+	binary.Read(&c.reader, binary.BigEndian, &auth)
+	head.src = auth.SrcID
+
+	// Try decrypting the message.
+	key := c.sc.readKey(auth.SrcID, fromAddr)
+	msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, key)
+	if err == errMessageDecrypt {
+		// It didn't work. Start the handshake since this is an ordinary message packet.
+		return &Unknown{Nonce: head.Nonce}, nil
+	}
+	return msg, err
+}
+
+func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet, error) {
+	msgdata, err := decryptGCM(readKey, nonce, input, headerData)
+	if err != nil {
+		return nil, errMessageDecrypt
+	}
+	if len(msgdata) == 0 {
+		return nil, errMessageTooShort
+	}
+	return DecodeMessage(msgdata[0], msgdata[1:])
+}
+
+// checkValid performs some basic validity checks on the header.
+// The packetLen here is the length remaining after the static header.
+func (h *StaticHeader) checkValid(packetLen int) error {
+	if h.ProtocolID != protocolID {
+		return errInvalidHeader
+	}
+	if h.Version < minVersion {
+		return errMinVersion
+	}
+	if h.Flag != flagWhoareyou && packetLen < minMessageSize {
+		return errMsgTooShort
+	}
+	if int(h.AuthSize) > packetLen {
+		return errAuthSize
+	}
+	return nil
+}
+
+// headerMask returns a cipher for 'masking' / 'unmasking' packet headers.
+func (h *Header) mask(destID enode.ID) cipher.Stream {
+	block, err := aes.NewCipher(destID[:16])
+	if err != nil {
+		panic("can't create cipher")
+	}
+	return cipher.NewCTR(block, h.IV[:])
+}
+
+func bytesCopy(r *bytes.Buffer) []byte {
+	b := make([]byte, r.Len())
+	copy(b, r.Bytes())
+	return b
+}

+ 249 - 0
p2p/discover/v5wire/msg.go

@@ -0,0 +1,249 @@
+// 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 v5wire
+
+import (
+	"fmt"
+	"net"
+
+	"blockchain-go/common/mclock"
+	"blockchain-go/p2p/enode"
+	"blockchain-go/p2p/enr"
+	"blockchain-go/rlp"
+)
+
+// Packet is implemented by all message types.
+type Packet interface {
+	Name() string        // Name returns a string corresponding to the message type.
+	Kind() byte          // Kind returns the message type.
+	RequestID() []byte   // Returns the request ID.
+	SetRequestID([]byte) // Sets the request ID.
+}
+
+// Message types.
+const (
+	PingMsg byte = iota + 1
+	PongMsg
+	FindnodeMsg
+	NodesMsg
+	TalkRequestMsg
+	TalkResponseMsg
+	RequestTicketMsg
+	TicketMsg
+	RegtopicMsg
+	RegconfirmationMsg
+	TopicQueryMsg
+
+	UnknownPacket   = byte(255) // any non-decryptable packet
+	WhoareyouPacket = byte(254) // the WHOAREYOU packet
+)
+
+// Protocol messages.
+type (
+	// Unknown represents any packet that can't be decrypted.
+	Unknown struct {
+		Nonce Nonce
+	}
+
+	// WHOAREYOU contains the handshake challenge.
+	Whoareyou struct {
+		ChallengeData []byte   // Encoded challenge
+		Nonce         Nonce    // Nonce of request packet
+		IDNonce       [16]byte // Identity proof data
+		RecordSeq     uint64   // ENR sequence number of recipient
+
+		// Node is the locally known node record of recipient.
+		// This must be set by the caller of Encode.
+		Node *enode.Node
+
+		sent mclock.AbsTime // for handshake GC.
+	}
+
+	// PING is sent during liveness checks.
+	Ping struct {
+		ReqID  []byte
+		ENRSeq uint64
+	}
+
+	// PONG is the reply to PING.
+	Pong struct {
+		ReqID  []byte
+		ENRSeq uint64
+		ToIP   net.IP // These fields should mirror the UDP envelope address of the ping
+		ToPort uint16 // packet, which provides a way to discover the the external address (after NAT).
+	}
+
+	// FINDNODE is a query for nodes in the given bucket.
+	Findnode struct {
+		ReqID     []byte
+		Distances []uint
+	}
+
+	// NODES is the reply to FINDNODE and TOPICQUERY.
+	Nodes struct {
+		ReqID []byte
+		Total uint8
+		Nodes []*enr.Record
+	}
+
+	// TALKREQ is an application-level request.
+	TalkRequest struct {
+		ReqID    []byte
+		Protocol string
+		Message  []byte
+	}
+
+	// TALKRESP is the reply to TALKREQ.
+	TalkResponse struct {
+		ReqID   []byte
+		Message []byte
+	}
+
+	// REQUESTTICKET requests a ticket for a topic queue.
+	RequestTicket struct {
+		ReqID []byte
+		Topic []byte
+	}
+
+	// TICKET is the response to REQUESTTICKET.
+	Ticket struct {
+		ReqID  []byte
+		Ticket []byte
+	}
+
+	// REGTOPIC registers the sender in a topic queue using a ticket.
+	Regtopic struct {
+		ReqID  []byte
+		Ticket []byte
+		ENR    *enr.Record
+	}
+
+	// REGCONFIRMATION is the reply to REGTOPIC.
+	Regconfirmation struct {
+		ReqID      []byte
+		Registered bool
+	}
+
+	// TOPICQUERY asks for nodes with the given topic.
+	TopicQuery struct {
+		ReqID []byte
+		Topic []byte
+	}
+)
+
+// DecodeMessage decodes the message body of a packet.
+func DecodeMessage(ptype byte, body []byte) (Packet, error) {
+	var dec Packet
+	switch ptype {
+	case PingMsg:
+		dec = new(Ping)
+	case PongMsg:
+		dec = new(Pong)
+	case FindnodeMsg:
+		dec = new(Findnode)
+	case NodesMsg:
+		dec = new(Nodes)
+	case TalkRequestMsg:
+		dec = new(TalkRequest)
+	case TalkResponseMsg:
+		dec = new(TalkResponse)
+	case RequestTicketMsg:
+		dec = new(RequestTicket)
+	case TicketMsg:
+		dec = new(Ticket)
+	case RegtopicMsg:
+		dec = new(Regtopic)
+	case RegconfirmationMsg:
+		dec = new(Regconfirmation)
+	case TopicQueryMsg:
+		dec = new(TopicQuery)
+	default:
+		return nil, fmt.Errorf("unknown packet type %d", ptype)
+	}
+	if err := rlp.DecodeBytes(body, dec); err != nil {
+		return nil, err
+	}
+	if dec.RequestID() != nil && len(dec.RequestID()) > 8 {
+		return nil, ErrInvalidReqID
+	}
+	return dec, nil
+}
+
+func (*Whoareyou) Name() string        { return "WHOAREYOU/v5" }
+func (*Whoareyou) Kind() byte          { return WhoareyouPacket }
+func (*Whoareyou) RequestID() []byte   { return nil }
+func (*Whoareyou) SetRequestID([]byte) {}
+
+func (*Unknown) Name() string        { return "UNKNOWN/v5" }
+func (*Unknown) Kind() byte          { return UnknownPacket }
+func (*Unknown) RequestID() []byte   { return nil }
+func (*Unknown) SetRequestID([]byte) {}
+
+func (*Ping) Name() string             { return "PING/v5" }
+func (*Ping) Kind() byte               { return PingMsg }
+func (p *Ping) RequestID() []byte      { return p.ReqID }
+func (p *Ping) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Pong) Name() string             { return "PONG/v5" }
+func (*Pong) Kind() byte               { return PongMsg }
+func (p *Pong) RequestID() []byte      { return p.ReqID }
+func (p *Pong) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Findnode) Name() string             { return "FINDNODE/v5" }
+func (*Findnode) Kind() byte               { return FindnodeMsg }
+func (p *Findnode) RequestID() []byte      { return p.ReqID }
+func (p *Findnode) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Nodes) Name() string             { return "NODES/v5" }
+func (*Nodes) Kind() byte               { return NodesMsg }
+func (p *Nodes) RequestID() []byte      { return p.ReqID }
+func (p *Nodes) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*TalkRequest) Name() string             { return "TALKREQ/v5" }
+func (*TalkRequest) Kind() byte               { return TalkRequestMsg }
+func (p *TalkRequest) RequestID() []byte      { return p.ReqID }
+func (p *TalkRequest) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*TalkResponse) Name() string             { return "TALKRESP/v5" }
+func (*TalkResponse) Kind() byte               { return TalkResponseMsg }
+func (p *TalkResponse) RequestID() []byte      { return p.ReqID }
+func (p *TalkResponse) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*RequestTicket) Name() string             { return "REQTICKET/v5" }
+func (*RequestTicket) Kind() byte               { return RequestTicketMsg }
+func (p *RequestTicket) RequestID() []byte      { return p.ReqID }
+func (p *RequestTicket) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Regtopic) Name() string             { return "REGTOPIC/v5" }
+func (*Regtopic) Kind() byte               { return RegtopicMsg }
+func (p *Regtopic) RequestID() []byte      { return p.ReqID }
+func (p *Regtopic) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Ticket) Name() string             { return "TICKET/v5" }
+func (*Ticket) Kind() byte               { return TicketMsg }
+func (p *Ticket) RequestID() []byte      { return p.ReqID }
+func (p *Ticket) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Regconfirmation) Name() string             { return "REGCONFIRMATION/v5" }
+func (*Regconfirmation) Kind() byte               { return RegconfirmationMsg }
+func (p *Regconfirmation) RequestID() []byte      { return p.ReqID }
+func (p *Regconfirmation) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*TopicQuery) Name() string             { return "TOPICQUERY/v5" }
+func (*TopicQuery) Kind() byte               { return TopicQueryMsg }
+func (p *TopicQuery) RequestID() []byte      { return p.ReqID }
+func (p *TopicQuery) SetRequestID(id []byte) { p.ReqID = id }

+ 142 - 0
p2p/discover/v5wire/session.go

@@ -0,0 +1,142 @@
+// Copyright 2020 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 v5wire
+
+import (
+	"crypto/ecdsa"
+	crand "crypto/rand"
+	"encoding/binary"
+	"time"
+
+	"blockchain-go/common/mclock"
+	"blockchain-go/p2p/enode"
+	"blockchain-go/simplelru"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+const handshakeTimeout = time.Second
+
+// The SessionCache keeps negotiated encryption keys and
+// state for in-progress handshakes in the Discovery v5 wire protocol.
+type SessionCache struct {
+	sessions   *simplelru.LRU
+	handshakes map[sessionID]*Whoareyou
+	clock      mclock.Clock
+
+	// hooks for overriding randomness.
+	nonceGen        func(uint32) (Nonce, error)
+	maskingIVGen    func([]byte) error
+	ephemeralKeyGen func() (*ecdsa.PrivateKey, error)
+}
+
+// sessionID identifies a session or handshake.
+type sessionID struct {
+	id   enode.ID
+	addr string
+}
+
+// session contains session information
+type session struct {
+	writeKey     []byte
+	readKey      []byte
+	nonceCounter uint32
+}
+
+// keysFlipped returns a copy of s with the read and write keys flipped.
+func (s *session) keysFlipped() *session {
+	return &session{s.readKey, s.writeKey, s.nonceCounter}
+}
+
+func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
+	cache, err := simplelru.NewLRU(maxItems, nil)
+	if err != nil {
+		panic("can't create session cache")
+	}
+	return &SessionCache{
+		sessions:        cache,
+		handshakes:      make(map[sessionID]*Whoareyou),
+		clock:           clock,
+		nonceGen:        generateNonce,
+		maskingIVGen:    generateMaskingIV,
+		ephemeralKeyGen: crypto.GenerateKey,
+	}
+}
+
+func generateNonce(counter uint32) (n Nonce, err error) {
+	binary.BigEndian.PutUint32(n[:4], counter)
+	_, err = crand.Read(n[4:])
+	return n, err
+}
+
+func generateMaskingIV(buf []byte) error {
+	_, err := crand.Read(buf)
+	return err
+}
+
+// nextNonce creates a nonce for encrypting a message to the given session.
+func (sc *SessionCache) nextNonce(s *session) (Nonce, error) {
+	s.nonceCounter++
+	return sc.nonceGen(s.nonceCounter)
+}
+
+// session returns the current session for the given node, if any.
+func (sc *SessionCache) session(id enode.ID, addr string) *session {
+	item, ok := sc.sessions.Get(sessionID{id, addr})
+	if !ok {
+		return nil
+	}
+	return item.(*session)
+}
+
+// readKey returns the current read key for the given node.
+func (sc *SessionCache) readKey(id enode.ID, addr string) []byte {
+	if s := sc.session(id, addr); s != nil {
+		return s.readKey
+	}
+	return nil
+}
+
+// storeNewSession stores new encryption keys in the cache.
+func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) {
+	sc.sessions.Add(sessionID{id, addr}, s)
+}
+
+// getHandshake gets the handshake challenge we previously sent to the given remote node.
+func (sc *SessionCache) getHandshake(id enode.ID, addr string) *Whoareyou {
+	return sc.handshakes[sessionID{id, addr}]
+}
+
+// storeSentHandshake stores the handshake challenge sent to the given remote node.
+func (sc *SessionCache) storeSentHandshake(id enode.ID, addr string, challenge *Whoareyou) {
+	challenge.sent = sc.clock.Now()
+	sc.handshakes[sessionID{id, addr}] = challenge
+}
+
+// deleteHandshake deletes handshake data for the given node.
+func (sc *SessionCache) deleteHandshake(id enode.ID, addr string) {
+	delete(sc.handshakes, sessionID{id, addr})
+}
+
+// handshakeGC deletes timed-out handshakes.
+func (sc *SessionCache) handshakeGC() {
+	deadline := sc.clock.Now().Add(-handshakeTimeout)
+	for key, challenge := range sc.handshakes {
+		if challenge.sent < deadline {
+			delete(sc.handshakes, key)
+		}
+	}
+}

+ 11 - 10
p2p/server.go

@@ -2,6 +2,7 @@ package p2p
 
 import (
 	"blockchain-go/log"
+	"blockchain-go/p2p/enode"
 	"errors"
 	"net"
 	"sync"
@@ -31,22 +32,22 @@ type Server struct {
 	//peerFeed event.Feed
 	log log.Logger
 
-	//nodedb    *enode.DB
-	//localnode *enode.LocalNode
+	nodedb    *enode.DB
+	localnode *enode.LocalNode
 	//ntab      *discover.UDPv4
 	//DiscV5    *discover.UDPv5
-	//discmix   *enode.FairMix
+	discmix *enode.FairMix
 	//dialsched *dialScheduler
 
 	// Channels into the run loop.
-	quit chan struct{}
-	//addtrusted              chan *enode.Node
-	//removetrusted           chan *enode.Node
+	quit          chan struct{}
+	addtrusted    chan *enode.Node
+	removetrusted chan *enode.Node
 	//peerOp                  chan peerOpFunc
-	peerOpDone chan struct{}
-	//delpeer                 chan peerDrop
-	//checkpointPostHandshake chan *conn
-	//checkpointAddPeer       chan *conn
+	peerOpDone              chan struct{}
+	delpeer                 chan peerDrop
+	checkpointPostHandshake chan *conn
+	checkpointAddPeer       chan *conn
 
 	// State of run loop and listenLoop.
 	//inboundHistory expHeap

+ 177 - 0
simplelru/lru.go

@@ -0,0 +1,177 @@
+package simplelru
+
+import (
+	"container/list"
+	"errors"
+)
+
+// EvictCallback is used to get a callback when a cache entry is evicted
+type EvictCallback func(key interface{}, value interface{})
+
+// LRU implements a non-thread safe fixed size LRU cache
+type LRU struct {
+	size      int
+	evictList *list.List
+	items     map[interface{}]*list.Element
+	onEvict   EvictCallback
+}
+
+// entry is used to hold a value in the evictList
+type entry struct {
+	key   interface{}
+	value interface{}
+}
+
+// NewLRU constructs an LRU of the given size
+func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
+	if size <= 0 {
+		return nil, errors.New("must provide a positive size")
+	}
+	c := &LRU{
+		size:      size,
+		evictList: list.New(),
+		items:     make(map[interface{}]*list.Element),
+		onEvict:   onEvict,
+	}
+	return c, nil
+}
+
+// Purge is used to completely clear the cache.
+func (c *LRU) Purge() {
+	for k, v := range c.items {
+		if c.onEvict != nil {
+			c.onEvict(k, v.Value.(*entry).value)
+		}
+		delete(c.items, k)
+	}
+	c.evictList.Init()
+}
+
+// Add adds a value to the cache.  Returns true if an eviction occurred.
+func (c *LRU) Add(key, value interface{}) (evicted bool) {
+	// Check for existing item
+	if ent, ok := c.items[key]; ok {
+		c.evictList.MoveToFront(ent)
+		ent.Value.(*entry).value = value
+		return false
+	}
+
+	// Add new item
+	ent := &entry{key, value}
+	entry := c.evictList.PushFront(ent)
+	c.items[key] = entry
+
+	evict := c.evictList.Len() > c.size
+	// Verify size not exceeded
+	if evict {
+		c.removeOldest()
+	}
+	return evict
+}
+
+// Get looks up a key's value from the cache.
+func (c *LRU) Get(key interface{}) (value interface{}, ok bool) {
+	if ent, ok := c.items[key]; ok {
+		c.evictList.MoveToFront(ent)
+		if ent.Value.(*entry) == nil {
+			return nil, false
+		}
+		return ent.Value.(*entry).value, true
+	}
+	return
+}
+
+// Contains checks if a key is in the cache, without updating the recent-ness
+// or deleting it for being stale.
+func (c *LRU) Contains(key interface{}) (ok bool) {
+	_, ok = c.items[key]
+	return ok
+}
+
+// Peek returns the key value (or undefined if not found) without updating
+// the "recently used"-ness of the key.
+func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) {
+	var ent *list.Element
+	if ent, ok = c.items[key]; ok {
+		return ent.Value.(*entry).value, true
+	}
+	return nil, ok
+}
+
+// Remove removes the provided key from the cache, returning if the
+// key was contained.
+func (c *LRU) Remove(key interface{}) (present bool) {
+	if ent, ok := c.items[key]; ok {
+		c.removeElement(ent)
+		return true
+	}
+	return false
+}
+
+// RemoveOldest removes the oldest item from the cache.
+func (c *LRU) RemoveOldest() (key, value interface{}, ok bool) {
+	ent := c.evictList.Back()
+	if ent != nil {
+		c.removeElement(ent)
+		kv := ent.Value.(*entry)
+		return kv.key, kv.value, true
+	}
+	return nil, nil, false
+}
+
+// GetOldest returns the oldest entry
+func (c *LRU) GetOldest() (key, value interface{}, ok bool) {
+	ent := c.evictList.Back()
+	if ent != nil {
+		kv := ent.Value.(*entry)
+		return kv.key, kv.value, true
+	}
+	return nil, nil, false
+}
+
+// Keys returns a slice of the keys in the cache, from oldest to newest.
+func (c *LRU) Keys() []interface{} {
+	keys := make([]interface{}, len(c.items))
+	i := 0
+	for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
+		keys[i] = ent.Value.(*entry).key
+		i++
+	}
+	return keys
+}
+
+// Len returns the number of items in the cache.
+func (c *LRU) Len() int {
+	return c.evictList.Len()
+}
+
+// Resize changes the cache size.
+func (c *LRU) Resize(size int) (evicted int) {
+	diff := c.Len() - size
+	if diff < 0 {
+		diff = 0
+	}
+	for i := 0; i < diff; i++ {
+		c.removeOldest()
+	}
+	c.size = size
+	return diff
+}
+
+// removeOldest removes the oldest item from the cache.
+func (c *LRU) removeOldest() {
+	ent := c.evictList.Back()
+	if ent != nil {
+		c.removeElement(ent)
+	}
+}
+
+// removeElement is used to remove a given list element from the cache
+func (c *LRU) removeElement(e *list.Element) {
+	c.evictList.Remove(e)
+	kv := e.Value.(*entry)
+	delete(c.items, kv.key)
+	if c.onEvict != nil {
+		c.onEvict(kv.key, kv.value)
+	}
+}

+ 40 - 0
simplelru/lru_interface.go

@@ -0,0 +1,40 @@
+// Package simplelru provides simple LRU implementation based on build-in container/list.
+package simplelru
+
+// LRUCache is the interface for simple LRU cache.
+type LRUCache interface {
+	// Adds a value to the cache, returns true if an eviction occurred and
+	// updates the "recently used"-ness of the key.
+	Add(key, value interface{}) bool
+
+	// Returns key's value from the cache and
+	// updates the "recently used"-ness of the key. #value, isFound
+	Get(key interface{}) (value interface{}, ok bool)
+
+	// Checks if a key exists in cache without updating the recent-ness.
+	Contains(key interface{}) (ok bool)
+
+	// Returns key's value without updating the "recently used"-ness of the key.
+	Peek(key interface{}) (value interface{}, ok bool)
+
+	// Removes a key from the cache.
+	Remove(key interface{}) bool
+
+	// Removes the oldest entry from cache.
+	RemoveOldest() (interface{}, interface{}, bool)
+
+	// Returns the oldest entry from the cache. #key, value, isFound
+	GetOldest() (interface{}, interface{}, bool)
+
+	// Returns a slice of the keys in the cache, from oldest to newest.
+	Keys() []interface{}
+
+	// Returns the number of items in the cache.
+	Len() int
+
+	// Clears all cache entries.
+	Purge()
+
+	// Resizes cache, returning number evicted
+	Resize(int) int
+}

+ 206 - 0
simplelru/lru_test.go

@@ -0,0 +1,206 @@
+package simplelru
+
+import "testing"
+
+func TestLRU(t *testing.T) {
+	evictCounter := 0
+	onEvicted := func(k interface{}, v interface{}) {
+		if k != v {
+			t.Fatalf("Evict values not equal (%v!=%v)", k, v)
+		}
+		evictCounter++
+	}
+	l, err := NewLRU(128, onEvicted)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	for i := 0; i < 256; i++ {
+		l.Add(i, i)
+	}
+	if l.Len() != 128 {
+		t.Fatalf("bad len: %v", l.Len())
+	}
+
+	if evictCounter != 128 {
+		t.Fatalf("bad evict count: %v", evictCounter)
+	}
+
+	for i, k := range l.Keys() {
+		if v, ok := l.Get(k); !ok || v != k || v != i+128 {
+			t.Fatalf("bad key: %v", k)
+		}
+	}
+	for i := 0; i < 128; i++ {
+		_, ok := l.Get(i)
+		if ok {
+			t.Fatalf("should be evicted")
+		}
+	}
+	for i := 128; i < 256; i++ {
+		_, ok := l.Get(i)
+		if !ok {
+			t.Fatalf("should not be evicted")
+		}
+	}
+	for i := 128; i < 192; i++ {
+		ok := l.Remove(i)
+		if !ok {
+			t.Fatalf("should be contained")
+		}
+		ok = l.Remove(i)
+		if ok {
+			t.Fatalf("should not be contained")
+		}
+		_, ok = l.Get(i)
+		if ok {
+			t.Fatalf("should be deleted")
+		}
+	}
+
+	l.Get(192) // expect 192 to be last key in l.Keys()
+
+	for i, k := range l.Keys() {
+		if (i < 63 && k != i+193) || (i == 63 && k != 192) {
+			t.Fatalf("out of order key: %v", k)
+		}
+	}
+
+	l.Purge()
+	if l.Len() != 0 {
+		t.Fatalf("bad len: %v", l.Len())
+	}
+	if _, ok := l.Get(200); ok {
+		t.Fatalf("should contain nothing")
+	}
+}
+
+func TestLRU_GetOldest_RemoveOldest(t *testing.T) {
+	l, err := NewLRU(128, nil)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+	for i := 0; i < 256; i++ {
+		l.Add(i, i)
+	}
+	k, _, ok := l.GetOldest()
+	if !ok {
+		t.Fatalf("missing")
+	}
+	if k.(int) != 128 {
+		t.Fatalf("bad: %v", k)
+	}
+
+	k, _, ok = l.RemoveOldest()
+	if !ok {
+		t.Fatalf("missing")
+	}
+	if k.(int) != 128 {
+		t.Fatalf("bad: %v", k)
+	}
+
+	k, _, ok = l.RemoveOldest()
+	if !ok {
+		t.Fatalf("missing")
+	}
+	if k.(int) != 129 {
+		t.Fatalf("bad: %v", k)
+	}
+}
+
+// Test that Add returns true/false if an eviction occurred
+func TestLRU_Add(t *testing.T) {
+	evictCounter := 0
+	onEvicted := func(k interface{}, v interface{}) {
+		evictCounter++
+	}
+
+	l, err := NewLRU(1, onEvicted)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	if l.Add(1, 1) == true || evictCounter != 0 {
+		t.Errorf("should not have an eviction")
+	}
+	if l.Add(2, 2) == false || evictCounter != 1 {
+		t.Errorf("should have an eviction")
+	}
+}
+
+// Test that Contains doesn't update recent-ness
+func TestLRU_Contains(t *testing.T) {
+	l, err := NewLRU(2, nil)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	l.Add(1, 1)
+	l.Add(2, 2)
+	if !l.Contains(1) {
+		t.Errorf("1 should be contained")
+	}
+
+	l.Add(3, 3)
+	if l.Contains(1) {
+		t.Errorf("Contains should not have updated recent-ness of 1")
+	}
+}
+
+// Test that Peek doesn't update recent-ness
+func TestLRU_Peek(t *testing.T) {
+	l, err := NewLRU(2, nil)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	l.Add(1, 1)
+	l.Add(2, 2)
+	if v, ok := l.Peek(1); !ok || v != 1 {
+		t.Errorf("1 should be set to 1: %v, %v", v, ok)
+	}
+
+	l.Add(3, 3)
+	if l.Contains(1) {
+		t.Errorf("should not have updated recent-ness of 1")
+	}
+}
+
+// Test that Resize can upsize and downsize
+func TestLRU_Resize(t *testing.T) {
+	onEvictCounter := 0
+	onEvicted := func(k interface{}, v interface{}) {
+		onEvictCounter++
+	}
+	l, err := NewLRU(2, onEvicted)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	// Downsize
+	l.Add(1, 1)
+	l.Add(2, 2)
+	evicted := l.Resize(1)
+	if evicted != 1 {
+		t.Errorf("1 element should have been evicted: %v", evicted)
+	}
+	if onEvictCounter != 1 {
+		t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter)
+	}
+
+	l.Add(3, 3)
+	if l.Contains(1) {
+		t.Errorf("Element 1 should have been evicted")
+	}
+
+	// Upsize
+	evicted = l.Resize(2)
+	if evicted != 0 {
+		t.Errorf("0 elements should have been evicted: %v", evicted)
+	}
+
+	l.Add(4, 4)
+	if !l.Contains(3) || !l.Contains(4) {
+		t.Errorf("Cache should have contained 2 elements")
+	}
+}