Unverified Commit f0c6f921 authored by Felix Lange's avatar Felix Lange Committed by GitHub

p2p/discover, p2p/enode: rework endpoint proof handling, packet logging (#18963)

This change resolves multiple issues around handling of endpoint proofs.
The proof is now done separately for each IP and completing the proof
requires a matching ping hash.

Also remove waitping because it's equivalent to sleep. waitping was
slightly more efficient, but that may cause issues with findnode if
packets are reordered and the remote end sees findnode before pong.

Logging of received packets was hitherto done after handling the packet,
which meant that sent replies were logged before the packet that
generated them. This change splits up packet handling into 'preverify'
and 'handle'. The error from 'preverify' is logged, but 'handle' happens
after the message is logged. This fixes the order. Packet logs now
contain the node ID.
parent 74c38902
...@@ -34,6 +34,7 @@ import ( ...@@ -34,6 +34,7 @@ import (
type node struct { type node struct {
enode.Node enode.Node
addedAt time.Time // time when the node was added to the table addedAt time.Time // time when the node was added to the table
livenessChecks uint // how often liveness was checked
} }
type encPubkey [64]byte type encPubkey [64]byte
......
...@@ -75,6 +75,8 @@ type Table struct { ...@@ -75,6 +75,8 @@ type Table struct {
net transport net transport
refreshReq chan chan struct{} refreshReq chan chan struct{}
initDone chan struct{} initDone chan struct{}
closeOnce sync.Once
closeReq chan struct{} closeReq chan struct{}
closed chan struct{} closed chan struct{}
...@@ -180,16 +182,14 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { ...@@ -180,16 +182,14 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) {
// Close terminates the network listener and flushes the node database. // Close terminates the network listener and flushes the node database.
func (tab *Table) Close() { func (tab *Table) Close() {
tab.closeOnce.Do(func() {
if tab.net != nil { if tab.net != nil {
tab.net.close() tab.net.close()
} }
// Wait for loop to end.
select { close(tab.closeReq)
case <-tab.closed: <-tab.closed
// already closed. })
case tab.closeReq <- struct{}{}:
<-tab.closed // wait for refreshLoop to end.
}
} }
// setFallbackNodes sets the initial points of contact. These nodes // setFallbackNodes sets the initial points of contact. These nodes
...@@ -290,31 +290,39 @@ func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node { ...@@ -290,31 +290,39 @@ func (tab *Table) lookup(targetKey encPubkey, refreshIfEmpty bool) []*node {
// we have asked all closest nodes, stop the search // we have asked all closest nodes, stop the search
break break
} }
// wait for the next reply select {
for _, n := range <-reply { case nodes := <-reply:
for _, n := range nodes {
if n != nil && !seen[n.ID()] { if n != nil && !seen[n.ID()] {
seen[n.ID()] = true seen[n.ID()] = true
result.push(n, bucketSize) result.push(n, bucketSize)
} }
} }
case <-tab.closeReq:
return nil // shutdown, no need to continue.
}
pendingQueries-- pendingQueries--
} }
return result.entries return result.entries
} }
func (tab *Table) findnode(n *node, targetKey encPubkey, reply chan<- []*node) { func (tab *Table) findnode(n *node, targetKey encPubkey, reply chan<- []*node) {
fails := tab.db.FindFails(n.ID()) fails := tab.db.FindFails(n.ID(), n.IP())
r, err := tab.net.findnode(n.ID(), n.addr(), targetKey) r, err := tab.net.findnode(n.ID(), n.addr(), targetKey)
if err != nil || len(r) == 0 { if err == errClosed {
// Avoid recording failures on shutdown.
reply <- nil
return
} else if err != nil || len(r) == 0 {
fails++ fails++
tab.db.UpdateFindFails(n.ID(), fails) tab.db.UpdateFindFails(n.ID(), n.IP(), fails)
log.Trace("Findnode failed", "id", n.ID(), "failcount", fails, "err", err) log.Trace("Findnode failed", "id", n.ID(), "failcount", fails, "err", err)
if fails >= maxFindnodeFailures { if fails >= maxFindnodeFailures {
log.Trace("Too many findnode failures, dropping", "id", n.ID(), "failcount", fails) log.Trace("Too many findnode failures, dropping", "id", n.ID(), "failcount", fails)
tab.delete(n) tab.delete(n)
} }
} else if fails > 0 { } else if fails > 0 {
tab.db.UpdateFindFails(n.ID(), fails-1) tab.db.UpdateFindFails(n.ID(), n.IP(), fails-1)
} }
// Grab as many nodes as possible. Some of them might not be alive anymore, but we'll // Grab as many nodes as possible. Some of them might not be alive anymore, but we'll
...@@ -329,7 +337,7 @@ func (tab *Table) refresh() <-chan struct{} { ...@@ -329,7 +337,7 @@ func (tab *Table) refresh() <-chan struct{} {
done := make(chan struct{}) done := make(chan struct{})
select { select {
case tab.refreshReq <- done: case tab.refreshReq <- done:
case <-tab.closed: case <-tab.closeReq:
close(done) close(done)
} }
return done return done
...@@ -433,7 +441,7 @@ func (tab *Table) loadSeedNodes() { ...@@ -433,7 +441,7 @@ func (tab *Table) loadSeedNodes() {
seeds = append(seeds, tab.nursery...) seeds = append(seeds, tab.nursery...)
for i := range seeds { for i := range seeds {
seed := seeds[i] seed := seeds[i]
age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID())) }} age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) }}
log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
tab.add(seed) tab.add(seed)
} }
...@@ -458,16 +466,17 @@ func (tab *Table) doRevalidate(done chan<- struct{}) { ...@@ -458,16 +466,17 @@ func (tab *Table) doRevalidate(done chan<- struct{}) {
b := tab.buckets[bi] b := tab.buckets[bi]
if err == nil { if err == nil {
// The node responded, move it to the front. // The node responded, move it to the front.
log.Debug("Revalidated node", "b", bi, "id", last.ID()) last.livenessChecks++
log.Debug("Revalidated node", "b", bi, "id", last.ID(), "checks", last.livenessChecks)
b.bump(last) b.bump(last)
return return
} }
// No reply received, pick a replacement or delete the node if there aren't // No reply received, pick a replacement or delete the node if there aren't
// any replacements. // any replacements.
if r := tab.replace(b, last); r != nil { if r := tab.replace(b, last); r != nil {
log.Debug("Replaced dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "r", r.ID(), "rip", r.IP()) log.Debug("Replaced dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks, "r", r.ID(), "rip", r.IP())
} else { } else {
log.Debug("Removed dead node", "b", bi, "id", last.ID(), "ip", last.IP()) log.Debug("Removed dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks)
} }
} }
...@@ -502,7 +511,7 @@ func (tab *Table) copyLiveNodes() { ...@@ -502,7 +511,7 @@ func (tab *Table) copyLiveNodes() {
now := time.Now() now := time.Now()
for _, b := range &tab.buckets { for _, b := range &tab.buckets {
for _, n := range b.entries { for _, n := range b.entries {
if now.Sub(n.addedAt) >= seedMinTableTime { if n.livenessChecks > 0 && now.Sub(n.addedAt) >= seedMinTableTime {
tab.db.UpdateNode(unwrapNode(n)) tab.db.UpdateNode(unwrapNode(n))
} }
} }
...@@ -518,9 +527,11 @@ func (tab *Table) closest(target enode.ID, nresults int) *nodesByDistance { ...@@ -518,9 +527,11 @@ func (tab *Table) closest(target enode.ID, nresults int) *nodesByDistance {
close := &nodesByDistance{target: target} close := &nodesByDistance{target: target}
for _, b := range &tab.buckets { for _, b := range &tab.buckets {
for _, n := range b.entries { for _, n := range b.entries {
if n.livenessChecks > 0 {
close.push(n, nresults) close.push(n, nresults)
} }
} }
}
return close return close
} }
...@@ -572,23 +583,6 @@ func (tab *Table) addThroughPing(n *node) { ...@@ -572,23 +583,6 @@ func (tab *Table) addThroughPing(n *node) {
tab.add(n) tab.add(n)
} }
// stuff adds nodes the table to the end of their corresponding bucket
// if the bucket is not full. The caller must not hold tab.mutex.
func (tab *Table) stuff(nodes []*node) {
tab.mutex.Lock()
defer tab.mutex.Unlock()
for _, n := range nodes {
if n.ID() == tab.self().ID() {
continue // don't add self
}
b := tab.bucket(n.ID())
if len(b.entries) < bucketSize {
tab.bumpOrAdd(b, n)
}
}
}
// delete removes an entry from the node table. It is used to evacuate dead nodes. // delete removes an entry from the node table. It is used to evacuate dead nodes.
func (tab *Table) delete(node *node) { func (tab *Table) delete(node *node) {
tab.mutex.Lock() tab.mutex.Lock()
......
...@@ -50,8 +50,8 @@ func TestTable_pingReplace(t *testing.T) { ...@@ -50,8 +50,8 @@ func TestTable_pingReplace(t *testing.T) {
func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) { func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) {
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, db := newTestTable(transport)
defer tab.Close()
defer db.Close() defer db.Close()
defer tab.Close()
<-tab.initDone <-tab.initDone
...@@ -137,8 +137,8 @@ func TestBucket_bumpNoDuplicates(t *testing.T) { ...@@ -137,8 +137,8 @@ func TestBucket_bumpNoDuplicates(t *testing.T) {
func TestTable_IPLimit(t *testing.T) { func TestTable_IPLimit(t *testing.T) {
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, db := newTestTable(transport)
defer tab.Close()
defer db.Close() defer db.Close()
defer tab.Close()
for i := 0; i < tableIPLimit+1; i++ { for i := 0; i < tableIPLimit+1; i++ {
n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)}) n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)})
...@@ -153,8 +153,8 @@ func TestTable_IPLimit(t *testing.T) { ...@@ -153,8 +153,8 @@ func TestTable_IPLimit(t *testing.T) {
func TestTable_BucketIPLimit(t *testing.T) { func TestTable_BucketIPLimit(t *testing.T) {
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, db := newTestTable(transport)
defer tab.Close()
defer db.Close() defer db.Close()
defer tab.Close()
d := 3 d := 3
for i := 0; i < bucketIPLimit+1; i++ { for i := 0; i < bucketIPLimit+1; i++ {
...@@ -173,9 +173,9 @@ func TestTable_closest(t *testing.T) { ...@@ -173,9 +173,9 @@ func TestTable_closest(t *testing.T) {
// for any node table, Target and N // for any node table, Target and N
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, db := newTestTable(transport)
defer tab.Close()
defer db.Close() defer db.Close()
tab.stuff(test.All) defer tab.Close()
fillTable(tab, test.All)
// check that closest(Target, N) returns nodes // check that closest(Target, N) returns nodes
result := tab.closest(test.Target, test.N).entries result := tab.closest(test.Target, test.N).entries
...@@ -234,13 +234,13 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) { ...@@ -234,13 +234,13 @@ func TestTable_ReadRandomNodesGetAll(t *testing.T) {
test := func(buf []*enode.Node) bool { test := func(buf []*enode.Node) bool {
transport := newPingRecorder() transport := newPingRecorder()
tab, db := newTestTable(transport) tab, db := newTestTable(transport)
defer tab.Close()
defer db.Close() defer db.Close()
defer tab.Close()
<-tab.initDone <-tab.initDone
for i := 0; i < len(buf); i++ { for i := 0; i < len(buf); i++ {
ld := cfg.Rand.Intn(len(tab.buckets)) ld := cfg.Rand.Intn(len(tab.buckets))
tab.stuff([]*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))}) fillTable(tab, []*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))})
} }
gotN := tab.ReadRandomNodes(buf) gotN := tab.ReadRandomNodes(buf)
if gotN != tab.len() { if gotN != tab.len() {
...@@ -272,16 +272,19 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { ...@@ -272,16 +272,19 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
N: rand.Intn(bucketSize), N: rand.Intn(bucketSize),
} }
for _, id := range gen([]enode.ID{}, rand).([]enode.ID) { for _, id := range gen([]enode.ID{}, rand).([]enode.ID) {
n := enode.SignNull(new(enr.Record), id) r := new(enr.Record)
t.All = append(t.All, wrapNode(n)) r.Set(enr.IP(genIP(rand)))
n := wrapNode(enode.SignNull(r, id))
n.livenessChecks = 1
t.All = append(t.All, n)
} }
return reflect.ValueOf(t) return reflect.ValueOf(t)
} }
func TestTable_Lookup(t *testing.T) { func TestTable_Lookup(t *testing.T) {
tab, db := newTestTable(lookupTestnet) tab, db := newTestTable(lookupTestnet)
defer tab.Close()
defer db.Close() defer db.Close()
defer tab.Close()
// lookup on empty table returns no nodes // lookup on empty table returns no nodes
if results := tab.lookup(lookupTestnet.target, false); len(results) > 0 { if results := tab.lookup(lookupTestnet.target, false); len(results) > 0 {
...@@ -289,8 +292,9 @@ func TestTable_Lookup(t *testing.T) { ...@@ -289,8 +292,9 @@ func TestTable_Lookup(t *testing.T) {
} }
// seed table with initial node (otherwise lookup will terminate immediately) // seed table with initial node (otherwise lookup will terminate immediately)
seedKey, _ := decodePubkey(lookupTestnet.dists[256][0]) seedKey, _ := decodePubkey(lookupTestnet.dists[256][0])
seed := wrapNode(enode.NewV4(seedKey, net.IP{}, 0, 256)) seed := wrapNode(enode.NewV4(seedKey, net.IP{127, 0, 0, 1}, 0, 256))
tab.stuff([]*node{seed}) seed.livenessChecks = 1
fillTable(tab, []*node{seed})
results := tab.lookup(lookupTestnet.target, true) results := tab.lookup(lookupTestnet.target, true)
t.Logf("results:") t.Logf("results:")
...@@ -578,6 +582,12 @@ func gen(typ interface{}, rand *rand.Rand) interface{} { ...@@ -578,6 +582,12 @@ func gen(typ interface{}, rand *rand.Rand) interface{} {
return v.Interface() return v.Interface()
} }
func genIP(rand *rand.Rand) net.IP {
ip := make(net.IP, 4)
rand.Read(ip)
return ip
}
func quickcfg() *quick.Config { func quickcfg() *quick.Config {
return &quick.Config{ return &quick.Config{
MaxCount: 5000, MaxCount: 5000,
......
...@@ -83,6 +83,23 @@ func fillBucket(tab *Table, n *node) (last *node) { ...@@ -83,6 +83,23 @@ func fillBucket(tab *Table, n *node) (last *node) {
return b.entries[bucketSize-1] return b.entries[bucketSize-1]
} }
// fillTable adds nodes the table to the end of their corresponding bucket
// if the bucket is not full. The caller must not hold tab.mutex.
func fillTable(tab *Table, nodes []*node) {
tab.mutex.Lock()
defer tab.mutex.Unlock()
for _, n := range nodes {
if n.ID() == tab.self().ID() {
continue // don't add self
}
b := tab.bucket(n.ID())
if len(b.entries) < bucketSize {
tab.bumpOrAdd(b, n)
}
}
}
type pingRecorder struct { type pingRecorder struct {
mu sync.Mutex mu sync.Mutex
dead, pinged map[enode.ID]bool dead, pinged map[enode.ID]bool
...@@ -109,10 +126,6 @@ func (t *pingRecorder) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPu ...@@ -109,10 +126,6 @@ func (t *pingRecorder) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPu
return nil, nil return nil, nil
} }
func (t *pingRecorder) waitping(from enode.ID) error {
return nil // remote always pings
}
func (t *pingRecorder) ping(toid enode.ID, toaddr *net.UDPAddr) error { func (t *pingRecorder) ping(toid enode.ID, toaddr *net.UDPAddr) error {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -28,42 +28,54 @@ import ( ...@@ -28,42 +28,54 @@ import (
"time" "time"
) )
var nodeDBKeyTests = []struct { var keytestID = HexID("51232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439")
id ID
field string func TestDBNodeKey(t *testing.T) {
key []byte enc := nodeKey(keytestID)
}{ want := []byte{
{ 'n', ':',
id: ID{},
field: "version",
key: []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field
},
{
id: HexID("51232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
field: ":discover",
key: []byte{
0x6e, 0x3a, // prefix
0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id
0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, //
0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, //
0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, //
0x3a, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, // field ':', 'v', '4',
}, }
}, if !bytes.Equal(enc, want) {
t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want)
}
id, _ := splitNodeKey(enc)
if id != keytestID {
t.Errorf("wrong ID from splitNodeKey")
}
} }
func TestDBKeys(t *testing.T) { func TestDBNodeItemKey(t *testing.T) {
for i, tt := range nodeDBKeyTests { wantIP := net.IP{127, 0, 0, 3}
if key := makeKey(tt.id, tt.field); !bytes.Equal(key, tt.key) { wantField := "foobar"
t.Errorf("make test %d: key mismatch: have 0x%x, want 0x%x", i, key, tt.key) enc := nodeItemKey(keytestID, wantIP, wantField)
want := []byte{
'n', ':',
0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id
0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, //
0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, //
0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, //
':', 'v', '4', ':',
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IP
0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x03, //
':', 'f', 'o', 'o', 'b', 'a', 'r',
} }
id, field := splitKey(tt.key) if !bytes.Equal(enc, want) {
if !bytes.Equal(id[:], tt.id[:]) { t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want)
t.Errorf("split test %d: id mismatch: have 0x%x, want 0x%x", i, id, tt.id)
} }
if field != tt.field { id, ip, field := splitNodeItemKey(enc)
t.Errorf("split test %d: field mismatch: have 0x%x, want 0x%x", i, field, tt.field) if id != keytestID {
t.Errorf("splitNodeItemKey returned wrong ID: %v", id)
} }
if !bytes.Equal(ip, wantIP) {
t.Errorf("splitNodeItemKey returned wrong IP: %v", ip)
}
if field != wantField {
t.Errorf("splitNodeItemKey returned wrong field: %q", field)
} }
} }
...@@ -113,33 +125,33 @@ func TestDBFetchStore(t *testing.T) { ...@@ -113,33 +125,33 @@ func TestDBFetchStore(t *testing.T) {
defer db.Close() defer db.Close()
// Check fetch/store operations on a node ping object // Check fetch/store operations on a node ping object
if stored := db.LastPingReceived(node.ID()); stored.Unix() != 0 { if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != 0 {
t.Errorf("ping: non-existing object: %v", stored) t.Errorf("ping: non-existing object: %v", stored)
} }
if err := db.UpdateLastPingReceived(node.ID(), inst); err != nil { if err := db.UpdateLastPingReceived(node.ID(), node.IP(), inst); err != nil {
t.Errorf("ping: failed to update: %v", err) t.Errorf("ping: failed to update: %v", err)
} }
if stored := db.LastPingReceived(node.ID()); stored.Unix() != inst.Unix() { if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() {
t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) t.Errorf("ping: value mismatch: have %v, want %v", stored, inst)
} }
// Check fetch/store operations on a node pong object // Check fetch/store operations on a node pong object
if stored := db.LastPongReceived(node.ID()); stored.Unix() != 0 { if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != 0 {
t.Errorf("pong: non-existing object: %v", stored) t.Errorf("pong: non-existing object: %v", stored)
} }
if err := db.UpdateLastPongReceived(node.ID(), inst); err != nil { if err := db.UpdateLastPongReceived(node.ID(), node.IP(), inst); err != nil {
t.Errorf("pong: failed to update: %v", err) t.Errorf("pong: failed to update: %v", err)
} }
if stored := db.LastPongReceived(node.ID()); stored.Unix() != inst.Unix() { if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() {
t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) t.Errorf("pong: value mismatch: have %v, want %v", stored, inst)
} }
// Check fetch/store operations on a node findnode-failure object // Check fetch/store operations on a node findnode-failure object
if stored := db.FindFails(node.ID()); stored != 0 { if stored := db.FindFails(node.ID(), node.IP()); stored != 0 {
t.Errorf("find-node fails: non-existing object: %v", stored) t.Errorf("find-node fails: non-existing object: %v", stored)
} }
if err := db.UpdateFindFails(node.ID(), num); err != nil { if err := db.UpdateFindFails(node.ID(), node.IP(), num); err != nil {
t.Errorf("find-node fails: failed to update: %v", err) t.Errorf("find-node fails: failed to update: %v", err)
} }
if stored := db.FindFails(node.ID()); stored != num { if stored := db.FindFails(node.ID(), node.IP()); stored != num {
t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num) t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num)
} }
// Check fetch/store operations on an actual node object // Check fetch/store operations on an actual node object
...@@ -256,7 +268,7 @@ func testSeedQuery() error { ...@@ -256,7 +268,7 @@ func testSeedQuery() error {
if err := db.UpdateNode(seed.node); err != nil { if err := db.UpdateNode(seed.node); err != nil {
return fmt.Errorf("node %d: failed to insert: %v", i, err) return fmt.Errorf("node %d: failed to insert: %v", i, err)
} }
if err := db.UpdateLastPongReceived(seed.node.ID(), seed.pong); err != nil { if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil {
return fmt.Errorf("node %d: failed to insert bondTime: %v", i, err) return fmt.Errorf("node %d: failed to insert bondTime: %v", i, err)
} }
} }
...@@ -323,8 +335,10 @@ func TestDBPersistency(t *testing.T) { ...@@ -323,8 +335,10 @@ func TestDBPersistency(t *testing.T) {
var nodeDBExpirationNodes = []struct { var nodeDBExpirationNodes = []struct {
node *Node node *Node
pong time.Time pong time.Time
storeNode bool
exp bool exp bool
}{ }{
// Node has new enough pong time and isn't expired:
{ {
node: NewV4( node: NewV4(
hexPubkey("8d110e2ed4b446d9b5fb50f117e5f37fb7597af455e1dab0e6f045a6eeaa786a6781141659020d38bdc5e698ed3d4d2bafa8b5061810dfa63e8ac038db2e9b67"), hexPubkey("8d110e2ed4b446d9b5fb50f117e5f37fb7597af455e1dab0e6f045a6eeaa786a6781141659020d38bdc5e698ed3d4d2bafa8b5061810dfa63e8ac038db2e9b67"),
...@@ -332,15 +346,77 @@ var nodeDBExpirationNodes = []struct { ...@@ -332,15 +346,77 @@ var nodeDBExpirationNodes = []struct {
30303, 30303,
30303, 30303,
), ),
storeNode: true,
pong: time.Now().Add(-dbNodeExpiration + time.Minute), pong: time.Now().Add(-dbNodeExpiration + time.Minute),
exp: false, exp: false,
}, { },
// Node with pong time before expiration is removed:
{
node: NewV4( node: NewV4(
hexPubkey("913a205579c32425b220dfba999d215066e5bdbf900226b11da1907eae5e93eb40616d47412cf819664e9eacbdfcca6b0c6e07e09847a38472d4be46ab0c3672"), hexPubkey("913a205579c32425b220dfba999d215066e5bdbf900226b11da1907eae5e93eb40616d47412cf819664e9eacbdfcca6b0c6e07e09847a38472d4be46ab0c3672"),
net.IP{127, 0, 0, 2}, net.IP{127, 0, 0, 2},
30303, 30303,
30303, 30303,
), ),
storeNode: true,
pong: time.Now().Add(-dbNodeExpiration - time.Minute),
exp: true,
},
// Just pong time, no node stored:
{
node: NewV4(
hexPubkey("b56670e0b6bad2c5dab9f9fe6f061a16cf78d68b6ae2cfda3144262d08d97ce5f46fd8799b6d1f709b1abe718f2863e224488bd7518e5e3b43809ac9bd1138ca"),
net.IP{127, 0, 0, 3},
30303,
30303,
),
storeNode: false,
pong: time.Now().Add(-dbNodeExpiration - time.Minute),
exp: true,
},
// Node with multiple pong times, all older than expiration.
{
node: NewV4(
hexPubkey("29f619cebfd32c9eab34aec797ed5e3fe15b9b45be95b4df3f5fe6a9ae892f433eb08d7698b2ef3621568b0fb70d57b515ab30d4e72583b798298e0f0a66b9d1"),
net.IP{127, 0, 0, 4},
30303,
30303,
),
storeNode: true,
pong: time.Now().Add(-dbNodeExpiration - time.Minute),
exp: true,
},
{
node: NewV4(
hexPubkey("29f619cebfd32c9eab34aec797ed5e3fe15b9b45be95b4df3f5fe6a9ae892f433eb08d7698b2ef3621568b0fb70d57b515ab30d4e72583b798298e0f0a66b9d1"),
net.IP{127, 0, 0, 5},
30303,
30303,
),
storeNode: false,
pong: time.Now().Add(-dbNodeExpiration - 2*time.Minute),
exp: true,
},
// Node with multiple pong times, one newer, one older than expiration.
{
node: NewV4(
hexPubkey("3b73a9e5f4af6c4701c57c73cc8cfa0f4802840b24c11eba92aac3aef65644a3728b4b2aec8199f6d72bd66be2c65861c773129039bd47daa091ca90a6d4c857"),
net.IP{127, 0, 0, 6},
30303,
30303,
),
storeNode: true,
pong: time.Now().Add(-dbNodeExpiration + time.Minute),
exp: false,
},
{
node: NewV4(
hexPubkey("3b73a9e5f4af6c4701c57c73cc8cfa0f4802840b24c11eba92aac3aef65644a3728b4b2aec8199f6d72bd66be2c65861c773129039bd47daa091ca90a6d4c857"),
net.IP{127, 0, 0, 7},
30303,
30303,
),
storeNode: false,
pong: time.Now().Add(-dbNodeExpiration - time.Minute), pong: time.Now().Add(-dbNodeExpiration - time.Minute),
exp: true, exp: true,
}, },
...@@ -350,23 +426,39 @@ func TestDBExpiration(t *testing.T) { ...@@ -350,23 +426,39 @@ func TestDBExpiration(t *testing.T) {
db, _ := OpenDB("") db, _ := OpenDB("")
defer db.Close() defer db.Close()
// Add all the test nodes and set their last pong time // Add all the test nodes and set their last pong time.
for i, seed := range nodeDBExpirationNodes { for i, seed := range nodeDBExpirationNodes {
if seed.storeNode {
if err := db.UpdateNode(seed.node); err != nil { if err := db.UpdateNode(seed.node); err != nil {
t.Fatalf("node %d: failed to insert: %v", i, err) t.Fatalf("node %d: failed to insert: %v", i, err)
} }
if err := db.UpdateLastPongReceived(seed.node.ID(), seed.pong); err != nil {
t.Fatalf("node %d: failed to update bondTime: %v", i, err)
} }
if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil {
t.Fatalf("node %d: failed to update bondTime: %v", i, err)
} }
// Expire some of them, and check the rest
if err := db.expireNodes(); err != nil {
t.Fatalf("failed to expire nodes: %v", err)
} }
db.expireNodes()
// Check that expired entries have been removed.
unixZeroTime := time.Unix(0, 0)
for i, seed := range nodeDBExpirationNodes { for i, seed := range nodeDBExpirationNodes {
node := db.Node(seed.node.ID()) node := db.Node(seed.node.ID())
if (node == nil && !seed.exp) || (node != nil && seed.exp) { pong := db.LastPongReceived(seed.node.ID(), seed.node.IP())
t.Errorf("node %d: expiration mismatch: have %v, want %v", i, node, seed.exp) if seed.exp {
if seed.storeNode && node != nil {
t.Errorf("node %d (%s) shouldn't be present after expiration", i, seed.node.ID().TerminalString())
}
if !pong.Equal(unixZeroTime) {
t.Errorf("pong time %d (%s %v) shouldn't be present after expiration", i, seed.node.ID().TerminalString(), seed.node.IP())
}
} else {
if seed.storeNode && node == nil {
t.Errorf("node %d (%s) should be present after expiration", i, seed.node.ID().TerminalString())
}
if !pong.Equal(seed.pong.Truncate(1 * time.Second)) {
t.Errorf("pong time %d (%s) should be %v after expiration, but is %v", i, seed.node.ID().TerminalString(), seed.pong, pong)
}
} }
} }
} }
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