diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c765108d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.swp +*.swo +*.bak +.DS_Store +vendor diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..81e30fcc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +## 0.4.0 (March 6, 2017) + +BREAKING CHANGES: + +- DialSeeds now takes an AddrBook and returns an error: `DialSeeds(*AddrBook, []string) error` +- NewNetAddressString now returns an error: `NewNetAddressString(string) (*NetAddress, error)` + +FEATURES: + +- `NewNetAddressStrings([]string) ([]*NetAddress, error)` +- `AddrBook.Save()` + +IMPROVEMENTS: + +- PexReactor responsible for starting and stopping the AddrBook + +BUG FIXES: + +- DialSeeds returns an error instead of panicking on bad addresses + +## 0.3.5 (January 12, 2017) + +FEATURES + +- Toggle strict routability in the AddrBook + +BUG FIXES + +- Close filtered out connections +- Fixes for MakeConnectedSwitches and Connect2Switches + +## 0.3.4 (August 10, 2016) + +FEATURES: + +- Optionally filter connections by address or public key + +## 0.3.3 (May 12, 2016) + +FEATURES: + +- FuzzConn + +## 0.3.2 (March 12, 2016) + +IMPROVEMENTS: + +- Memory optimizations + +## 0.3.1 () + +FEATURES: + +- Configurable parameters + diff --git a/addrbook.go b/addrbook.go index 2bbae64f..ace0dba4 100644 --- a/addrbook.go +++ b/addrbook.go @@ -135,6 +135,9 @@ func (a *AddrBook) OnStart() error { func (a *AddrBook) OnStop() { a.BaseService.OnStop() +} + +func (a *AddrBook) Wait() { a.wg.Wait() } @@ -153,6 +156,7 @@ func (a *AddrBook) OurAddresses() []*NetAddress { return addrs } +// NOTE: addr must not be nil func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() @@ -368,6 +372,12 @@ func (a *AddrBook) loadFromFile(filePath string) bool { return true } +// Save saves the book. +func (a *AddrBook) Save() { + log.Info("Saving AddrBook to file", "size", a.Size()) + a.saveToFile(a.filePath) +} + /* Private methods */ func (a *AddrBook) saveRoutine() { diff --git a/addrbook_test.go b/addrbook_test.go index d5a83da8..7e8cb8d7 100644 --- a/addrbook_test.go +++ b/addrbook_test.go @@ -5,9 +5,9 @@ import ( "io/ioutil" "math/rand" "testing" -) -const addrBookStrict = true + "github.com/stretchr/testify/assert" +) func createTempFileName(prefix string) string { f, err := ioutil.TempFile("", prefix) @@ -22,23 +22,117 @@ func createTempFileName(prefix string) string { return fname } -func TestEmpty(t *testing.T) { +func TestAddrBookSaveLoad(t *testing.T) { fname := createTempFileName("addrbook_test") - // t.Logf("New tempfile name: %v", fname) - // Save an empty book & load it - book := NewAddrBook(fname, addrBookStrict) + // 0 addresses + book := NewAddrBook(fname, true) book.saveToFile(fname) - book = NewAddrBook(fname, addrBookStrict) + book = NewAddrBook(fname, true) book.loadFromFile(fname) - if book.Size() != 0 { - t.Errorf("Expected 0 addresses, found %v", book.Size()) + 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.loadFromFile(fname) + + assert.Equal(t, 100, book.Size()) +} + +func TestAddrBookLookup(t *testing.T) { + fname := createTempFileName("addrbook_test") + + randAddrs := randNetAddressPairs(t, 100) + + book := NewAddrBook(fname, true) + for _, addrSrc := range randAddrs { + addr := addrSrc.addr + src := addrSrc.src + book.AddAddress(addr, src) + + ka := book.addrLookup[addr.String()] + 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 randIPv4Address() *NetAddress { +func TestAddrBookPromoteToOld(t *testing.T) { + fname := createTempFileName("addrbook_test") + + randAddrs := randNetAddressPairs(t, 100) + + book := NewAddrBook(fname, true) + 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") + } +} + +func TestAddrBookHandlesDuplicates(t *testing.T) { + fname := createTempFileName("addrbook_test") + + book := NewAddrBook(fname, true) + + 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 *NetAddress + src *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) *NetAddress { for { ip := fmt.Sprintf("%v.%v.%v.%v", rand.Intn(254)+1, @@ -47,117 +141,10 @@ func randIPv4Address() *NetAddress { rand.Intn(255), ) port := rand.Intn(65535-1) + 1 - addr := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port)) + addr, err := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port)) + assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr } } } - -func TestSaveAddresses(t *testing.T) { - fname := createTempFileName("addrbook_test") - //t.Logf("New tempfile name: %v", fname) - - // Create some random addresses - randAddrs := []struct { - addr *NetAddress - src *NetAddress - }{} - for i := 0; i < 100; i++ { - addr := randIPv4Address() - src := randIPv4Address() - randAddrs = append(randAddrs, struct { - addr *NetAddress - src *NetAddress - }{ - addr: addr, - src: src, - }) - } - - // Create the book & populate & save - book := NewAddrBook(fname, addrBookStrict) - for _, addrSrc := range randAddrs { - book.AddAddress(addrSrc.addr, addrSrc.src) - } - if book.Size() != 100 { - t.Errorf("Expected 100 addresses, found %v", book.Size()) - } - book.saveToFile(fname) - - // Reload the book - book = NewAddrBook(fname, addrBookStrict) - book.loadFromFile(fname) - - // Test ... - - if book.Size() != 100 { - t.Errorf("Expected 100 addresses, found %v", book.Size()) - } - - for _, addrSrc := range randAddrs { - addr := addrSrc.addr - src := addrSrc.src - ka := book.addrLookup[addr.String()] - if ka == nil { - t.Fatalf("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 TestPromoteToOld(t *testing.T) { - fname := createTempFileName("addrbook_test") - t.Logf("New tempfile name: %v", fname) - - // Create some random addresses - randAddrs := []struct { - addr *NetAddress - src *NetAddress - }{} - for i := 0; i < 100; i++ { - addr := randIPv4Address() - src := randIPv4Address() - randAddrs = append(randAddrs, struct { - addr *NetAddress - src *NetAddress - }{ - addr: addr, - src: src, - }) - } - - // Create the book & populate & save - book := NewAddrBook(fname, addrBookStrict) - 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) - } - } - book.saveToFile(fname) - - // Reload the book - book = NewAddrBook(fname, addrBookStrict) - book.loadFromFile(fname) - - // Test ... - - if book.Size() != 100 { - t.Errorf("Expected 100 addresses, found %v", book.Size()) - } - - // TODO: do more testing :) - - selection := book.GetSelection() - t.Logf("selection: %v", selection) -} diff --git a/listener.go b/listener.go index 1d7fb956..962c2b14 100644 --- a/listener.go +++ b/listener.go @@ -70,7 +70,11 @@ func NewDefaultListener(protocol string, lAddr string, skipUPNP bool) Listener { log.Info("Local listener", "ip", listenerIP, "port", listenerPort) // Determine internal address... - var intAddr *NetAddress = NewNetAddressString(lAddr) + var intAddr *NetAddress + intAddr, err = NewNetAddressString(lAddr) + if err != nil { + PanicCrisis(err) + } // Determine external address... var extAddr *NetAddress diff --git a/netaddress.go b/netaddress.go index 34dc5caf..90fcf6a4 100644 --- a/netaddress.go +++ b/netaddress.go @@ -5,11 +5,12 @@ package p2p import ( + "errors" "net" "strconv" "time" - . "github.com/tendermint/go-common" + cmn "github.com/tendermint/go-common" ) type NetAddress struct { @@ -34,27 +35,43 @@ func NewNetAddress(addr net.Addr) *NetAddress { } // Also resolves the host if host is not an IP. -func NewNetAddressString(addr string) *NetAddress { +func NewNetAddressString(addr string) (*NetAddress, error) { + host, portStr, err := net.SplitHostPort(addr) if err != nil { - PanicSanity(err) + return nil, err } + ip := net.ParseIP(host) if ip == nil { if len(host) > 0 { ips, err := net.LookupIP(host) if err != nil { - PanicSanity(err) + return nil, err } ip = ips[0] } } + port, err := strconv.ParseUint(portStr, 10, 16) if err != nil { - PanicSanity(err) + return nil, err } + na := NewNetAddressIPPort(ip, uint16(port)) - return na + return na, nil +} + +func NewNetAddressStrings(addrs []string) ([]*NetAddress, error) { + netAddrs := make([]*NetAddress, len(addrs)) + for i, addr := range addrs { + netAddr, err := NewNetAddressString(addr) + if err != nil { + return nil, errors.New(cmn.Fmt("Error in address %s: %v", addr, err)) + } + netAddrs[i] = netAddr + } + return netAddrs, nil } func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress { @@ -81,7 +98,7 @@ func (na *NetAddress) Less(other interface{}) bool { if o, ok := other.(*NetAddress); ok { return na.String() < o.String() } else { - PanicSanity("Cannot compare unequal types") + cmn.PanicSanity("Cannot compare unequal types") return false } } diff --git a/pex_reactor.go b/pex_reactor.go index d7ccc761..45c4c96d 100644 --- a/pex_reactor.go +++ b/pex_reactor.go @@ -42,12 +42,14 @@ func NewPEXReactor(book *AddrBook) *PEXReactor { func (pexR *PEXReactor) OnStart() error { pexR.BaseReactor.OnStart() + pexR.book.Start() go pexR.ensurePeersRoutine() return nil } func (pexR *PEXReactor) OnStop() { pexR.BaseReactor.OnStop() + pexR.book.Stop() } // Implements Reactor @@ -64,7 +66,13 @@ func (pexR *PEXReactor) GetChannels() []*ChannelDescriptor { // Implements Reactor func (pexR *PEXReactor) AddPeer(peer *Peer) { // Add the peer to the address book - netAddr := NewNetAddressString(peer.ListenAddr) + netAddr, err := NewNetAddressString(peer.ListenAddr) + if err != nil { + // this should never happen + log.Error("Error in AddPeer: invalid peer address", "addr", peer.ListenAddr, "error", err) + return + } + if peer.IsOutbound() { if pexR.book.NeedMoreAddrs() { pexR.RequestPEX(peer) @@ -104,12 +112,13 @@ func (pexR *PEXReactor) Receive(chID byte, src *Peer, msgBytes []byte) { // (We don't want to get spammed with bad peers) srcAddr := src.Connection().RemoteAddress for _, addr := range msg.Addrs { - pexR.book.AddAddress(addr, srcAddr) + if addr != nil { + pexR.book.AddAddress(addr, srcAddr) + } } default: log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg))) } - } // Asks peer for more addresses. diff --git a/switch.go b/switch.go index 841c44f8..eed8ceea 100644 --- a/switch.go +++ b/switch.go @@ -296,20 +296,38 @@ func (sw *Switch) startInitPeer(peer *Peer) { sw.addPeerToReactors(peer) // run AddPeer on each reactor } -// Dial a list of seeds in random order -// Spawns a go routine for each dial -func (sw *Switch) DialSeeds(seeds []string) { +// Dial a list of seeds asynchronously in random order +func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error { + + netAddrs, err := NewNetAddressStrings(seeds) + if err != nil { + return err + } + + if addrBook != nil { + // add seeds to `addrBook` + ourAddrS := sw.nodeInfo.ListenAddr + ourAddr, _ := NewNetAddressString(ourAddrS) + for _, netAddr := range netAddrs { + // do not add ourselves + if netAddr.Equals(ourAddr) { + continue + } + addrBook.AddAddress(netAddr, ourAddr) + } + addrBook.Save() + } + // permute the list, dial them in random order. - perm := rand.Perm(len(seeds)) + perm := rand.Perm(len(netAddrs)) for i := 0; i < len(perm); i++ { go func(i int) { time.Sleep(time.Duration(rand.Int63n(3000)) * time.Millisecond) j := perm[i] - addr := NewNetAddressString(seeds[j]) - - sw.dialSeed(addr) + sw.dialSeed(netAddrs[j]) }(i) } + return nil } func (sw *Switch) dialSeed(addr *NetAddress) { diff --git a/version.go b/version.go index af98bef3..c13ec447 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package p2p -const Version = "0.3.5" // minor fixes +const Version = "0.4.0" // DialSeeds returns an error