github.com/core-coin/go-core/v2@v2.1.9/p2p/discover/table_test.go (about) 1 // Copyright 2015 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package discover 18 19 import ( 20 crand "crypto/rand" 21 "fmt" 22 "math/rand" 23 "net" 24 "reflect" 25 "testing" 26 "testing/quick" 27 "time" 28 29 "github.com/core-coin/go-core/v2/crypto" 30 "github.com/core-coin/go-core/v2/p2p/enode" 31 "github.com/core-coin/go-core/v2/p2p/enr" 32 "github.com/core-coin/go-core/v2/p2p/netutil" 33 ) 34 35 func TestTable_pingReplace(t *testing.T) { 36 run := func(newNodeResponding, lastInBucketResponding bool) { 37 name := fmt.Sprintf("newNodeResponding=%t/lastInBucketResponding=%t", newNodeResponding, lastInBucketResponding) 38 t.Run(name, func(t *testing.T) { 39 t.Parallel() 40 testPingReplace(t, newNodeResponding, lastInBucketResponding) 41 }) 42 } 43 44 run(true, true) 45 run(false, true) 46 run(true, false) 47 run(false, false) 48 } 49 50 func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) { 51 transport := newPingRecorder() 52 tab, db := newTestTable(transport) 53 defer db.Close() 54 defer tab.close() 55 56 <-tab.initDone 57 58 // Fill up the sender's bucket. 59 pingKey, _ := crypto.UnmarshalPrivateKeyHex("ab856a9af6b0b651dd2f43b5e12193652ec1701c4da6f1c0d2a366ac4b9dabc9433ef09e41ca129552bd2c029086d9b03604de872a3b343204") 60 pingSender := wrapNode(enode.NewV4(pingKey.PublicKey(), net.IP{127, 0, 0, 1}, 99, 99)) 61 last := fillBucket(tab, pingSender) 62 63 // Add the sender as if it just pinged us. Revalidate should replace the last node in 64 // its bucket if it is unresponsive. Revalidate again to ensure that 65 transport.dead[last.ID()] = !lastInBucketIsResponding 66 transport.dead[pingSender.ID()] = !newNodeIsResponding 67 tab.addSeenNode(pingSender) 68 tab.doRevalidate(make(chan struct{}, 1)) 69 tab.doRevalidate(make(chan struct{}, 1)) 70 71 if !transport.pinged[last.ID()] { 72 // Oldest node in bucket is pinged to see whether it is still alive. 73 t.Error("table did not ping last node in bucket") 74 } 75 76 tab.mutex.Lock() 77 defer tab.mutex.Unlock() 78 wantSize := bucketSize 79 if !lastInBucketIsResponding && !newNodeIsResponding { 80 wantSize-- 81 } 82 if l := len(tab.bucket(pingSender.ID()).entries); l != wantSize { 83 t.Errorf("wrong bucket size after bond: got %d, want %d", l, wantSize) 84 } 85 if found := contains(tab.bucket(pingSender.ID()).entries, last.ID()); found != lastInBucketIsResponding { 86 t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding) 87 } 88 wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding 89 if found := contains(tab.bucket(pingSender.ID()).entries, pingSender.ID()); found != wantNewEntry { 90 t.Errorf("new entry found: %t, want: %t", found, wantNewEntry) 91 } 92 } 93 94 func TestBucket_bumpNoDuplicates(t *testing.T) { 95 t.Parallel() 96 cfg := &quick.Config{ 97 MaxCount: 1000, 98 Rand: rand.New(rand.NewSource(time.Now().Unix())), 99 Values: func(args []reflect.Value, rand *rand.Rand) { 100 // generate a random list of nodes. this will be the content of the bucket. 101 n := rand.Intn(bucketSize-1) + 1 102 nodes := make([]*node, n) 103 for i := range nodes { 104 nodes[i] = nodeAtDistance(enode.ID{}, 200, intIP(200)) 105 } 106 args[0] = reflect.ValueOf(nodes) 107 // generate random bump positions. 108 bumps := make([]int, rand.Intn(100)) 109 for i := range bumps { 110 bumps[i] = rand.Intn(len(nodes)) 111 } 112 args[1] = reflect.ValueOf(bumps) 113 }, 114 } 115 116 prop := func(nodes []*node, bumps []int) (ok bool) { 117 tab, db := newTestTable(newPingRecorder()) 118 defer db.Close() 119 defer tab.close() 120 121 b := &bucket{entries: make([]*node, len(nodes))} 122 copy(b.entries, nodes) 123 for i, pos := range bumps { 124 tab.bumpInBucket(b, b.entries[pos]) 125 if hasDuplicates(b.entries) { 126 t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps)) 127 for _, n := range b.entries { 128 t.Logf(" %p", n) 129 } 130 return false 131 } 132 } 133 checkIPLimitInvariant(t, tab) 134 return true 135 } 136 if err := quick.Check(prop, cfg); err != nil { 137 t.Error(err) 138 } 139 } 140 141 // This checks that the table-wide IP limit is applied correctly. 142 func TestTable_IPLimit(t *testing.T) { 143 transport := newPingRecorder() 144 tab, db := newTestTable(transport) 145 defer db.Close() 146 defer tab.close() 147 148 for i := 0; i < tableIPLimit+1; i++ { 149 n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)}) 150 tab.addSeenNode(n) 151 } 152 if tab.len() > tableIPLimit { 153 t.Errorf("too many nodes in table") 154 } 155 checkIPLimitInvariant(t, tab) 156 } 157 158 // This checks that the per-bucket IP limit is applied correctly. 159 func TestTable_BucketIPLimit(t *testing.T) { 160 transport := newPingRecorder() 161 tab, db := newTestTable(transport) 162 defer db.Close() 163 defer tab.close() 164 165 d := 3 166 for i := 0; i < bucketIPLimit+1; i++ { 167 n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)}) 168 tab.addSeenNode(n) 169 } 170 if tab.len() > bucketIPLimit { 171 t.Errorf("too many nodes in table") 172 } 173 checkIPLimitInvariant(t, tab) 174 } 175 176 // checkIPLimitInvariant checks that ip limit sets contain an entry for every 177 // node in the table and no extra entries. 178 func checkIPLimitInvariant(t *testing.T, tab *Table) { 179 t.Helper() 180 181 tabset := netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit} 182 for _, b := range tab.buckets { 183 for _, n := range b.entries { 184 tabset.Add(n.IP()) 185 } 186 } 187 if tabset.String() != tab.ips.String() { 188 t.Errorf("table IP set is incorrect:\nhave: %v\nwant: %v", tab.ips, tabset) 189 } 190 } 191 192 func TestTable_findnodeByID(t *testing.T) { 193 t.Parallel() 194 195 test := func(test *closeTest) bool { 196 // for any node table, Target and N 197 transport := newPingRecorder() 198 tab, db := newTestTable(transport) 199 defer db.Close() 200 defer tab.close() 201 fillTable(tab, test.All) 202 203 // check that closest(Target, N) returns nodes 204 result := tab.findnodeByID(test.Target, test.N, false).entries 205 if hasDuplicates(result) { 206 t.Errorf("result contains duplicates") 207 return false 208 } 209 if !sortedByDistanceTo(test.Target, result) { 210 t.Errorf("result is not sorted by distance to target") 211 return false 212 } 213 214 // check that the number of results is min(N, tablen) 215 wantN := test.N 216 if tlen := tab.len(); tlen < test.N { 217 wantN = tlen 218 } 219 if len(result) != wantN { 220 t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN) 221 return false 222 } else if len(result) == 0 { 223 return true // no need to check distance 224 } 225 226 // check that the result nodes have minimum distance to target. 227 for _, b := range tab.buckets { 228 for _, n := range b.entries { 229 if contains(result, n.ID()) { 230 continue // don't run the check below for nodes in result 231 } 232 farthestResult := result[len(result)-1].ID() 233 if enode.DistCmp(test.Target, n.ID(), farthestResult) < 0 { 234 t.Errorf("table contains node that is closer to target but it's not in result") 235 t.Logf(" Target: %v", test.Target) 236 t.Logf(" Farthest Result: %v", farthestResult) 237 t.Logf(" ID: %v", n.ID()) 238 return false 239 } 240 } 241 } 242 return true 243 } 244 if err := quick.Check(test, quickcfg()); err != nil { 245 t.Error(err) 246 } 247 } 248 249 func TestTable_ReadRandomNodesGetAll(t *testing.T) { 250 cfg := &quick.Config{ 251 MaxCount: 200, 252 Rand: rand.New(rand.NewSource(time.Now().Unix())), 253 Values: func(args []reflect.Value, rand *rand.Rand) { 254 args[0] = reflect.ValueOf(make([]*enode.Node, rand.Intn(1000))) 255 }, 256 } 257 test := func(buf []*enode.Node) bool { 258 transport := newPingRecorder() 259 tab, db := newTestTable(transport) 260 defer db.Close() 261 defer tab.close() 262 <-tab.initDone 263 264 for i := 0; i < len(buf); i++ { 265 ld := cfg.Rand.Intn(len(tab.buckets)) 266 fillTable(tab, []*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))}) 267 } 268 gotN := tab.ReadRandomNodes(buf) 269 if gotN != tab.len() { 270 t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.len()) 271 return false 272 } 273 if hasDuplicates(wrapNodes(buf[:gotN])) { 274 t.Errorf("result contains duplicates") 275 return false 276 } 277 return true 278 } 279 if err := quick.Check(test, cfg); err != nil { 280 t.Error(err) 281 } 282 } 283 284 type closeTest struct { 285 Self enode.ID 286 Target enode.ID 287 All []*node 288 N int 289 } 290 291 func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { 292 t := &closeTest{ 293 Self: gen(enode.ID{}, rand).(enode.ID), 294 Target: gen(enode.ID{}, rand).(enode.ID), 295 N: rand.Intn(bucketSize), 296 } 297 for _, id := range gen([]enode.ID{}, rand).([]enode.ID) { 298 r := new(enr.Record) 299 r.Set(enr.IP(genIP(rand))) 300 n := wrapNode(enode.SignNull(r, id)) 301 n.livenessChecks = 1 302 t.All = append(t.All, n) 303 } 304 return reflect.ValueOf(t) 305 } 306 307 func TestTable_addVerifiedNode(t *testing.T) { 308 tab, db := newTestTable(newPingRecorder()) 309 <-tab.initDone 310 defer db.Close() 311 defer tab.close() 312 313 // Insert two nodes. 314 n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1}) 315 n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2}) 316 tab.addSeenNode(n1) 317 tab.addSeenNode(n2) 318 319 // Verify bucket content: 320 bcontent := []*node{n1, n2} 321 if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) { 322 t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries) 323 } 324 325 // Add a changed version of n2. 326 newrec := n2.Record() 327 newrec.Set(enr.IP{99, 99, 99, 99}) 328 newn2 := wrapNode(enode.SignNull(newrec, n2.ID())) 329 tab.addVerifiedNode(newn2) 330 331 // Check that bucket is updated correctly. 332 newBcontent := []*node{newn2, n1} 333 if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, newBcontent) { 334 t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries) 335 } 336 checkIPLimitInvariant(t, tab) 337 } 338 339 func TestTable_addSeenNode(t *testing.T) { 340 tab, db := newTestTable(newPingRecorder()) 341 <-tab.initDone 342 defer db.Close() 343 defer tab.close() 344 345 // Insert two nodes. 346 n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1}) 347 n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2}) 348 tab.addSeenNode(n1) 349 tab.addSeenNode(n2) 350 351 // Verify bucket content: 352 bcontent := []*node{n1, n2} 353 if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) { 354 t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries) 355 } 356 357 // Add a changed version of n2. 358 newrec := n2.Record() 359 newrec.Set(enr.IP{99, 99, 99, 99}) 360 newn2 := wrapNode(enode.SignNull(newrec, n2.ID())) 361 tab.addSeenNode(newn2) 362 363 // Check that bucket content is unchanged. 364 if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) { 365 t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries) 366 } 367 checkIPLimitInvariant(t, tab) 368 } 369 370 // This test checks that ENR updates happen during revalidation. If a node in the table 371 // announces a new sequence number, the new record should be pulled. 372 func TestTable_revalidateSyncRecord(t *testing.T) { 373 transport := newPingRecorder() 374 tab, db := newTestTable(transport) 375 <-tab.initDone 376 defer db.Close() 377 defer tab.close() 378 379 // Insert a node. 380 var r enr.Record 381 r.Set(enr.IP(net.IP{127, 0, 0, 1})) 382 id := enode.ID{1} 383 n1 := wrapNode(enode.SignNull(&r, id)) 384 tab.addSeenNode(n1) 385 386 // Update the node record. 387 r.Set(enr.WithEntry("foo", "bar")) 388 n2 := enode.SignNull(&r, id) 389 transport.updateRecord(n2) 390 391 tab.doRevalidate(make(chan struct{}, 1)) 392 intable := tab.getNode(id) 393 if !reflect.DeepEqual(intable, n2) { 394 t.Fatalf("table contains old record with seq %d, want seq %d", intable.Seq(), n2.Seq()) 395 } 396 } 397 398 // gen wraps quick.Value so it's easier to use. 399 // it generates a random value of the given value's type. 400 func gen(typ interface{}, rand *rand.Rand) interface{} { 401 v, ok := quick.Value(reflect.TypeOf(typ), rand) 402 if !ok { 403 panic(fmt.Sprintf("couldn't generate random value of type %T", typ)) 404 } 405 return v.Interface() 406 } 407 408 func genIP(rand *rand.Rand) net.IP { 409 ip := make(net.IP, 4) 410 rand.Read(ip) 411 return ip 412 } 413 414 func quickcfg() *quick.Config { 415 return &quick.Config{ 416 MaxCount: 5000, 417 Rand: rand.New(rand.NewSource(time.Now().Unix())), 418 } 419 } 420 421 func newkey() *crypto.PrivateKey { 422 key, err := crypto.GenerateKey(crand.Reader) 423 if err != nil { 424 panic("couldn't generate key: " + err.Error()) 425 } 426 return key 427 }