github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/p2p/discv5/table_test.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package discv5 13 14 import ( 15 "crypto/ecdsa" 16 "fmt" 17 "math/rand" 18 19 "net" 20 "reflect" 21 "testing" 22 "testing/quick" 23 "time" 24 25 "github.com/Sberex/go-sberex/common" 26 "github.com/Sberex/go-sberex/crypto" 27 ) 28 29 type nullTransport struct{} 30 31 func (nullTransport) sendPing(remote *Node, remoteAddr *net.UDPAddr) []byte { return []byte{1} } 32 func (nullTransport) sendPong(remote *Node, pingHash []byte) {} 33 func (nullTransport) sendFindnode(remote *Node, target NodeID) {} 34 func (nullTransport) sendNeighbours(remote *Node, nodes []*Node) {} 35 func (nullTransport) localAddr() *net.UDPAddr { return new(net.UDPAddr) } 36 func (nullTransport) Close() {} 37 38 // func TestTable_pingReplace(t *testing.T) { 39 // doit := func(newNodeIsResponding, lastInBucketIsResponding bool) { 40 // transport := newPingRecorder() 41 // tab, _ := newTable(transport, NodeID{}, &net.UDPAddr{}) 42 // defer tab.Close() 43 // pingSender := NewNode(MustHexID("a502af0f59b2aab7746995408c79e9ca312d2793cc997e44fc55eda62f0150bbb8c59a6f9269ba3a081518b62699ee807c7c19c20125ddfccca872608af9e370"), net.IP{}, 99, 99) 44 // 45 // // fill up the sender's bucket. 46 // last := fillBucket(tab, 253) 47 // 48 // // this call to bond should replace the last node 49 // // in its bucket if the node is not responding. 50 // transport.responding[last.ID] = lastInBucketIsResponding 51 // transport.responding[pingSender.ID] = newNodeIsResponding 52 // tab.bond(true, pingSender.ID, &net.UDPAddr{}, 0) 53 // 54 // // first ping goes to sender (bonding pingback) 55 // if !transport.pinged[pingSender.ID] { 56 // t.Error("table did not ping back sender") 57 // } 58 // if newNodeIsResponding { 59 // // second ping goes to oldest node in bucket 60 // // to see whether it is still alive. 61 // if !transport.pinged[last.ID] { 62 // t.Error("table did not ping last node in bucket") 63 // } 64 // } 65 // 66 // tab.mutex.Lock() 67 // defer tab.mutex.Unlock() 68 // if l := len(tab.buckets[253].entries); l != bucketSize { 69 // t.Errorf("wrong bucket size after bond: got %d, want %d", l, bucketSize) 70 // } 71 // 72 // if lastInBucketIsResponding || !newNodeIsResponding { 73 // if !contains(tab.buckets[253].entries, last.ID) { 74 // t.Error("last entry was removed") 75 // } 76 // if contains(tab.buckets[253].entries, pingSender.ID) { 77 // t.Error("new entry was added") 78 // } 79 // } else { 80 // if contains(tab.buckets[253].entries, last.ID) { 81 // t.Error("last entry was not removed") 82 // } 83 // if !contains(tab.buckets[253].entries, pingSender.ID) { 84 // t.Error("new entry was not added") 85 // } 86 // } 87 // } 88 // 89 // doit(true, true) 90 // doit(false, true) 91 // doit(true, false) 92 // doit(false, false) 93 // } 94 95 func TestBucket_bumpNoDuplicates(t *testing.T) { 96 t.Parallel() 97 cfg := &quick.Config{ 98 MaxCount: 1000, 99 Rand: rand.New(rand.NewSource(time.Now().Unix())), 100 Values: func(args []reflect.Value, rand *rand.Rand) { 101 // generate a random list of nodes. this will be the content of the bucket. 102 n := rand.Intn(bucketSize-1) + 1 103 nodes := make([]*Node, n) 104 for i := range nodes { 105 nodes[i] = nodeAtDistance(common.Hash{}, 200) 106 } 107 args[0] = reflect.ValueOf(nodes) 108 // generate random bump positions. 109 bumps := make([]int, rand.Intn(100)) 110 for i := range bumps { 111 bumps[i] = rand.Intn(len(nodes)) 112 } 113 args[1] = reflect.ValueOf(bumps) 114 }, 115 } 116 117 prop := func(nodes []*Node, bumps []int) (ok bool) { 118 b := &bucket{entries: make([]*Node, len(nodes))} 119 copy(b.entries, nodes) 120 for i, pos := range bumps { 121 b.bump(b.entries[pos]) 122 if hasDuplicates(b.entries) { 123 t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps)) 124 for _, n := range b.entries { 125 t.Logf(" %p", n) 126 } 127 return false 128 } 129 } 130 return true 131 } 132 if err := quick.Check(prop, cfg); err != nil { 133 t.Error(err) 134 } 135 } 136 137 // fillBucket inserts nodes into the given bucket until 138 // it is full. The node's IDs dont correspond to their 139 // hashes. 140 func fillBucket(tab *Table, ld int) (last *Node) { 141 b := tab.buckets[ld] 142 for len(b.entries) < bucketSize { 143 b.entries = append(b.entries, nodeAtDistance(tab.self.sha, ld)) 144 } 145 return b.entries[bucketSize-1] 146 } 147 148 // nodeAtDistance creates a node for which logdist(base, n.sha) == ld. 149 // The node's ID does not correspond to n.sha. 150 func nodeAtDistance(base common.Hash, ld int) (n *Node) { 151 n = new(Node) 152 n.sha = hashAtDistance(base, ld) 153 copy(n.ID[:], n.sha[:]) // ensure the node still has a unique ID 154 return n 155 } 156 157 type pingRecorder struct{ responding, pinged map[NodeID]bool } 158 159 func newPingRecorder() *pingRecorder { 160 return &pingRecorder{make(map[NodeID]bool), make(map[NodeID]bool)} 161 } 162 163 func (t *pingRecorder) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node, error) { 164 panic("findnode called on pingRecorder") 165 } 166 func (t *pingRecorder) close() {} 167 func (t *pingRecorder) waitping(from NodeID) error { 168 return nil // remote always pings 169 } 170 func (t *pingRecorder) ping(toid NodeID, toaddr *net.UDPAddr) error { 171 t.pinged[toid] = true 172 if t.responding[toid] { 173 return nil 174 } else { 175 return errTimeout 176 } 177 } 178 179 func TestTable_closest(t *testing.T) { 180 t.Parallel() 181 182 test := func(test *closeTest) bool { 183 // for any node table, Target and N 184 tab := newTable(test.Self, &net.UDPAddr{}) 185 tab.stuff(test.All) 186 187 // check that doClosest(Target, N) returns nodes 188 result := tab.closest(test.Target, test.N).entries 189 if hasDuplicates(result) { 190 t.Errorf("result contains duplicates") 191 return false 192 } 193 if !sortedByDistanceTo(test.Target, result) { 194 t.Errorf("result is not sorted by distance to target") 195 return false 196 } 197 198 // check that the number of results is min(N, tablen) 199 wantN := test.N 200 if tab.count < test.N { 201 wantN = tab.count 202 } 203 if len(result) != wantN { 204 t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN) 205 return false 206 } else if len(result) == 0 { 207 return true // no need to check distance 208 } 209 210 // check that the result nodes have minimum distance to target. 211 for _, b := range tab.buckets { 212 for _, n := range b.entries { 213 if contains(result, n.ID) { 214 continue // don't run the check below for nodes in result 215 } 216 farthestResult := result[len(result)-1].sha 217 if distcmp(test.Target, n.sha, farthestResult) < 0 { 218 t.Errorf("table contains node that is closer to target but it's not in result") 219 t.Logf(" Target: %v", test.Target) 220 t.Logf(" Farthest Result: %v", farthestResult) 221 t.Logf(" ID: %v", n.ID) 222 return false 223 } 224 } 225 } 226 return true 227 } 228 if err := quick.Check(test, quickcfg()); err != nil { 229 t.Error(err) 230 } 231 } 232 233 func TestTable_ReadRandomNodesGetAll(t *testing.T) { 234 cfg := &quick.Config{ 235 MaxCount: 200, 236 Rand: rand.New(rand.NewSource(time.Now().Unix())), 237 Values: func(args []reflect.Value, rand *rand.Rand) { 238 args[0] = reflect.ValueOf(make([]*Node, rand.Intn(1000))) 239 }, 240 } 241 test := func(buf []*Node) bool { 242 tab := newTable(NodeID{}, &net.UDPAddr{}) 243 for i := 0; i < len(buf); i++ { 244 ld := cfg.Rand.Intn(len(tab.buckets)) 245 tab.stuff([]*Node{nodeAtDistance(tab.self.sha, ld)}) 246 } 247 gotN := tab.readRandomNodes(buf) 248 if gotN != tab.count { 249 t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.count) 250 return false 251 } 252 if hasDuplicates(buf[:gotN]) { 253 t.Errorf("result contains duplicates") 254 return false 255 } 256 return true 257 } 258 if err := quick.Check(test, cfg); err != nil { 259 t.Error(err) 260 } 261 } 262 263 type closeTest struct { 264 Self NodeID 265 Target common.Hash 266 All []*Node 267 N int 268 } 269 270 func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { 271 t := &closeTest{ 272 Self: gen(NodeID{}, rand).(NodeID), 273 Target: gen(common.Hash{}, rand).(common.Hash), 274 N: rand.Intn(bucketSize), 275 } 276 for _, id := range gen([]NodeID{}, rand).([]NodeID) { 277 t.All = append(t.All, &Node{ID: id}) 278 } 279 return reflect.ValueOf(t) 280 } 281 282 func hasDuplicates(slice []*Node) bool { 283 seen := make(map[NodeID]bool) 284 for i, e := range slice { 285 if e == nil { 286 panic(fmt.Sprintf("nil *Node at %d", i)) 287 } 288 if seen[e.ID] { 289 return true 290 } 291 seen[e.ID] = true 292 } 293 return false 294 } 295 296 func sortedByDistanceTo(distbase common.Hash, slice []*Node) bool { 297 var last common.Hash 298 for i, e := range slice { 299 if i > 0 && distcmp(distbase, e.sha, last) < 0 { 300 return false 301 } 302 last = e.sha 303 } 304 return true 305 } 306 307 func contains(ns []*Node, id NodeID) bool { 308 for _, n := range ns { 309 if n.ID == id { 310 return true 311 } 312 } 313 return false 314 } 315 316 // gen wraps quick.Value so it's easier to use. 317 // it generates a random value of the given value's type. 318 func gen(typ interface{}, rand *rand.Rand) interface{} { 319 v, ok := quick.Value(reflect.TypeOf(typ), rand) 320 if !ok { 321 panic(fmt.Sprintf("couldn't generate random value of type %T", typ)) 322 } 323 return v.Interface() 324 } 325 326 func newkey() *ecdsa.PrivateKey { 327 key, err := crypto.GenerateKey() 328 if err != nil { 329 panic("couldn't generate key: " + err.Error()) 330 } 331 return key 332 }