package pex import ( "encoding/hex" "fmt" "io/ioutil" "os" "testing" "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" ) func createTempFileName(prefix string) string { f, err := ioutil.TempFile("", prefix) if err != nil { panic(err) } fname := f.Name() err = f.Close() if err != nil { panic(err) } return fname } func deleteTempFile(fname string) { err := os.Remove(fname) if err != nil { panic(err) } } func TestAddrBookPickAddress(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) // 0 addresses book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) assert.Zero(t, book.Size()) addr := book.PickAddress(50) assert.Nil(t, addr, "expected no address") randAddrs := randNetAddressPairs(t, 1) addrSrc := randAddrs[0] book.AddAddress(addrSrc.addr, addrSrc.src) // pick an address when we only have new address addr = book.PickAddress(0) assert.NotNil(t, addr, "expected an address") addr = book.PickAddress(50) assert.NotNil(t, addr, "expected an address") addr = book.PickAddress(100) assert.NotNil(t, addr, "expected an address") // pick an address when we only have old address book.MarkGood(addrSrc.addr) addr = book.PickAddress(0) assert.NotNil(t, addr, "expected an address") addr = book.PickAddress(50) assert.NotNil(t, addr, "expected an address") // in this case, nNew==0 but we biased 100% to new, so we return nil addr = book.PickAddress(100) assert.Nil(t, addr, "did not expected an address") } func TestAddrBookSaveLoad(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) // 0 addresses book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) book.saveToFile(fname) book = NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) book.loadFromFile(fname) assert.Zero(t, book.Size()) // 100 addresses randAddrs := randNetAddressPairs(t, 100) for _, addrSrc := range randAddrs { book.AddAddress(addrSrc.addr, addrSrc.src) } assert.Equal(t, 100, book.Size()) book.saveToFile(fname) book = NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) book.loadFromFile(fname) assert.Equal(t, 100, book.Size()) } func TestAddrBookLookup(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) randAddrs := randNetAddressPairs(t, 100) book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) for _, addrSrc := range randAddrs { addr := addrSrc.addr src := addrSrc.src book.AddAddress(addr, src) ka := book.addrLookup[addr.ID] assert.NotNil(t, ka, "Expected to find KnownAddress %v but wasn't there.", addr) if !(ka.Addr.Equals(addr) && ka.Src.Equals(src)) { t.Fatalf("KnownAddress doesn't match addr & src") } } } func TestAddrBookPromoteToOld(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) randAddrs := randNetAddressPairs(t, 100) book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) for _, addrSrc := range randAddrs { book.AddAddress(addrSrc.addr, addrSrc.src) } // Attempt all addresses. for _, addrSrc := range randAddrs { book.MarkAttempt(addrSrc.addr) } // Promote half of them for i, addrSrc := range randAddrs { if i%2 == 0 { book.MarkGood(addrSrc.addr) } } // TODO: do more testing :) selection := book.GetSelection() t.Logf("selection: %v", selection) if len(selection) > book.Size() { t.Errorf("selection could not be bigger than the book") } selection = book.GetSelectionWithBias(30) t.Logf("selection: %v", selection) if len(selection) > book.Size() { t.Errorf("selection with bias could not be bigger than the book") } assert.Equal(t, book.Size(), 100, "expecting book size to be 100") } func TestAddrBookHandlesDuplicates(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) randAddrs := randNetAddressPairs(t, 100) differentSrc := randIPv4Address(t) for _, addrSrc := range randAddrs { book.AddAddress(addrSrc.addr, addrSrc.src) book.AddAddress(addrSrc.addr, addrSrc.src) // duplicate book.AddAddress(addrSrc.addr, differentSrc) // different src } assert.Equal(t, 100, book.Size()) } type netAddressPair struct { addr *p2p.NetAddress src *p2p.NetAddress } func randNetAddressPairs(t *testing.T, n int) []netAddressPair { randAddrs := make([]netAddressPair, n) for i := 0; i < n; i++ { randAddrs[i] = netAddressPair{addr: randIPv4Address(t), src: randIPv4Address(t)} } return randAddrs } func randIPv4Address(t *testing.T) *p2p.NetAddress { for { ip := fmt.Sprintf("%v.%v.%v.%v", cmn.RandIntn(254)+1, cmn.RandIntn(255), cmn.RandIntn(255), cmn.RandIntn(255), ) port := cmn.RandIntn(65535-1) + 1 id := p2p.ID(hex.EncodeToString(cmn.RandBytes(p2p.IDByteLength))) idAddr := p2p.IDAddressString(id, fmt.Sprintf("%v:%v", ip, port)) addr, err := p2p.NewNetAddressString(idAddr) assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr } } } func TestAddrBookRemoveAddress(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) addr := randIPv4Address(t) book.AddAddress(addr, addr) assert.Equal(t, 1, book.Size()) book.RemoveAddress(addr) assert.Equal(t, 0, book.Size()) nonExistingAddr := randIPv4Address(t) book.RemoveAddress(nonExistingAddr) assert.Equal(t, 0, book.Size()) } func TestAddrBookGetSelection(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) // 1) empty book assert.Empty(t, book.GetSelection()) // 2) add one address addr := randIPv4Address(t) book.AddAddress(addr, addr) assert.Equal(t, 1, len(book.GetSelection())) assert.Equal(t, addr, book.GetSelection()[0]) // 3) add a bunch of addresses randAddrs := randNetAddressPairs(t, 100) for _, addrSrc := range randAddrs { book.AddAddress(addrSrc.addr, addrSrc.src) } // check there is no duplicates addrs := make(map[string]*p2p.NetAddress) selection := book.GetSelection() for _, addr := range selection { if dup, ok := addrs[addr.String()]; ok { t.Fatalf("selection %v contains duplicates %v", selection, dup) } addrs[addr.String()] = addr } if len(selection) > book.Size() { t.Errorf("selection %v could not be bigger than the book", selection) } } func TestAddrBookGetSelectionWithBias(t *testing.T) { const biasTowardsNewAddrs = 30 fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) // 1) empty book selection := book.GetSelectionWithBias(biasTowardsNewAddrs) assert.Empty(t, selection) // 2) add one address addr := randIPv4Address(t) book.AddAddress(addr, addr) selection = book.GetSelectionWithBias(biasTowardsNewAddrs) assert.Equal(t, 1, len(selection)) assert.Equal(t, addr, selection[0]) // 3) add a bunch of addresses randAddrs := randNetAddressPairs(t, 100) for _, addrSrc := range randAddrs { book.AddAddress(addrSrc.addr, addrSrc.src) } // check there is no duplicates addrs := make(map[string]*p2p.NetAddress) selection = book.GetSelectionWithBias(biasTowardsNewAddrs) for _, addr := range selection { if dup, ok := addrs[addr.String()]; ok { t.Fatalf("selection %v contains duplicates %v", selection, dup) } addrs[addr.String()] = addr } if len(selection) > book.Size() { t.Fatalf("selection %v could not be bigger than the book", selection) } // 4) mark 80% of the addresses as good randAddrsLen := len(randAddrs) for i, addrSrc := range randAddrs { if int((float64(i)/float64(randAddrsLen))*100) >= 20 { book.MarkGood(addrSrc.addr) } } selection = book.GetSelectionWithBias(biasTowardsNewAddrs) // check that ~70% of addresses returned are good good := 0 for _, addr := range selection { if book.IsGood(addr) { good++ } } got, expected := int((float64(good)/float64(len(selection)))*100), (100 - biasTowardsNewAddrs) if got >= expected { t.Fatalf("expected more good peers (%% got: %d, %% expected: %d, number of good addrs: %d, total: %d)", got, expected, good, len(selection)) } } func TestAddrBookHasAddress(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) addr := randIPv4Address(t) book.AddAddress(addr, addr) assert.True(t, book.HasAddress(addr)) book.RemoveAddress(addr) assert.False(t, book.HasAddress(addr)) } func testCreatePrivateAddrs(t *testing.T, numAddrs int) ([]*p2p.NetAddress, []string) { addrs := make([]*p2p.NetAddress, numAddrs) for i := 0; i < numAddrs; i++ { addrs[i] = randIPv4Address(t) } private := make([]string, numAddrs) for i, addr := range addrs { private[i] = string(addr.ID) } return addrs, private } func TestAddrBookEmpty(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) // Check that empty book is empty require.True(t, book.Empty()) // Check that book with our address is empty book.AddOurAddress(randIPv4Address(t)) require.True(t, book.Empty()) // Check that book with private addrs is empty _, privateIds := testCreatePrivateAddrs(t, 5) book.AddPrivateIDs(privateIds) require.True(t, book.Empty()) // Check that book with address is not empty book.AddAddress(randIPv4Address(t), randIPv4Address(t)) require.False(t, book.Empty()) } func TestPrivatePeers(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) book := NewAddrBook(fname, true) book.SetLogger(log.TestingLogger()) addrs, private := testCreatePrivateAddrs(t, 10) book.AddPrivateIDs(private) // private addrs must not be added for _, addr := range addrs { err := book.AddAddress(addr, addr) if assert.Error(t, err) { _, ok := err.(ErrAddrBookPrivate) assert.True(t, ok) } } // addrs coming from private peers must not be added err := book.AddAddress(randIPv4Address(t), addrs[0]) if assert.Error(t, err) { _, ok := err.(ErrAddrBookPrivateSrc) assert.True(t, ok) } }