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:
@ -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{}
|
||||
|
Reference in New Issue
Block a user