github.com/evdatsion/aphelion-dpos-bft@v0.32.1/p2p/pex/addrbook_test.go (about) 1 package pex 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "io/ioutil" 7 "math" 8 "os" 9 "testing" 10 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 14 cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common" 15 "github.com/evdatsion/aphelion-dpos-bft/libs/log" 16 "github.com/evdatsion/aphelion-dpos-bft/p2p" 17 ) 18 19 func TestAddrBookPickAddress(t *testing.T) { 20 fname := createTempFileName("addrbook_test") 21 defer deleteTempFile(fname) 22 23 // 0 addresses 24 book := NewAddrBook(fname, true) 25 book.SetLogger(log.TestingLogger()) 26 assert.Zero(t, book.Size()) 27 28 addr := book.PickAddress(50) 29 assert.Nil(t, addr, "expected no address") 30 31 randAddrs := randNetAddressPairs(t, 1) 32 addrSrc := randAddrs[0] 33 book.AddAddress(addrSrc.addr, addrSrc.src) 34 35 // pick an address when we only have new address 36 addr = book.PickAddress(0) 37 assert.NotNil(t, addr, "expected an address") 38 addr = book.PickAddress(50) 39 assert.NotNil(t, addr, "expected an address") 40 addr = book.PickAddress(100) 41 assert.NotNil(t, addr, "expected an address") 42 43 // pick an address when we only have old address 44 book.MarkGood(addrSrc.addr.ID) 45 addr = book.PickAddress(0) 46 assert.NotNil(t, addr, "expected an address") 47 addr = book.PickAddress(50) 48 assert.NotNil(t, addr, "expected an address") 49 50 // in this case, nNew==0 but we biased 100% to new, so we return nil 51 addr = book.PickAddress(100) 52 assert.Nil(t, addr, "did not expected an address") 53 } 54 55 func TestAddrBookSaveLoad(t *testing.T) { 56 fname := createTempFileName("addrbook_test") 57 defer deleteTempFile(fname) 58 59 // 0 addresses 60 book := NewAddrBook(fname, true) 61 book.SetLogger(log.TestingLogger()) 62 book.saveToFile(fname) 63 64 book = NewAddrBook(fname, true) 65 book.SetLogger(log.TestingLogger()) 66 book.loadFromFile(fname) 67 68 assert.Zero(t, book.Size()) 69 70 // 100 addresses 71 randAddrs := randNetAddressPairs(t, 100) 72 73 for _, addrSrc := range randAddrs { 74 book.AddAddress(addrSrc.addr, addrSrc.src) 75 } 76 77 assert.Equal(t, 100, book.Size()) 78 book.saveToFile(fname) 79 80 book = NewAddrBook(fname, true) 81 book.SetLogger(log.TestingLogger()) 82 book.loadFromFile(fname) 83 84 assert.Equal(t, 100, book.Size()) 85 } 86 87 func TestAddrBookLookup(t *testing.T) { 88 fname := createTempFileName("addrbook_test") 89 defer deleteTempFile(fname) 90 91 randAddrs := randNetAddressPairs(t, 100) 92 93 book := NewAddrBook(fname, true) 94 book.SetLogger(log.TestingLogger()) 95 for _, addrSrc := range randAddrs { 96 addr := addrSrc.addr 97 src := addrSrc.src 98 book.AddAddress(addr, src) 99 100 ka := book.addrLookup[addr.ID] 101 assert.NotNil(t, ka, "Expected to find KnownAddress %v but wasn't there.", addr) 102 103 if !(ka.Addr.Equals(addr) && ka.Src.Equals(src)) { 104 t.Fatalf("KnownAddress doesn't match addr & src") 105 } 106 } 107 } 108 109 func TestAddrBookPromoteToOld(t *testing.T) { 110 fname := createTempFileName("addrbook_test") 111 defer deleteTempFile(fname) 112 113 randAddrs := randNetAddressPairs(t, 100) 114 115 book := NewAddrBook(fname, true) 116 book.SetLogger(log.TestingLogger()) 117 for _, addrSrc := range randAddrs { 118 book.AddAddress(addrSrc.addr, addrSrc.src) 119 } 120 121 // Attempt all addresses. 122 for _, addrSrc := range randAddrs { 123 book.MarkAttempt(addrSrc.addr) 124 } 125 126 // Promote half of them 127 for i, addrSrc := range randAddrs { 128 if i%2 == 0 { 129 book.MarkGood(addrSrc.addr.ID) 130 } 131 } 132 133 // TODO: do more testing :) 134 135 selection := book.GetSelection() 136 t.Logf("selection: %v", selection) 137 138 if len(selection) > book.Size() { 139 t.Errorf("selection could not be bigger than the book") 140 } 141 142 selection = book.GetSelectionWithBias(30) 143 t.Logf("selection: %v", selection) 144 145 if len(selection) > book.Size() { 146 t.Errorf("selection with bias could not be bigger than the book") 147 } 148 149 assert.Equal(t, book.Size(), 100, "expecting book size to be 100") 150 } 151 152 func TestAddrBookHandlesDuplicates(t *testing.T) { 153 fname := createTempFileName("addrbook_test") 154 defer deleteTempFile(fname) 155 156 book := NewAddrBook(fname, true) 157 book.SetLogger(log.TestingLogger()) 158 159 randAddrs := randNetAddressPairs(t, 100) 160 161 differentSrc := randIPv4Address(t) 162 for _, addrSrc := range randAddrs { 163 book.AddAddress(addrSrc.addr, addrSrc.src) 164 book.AddAddress(addrSrc.addr, addrSrc.src) // duplicate 165 book.AddAddress(addrSrc.addr, differentSrc) // different src 166 } 167 168 assert.Equal(t, 100, book.Size()) 169 } 170 171 type netAddressPair struct { 172 addr *p2p.NetAddress 173 src *p2p.NetAddress 174 } 175 176 func randNetAddressPairs(t *testing.T, n int) []netAddressPair { 177 randAddrs := make([]netAddressPair, n) 178 for i := 0; i < n; i++ { 179 randAddrs[i] = netAddressPair{addr: randIPv4Address(t), src: randIPv4Address(t)} 180 } 181 return randAddrs 182 } 183 184 func randIPv4Address(t *testing.T) *p2p.NetAddress { 185 for { 186 ip := fmt.Sprintf("%v.%v.%v.%v", 187 cmn.RandIntn(254)+1, 188 cmn.RandIntn(255), 189 cmn.RandIntn(255), 190 cmn.RandIntn(255), 191 ) 192 port := cmn.RandIntn(65535-1) + 1 193 id := p2p.ID(hex.EncodeToString(cmn.RandBytes(p2p.IDByteLength))) 194 idAddr := p2p.IDAddressString(id, fmt.Sprintf("%v:%v", ip, port)) 195 addr, err := p2p.NewNetAddressString(idAddr) 196 assert.Nil(t, err, "error generating rand network address") 197 if addr.Routable() { 198 return addr 199 } 200 } 201 } 202 203 func TestAddrBookRemoveAddress(t *testing.T) { 204 fname := createTempFileName("addrbook_test") 205 defer deleteTempFile(fname) 206 207 book := NewAddrBook(fname, true) 208 book.SetLogger(log.TestingLogger()) 209 210 addr := randIPv4Address(t) 211 book.AddAddress(addr, addr) 212 assert.Equal(t, 1, book.Size()) 213 214 book.RemoveAddress(addr) 215 assert.Equal(t, 0, book.Size()) 216 217 nonExistingAddr := randIPv4Address(t) 218 book.RemoveAddress(nonExistingAddr) 219 assert.Equal(t, 0, book.Size()) 220 } 221 222 func TestAddrBookGetSelectionWithOneMarkedGood(t *testing.T) { 223 // create a book with 10 addresses, 1 good/old and 9 new 224 book, fname := createAddrBookWithMOldAndNNewAddrs(t, 1, 9) 225 defer deleteTempFile(fname) 226 227 addrs := book.GetSelectionWithBias(biasToSelectNewPeers) 228 assert.NotNil(t, addrs) 229 assertMOldAndNNewAddrsInSelection(t, 1, 9, addrs, book) 230 } 231 232 func TestAddrBookGetSelectionWithOneNotMarkedGood(t *testing.T) { 233 // create a book with 10 addresses, 9 good/old and 1 new 234 book, fname := createAddrBookWithMOldAndNNewAddrs(t, 9, 1) 235 defer deleteTempFile(fname) 236 237 addrs := book.GetSelectionWithBias(biasToSelectNewPeers) 238 assert.NotNil(t, addrs) 239 assertMOldAndNNewAddrsInSelection(t, 9, 1, addrs, book) 240 } 241 242 func TestAddrBookGetSelectionReturnsNilWhenAddrBookIsEmpty(t *testing.T) { 243 book, fname := createAddrBookWithMOldAndNNewAddrs(t, 0, 0) 244 defer deleteTempFile(fname) 245 246 addrs := book.GetSelectionWithBias(biasToSelectNewPeers) 247 assert.Nil(t, addrs) 248 } 249 250 func TestAddrBookGetSelection(t *testing.T) { 251 fname := createTempFileName("addrbook_test") 252 defer deleteTempFile(fname) 253 254 book := NewAddrBook(fname, true) 255 book.SetLogger(log.TestingLogger()) 256 257 // 1) empty book 258 assert.Empty(t, book.GetSelection()) 259 260 // 2) add one address 261 addr := randIPv4Address(t) 262 book.AddAddress(addr, addr) 263 264 assert.Equal(t, 1, len(book.GetSelection())) 265 assert.Equal(t, addr, book.GetSelection()[0]) 266 267 // 3) add a bunch of addresses 268 randAddrs := randNetAddressPairs(t, 100) 269 for _, addrSrc := range randAddrs { 270 book.AddAddress(addrSrc.addr, addrSrc.src) 271 } 272 273 // check there is no duplicates 274 addrs := make(map[string]*p2p.NetAddress) 275 selection := book.GetSelection() 276 for _, addr := range selection { 277 if dup, ok := addrs[addr.String()]; ok { 278 t.Fatalf("selection %v contains duplicates %v", selection, dup) 279 } 280 addrs[addr.String()] = addr 281 } 282 283 if len(selection) > book.Size() { 284 t.Errorf("selection %v could not be bigger than the book", selection) 285 } 286 } 287 288 func TestAddrBookGetSelectionWithBias(t *testing.T) { 289 const biasTowardsNewAddrs = 30 290 291 fname := createTempFileName("addrbook_test") 292 defer deleteTempFile(fname) 293 294 book := NewAddrBook(fname, true) 295 book.SetLogger(log.TestingLogger()) 296 297 // 1) empty book 298 selection := book.GetSelectionWithBias(biasTowardsNewAddrs) 299 assert.Empty(t, selection) 300 301 // 2) add one address 302 addr := randIPv4Address(t) 303 book.AddAddress(addr, addr) 304 305 selection = book.GetSelectionWithBias(biasTowardsNewAddrs) 306 assert.Equal(t, 1, len(selection)) 307 assert.Equal(t, addr, selection[0]) 308 309 // 3) add a bunch of addresses 310 randAddrs := randNetAddressPairs(t, 100) 311 for _, addrSrc := range randAddrs { 312 book.AddAddress(addrSrc.addr, addrSrc.src) 313 } 314 315 // check there is no duplicates 316 addrs := make(map[string]*p2p.NetAddress) 317 selection = book.GetSelectionWithBias(biasTowardsNewAddrs) 318 for _, addr := range selection { 319 if dup, ok := addrs[addr.String()]; ok { 320 t.Fatalf("selection %v contains duplicates %v", selection, dup) 321 } 322 addrs[addr.String()] = addr 323 } 324 325 if len(selection) > book.Size() { 326 t.Fatalf("selection %v could not be bigger than the book", selection) 327 } 328 329 // 4) mark 80% of the addresses as good 330 randAddrsLen := len(randAddrs) 331 for i, addrSrc := range randAddrs { 332 if int((float64(i)/float64(randAddrsLen))*100) >= 20 { 333 book.MarkGood(addrSrc.addr.ID) 334 } 335 } 336 337 selection = book.GetSelectionWithBias(biasTowardsNewAddrs) 338 339 // check that ~70% of addresses returned are good 340 good := 0 341 for _, addr := range selection { 342 if book.IsGood(addr) { 343 good++ 344 } 345 } 346 347 got, expected := int((float64(good)/float64(len(selection)))*100), (100 - biasTowardsNewAddrs) 348 349 // compute some slack to protect against small differences due to rounding: 350 slack := int(math.Round(float64(100) / float64(len(selection)))) 351 if got > expected+slack { 352 t.Fatalf("got more good peers (%% got: %d, %% expected: %d, number of good addrs: %d, total: %d)", got, expected, good, len(selection)) 353 } 354 if got < expected-slack { 355 t.Fatalf("got fewer good peers (%% got: %d, %% expected: %d, number of good addrs: %d, total: %d)", got, expected, good, len(selection)) 356 } 357 } 358 359 func TestAddrBookHasAddress(t *testing.T) { 360 fname := createTempFileName("addrbook_test") 361 defer deleteTempFile(fname) 362 363 book := NewAddrBook(fname, true) 364 book.SetLogger(log.TestingLogger()) 365 addr := randIPv4Address(t) 366 book.AddAddress(addr, addr) 367 368 assert.True(t, book.HasAddress(addr)) 369 370 book.RemoveAddress(addr) 371 372 assert.False(t, book.HasAddress(addr)) 373 } 374 375 func testCreatePrivateAddrs(t *testing.T, numAddrs int) ([]*p2p.NetAddress, []string) { 376 addrs := make([]*p2p.NetAddress, numAddrs) 377 for i := 0; i < numAddrs; i++ { 378 addrs[i] = randIPv4Address(t) 379 } 380 381 private := make([]string, numAddrs) 382 for i, addr := range addrs { 383 private[i] = string(addr.ID) 384 } 385 return addrs, private 386 } 387 388 func TestAddrBookEmpty(t *testing.T) { 389 fname := createTempFileName("addrbook_test") 390 defer deleteTempFile(fname) 391 392 book := NewAddrBook(fname, true) 393 book.SetLogger(log.TestingLogger()) 394 // Check that empty book is empty 395 require.True(t, book.Empty()) 396 // Check that book with our address is empty 397 book.AddOurAddress(randIPv4Address(t)) 398 require.True(t, book.Empty()) 399 // Check that book with private addrs is empty 400 _, privateIds := testCreatePrivateAddrs(t, 5) 401 book.AddPrivateIDs(privateIds) 402 require.True(t, book.Empty()) 403 404 // Check that book with address is not empty 405 book.AddAddress(randIPv4Address(t), randIPv4Address(t)) 406 require.False(t, book.Empty()) 407 } 408 409 func TestPrivatePeers(t *testing.T) { 410 fname := createTempFileName("addrbook_test") 411 defer deleteTempFile(fname) 412 413 book := NewAddrBook(fname, true) 414 book.SetLogger(log.TestingLogger()) 415 416 addrs, private := testCreatePrivateAddrs(t, 10) 417 book.AddPrivateIDs(private) 418 419 // private addrs must not be added 420 for _, addr := range addrs { 421 err := book.AddAddress(addr, addr) 422 if assert.Error(t, err) { 423 _, ok := err.(ErrAddrBookPrivate) 424 assert.True(t, ok) 425 } 426 } 427 428 // addrs coming from private peers must not be added 429 err := book.AddAddress(randIPv4Address(t), addrs[0]) 430 if assert.Error(t, err) { 431 _, ok := err.(ErrAddrBookPrivateSrc) 432 assert.True(t, ok) 433 } 434 } 435 436 func testAddrBookAddressSelection(t *testing.T, bookSize int) { 437 // generate all combinations of old (m) and new addresses 438 for nBookOld := 0; nBookOld <= bookSize; nBookOld++ { 439 nBookNew := bookSize - nBookOld 440 dbgStr := fmt.Sprintf("book of size %d (new %d, old %d)", bookSize, nBookNew, nBookOld) 441 442 // create book and get selection 443 book, fname := createAddrBookWithMOldAndNNewAddrs(t, nBookOld, nBookNew) 444 defer deleteTempFile(fname) 445 addrs := book.GetSelectionWithBias(biasToSelectNewPeers) 446 assert.NotNil(t, addrs, "%s - expected a non-nil selection", dbgStr) 447 nAddrs := len(addrs) 448 assert.NotZero(t, nAddrs, "%s - expected at least one address in selection", dbgStr) 449 450 // check there's no nil addresses 451 for _, addr := range addrs { 452 if addr == nil { 453 t.Fatalf("%s - got nil address in selection %v", dbgStr, addrs) 454 } 455 } 456 457 // XXX: shadowing 458 nOld, nNew := countOldAndNewAddrsInSelection(addrs, book) 459 460 // Given: 461 // n - num new addrs, m - num old addrs 462 // k - num new addrs expected in the beginning (based on bias %) 463 // i=min(n, max(k,r-m)), aka expNew 464 // j=min(m, r-i), aka expOld 465 // 466 // We expect this layout: 467 // indices: 0...i-1 i...i+j-1 468 // addresses: N0..Ni-1 O0..Oj-1 469 // 470 // There is at least one partition and at most three. 471 var ( 472 k = percentageOfNum(biasToSelectNewPeers, nAddrs) 473 expNew = cmn.MinInt(nNew, cmn.MaxInt(k, nAddrs-nBookOld)) 474 expOld = cmn.MinInt(nOld, nAddrs-expNew) 475 ) 476 477 // Verify that the number of old and new addresses are as expected 478 if nNew != expNew { 479 t.Fatalf("%s - expected new addrs %d, got %d", dbgStr, expNew, nNew) 480 } 481 if nOld != expOld { 482 t.Fatalf("%s - expected old addrs %d, got %d", dbgStr, expOld, nOld) 483 } 484 485 // Verify that the order of addresses is as expected 486 // Get the sequence types and lengths of the selection 487 seqLens, seqTypes, err := analyseSelectionLayout(book, addrs) 488 assert.NoError(t, err, "%s", dbgStr) 489 490 // Build a list with the expected lengths of partitions and another with the expected types, e.g.: 491 // expSeqLens = [10, 22], expSeqTypes = [1, 2] 492 // means we expect 10 new (type 1) addresses followed by 22 old (type 2) addresses. 493 var expSeqLens []int 494 var expSeqTypes []int 495 496 switch { 497 case expOld == 0: // all new addresses 498 expSeqLens = []int{nAddrs} 499 expSeqTypes = []int{1} 500 case expNew == 0: // all old addresses 501 expSeqLens = []int{nAddrs} 502 expSeqTypes = []int{2} 503 case nAddrs-expNew-expOld == 0: // new addresses, old addresses 504 expSeqLens = []int{expNew, expOld} 505 expSeqTypes = []int{1, 2} 506 } 507 508 assert.Equal(t, expSeqLens, seqLens, 509 "%s - expected sequence lengths of old/new %v, got %v", 510 dbgStr, expSeqLens, seqLens) 511 assert.Equal(t, expSeqTypes, seqTypes, 512 "%s - expected sequence types (1-new, 2-old) was %v, got %v", 513 dbgStr, expSeqTypes, seqTypes) 514 } 515 } 516 517 func TestMultipleAddrBookAddressSelection(t *testing.T) { 518 // test books with smaller size, < N 519 const N = 32 520 for bookSize := 1; bookSize < N; bookSize++ { 521 testAddrBookAddressSelection(t, bookSize) 522 } 523 524 // Test for two books with sizes from following ranges 525 ranges := [...][]int{{33, 100}, {100, 175}} 526 bookSizes := make([]int, 0, len(ranges)) 527 for _, r := range ranges { 528 bookSizes = append(bookSizes, cmn.RandIntn(r[1]-r[0])+r[0]) 529 } 530 t.Logf("Testing address selection for the following book sizes %v\n", bookSizes) 531 for _, bookSize := range bookSizes { 532 testAddrBookAddressSelection(t, bookSize) 533 } 534 } 535 536 func assertMOldAndNNewAddrsInSelection(t *testing.T, m, n int, addrs []*p2p.NetAddress, book *addrBook) { 537 nOld, nNew := countOldAndNewAddrsInSelection(addrs, book) 538 assert.Equal(t, m, nOld, "old addresses") 539 assert.Equal(t, n, nNew, "new addresses") 540 } 541 542 func createTempFileName(prefix string) string { 543 f, err := ioutil.TempFile("", prefix) 544 if err != nil { 545 panic(err) 546 } 547 fname := f.Name() 548 err = f.Close() 549 if err != nil { 550 panic(err) 551 } 552 return fname 553 } 554 555 func deleteTempFile(fname string) { 556 err := os.Remove(fname) 557 if err != nil { 558 panic(err) 559 } 560 } 561 562 func createAddrBookWithMOldAndNNewAddrs(t *testing.T, nOld, nNew int) (book *addrBook, fname string) { 563 fname = createTempFileName("addrbook_test") 564 565 book = NewAddrBook(fname, true) 566 book.SetLogger(log.TestingLogger()) 567 assert.Zero(t, book.Size()) 568 569 randAddrs := randNetAddressPairs(t, nOld) 570 for _, addr := range randAddrs { 571 book.AddAddress(addr.addr, addr.src) 572 book.MarkGood(addr.addr.ID) 573 } 574 575 randAddrs = randNetAddressPairs(t, nNew) 576 for _, addr := range randAddrs { 577 book.AddAddress(addr.addr, addr.src) 578 } 579 580 return 581 } 582 583 func countOldAndNewAddrsInSelection(addrs []*p2p.NetAddress, book *addrBook) (nOld, nNew int) { 584 for _, addr := range addrs { 585 if book.IsGood(addr) { 586 nOld++ 587 } else { 588 nNew++ 589 } 590 } 591 return 592 } 593 594 // Analyse the layout of the selection specified by 'addrs' 595 // Returns: 596 // - seqLens - the lengths of the sequences of addresses of same type 597 // - seqTypes - the types of sequences in selection 598 func analyseSelectionLayout(book *addrBook, addrs []*p2p.NetAddress) (seqLens, seqTypes []int, err error) { 599 // address types are: 0 - nil, 1 - new, 2 - old 600 var ( 601 prevType = 0 602 currentSeqLen = 0 603 ) 604 605 for _, addr := range addrs { 606 addrType := 0 607 if book.IsGood(addr) { 608 addrType = 2 609 } else { 610 addrType = 1 611 } 612 if addrType != prevType && prevType != 0 { 613 seqLens = append(seqLens, currentSeqLen) 614 seqTypes = append(seqTypes, prevType) 615 currentSeqLen = 0 616 } 617 currentSeqLen++ 618 prevType = addrType 619 } 620 621 seqLens = append(seqLens, currentSeqLen) 622 seqTypes = append(seqTypes, prevType) 623 624 return 625 }