github.com/ethereum/go-ethereum@v1.16.1/p2p/discover/table_test.go (about) 1 // Copyright 2015 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 discover 18 19 import ( 20 "crypto/ecdsa" 21 "fmt" 22 "math/rand" 23 "net" 24 "reflect" 25 "slices" 26 "testing" 27 "testing/quick" 28 "time" 29 30 "github.com/ethereum/go-ethereum/common/mclock" 31 "github.com/ethereum/go-ethereum/crypto" 32 "github.com/ethereum/go-ethereum/internal/testlog" 33 "github.com/ethereum/go-ethereum/log" 34 "github.com/ethereum/go-ethereum/p2p/enode" 35 "github.com/ethereum/go-ethereum/p2p/enr" 36 "github.com/ethereum/go-ethereum/p2p/netutil" 37 ) 38 39 func TestTable_pingReplace(t *testing.T) { 40 run := func(newNodeResponding, lastInBucketResponding bool) { 41 name := fmt.Sprintf("newNodeResponding=%t/lastInBucketResponding=%t", newNodeResponding, lastInBucketResponding) 42 t.Run(name, func(t *testing.T) { 43 t.Parallel() 44 testPingReplace(t, newNodeResponding, lastInBucketResponding) 45 }) 46 } 47 48 run(true, true) 49 run(false, true) 50 run(true, false) 51 run(false, false) 52 } 53 54 func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) { 55 simclock := new(mclock.Simulated) 56 transport := newPingRecorder() 57 tab, db := newTestTable(transport, Config{ 58 Clock: simclock, 59 Log: testlog.Logger(t, log.LevelTrace), 60 }) 61 defer db.Close() 62 defer tab.close() 63 64 <-tab.initDone 65 66 // Fill up the sender's bucket. 67 replacementNodeKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") 68 replacementNode := enode.NewV4(&replacementNodeKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99) 69 last := fillBucket(tab, replacementNode.ID()) 70 tab.mutex.Lock() 71 nodeEvents := newNodeEventRecorder(128) 72 tab.nodeAddedHook = nodeEvents.nodeAdded 73 tab.nodeRemovedHook = nodeEvents.nodeRemoved 74 tab.mutex.Unlock() 75 76 // The revalidation process should replace 77 // this node in the bucket if it is unresponsive. 78 transport.dead[last.ID()] = !lastInBucketIsResponding 79 transport.dead[replacementNode.ID()] = !newNodeIsResponding 80 81 // Add replacement node to table. 82 tab.addFoundNode(replacementNode, false) 83 84 t.Log("last:", last.ID()) 85 t.Log("replacement:", replacementNode.ID()) 86 87 // Wait until the last node was pinged. 88 waitForRevalidationPing(t, transport, tab, last.ID()) 89 90 if !lastInBucketIsResponding { 91 if !nodeEvents.waitNodeAbsent(last.ID(), 2*time.Second) { 92 t.Error("last node was not removed") 93 } 94 if !nodeEvents.waitNodePresent(replacementNode.ID(), 2*time.Second) { 95 t.Error("replacement node was not added") 96 } 97 98 // If a replacement is expected, we also need to wait until the replacement node 99 // was pinged and added/removed. 100 waitForRevalidationPing(t, transport, tab, replacementNode.ID()) 101 if !newNodeIsResponding { 102 if !nodeEvents.waitNodeAbsent(replacementNode.ID(), 2*time.Second) { 103 t.Error("replacement node was not removed") 104 } 105 } 106 } 107 108 // Check bucket content. 109 tab.mutex.Lock() 110 defer tab.mutex.Unlock() 111 wantSize := bucketSize 112 if !lastInBucketIsResponding && !newNodeIsResponding { 113 wantSize-- 114 } 115 bucket := tab.bucket(replacementNode.ID()) 116 if l := len(bucket.entries); l != wantSize { 117 t.Errorf("wrong bucket size after revalidation: got %d, want %d", l, wantSize) 118 } 119 if ok := containsID(bucket.entries, last.ID()); ok != lastInBucketIsResponding { 120 t.Errorf("revalidated node found: %t, want: %t", ok, lastInBucketIsResponding) 121 } 122 wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding 123 if ok := containsID(bucket.entries, replacementNode.ID()); ok != wantNewEntry { 124 t.Errorf("replacement node found: %t, want: %t", ok, wantNewEntry) 125 } 126 } 127 128 // waitForRevalidationPing waits until a PING message is sent to a node with the given id. 129 func waitForRevalidationPing(t *testing.T, transport *pingRecorder, tab *Table, id enode.ID) *enode.Node { 130 t.Helper() 131 132 simclock := tab.cfg.Clock.(*mclock.Simulated) 133 maxAttempts := tab.len() * 8 134 for i := 0; i < maxAttempts; i++ { 135 simclock.Run(tab.cfg.PingInterval * slowRevalidationFactor) 136 p := transport.waitPing(2 * time.Second) 137 if p == nil { 138 continue 139 } 140 if id == (enode.ID{}) || p.ID() == id { 141 return p 142 } 143 } 144 t.Fatalf("Table did not ping node %v (%d attempts)", id, maxAttempts) 145 return nil 146 } 147 148 // This checks that the table-wide IP limit is applied correctly. 149 func TestTable_IPLimit(t *testing.T) { 150 transport := newPingRecorder() 151 tab, db := newTestTable(transport, Config{}) 152 defer db.Close() 153 defer tab.close() 154 155 for i := 0; i < tableIPLimit+1; i++ { 156 n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)}) 157 tab.addFoundNode(n, false) 158 } 159 if tab.len() > tableIPLimit { 160 t.Errorf("too many nodes in table") 161 } 162 checkIPLimitInvariant(t, tab) 163 } 164 165 // This checks that the per-bucket IP limit is applied correctly. 166 func TestTable_BucketIPLimit(t *testing.T) { 167 transport := newPingRecorder() 168 tab, db := newTestTable(transport, Config{}) 169 defer db.Close() 170 defer tab.close() 171 172 d := 3 173 for i := 0; i < bucketIPLimit+1; i++ { 174 n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)}) 175 tab.addFoundNode(n, false) 176 } 177 if tab.len() > bucketIPLimit { 178 t.Errorf("too many nodes in table") 179 } 180 checkIPLimitInvariant(t, tab) 181 } 182 183 // checkIPLimitInvariant checks that ip limit sets contain an entry for every 184 // node in the table and no extra entries. 185 func checkIPLimitInvariant(t *testing.T, tab *Table) { 186 t.Helper() 187 188 tabset := netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit} 189 for _, b := range tab.buckets { 190 for _, n := range b.entries { 191 tabset.AddAddr(n.IPAddr()) 192 } 193 } 194 if tabset.String() != tab.ips.String() { 195 t.Errorf("table IP set is incorrect:\nhave: %v\nwant: %v", tab.ips, tabset) 196 } 197 } 198 199 func TestTable_findnodeByID(t *testing.T) { 200 t.Parallel() 201 202 test := func(test *closeTest) bool { 203 // for any node table, Target and N 204 transport := newPingRecorder() 205 tab, db := newTestTable(transport, Config{}) 206 defer db.Close() 207 defer tab.close() 208 fillTable(tab, test.All, true) 209 210 // check that closest(Target, N) returns nodes 211 result := tab.findnodeByID(test.Target, test.N, false).entries 212 if hasDuplicates(result) { 213 t.Errorf("result contains duplicates") 214 return false 215 } 216 if !sortedByDistanceTo(test.Target, result) { 217 t.Errorf("result is not sorted by distance to target") 218 return false 219 } 220 221 // check that the number of results is min(N, tablen) 222 wantN := test.N 223 if tlen := tab.len(); tlen < test.N { 224 wantN = tlen 225 } 226 if len(result) != wantN { 227 t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN) 228 return false 229 } else if len(result) == 0 { 230 return true // no need to check distance 231 } 232 233 // check that the result nodes have minimum distance to target. 234 for _, b := range tab.buckets { 235 for _, n := range b.entries { 236 if containsID(result, n.ID()) { 237 continue // don't run the check below for nodes in result 238 } 239 farthestResult := result[len(result)-1].ID() 240 if enode.DistCmp(test.Target, n.ID(), farthestResult) < 0 { 241 t.Errorf("table contains node that is closer to target but it's not in result") 242 t.Logf(" Target: %v", test.Target) 243 t.Logf(" Farthest Result: %v", farthestResult) 244 t.Logf(" ID: %v", n.ID()) 245 return false 246 } 247 } 248 } 249 return true 250 } 251 if err := quick.Check(test, quickcfg()); err != nil { 252 t.Error(err) 253 } 254 } 255 256 type closeTest struct { 257 Self enode.ID 258 Target enode.ID 259 All []*enode.Node 260 N int 261 } 262 263 func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { 264 t := &closeTest{ 265 Self: gen(enode.ID{}, rand).(enode.ID), 266 Target: gen(enode.ID{}, rand).(enode.ID), 267 N: rand.Intn(bucketSize), 268 } 269 for _, id := range gen([]enode.ID{}, rand).([]enode.ID) { 270 r := new(enr.Record) 271 r.Set(enr.IPv4Addr(netutil.RandomAddr(rand, true))) 272 n := enode.SignNull(r, id) 273 t.All = append(t.All, n) 274 } 275 return reflect.ValueOf(t) 276 } 277 278 func TestTable_addInboundNode(t *testing.T) { 279 tab, db := newTestTable(newPingRecorder(), Config{}) 280 <-tab.initDone 281 defer db.Close() 282 defer tab.close() 283 284 // Insert two nodes. 285 n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1}) 286 n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2}) 287 tab.addFoundNode(n1, false) 288 tab.addFoundNode(n2, false) 289 checkBucketContent(t, tab, []*enode.Node{n1, n2}) 290 291 // Add a changed version of n2. The bucket should be updated. 292 newrec := n2.Record() 293 newrec.Set(enr.IP{99, 99, 99, 99}) 294 n2v2 := enode.SignNull(newrec, n2.ID()) 295 tab.addInboundNode(n2v2) 296 checkBucketContent(t, tab, []*enode.Node{n1, n2v2}) 297 298 // Try updating n2 without sequence number change. The update is accepted 299 // because it's inbound. 300 newrec = n2.Record() 301 newrec.Set(enr.IP{100, 100, 100, 100}) 302 newrec.SetSeq(n2.Seq()) 303 n2v3 := enode.SignNull(newrec, n2.ID()) 304 tab.addInboundNode(n2v3) 305 checkBucketContent(t, tab, []*enode.Node{n1, n2v3}) 306 } 307 308 func TestTable_addFoundNode(t *testing.T) { 309 tab, db := newTestTable(newPingRecorder(), Config{}) 310 <-tab.initDone 311 defer db.Close() 312 defer tab.close() 313 314 // Insert two nodes. 315 n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1}) 316 n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2}) 317 tab.addFoundNode(n1, false) 318 tab.addFoundNode(n2, false) 319 checkBucketContent(t, tab, []*enode.Node{n1, n2}) 320 321 // Add a changed version of n2. The bucket should be updated. 322 newrec := n2.Record() 323 newrec.Set(enr.IP{99, 99, 99, 99}) 324 n2v2 := enode.SignNull(newrec, n2.ID()) 325 tab.addFoundNode(n2v2, false) 326 checkBucketContent(t, tab, []*enode.Node{n1, n2v2}) 327 328 // Try updating n2 without a sequence number change. 329 // The update should not be accepted. 330 newrec = n2.Record() 331 newrec.Set(enr.IP{100, 100, 100, 100}) 332 newrec.SetSeq(n2.Seq()) 333 n2v3 := enode.SignNull(newrec, n2.ID()) 334 tab.addFoundNode(n2v3, false) 335 checkBucketContent(t, tab, []*enode.Node{n1, n2v2}) 336 } 337 338 // This test checks that discv4 nodes can update their own endpoint via PING. 339 func TestTable_addInboundNodeUpdateV4Accept(t *testing.T) { 340 tab, db := newTestTable(newPingRecorder(), Config{}) 341 <-tab.initDone 342 defer db.Close() 343 defer tab.close() 344 345 // Add a v4 node. 346 key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3") 347 n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000) 348 tab.addInboundNode(n1) 349 checkBucketContent(t, tab, []*enode.Node{n1}) 350 351 // Add an updated version with changed IP. 352 // The update will be accepted because it is inbound. 353 n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000) 354 tab.addInboundNode(n1v2) 355 checkBucketContent(t, tab, []*enode.Node{n1v2}) 356 } 357 358 // This test checks that discv4 node entries will NOT be updated when a 359 // changed record is found. 360 func TestTable_addFoundNodeV4UpdateReject(t *testing.T) { 361 tab, db := newTestTable(newPingRecorder(), Config{}) 362 <-tab.initDone 363 defer db.Close() 364 defer tab.close() 365 366 // Add a v4 node. 367 key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3") 368 n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000) 369 tab.addFoundNode(n1, false) 370 checkBucketContent(t, tab, []*enode.Node{n1}) 371 372 // Add an updated version with changed IP. 373 // The update won't be accepted because it isn't inbound. 374 n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000) 375 tab.addFoundNode(n1v2, false) 376 checkBucketContent(t, tab, []*enode.Node{n1}) 377 } 378 379 func checkBucketContent(t *testing.T, tab *Table, nodes []*enode.Node) { 380 t.Helper() 381 382 b := tab.bucket(nodes[0].ID()) 383 if reflect.DeepEqual(unwrapNodes(b.entries), nodes) { 384 return 385 } 386 t.Log("wrong bucket content. have nodes:") 387 for _, n := range b.entries { 388 t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IPAddr()) 389 } 390 t.Log("want nodes:") 391 for _, n := range nodes { 392 t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IPAddr()) 393 } 394 t.FailNow() 395 396 // Also check IP limits. 397 checkIPLimitInvariant(t, tab) 398 } 399 400 // This test checks that ENR updates happen during revalidation. If a node in the table 401 // announces a new sequence number, the new record should be pulled. 402 func TestTable_revalidateSyncRecord(t *testing.T) { 403 transport := newPingRecorder() 404 tab, db := newTestTable(transport, Config{ 405 Clock: new(mclock.Simulated), 406 Log: testlog.Logger(t, log.LevelTrace), 407 }) 408 <-tab.initDone 409 defer db.Close() 410 defer tab.close() 411 412 // Insert a node. 413 var r enr.Record 414 r.Set(enr.IP(net.IP{127, 0, 0, 1})) 415 id := enode.ID{1} 416 n1 := enode.SignNull(&r, id) 417 tab.addFoundNode(n1, false) 418 419 // Update the node record. 420 r.Set(enr.WithEntry("foo", "bar")) 421 n2 := enode.SignNull(&r, id) 422 transport.updateRecord(n2) 423 424 // Wait for revalidation. We wait for the node to be revalidated two times 425 // in order to synchronize with the update in the table. 426 waitForRevalidationPing(t, transport, tab, n2.ID()) 427 waitForRevalidationPing(t, transport, tab, n2.ID()) 428 429 intable := tab.getNode(id) 430 if !reflect.DeepEqual(intable, n2) { 431 t.Fatalf("table contains old record with seq %d, want seq %d", intable.Seq(), n2.Seq()) 432 } 433 } 434 435 func TestNodesPush(t *testing.T) { 436 var target enode.ID 437 n1 := nodeAtDistance(target, 255, intIP(1)) 438 n2 := nodeAtDistance(target, 254, intIP(2)) 439 n3 := nodeAtDistance(target, 253, intIP(3)) 440 perm := [][]*enode.Node{ 441 {n3, n2, n1}, 442 {n3, n1, n2}, 443 {n2, n3, n1}, 444 {n2, n1, n3}, 445 {n1, n3, n2}, 446 {n1, n2, n3}, 447 } 448 449 // Insert all permutations into lists with size limit 3. 450 for _, nodes := range perm { 451 list := nodesByDistance{target: target} 452 for _, n := range nodes { 453 list.push(n, 3) 454 } 455 if !slices.EqualFunc(list.entries, perm[0], nodeIDEqual) { 456 t.Fatal("not equal") 457 } 458 } 459 460 // Insert all permutations into lists with size limit 2. 461 for _, nodes := range perm { 462 list := nodesByDistance{target: target} 463 for _, n := range nodes { 464 list.push(n, 2) 465 } 466 if !slices.EqualFunc(list.entries, perm[0][:2], nodeIDEqual) { 467 t.Fatal("not equal") 468 } 469 } 470 } 471 472 func nodeIDEqual[N nodeType](n1, n2 N) bool { 473 return n1.ID() == n2.ID() 474 } 475 476 // gen wraps quick.Value so it's easier to use. 477 // it generates a random value of the given value's type. 478 func gen(typ interface{}, rand *rand.Rand) interface{} { 479 v, ok := quick.Value(reflect.TypeOf(typ), rand) 480 if !ok { 481 panic(fmt.Sprintf("couldn't generate random value of type %T", typ)) 482 } 483 return v.Interface() 484 } 485 486 func quickcfg() *quick.Config { 487 return &quick.Config{ 488 MaxCount: 5000, 489 Rand: rand.New(rand.NewSource(time.Now().Unix())), 490 } 491 } 492 493 func newkey() *ecdsa.PrivateKey { 494 key, err := crypto.GenerateKey() 495 if err != nil { 496 panic("couldn't generate key: " + err.Error()) 497 } 498 return key 499 }