github.com/prajjawalk/go-ethereum@v1.9.7/les/clientpool_test.go (about) 1 // Copyright 2019 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 les 18 19 import ( 20 "bytes" 21 "fmt" 22 "math" 23 "math/rand" 24 "reflect" 25 "testing" 26 "time" 27 28 "github.com/ethereum/go-ethereum/common/mclock" 29 "github.com/ethereum/go-ethereum/core/rawdb" 30 "github.com/ethereum/go-ethereum/p2p/enode" 31 ) 32 33 func TestClientPoolL10C100Free(t *testing.T) { 34 testClientPool(t, 10, 100, 0, true) 35 } 36 37 func TestClientPoolL40C200Free(t *testing.T) { 38 testClientPool(t, 40, 200, 0, true) 39 } 40 41 func TestClientPoolL100C300Free(t *testing.T) { 42 testClientPool(t, 100, 300, 0, true) 43 } 44 45 func TestClientPoolL10C100P4(t *testing.T) { 46 testClientPool(t, 10, 100, 4, false) 47 } 48 49 func TestClientPoolL40C200P30(t *testing.T) { 50 testClientPool(t, 40, 200, 30, false) 51 } 52 53 func TestClientPoolL100C300P20(t *testing.T) { 54 testClientPool(t, 100, 300, 20, false) 55 } 56 57 const testClientPoolTicks = 100000 58 59 type poolTestPeer int 60 61 func (i poolTestPeer) ID() enode.ID { 62 return enode.ID{byte(i % 256), byte(i >> 8)} 63 } 64 65 func (i poolTestPeer) freeClientId() string { 66 return fmt.Sprintf("addr #%d", i) 67 } 68 69 func (i poolTestPeer) updateCapacity(uint64) {} 70 71 type poolTestPeerWithCap struct { 72 poolTestPeer 73 74 cap uint64 75 } 76 77 func (i *poolTestPeerWithCap) updateCapacity(cap uint64) { i.cap = cap } 78 79 func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomDisconnect bool) { 80 rand.Seed(time.Now().UnixNano()) 81 var ( 82 clock mclock.Simulated 83 db = rawdb.NewMemoryDatabase() 84 connected = make([]bool, clientCount) 85 connTicks = make([]int, clientCount) 86 disconnCh = make(chan int, clientCount) 87 disconnFn = func(id enode.ID) { 88 disconnCh <- int(id[0]) + int(id[1])<<8 89 } 90 pool = newClientPool(db, 1, &clock, disconnFn) 91 ) 92 pool.disableBias = true 93 pool.setLimits(connLimit, uint64(connLimit)) 94 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 95 96 // pool should accept new peers up to its connected limit 97 for i := 0; i < connLimit; i++ { 98 if pool.connect(poolTestPeer(i), 0) { 99 connected[i] = true 100 } else { 101 t.Fatalf("Test peer #%d rejected", i) 102 } 103 } 104 // randomly connect and disconnect peers, expect to have a similar total connection time at the end 105 for tickCounter := 0; tickCounter < testClientPoolTicks; tickCounter++ { 106 clock.Run(1 * time.Second) 107 108 if tickCounter == testClientPoolTicks/4 { 109 // give a positive balance to some of the peers 110 amount := uint64(testClientPoolTicks / 2 * 1000000000) // enough for half of the simulation period 111 for i := 0; i < paidCount; i++ { 112 pool.addBalance(poolTestPeer(i).ID(), amount, false) 113 } 114 } 115 116 i := rand.Intn(clientCount) 117 if connected[i] { 118 if randomDisconnect { 119 pool.disconnect(poolTestPeer(i)) 120 connected[i] = false 121 connTicks[i] += tickCounter 122 } 123 } else { 124 if pool.connect(poolTestPeer(i), 0) { 125 connected[i] = true 126 connTicks[i] -= tickCounter 127 } 128 } 129 pollDisconnects: 130 for { 131 select { 132 case i := <-disconnCh: 133 pool.disconnect(poolTestPeer(i)) 134 if connected[i] { 135 connTicks[i] += tickCounter 136 connected[i] = false 137 } 138 default: 139 break pollDisconnects 140 } 141 } 142 } 143 144 expTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2*(connLimit-paidCount)/(clientCount-paidCount) 145 expMin := expTicks - expTicks/5 146 expMax := expTicks + expTicks/5 147 paidTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2 148 paidMin := paidTicks - paidTicks/5 149 paidMax := paidTicks + paidTicks/5 150 151 // check if the total connected time of peers are all in the expected range 152 for i, c := range connected { 153 if c { 154 connTicks[i] += testClientPoolTicks 155 } 156 min, max := expMin, expMax 157 if i < paidCount { 158 // expect a higher amount for clients with a positive balance 159 min, max = paidMin, paidMax 160 } 161 if connTicks[i] < min || connTicks[i] > max { 162 t.Errorf("Total connected time of test node #%d (%d) outside expected range (%d to %d)", i, connTicks[i], min, max) 163 } 164 } 165 pool.stop() 166 } 167 168 func TestConnectPaidClient(t *testing.T) { 169 var ( 170 clock mclock.Simulated 171 db = rawdb.NewMemoryDatabase() 172 ) 173 pool := newClientPool(db, 1, &clock, nil) 174 defer pool.stop() 175 pool.setLimits(10, uint64(10)) 176 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 177 178 // Add balance for an external client and mark it as paid client 179 pool.addBalance(poolTestPeer(0).ID(), 1000, false) 180 181 if !pool.connect(poolTestPeer(0), 10) { 182 t.Fatalf("Failed to connect paid client") 183 } 184 } 185 186 func TestConnectPaidClientToSmallPool(t *testing.T) { 187 var ( 188 clock mclock.Simulated 189 db = rawdb.NewMemoryDatabase() 190 ) 191 pool := newClientPool(db, 1, &clock, nil) 192 defer pool.stop() 193 pool.setLimits(10, uint64(10)) // Total capacity limit is 10 194 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 195 196 // Add balance for an external client and mark it as paid client 197 pool.addBalance(poolTestPeer(0).ID(), 1000, false) 198 199 // Connect a fat paid client to pool, should reject it. 200 if pool.connect(poolTestPeer(0), 100) { 201 t.Fatalf("Connected fat paid client, should reject it") 202 } 203 } 204 205 func TestConnectPaidClientToFullPool(t *testing.T) { 206 var ( 207 clock mclock.Simulated 208 db = rawdb.NewMemoryDatabase() 209 ) 210 removeFn := func(enode.ID) {} // Noop 211 pool := newClientPool(db, 1, &clock, removeFn) 212 defer pool.stop() 213 pool.setLimits(10, uint64(10)) // Total capacity limit is 10 214 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 215 216 for i := 0; i < 10; i++ { 217 pool.addBalance(poolTestPeer(i).ID(), 1000000000, false) 218 pool.connect(poolTestPeer(i), 1) 219 } 220 pool.addBalance(poolTestPeer(11).ID(), 1000, false) // Add low balance to new paid client 221 if pool.connect(poolTestPeer(11), 1) { 222 t.Fatalf("Low balance paid client should be rejected") 223 } 224 clock.Run(time.Second) 225 pool.addBalance(poolTestPeer(12).ID(), 1000000000*60*3, false) // Add high balance to new paid client 226 if !pool.connect(poolTestPeer(12), 1) { 227 t.Fatalf("High balance paid client should be accpected") 228 } 229 } 230 231 func TestPaidClientKickedOut(t *testing.T) { 232 var ( 233 clock mclock.Simulated 234 db = rawdb.NewMemoryDatabase() 235 kickedCh = make(chan int, 1) 236 ) 237 removeFn := func(id enode.ID) { kickedCh <- int(id[0]) } 238 pool := newClientPool(db, 1, &clock, removeFn) 239 defer pool.stop() 240 pool.setLimits(10, uint64(10)) // Total capacity limit is 10 241 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 242 243 for i := 0; i < 10; i++ { 244 pool.addBalance(poolTestPeer(i).ID(), 1000000000, false) // 1 second allowance 245 pool.connect(poolTestPeer(i), 1) 246 clock.Run(time.Millisecond) 247 } 248 clock.Run(time.Second) 249 clock.Run(connectedBias) 250 if !pool.connect(poolTestPeer(11), 0) { 251 t.Fatalf("Free client should be accectped") 252 } 253 select { 254 case id := <-kickedCh: 255 if id != 0 { 256 t.Fatalf("Kicked client mismatch, want %v, got %v", 0, id) 257 } 258 case <-time.NewTimer(time.Second).C: 259 t.Fatalf("timeout") 260 } 261 } 262 263 func TestConnectFreeClient(t *testing.T) { 264 var ( 265 clock mclock.Simulated 266 db = rawdb.NewMemoryDatabase() 267 ) 268 pool := newClientPool(db, 1, &clock, nil) 269 defer pool.stop() 270 pool.setLimits(10, uint64(10)) 271 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 272 if !pool.connect(poolTestPeer(0), 10) { 273 t.Fatalf("Failed to connect free client") 274 } 275 } 276 277 func TestConnectFreeClientToFullPool(t *testing.T) { 278 var ( 279 clock mclock.Simulated 280 db = rawdb.NewMemoryDatabase() 281 ) 282 removeFn := func(enode.ID) {} // Noop 283 pool := newClientPool(db, 1, &clock, removeFn) 284 defer pool.stop() 285 pool.setLimits(10, uint64(10)) // Total capacity limit is 10 286 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 287 288 for i := 0; i < 10; i++ { 289 pool.connect(poolTestPeer(i), 1) 290 } 291 if pool.connect(poolTestPeer(11), 1) { 292 t.Fatalf("New free client should be rejected") 293 } 294 clock.Run(time.Minute) 295 if pool.connect(poolTestPeer(12), 1) { 296 t.Fatalf("New free client should be rejected") 297 } 298 clock.Run(time.Millisecond) 299 clock.Run(4 * time.Minute) 300 if !pool.connect(poolTestPeer(13), 1) { 301 t.Fatalf("Old client connects more than 5min should be kicked") 302 } 303 } 304 305 func TestFreeClientKickedOut(t *testing.T) { 306 var ( 307 clock mclock.Simulated 308 db = rawdb.NewMemoryDatabase() 309 kicked = make(chan int, 10) 310 ) 311 removeFn := func(id enode.ID) { kicked <- int(id[0]) } 312 pool := newClientPool(db, 1, &clock, removeFn) 313 defer pool.stop() 314 pool.setLimits(10, uint64(10)) // Total capacity limit is 10 315 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 316 317 for i := 0; i < 10; i++ { 318 pool.connect(poolTestPeer(i), 1) 319 clock.Run(time.Millisecond) 320 } 321 if pool.connect(poolTestPeer(10), 1) { 322 t.Fatalf("New free client should be rejected") 323 } 324 clock.Run(5 * time.Minute) 325 for i := 0; i < 10; i++ { 326 pool.connect(poolTestPeer(i+10), 1) 327 } 328 for i := 0; i < 10; i++ { 329 select { 330 case id := <-kicked: 331 if id >= 10 { 332 t.Fatalf("Old client should be kicked, now got: %d", id) 333 } 334 case <-time.NewTimer(time.Second).C: 335 t.Fatalf("timeout") 336 } 337 } 338 } 339 340 func TestPositiveBalanceCalculation(t *testing.T) { 341 var ( 342 clock mclock.Simulated 343 db = rawdb.NewMemoryDatabase() 344 kicked = make(chan int, 10) 345 ) 346 removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop 347 pool := newClientPool(db, 1, &clock, removeFn) 348 defer pool.stop() 349 pool.setLimits(10, uint64(10)) // Total capacity limit is 10 350 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 351 352 pool.addBalance(poolTestPeer(0).ID(), uint64(time.Minute*3), false) 353 pool.connect(poolTestPeer(0), 10) 354 clock.Run(time.Minute) 355 356 pool.disconnect(poolTestPeer(0)) 357 pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID()) 358 if pb.value != uint64(time.Minute*2) { 359 t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb.value) 360 } 361 } 362 363 func TestDowngradePriorityClient(t *testing.T) { 364 var ( 365 clock mclock.Simulated 366 db = rawdb.NewMemoryDatabase() 367 kicked = make(chan int, 10) 368 ) 369 removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop 370 pool := newClientPool(db, 1, &clock, removeFn) 371 defer pool.stop() 372 pool.setLimits(10, uint64(10)) // Total capacity limit is 10 373 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 374 375 p := &poolTestPeerWithCap{ 376 poolTestPeer: poolTestPeer(0), 377 } 378 pool.addBalance(p.ID(), uint64(time.Minute), false) 379 pool.connect(p, 10) 380 if p.cap != 10 { 381 t.Fatalf("The capcacity of priority peer hasn't been updated, got: %d", p.cap) 382 } 383 384 clock.Run(time.Minute) // All positive balance should be used up. 385 time.Sleep(300 * time.Millisecond) // Ensure the callback is called 386 if p.cap != 1 { 387 t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap) 388 } 389 pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID()) 390 if pb.value != 0 { 391 t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb.value) 392 } 393 394 pool.addBalance(poolTestPeer(0).ID(), uint64(time.Minute), false) 395 pb = pool.ndb.getOrNewPB(poolTestPeer(0).ID()) 396 if pb.value != uint64(time.Minute) { 397 t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb.value) 398 } 399 } 400 401 func TestNegativeBalanceCalculation(t *testing.T) { 402 var ( 403 clock mclock.Simulated 404 db = rawdb.NewMemoryDatabase() 405 kicked = make(chan int, 10) 406 ) 407 removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop 408 pool := newClientPool(db, 1, &clock, removeFn) 409 defer pool.stop() 410 pool.setLimits(10, uint64(10)) // Total capacity limit is 10 411 pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) 412 413 for i := 0; i < 10; i++ { 414 pool.connect(poolTestPeer(i), 1) 415 } 416 clock.Run(time.Second) 417 418 for i := 0; i < 10; i++ { 419 pool.disconnect(poolTestPeer(i)) 420 nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId()) 421 if nb.logValue != 0 { 422 t.Fatalf("Short connection shouldn't be recorded") 423 } 424 } 425 426 for i := 0; i < 10; i++ { 427 pool.connect(poolTestPeer(i), 1) 428 } 429 clock.Run(time.Minute) 430 for i := 0; i < 10; i++ { 431 pool.disconnect(poolTestPeer(i)) 432 nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId()) 433 nb.logValue -= pool.logOffset(clock.Now()) 434 nb.logValue /= fixedPointMultiplier 435 if nb.logValue != int64(math.Log(float64(time.Minute/time.Second))) { 436 t.Fatalf("Negative balance mismatch, want %v, got %v", int64(math.Log(float64(time.Minute/time.Second))), nb.logValue) 437 } 438 } 439 } 440 441 func TestNodeDB(t *testing.T) { 442 ndb := newNodeDB(rawdb.NewMemoryDatabase(), mclock.System{}) 443 defer ndb.close() 444 445 if !bytes.Equal(ndb.verbuf[:], []byte{0x00, 0x00}) { 446 t.Fatalf("version buffer mismatch, want %v, got %v", []byte{0x00, 0x00}, ndb.verbuf) 447 } 448 var cases = []struct { 449 id enode.ID 450 ip string 451 balance interface{} 452 positive bool 453 }{ 454 {enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: 100, lastTotal: 200}, true}, 455 {enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: 200, lastTotal: 300}, true}, 456 {enode.ID{}, "127.0.0.1", negBalance{logValue: 10}, false}, 457 {enode.ID{}, "127.0.0.1", negBalance{logValue: 20}, false}, 458 } 459 for _, c := range cases { 460 if c.positive { 461 ndb.setPB(c.id, c.balance.(posBalance)) 462 if pb := ndb.getOrNewPB(c.id); !reflect.DeepEqual(pb, c.balance.(posBalance)) { 463 t.Fatalf("Positive balance mismatch, want %v, got %v", c.balance.(posBalance), pb) 464 } 465 } else { 466 ndb.setNB(c.ip, c.balance.(negBalance)) 467 if nb := ndb.getOrNewNB(c.ip); !reflect.DeepEqual(nb, c.balance.(negBalance)) { 468 t.Fatalf("Negative balance mismatch, want %v, got %v", c.balance.(negBalance), nb) 469 } 470 } 471 } 472 for _, c := range cases { 473 if c.positive { 474 ndb.delPB(c.id) 475 if pb := ndb.getOrNewPB(c.id); !reflect.DeepEqual(pb, posBalance{}) { 476 t.Fatalf("Positive balance mismatch, want %v, got %v", posBalance{}, pb) 477 } 478 } else { 479 ndb.delNB(c.ip) 480 if nb := ndb.getOrNewNB(c.ip); !reflect.DeepEqual(nb, negBalance{}) { 481 t.Fatalf("Negative balance mismatch, want %v, got %v", negBalance{}, nb) 482 } 483 } 484 } 485 ndb.setCumulativeTime(100) 486 if ndb.getCumulativeTime() != 100 { 487 t.Fatalf("Cumulative time mismatch, want %v, got %v", 100, ndb.getCumulativeTime()) 488 } 489 } 490 491 func TestNodeDBExpiration(t *testing.T) { 492 var ( 493 iterated int 494 done = make(chan struct{}, 1) 495 ) 496 callback := func(now mclock.AbsTime, b negBalance) bool { 497 iterated += 1 498 return true 499 } 500 clock := &mclock.Simulated{} 501 ndb := newNodeDB(rawdb.NewMemoryDatabase(), clock) 502 defer ndb.close() 503 ndb.nbEvictCallBack = callback 504 ndb.cleanupHook = func() { done <- struct{}{} } 505 506 var cases = []struct { 507 ip string 508 balance negBalance 509 }{ 510 {"127.0.0.1", negBalance{logValue: 1}}, 511 {"127.0.0.2", negBalance{logValue: 1}}, 512 {"127.0.0.3", negBalance{logValue: 1}}, 513 {"127.0.0.4", negBalance{logValue: 1}}, 514 } 515 for _, c := range cases { 516 ndb.setNB(c.ip, c.balance) 517 } 518 time.Sleep(100 * time.Millisecond) // Ensure the db expirer is registered. 519 clock.Run(time.Hour + time.Minute) 520 select { 521 case <-done: 522 case <-time.NewTimer(time.Second).C: 523 t.Fatalf("timeout") 524 } 525 if iterated != 4 { 526 t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated) 527 } 528 529 for _, c := range cases { 530 ndb.setNB(c.ip, c.balance) 531 } 532 clock.Run(time.Hour + time.Minute) 533 select { 534 case <-done: 535 case <-time.NewTimer(time.Second).C: 536 t.Fatalf("timeout") 537 } 538 if iterated != 8 { 539 t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated) 540 } 541 }