p2p/discover: add initial discovery v5 implementation (#20750)

This adds an implementation of the current discovery v5 spec.

There is full integration with cmd/devp2p and enode.Iterator in this
version. In theory we could enable the new protocol as a replacement of
discovery v4 at any time. In practice, there will likely be a few more
changes to the spec and implementation before this can happen.
This commit is contained in:
Felix Lange
2020-04-08 09:57:23 +02:00
committed by GitHub
parent 671f22be38
commit b7394d7942
19 changed files with 2976 additions and 80 deletions

View File

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
)
func TestUDPv4_Lookup(t *testing.T) {
@ -32,7 +33,7 @@ func TestUDPv4_Lookup(t *testing.T) {
test := newUDPTest(t)
// Lookup on empty table returns no nodes.
targetKey, _ := decodePubkey(lookupTestnet.target)
targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target)
if results := test.udp.LookupPubkey(targetKey); len(results) > 0 {
t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results)
}
@ -59,15 +60,7 @@ func TestUDPv4_Lookup(t *testing.T) {
if len(results) != bucketSize {
t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize)
}
if hasDuplicates(wrapNodes(results)) {
t.Errorf("result set contains duplicate entries")
}
if !sortedByDistanceTo(lookupTestnet.target.id(), wrapNodes(results)) {
t.Errorf("result set not sorted by distance to target")
}
if err := checkNodesEqual(results, lookupTestnet.closest(bucketSize)); err != nil {
t.Errorf("results aren't the closest %d nodes\n%v", bucketSize, err)
}
checkLookupResults(t, lookupTestnet, results)
}
func TestUDPv4_LookupIterator(t *testing.T) {
@ -156,6 +149,26 @@ func serveTestnet(test *udpTest, testnet *preminedTestnet) {
}
}
// checkLookupResults verifies that the results of a lookup are the closest nodes to
// the testnet's target.
func checkLookupResults(t *testing.T, tn *preminedTestnet, results []*enode.Node) {
t.Helper()
t.Logf("results:")
for _, e := range results {
t.Logf(" ld=%d, %x", enode.LogDist(tn.target.id(), e.ID()), e.ID().Bytes())
}
if hasDuplicates(wrapNodes(results)) {
t.Errorf("result set contains duplicate entries")
}
if !sortedByDistanceTo(tn.target.id(), wrapNodes(results)) {
t.Errorf("result set not sorted by distance to target")
}
wantNodes := tn.closest(len(results))
if err := checkNodesEqual(results, wantNodes); err != nil {
t.Error(err)
}
}
// This is the test network for the Lookup test.
// The nodes were obtained by running lookupTestnet.mine with a random NodeID as target.
var lookupTestnet = &preminedTestnet{
@ -242,8 +255,12 @@ func (tn *preminedTestnet) nodes() []*enode.Node {
func (tn *preminedTestnet) node(dist, index int) *enode.Node {
key := tn.dists[dist][index]
ip := net.IP{127, byte(dist >> 8), byte(dist), byte(index)}
return enode.NewV4(&key.PublicKey, ip, 0, 5000)
rec := new(enr.Record)
rec.Set(enr.IP{127, byte(dist >> 8), byte(dist), byte(index)})
rec.Set(enr.UDP(5000))
enode.SignV4(rec, key)
n, _ := enode.New(enode.ValidSchemes, rec)
return n
}
func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.PrivateKey) {
@ -261,6 +278,19 @@ func (tn *preminedTestnet) nodesAtDistance(dist int) []rpcNode {
return result
}
func (tn *preminedTestnet) neighborsAtDistance(base *enode.Node, distance uint, elems int) []*enode.Node {
nodes := nodesByDistance{target: base.ID()}
for d := range lookupTestnet.dists {
for i := range lookupTestnet.dists[d] {
n := lookupTestnet.node(d, i)
if uint(enode.LogDist(n.ID(), base.ID())) == distance {
nodes.push(wrapNode(n), elems)
}
}
}
return unwrapNodes(nodes.entries)
}
func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) {
for d := range tn.dists {
for i := range tn.dists[d] {