Unverified Commit 350a87dd authored by Felix Lange's avatar Felix Lange Committed by GitHub

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.
parent 8deec2e4
...@@ -53,15 +53,17 @@ const ( ...@@ -53,15 +53,17 @@ const (
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24 bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
tableIPLimit, tableSubnet = 10, 24 tableIPLimit, tableSubnet = 10, 24
maxFindnodeFailures = 5 // Nodes exceeding this limit are dropped refreshInterval = 30 * time.Minute
refreshInterval = 30 * time.Minute revalidateInterval = 10 * time.Second
revalidateInterval = 10 * time.Second copyNodesInterval = 30 * time.Second
copyNodesInterval = 30 * time.Second seedMinTableTime = 5 * time.Minute
seedMinTableTime = 5 * time.Minute seedCount = 30
seedCount = 30 seedMaxAge = 5 * 24 * time.Hour
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 { type Table struct {
mutex sync.Mutex // protects buckets, bucket content, nursery, rand mutex sync.Mutex // protects buckets, bucket content, nursery, rand
buckets [nBuckets]*bucket // index of known nodes by distance buckets [nBuckets]*bucket // index of known nodes by distance
...@@ -80,12 +82,13 @@ type Table struct { ...@@ -80,12 +82,13 @@ type Table struct {
nodeAddedHook func(*node) // for testing nodeAddedHook func(*node) // for testing
} }
// transport is implemented by UDP transports. // transport is implemented by the UDP transports.
type transport interface { type transport interface {
Self() *enode.Node Self() *enode.Node
lookupRandom() []*enode.Node lookupRandom() []*enode.Node
lookupSelf() []*enode.Node lookupSelf() []*enode.Node
ping(*enode.Node) error ping(*enode.Node) (seq uint64, err error)
requestENR(*enode.Node) (*enode.Node, error)
} }
// bucket contains nodes, ordered by their last activity. the entry // bucket contains nodes, ordered by their last activity. the entry
...@@ -175,14 +178,16 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { ...@@ -175,14 +178,16 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) {
return i + 1 return i + 1
} }
// Resolve searches for a specific node with the given ID. // getNode returns the node with the given ID or nil if it isn't in the table.
// It returns nil if the node could not be found. func (tab *Table) getNode(id enode.ID) *enode.Node {
func (tab *Table) Resolve(n *enode.Node) *enode.Node {
tab.mutex.Lock() tab.mutex.Lock()
cl := tab.closest(n.ID(), 1, false) defer tab.mutex.Unlock()
tab.mutex.Unlock()
if len(cl.entries) > 0 && cl.entries[0].ID() == n.ID() { b := tab.bucket(id)
return unwrapNode(cl.entries[0]) for _, e := range b.entries {
if e.ID() == id {
return unwrapNode(e)
}
} }
return nil return nil
} }
...@@ -226,7 +231,7 @@ func (tab *Table) refresh() <-chan struct{} { ...@@ -226,7 +231,7 @@ func (tab *Table) refresh() <-chan struct{} {
return done return done
} }
// loop schedules refresh, revalidate runs and coordinates shutdown. // loop schedules runs of doRefresh, doRevalidate and copyLiveNodes.
func (tab *Table) loop() { func (tab *Table) loop() {
var ( var (
revalidate = time.NewTimer(tab.nextRevalidateTime()) revalidate = time.NewTimer(tab.nextRevalidateTime())
...@@ -288,9 +293,8 @@ loop: ...@@ -288,9 +293,8 @@ loop:
close(tab.closed) close(tab.closed)
} }
// doRefresh performs a lookup for a random target to keep buckets // doRefresh performs a lookup for a random target to keep buckets full. seed nodes are
// full. seed nodes are inserted if the table is empty (initial // inserted if the table is empty (initial bootstrap or discarded faulty peers).
// bootstrap or discarded faulty peers).
func (tab *Table) doRefresh(done chan struct{}) { func (tab *Table) doRefresh(done chan struct{}) {
defer close(done) defer close(done)
...@@ -324,8 +328,8 @@ func (tab *Table) loadSeedNodes() { ...@@ -324,8 +328,8 @@ func (tab *Table) loadSeedNodes() {
} }
} }
// doRevalidate checks that the last node in a random bucket is still live // doRevalidate checks that the last node in a random bucket is still live and replaces or
// and replaces or deletes the node if it isn't. // deletes the node if it isn't.
func (tab *Table) doRevalidate(done chan<- struct{}) { func (tab *Table) doRevalidate(done chan<- struct{}) {
defer func() { done <- struct{}{} }() defer func() { done <- struct{}{} }()
...@@ -336,7 +340,17 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { ...@@ -336,7 +340,17 @@ func (tab *Table) doRevalidate(done chan<- struct{}) {
} }
// Ping the selected node and wait for a pong. // Ping the selected node and wait for a pong.
err := tab.net.ping(unwrapNode(last)) 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() tab.mutex.Lock()
defer tab.mutex.Unlock() defer tab.mutex.Unlock()
......
...@@ -368,6 +368,34 @@ func TestTable_addSeenNode(t *testing.T) { ...@@ -368,6 +368,34 @@ func TestTable_addSeenNode(t *testing.T) {
checkIPLimitInvariant(t, tab) checkIPLimitInvariant(t, tab)
} }
// This test checks that ENR updates happen during revalidation. If a node in the table
// announces a new sequence number, the new record should be pulled.
func TestTable_revalidateSyncRecord(t *testing.T) {
transport := newPingRecorder()
tab, db := newTestTable(transport)
<-tab.initDone
defer db.Close()
defer tab.close()
// Insert a node.
var r enr.Record
r.Set(enr.IP(net.IP{127, 0, 0, 1}))
id := enode.ID{1}
n1 := wrapNode(enode.SignNull(&r, id))
tab.addSeenNode(n1)
// Update the node record.
r.Set(enr.WithEntry("foo", "bar"))
n2 := enode.SignNull(&r, id)
transport.updateRecord(n2)
tab.doRevalidate(make(chan struct{}, 1))
intable := tab.getNode(id)
if !reflect.DeepEqual(intable, n2) {
t.Fatalf("table contains old record with seq %d, want seq %d", intable.Seq(), n2.Seq())
}
}
// gen wraps quick.Value so it's easier to use. // gen wraps quick.Value so it's easier to use.
// it generates a random value of the given value's type. // it generates a random value of the given value's type.
func gen(typ interface{}, rand *rand.Rand) interface{} { func gen(typ interface{}, rand *rand.Rand) interface{} {
......
...@@ -98,6 +98,7 @@ func fillTable(tab *Table, nodes []*node) { ...@@ -98,6 +98,7 @@ func fillTable(tab *Table, nodes []*node) {
type pingRecorder struct { type pingRecorder struct {
mu sync.Mutex mu sync.Mutex
dead, pinged map[enode.ID]bool dead, pinged map[enode.ID]bool
records map[enode.ID]*enode.Node
n *enode.Node n *enode.Node
} }
...@@ -107,38 +108,53 @@ func newPingRecorder() *pingRecorder { ...@@ -107,38 +108,53 @@ func newPingRecorder() *pingRecorder {
n := enode.SignNull(&r, enode.ID{}) n := enode.SignNull(&r, enode.ID{})
return &pingRecorder{ return &pingRecorder{
dead: make(map[enode.ID]bool), dead: make(map[enode.ID]bool),
pinged: make(map[enode.ID]bool), pinged: make(map[enode.ID]bool),
n: n, records: make(map[enode.ID]*enode.Node),
n: n,
} }
} }
func (t *pingRecorder) Self() *enode.Node { // setRecord updates a node record. Future calls to ping and
return nullNode // requestENR will return this record.
func (t *pingRecorder) updateRecord(n *enode.Node) {
t.mu.Lock()
defer t.mu.Unlock()
t.records[n.ID()] = n
} }
func (t *pingRecorder) ping(n *enode.Node) error { // Stubs to satisfy the transport interface.
func (t *pingRecorder) Self() *enode.Node { return nullNode }
func (t *pingRecorder) lookupSelf() []*enode.Node { return nil }
func (t *pingRecorder) lookupRandom() []*enode.Node { return nil }
func (t *pingRecorder) close() {}
// ping simulates a ping request.
func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
t.pinged[n.ID()] = true t.pinged[n.ID()] = true
if t.dead[n.ID()] { if t.dead[n.ID()] {
return errTimeout return 0, errTimeout
} else {
return nil
} }
if t.records[n.ID()] != nil {
seq = t.records[n.ID()].Seq()
}
return seq, nil
} }
func (t *pingRecorder) lookupSelf() []*enode.Node { // requestENR simulates an ENR request.
return nil func (t *pingRecorder) requestENR(n *enode.Node) (*enode.Node, error) {
} t.mu.Lock()
defer t.mu.Unlock()
func (t *pingRecorder) lookupRandom() []*enode.Node { if t.dead[n.ID()] || t.records[n.ID()] == nil {
return nil return nil, errTimeout
}
return t.records[n.ID()], nil
} }
func (t *pingRecorder) close() {}
func hasDuplicates(slice []*node) bool { func hasDuplicates(slice []*node) bool {
seen := make(map[enode.ID]bool) seen := make(map[enode.ID]bool)
for i, e := range slice { for i, e := range slice {
......
This diff is collapsed.
...@@ -54,11 +54,11 @@ func TestUDPv4_Lookup(t *testing.T) { ...@@ -54,11 +54,11 @@ func TestUDPv4_Lookup(t *testing.T) {
n, key := lookupTestnet.nodeByAddr(to) n, key := lookupTestnet.nodeByAddr(to)
switch p.(type) { switch p.(type) {
case *pingV4: case *pingV4:
test.packetInFrom(nil, key, to, p_pongV4, &pongV4{Expiration: futureExp, ReplyTok: hash}) test.packetInFrom(nil, key, to, &pongV4{Expiration: futureExp, ReplyTok: hash})
case *findnodeV4: case *findnodeV4:
dist := enode.LogDist(n.ID(), lookupTestnet.target.id()) dist := enode.LogDist(n.ID(), lookupTestnet.target.id())
nodes := lookupTestnet.nodesAtDistance(dist - 1) nodes := lookupTestnet.nodesAtDistance(dist - 1)
test.packetInFrom(nil, key, to, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: nodes}) test.packetInFrom(nil, key, to, &neighborsV4{Expiration: futureExp, Nodes: nodes})
} }
}) })
} }
......
...@@ -37,6 +37,7 @@ import ( ...@@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/internal/testlog" "github.com/ethereum/go-ethereum/internal/testlog"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
...@@ -91,19 +92,19 @@ func (test *udpTest) close() { ...@@ -91,19 +92,19 @@ func (test *udpTest) close() {
} }
// handles a packet as if it had been sent to the transport. // 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.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. // 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() test.t.Helper()
enc, _, err := test.udp.encode(key, ptype, data) enc, _, err := test.udp.encode(key, data)
if err != nil { 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) test.sent = append(test.sent, enc)
if err = test.udp.handlePacket(addr, enc); err != wantError { if err = test.udp.handlePacket(addr, enc); err != wantError {
...@@ -139,10 +140,10 @@ func TestUDPv4_packetErrors(t *testing.T) { ...@@ -139,10 +140,10 @@ func TestUDPv4_packetErrors(t *testing.T) {
test := newUDPTest(t) test := newUDPTest(t)
defer test.close() defer test.close()
test.packetIn(errExpired, p_pingV4, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4}) test.packetIn(errExpired, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4})
test.packetIn(errUnsolicitedReply, p_pongV4, &pongV4{ReplyTok: []byte{}, Expiration: futureExp}) test.packetIn(errUnsolicitedReply, &pongV4{ReplyTok: []byte{}, Expiration: futureExp})
test.packetIn(errUnknownNode, p_findnodeV4, &findnodeV4{Expiration: futureExp}) test.packetIn(errUnknownNode, &findnodeV4{Expiration: futureExp})
test.packetIn(errUnsolicitedReply, p_neighborsV4, &neighborsV4{Expiration: futureExp}) test.packetIn(errUnsolicitedReply, &neighborsV4{Expiration: futureExp})
} }
func TestUDPv4_pingTimeout(t *testing.T) { func TestUDPv4_pingTimeout(t *testing.T) {
...@@ -153,11 +154,21 @@ func TestUDPv4_pingTimeout(t *testing.T) { ...@@ -153,11 +154,21 @@ func TestUDPv4_pingTimeout(t *testing.T) {
key := newkey() key := newkey()
toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
node := enode.NewV4(&key.PublicKey, toaddr.IP, 0, toaddr.Port) 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) 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) { func TestUDPv4_responseTimeouts(t *testing.T) {
t.Parallel() t.Parallel()
test := newUDPTest(t) test := newUDPTest(t)
...@@ -192,7 +203,7 @@ func TestUDPv4_responseTimeouts(t *testing.T) { ...@@ -192,7 +203,7 @@ func TestUDPv4_responseTimeouts(t *testing.T) {
p.errc = nilErr p.errc = nilErr
test.udp.addReplyMatcher <- p test.udp.addReplyMatcher <- p
time.AfterFunc(randomDuration(60*time.Millisecond), func() { 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) t.Logf("not matched: %v", p)
} }
}) })
...@@ -277,7 +288,7 @@ func TestUDPv4_findnode(t *testing.T) { ...@@ -277,7 +288,7 @@ func TestUDPv4_findnode(t *testing.T) {
// check that closest neighbors are returned. // check that closest neighbors are returned.
expected := test.table.closest(testTarget.id(), bucketSize, true) 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) { waitNeighbors := func(want []*node) {
test.waitPacketOut(func(p *neighborsV4, to *net.UDPAddr, hash []byte) { test.waitPacketOut(func(p *neighborsV4, to *net.UDPAddr, hash []byte) {
if len(p.Nodes) != len(want) { if len(p.Nodes) != len(want) {
...@@ -340,8 +351,8 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { ...@@ -340,8 +351,8 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
for i := range list { for i := range list {
rpclist[i] = nodeToRPC(list[i]) rpclist[i] = nodeToRPC(list[i])
} }
test.packetIn(nil, p_neighborsV4, &neighborsV4{Expiration: futureExp, Nodes: rpclist[:2]}) test.packetIn(nil, &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:]})
// check that the sent neighbors are all returned by findnode // check that the sent neighbors are all returned by findnode
select { select {
...@@ -357,6 +368,7 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) { ...@@ -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) { func TestUDPv4_pingMatch(t *testing.T) {
test := newUDPTest(t) test := newUDPTest(t)
defer test.close() defer test.close()
...@@ -364,22 +376,23 @@ func TestUDPv4_pingMatch(t *testing.T) { ...@@ -364,22 +376,23 @@ func TestUDPv4_pingMatch(t *testing.T) {
randToken := make([]byte, 32) randToken := make([]byte, 32)
crand.Read(randToken) 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(*pongV4, *net.UDPAddr, []byte) {})
test.waitPacketOut(func(*pingV4, *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) { func TestUDPv4_pingMatchIP(t *testing.T) {
test := newUDPTest(t) test := newUDPTest(t)
defer test.close() 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(*pongV4, *net.UDPAddr, []byte) {})
test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) { test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) {
wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000} 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, ReplyTok: hash,
To: testLocalAnnounced, To: testLocalAnnounced,
Expiration: futureExp, Expiration: futureExp,
...@@ -394,9 +407,9 @@ func TestUDPv4_successfulPing(t *testing.T) { ...@@ -394,9 +407,9 @@ func TestUDPv4_successfulPing(t *testing.T) {
defer test.close() defer test.close()
// The remote side sends a ping packet to initiate the exchange. // 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) { test.waitPacketOut(func(p *pongV4, to *net.UDPAddr, hash []byte) {
pinghash := test.sent[0][:macSize] pinghash := test.sent[0][:macSize]
if !bytes.Equal(p.ReplyTok, pinghash) { if !bytes.Equal(p.ReplyTok, pinghash) {
...@@ -413,7 +426,7 @@ func TestUDPv4_successfulPing(t *testing.T) { ...@@ -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) { test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) {
if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) { if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) {
t.Errorf("got ping.From %#v, want %#v", 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) { ...@@ -427,10 +440,10 @@ func TestUDPv4_successfulPing(t *testing.T) {
if !reflect.DeepEqual(p.To, wantTo) { if !reflect.DeepEqual(p.To, wantTo) {
t.Errorf("got ping.To %v, want %v", 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. // pong packet.
select { select {
case n := <-added: case n := <-added:
...@@ -452,6 +465,45 @@ func TestUDPv4_successfulPing(t *testing.T) { ...@@ -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 { var testPackets = []struct {
input string input string
wantPacket interface{} wantPacket interface{}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment