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