github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/p2p/discover/table_test.go (about) 1 package discover 2 3 import ( 4 "crypto/ecdsa" 5 "errors" 6 "fmt" 7 "math/rand" 8 "net" 9 "reflect" 10 "testing" 11 "testing/quick" 12 "time" 13 14 "github.com/jonasnick/go-ethereum/crypto" 15 ) 16 17 func TestTable_bumpOrAddBucketAssign(t *testing.T) { 18 tab := newTable(nil, NodeID{}, &net.UDPAddr{}) 19 for i := 1; i < len(tab.buckets); i++ { 20 tab.bumpOrAdd(randomID(tab.self.ID, i), &net.UDPAddr{}) 21 } 22 for i, b := range tab.buckets { 23 if i > 0 && len(b.entries) != 1 { 24 t.Errorf("bucket %d has %d entries, want 1", i, len(b.entries)) 25 } 26 } 27 } 28 29 func TestTable_bumpOrAddPingReplace(t *testing.T) { 30 pingC := make(pingC) 31 tab := newTable(pingC, NodeID{}, &net.UDPAddr{}) 32 last := fillBucket(tab, 200) 33 34 // this bumpOrAdd should not replace the last node 35 // because the node replies to ping. 36 new := tab.bumpOrAdd(randomID(tab.self.ID, 200), &net.UDPAddr{}) 37 38 pinged := <-pingC 39 if pinged != last.ID { 40 t.Fatalf("pinged wrong node: %v\nwant %v", pinged, last.ID) 41 } 42 43 tab.mutex.Lock() 44 defer tab.mutex.Unlock() 45 if l := len(tab.buckets[200].entries); l != bucketSize { 46 t.Errorf("wrong bucket size after bumpOrAdd: got %d, want %d", bucketSize, l) 47 } 48 if !contains(tab.buckets[200].entries, last.ID) { 49 t.Error("last entry was removed") 50 } 51 if contains(tab.buckets[200].entries, new.ID) { 52 t.Error("new entry was added") 53 } 54 } 55 56 func TestTable_bumpOrAddPingTimeout(t *testing.T) { 57 tab := newTable(pingC(nil), NodeID{}, &net.UDPAddr{}) 58 last := fillBucket(tab, 200) 59 60 // this bumpOrAdd should replace the last node 61 // because the node does not reply to ping. 62 new := tab.bumpOrAdd(randomID(tab.self.ID, 200), &net.UDPAddr{}) 63 64 // wait for async bucket update. damn. this needs to go away. 65 time.Sleep(2 * time.Millisecond) 66 67 tab.mutex.Lock() 68 defer tab.mutex.Unlock() 69 if l := len(tab.buckets[200].entries); l != bucketSize { 70 t.Errorf("wrong bucket size after bumpOrAdd: got %d, want %d", bucketSize, l) 71 } 72 if contains(tab.buckets[200].entries, last.ID) { 73 t.Error("last entry was not removed") 74 } 75 if !contains(tab.buckets[200].entries, new.ID) { 76 t.Error("new entry was not added") 77 } 78 } 79 80 func fillBucket(tab *Table, ld int) (last *Node) { 81 b := tab.buckets[ld] 82 for len(b.entries) < bucketSize { 83 b.entries = append(b.entries, &Node{ID: randomID(tab.self.ID, ld)}) 84 } 85 return b.entries[bucketSize-1] 86 } 87 88 type pingC chan NodeID 89 90 func (t pingC) findnode(n *Node, target NodeID) ([]*Node, error) { 91 panic("findnode called on pingRecorder") 92 } 93 func (t pingC) close() { 94 panic("close called on pingRecorder") 95 } 96 func (t pingC) ping(n *Node) error { 97 if t == nil { 98 return errTimeout 99 } 100 t <- n.ID 101 return nil 102 } 103 104 func TestTable_bump(t *testing.T) { 105 tab := newTable(nil, NodeID{}, &net.UDPAddr{}) 106 107 // add an old entry and two recent ones 108 oldactive := time.Now().Add(-2 * time.Minute) 109 old := &Node{ID: randomID(tab.self.ID, 200), active: oldactive} 110 others := []*Node{ 111 &Node{ID: randomID(tab.self.ID, 200), active: time.Now()}, 112 &Node{ID: randomID(tab.self.ID, 200), active: time.Now()}, 113 } 114 tab.add(append(others, old)) 115 if tab.buckets[200].entries[0] == old { 116 t.Fatal("old entry is at front of bucket") 117 } 118 119 // bumping the old entry should move it to the front 120 tab.bump(old.ID) 121 if old.active == oldactive { 122 t.Error("activity timestamp not updated") 123 } 124 if tab.buckets[200].entries[0] != old { 125 t.Errorf("bumped entry did not move to the front of bucket") 126 } 127 } 128 129 func TestTable_closest(t *testing.T) { 130 t.Parallel() 131 132 test := func(test *closeTest) bool { 133 // for any node table, Target and N 134 tab := newTable(nil, test.Self, &net.UDPAddr{}) 135 tab.add(test.All) 136 137 // check that doClosest(Target, N) returns nodes 138 result := tab.closest(test.Target, test.N).entries 139 if hasDuplicates(result) { 140 t.Errorf("result contains duplicates") 141 return false 142 } 143 if !sortedByDistanceTo(test.Target, result) { 144 t.Errorf("result is not sorted by distance to target") 145 return false 146 } 147 148 // check that the number of results is min(N, tablen) 149 wantN := test.N 150 if tlen := tab.len(); tlen < test.N { 151 wantN = tlen 152 } 153 if len(result) != wantN { 154 t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN) 155 return false 156 } else if len(result) == 0 { 157 return true // no need to check distance 158 } 159 160 // check that the result nodes have minimum distance to target. 161 for _, b := range tab.buckets { 162 for _, n := range b.entries { 163 if contains(result, n.ID) { 164 continue // don't run the check below for nodes in result 165 } 166 farthestResult := result[len(result)-1].ID 167 if distcmp(test.Target, n.ID, farthestResult) < 0 { 168 t.Errorf("table contains node that is closer to target but it's not in result") 169 t.Logf(" Target: %v", test.Target) 170 t.Logf(" Farthest Result: %v", farthestResult) 171 t.Logf(" ID: %v", n.ID) 172 return false 173 } 174 } 175 } 176 return true 177 } 178 if err := quick.Check(test, quickcfg); err != nil { 179 t.Error(err) 180 } 181 } 182 183 type closeTest struct { 184 Self NodeID 185 Target NodeID 186 All []*Node 187 N int 188 } 189 190 func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { 191 t := &closeTest{ 192 Self: gen(NodeID{}, rand).(NodeID), 193 Target: gen(NodeID{}, rand).(NodeID), 194 N: rand.Intn(bucketSize), 195 } 196 for _, id := range gen([]NodeID{}, rand).([]NodeID) { 197 t.All = append(t.All, &Node{ID: id}) 198 } 199 return reflect.ValueOf(t) 200 } 201 202 func TestTable_Lookup(t *testing.T) { 203 self := gen(NodeID{}, quickrand).(NodeID) 204 target := randomID(self, 200) 205 transport := findnodeOracle{t, target} 206 tab := newTable(transport, self, &net.UDPAddr{}) 207 208 // lookup on empty table returns no nodes 209 if results := tab.Lookup(target); len(results) > 0 { 210 t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) 211 } 212 // seed table with initial node (otherwise lookup will terminate immediately) 213 tab.bumpOrAdd(randomID(target, 200), &net.UDPAddr{Port: 200}) 214 215 results := tab.Lookup(target) 216 t.Logf("results:") 217 for _, e := range results { 218 t.Logf(" ld=%d, %v", logdist(target, e.ID), e.ID) 219 } 220 if len(results) != bucketSize { 221 t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) 222 } 223 if hasDuplicates(results) { 224 t.Errorf("result set contains duplicate entries") 225 } 226 if !sortedByDistanceTo(target, results) { 227 t.Errorf("result set not sorted by distance to target") 228 } 229 if !contains(results, target) { 230 t.Errorf("result set does not contain target") 231 } 232 } 233 234 // findnode on this transport always returns at least one node 235 // that is one bucket closer to the target. 236 type findnodeOracle struct { 237 t *testing.T 238 target NodeID 239 } 240 241 func (t findnodeOracle) findnode(n *Node, target NodeID) ([]*Node, error) { 242 t.t.Logf("findnode query at dist %d", n.DiscPort) 243 // current log distance is encoded in port number 244 var result []*Node 245 switch n.DiscPort { 246 case 0: 247 panic("query to node at distance 0") 248 default: 249 // TODO: add more randomness to distances 250 next := n.DiscPort - 1 251 for i := 0; i < bucketSize; i++ { 252 result = append(result, &Node{ID: randomID(t.target, next), DiscPort: next}) 253 } 254 } 255 return result, nil 256 } 257 258 func (t findnodeOracle) close() {} 259 260 func (t findnodeOracle) ping(n *Node) error { 261 return errors.New("ping is not supported by this transport") 262 } 263 264 func hasDuplicates(slice []*Node) bool { 265 seen := make(map[NodeID]bool) 266 for _, e := range slice { 267 if seen[e.ID] { 268 return true 269 } 270 seen[e.ID] = true 271 } 272 return false 273 } 274 275 func sortedByDistanceTo(distbase NodeID, slice []*Node) bool { 276 var last NodeID 277 for i, e := range slice { 278 if i > 0 && distcmp(distbase, e.ID, last) < 0 { 279 return false 280 } 281 last = e.ID 282 } 283 return true 284 } 285 286 func contains(ns []*Node, id NodeID) bool { 287 for _, n := range ns { 288 if n.ID == id { 289 return true 290 } 291 } 292 return false 293 } 294 295 // gen wraps quick.Value so it's easier to use. 296 // it generates a random value of the given value's type. 297 func gen(typ interface{}, rand *rand.Rand) interface{} { 298 v, ok := quick.Value(reflect.TypeOf(typ), rand) 299 if !ok { 300 panic(fmt.Sprintf("couldn't generate random value of type %T", typ)) 301 } 302 return v.Interface() 303 } 304 305 func newkey() *ecdsa.PrivateKey { 306 key, err := crypto.GenerateKey() 307 if err != nil { 308 panic("couldn't generate key: " + err.Error()) 309 } 310 return key 311 }