p2p/discover: add support for EIP-868 (v4 ENR extension) (#19540)

This change implements EIP-868. The UDPv4 transport announces support
for the extension in ping/pong and handles enrRequest messages.

There are two uses of the extension: If a remote node announces support
for EIP-868 in their pong, node revalidation pulls the node's record.
The Resolve method requests the record unconditionally.
This commit is contained in:
Felix Lange
2019-05-15 06:47:45 +02:00
committed by GitHub
parent 8deec2e45a
commit 350a87dd3c
6 changed files with 405 additions and 135 deletions

View File

@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/internal/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/rlp"
)
@ -91,19 +92,19 @@ func (test *udpTest) close() {
}
// handles a packet as if it had been sent to the transport.
func (test *udpTest) packetIn(wantError error, ptype byte, data packetV4) {
func (test *udpTest) packetIn(wantError error, data packetV4) {
test.t.Helper()
test.packetInFrom(wantError, test.remotekey, test.remoteaddr, ptype, data)
test.packetInFrom(wantError, test.remotekey, test.remoteaddr, data)
}
// handles a packet as if it had been sent to the transport by the key/endpoint.
func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, ptype byte, data packetV4) {
func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, data packetV4) {
test.t.Helper()
enc, _, err := test.udp.encode(key, ptype, data)
enc, _, err := test.udp.encode(key, data)
if err != nil {
test.t.Errorf("packet (%d) encode error: %v", ptype, err)
test.t.Errorf("%s encode error: %v", data.name(), err)
}
test.sent = append(test.sent, enc)
if err = test.udp.handlePacket(addr, enc); err != wantError {
@ -139,10 +140,10 @@ func TestUDPv4_packetErrors(t *testing.T) {
test := newUDPTest(t)
defer test.close()
test.packetIn(errExpired, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4})
test.packetIn(errUnsolicitedReply, p_pongV4, &pongV4{ReplyTok: []byte{}, Expiration: futureExp})
test.packetIn(errUnknownNode, p_findnodeV4, &findnodeV4{Expiration: futureExp})
test.packetIn(errUnsolicitedReply, p_neighborsV4, &neighborsV4{Expiration: futureExp})
test.packetIn(errExpired, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4})
test.packetIn(errUnsolicitedReply, &pongV4{ReplyTok: []byte{}, Expiration: futureExp})
test.packetIn(errUnknownNode, &findnodeV4{Expiration: futureExp})
test.packetIn(errUnsolicitedReply, &neighborsV4{Expiration: futureExp})
}
func TestUDPv4_pingTimeout(t *testing.T) {
@ -153,11 +154,21 @@ func TestUDPv4_pingTimeout(t *testing.T) {
key := newkey()
toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
node := enode.NewV4(&key.PublicKey, toaddr.IP, 0, toaddr.Port)
if err := test.udp.ping(node); err != errTimeout {
if _, err := test.udp.ping(node); err != errTimeout {
t.Error("expected timeout error, got", err)
}
}
type testPacket byte
func (req testPacket) kind() byte { return byte(req) }
func (req testPacket) name() string { return "" }
func (req testPacket) preverify(*UDPv4, *net.UDPAddr, enode.ID, encPubkey) error {
return nil
}
func (req testPacket) handle(*UDPv4, *net.UDPAddr, enode.ID, []byte) {
}
func TestUDPv4_responseTimeouts(t *testing.T) {
t.Parallel()
test := newUDPTest(t)
@ -192,7 +203,7 @@ func TestUDPv4_responseTimeouts(t *testing.T) {
p.errc = nilErr
test.udp.addReplyMatcher <- p
time.AfterFunc(randomDuration(60*time.Millisecond), func() {
if !test.udp.handleReply(p.from, p.ip, p.ptype, nil) {
if !test.udp.handleReply(p.from, p.ip, testPacket(p.ptype)) {
t.Logf("not matched: %v", p)
}
})
@ -277,7 +288,7 @@ func TestUDPv4_findnode(t *testing.T) {
// check that closest neighbors are returned.
expected := test.table.closest(testTarget.id(), bucketSize, true)
test.packetIn(nil, p_findnodeV4, &findnodeV4{Target: testTarget, Expiration: futureExp})
test.packetIn(nil, &findnodeV4{Target: testTarget, Expiration: futureExp})
waitNeighbors := func(want []*node) {
test.waitPacketOut(func(p *neighborsV4, to *net.UDPAddr, hash []byte) {
if len(p.Nodes) != len(want) {
@ -340,8 +351,8 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
for i := range list {
rpclist[i] = nodeToRPC(list[i])
}
test.packetIn(nil, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: rpclist[:2]})
test.packetIn(nil, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: rpclist[2:]})
test.packetIn(nil, &neighborsV4{Expiration: futureExp, Nodes: rpclist[:2]})
test.packetIn(nil, &neighborsV4{Expiration: futureExp, Nodes: rpclist[2:]})
// check that the sent neighbors are all returned by findnode
select {
@ -357,6 +368,7 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
}
}
// This test checks that reply matching of pong verifies the ping hash.
func TestUDPv4_pingMatch(t *testing.T) {
test := newUDPTest(t)
defer test.close()
@ -364,22 +376,23 @@ func TestUDPv4_pingMatch(t *testing.T) {
randToken := make([]byte, 32)
crand.Read(randToken)
test.packetIn(nil, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
test.packetIn(nil, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {})
test.waitPacketOut(func(*pingV4, *net.UDPAddr, []byte) {})
test.packetIn(errUnsolicitedReply, p_pongV4, &pongV4{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp})
test.packetIn(errUnsolicitedReply, &pongV4{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp})
}
// This test checks that reply matching of pong verifies the sender IP address.
func TestUDPv4_pingMatchIP(t *testing.T) {
test := newUDPTest(t)
defer test.close()
test.packetIn(nil, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
test.packetIn(nil, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {})
test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) {
wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000}
test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, p_pongV4, &pongV4{
test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &pongV4{
ReplyTok: hash,
To: testLocalAnnounced,
Expiration: futureExp,
@ -394,9 +407,9 @@ func TestUDPv4_successfulPing(t *testing.T) {
defer test.close()
// The remote side sends a ping packet to initiate the exchange.
go test.packetIn(nil, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
go test.packetIn(nil, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
// the ping is replied to.
// The ping is replied to.
test.waitPacketOut(func(p *pongV4, to *net.UDPAddr, hash []byte) {
pinghash := test.sent[0][:macSize]
if !bytes.Equal(p.ReplyTok, pinghash) {
@ -413,7 +426,7 @@ func TestUDPv4_successfulPing(t *testing.T) {
}
})
// remote is unknown, the table pings back.
// Remote is unknown, the table pings back.
test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) {
if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) {
t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint())
@ -427,10 +440,10 @@ func TestUDPv4_successfulPing(t *testing.T) {
if !reflect.DeepEqual(p.To, wantTo) {
t.Errorf("got ping.To %v, want %v", p.To, wantTo)
}
test.packetIn(nil, p_pongV4, &pongV4{ReplyTok: hash, Expiration: futureExp})
test.packetIn(nil, &pongV4{ReplyTok: hash, Expiration: futureExp})
})
// the node should be added to the table shortly after getting the
// The node should be added to the table shortly after getting the
// pong packet.
select {
case n := <-added:
@ -452,6 +465,45 @@ func TestUDPv4_successfulPing(t *testing.T) {
}
}
// This test checks that EIP-868 requests work.
func TestUDPv4_EIP868(t *testing.T) {
test := newUDPTest(t)
defer test.close()
test.udp.localNode.Set(enr.WithEntry("foo", "bar"))
wantNode := test.udp.localNode.Node()
// ENR requests aren't allowed before endpoint proof.
test.packetIn(errUnknownNode, &enrRequestV4{Expiration: futureExp})
// Perform endpoint proof and check for sequence number in packet tail.
test.packetIn(nil, &pingV4{Expiration: futureExp})
test.waitPacketOut(func(p *pongV4, addr *net.UDPAddr, hash []byte) {
if seq := seqFromTail(p.Rest); seq != wantNode.Seq() {
t.Errorf("wrong sequence number in pong: %d, want %d", seq, wantNode.Seq())
}
})
test.waitPacketOut(func(p *pingV4, addr *net.UDPAddr, hash []byte) {
if seq := seqFromTail(p.Rest); seq != wantNode.Seq() {
t.Errorf("wrong sequence number in ping: %d, want %d", seq, wantNode.Seq())
}
test.packetIn(nil, &pongV4{Expiration: futureExp, ReplyTok: hash})
})
// Request should work now.
test.packetIn(nil, &enrRequestV4{Expiration: futureExp})
test.waitPacketOut(func(p *enrResponseV4, addr *net.UDPAddr, hash []byte) {
n, err := enode.New(enode.ValidSchemes, &p.Record)
if err != nil {
t.Fatalf("invalid record: %v", err)
}
if !reflect.DeepEqual(n, wantNode) {
t.Fatalf("wrong node in enrResponse: %v", n)
}
})
}
// EIP-8 test vectors.
var testPackets = []struct {
input string
wantPacket interface{}