package pex import ( "fmt" "io/ioutil" "net" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/p2p/conn" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" ) var ( config *cfg.P2PConfig ) func init() { config = cfg.DefaultP2PConfig() config.PexReactor = true } func TestPEXReactorBasic(t *testing.T) { r, book := createReactor(&PEXReactorConfig{}) defer teardownReactor(book) assert.NotNil(t, r) assert.NotEmpty(t, r.GetChannels()) } func TestPEXReactorAddRemovePeer(t *testing.T) { r, book := createReactor(&PEXReactorConfig{}) defer teardownReactor(book) size := book.Size() peer := p2p.CreateRandomPeer(false) r.AddPeer(peer) assert.Equal(t, size+1, book.Size()) r.RemovePeer(peer, "peer not available") assert.Equal(t, size+1, book.Size()) outboundPeer := p2p.CreateRandomPeer(true) r.AddPeer(outboundPeer) assert.Equal(t, size+1, book.Size(), "outbound peers should not be added to the address book") r.RemovePeer(outboundPeer, "peer not available") assert.Equal(t, size+1, book.Size()) } func TestPEXReactorRunning(t *testing.T) { N := 3 switches := make([]*p2p.Switch, N) // directory to store address books dir, err := ioutil.TempDir("", "pex_reactor") require.Nil(t, err) defer os.RemoveAll(dir) // nolint: errcheck books := make([]*addrBook, N) logger := log.TestingLogger() // create switches for i := 0; i < N; i++ { switches[i] = p2p.MakeSwitch(config, i, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { books[i] = NewAddrBook(filepath.Join(dir, fmt.Sprintf("addrbook%d.json", i)), false) books[i].SetLogger(logger.With("pex", i)) sw.SetAddrBook(books[i]) sw.SetLogger(logger.With("pex", i)) r := NewPEXReactor(books[i], &PEXReactorConfig{}) r.SetLogger(logger.With("pex", i)) r.SetEnsurePeersPeriod(250 * time.Millisecond) sw.AddReactor("pex", r) return sw }) } addOtherNodeAddrToAddrBook := func(switchIndex, otherSwitchIndex int) { addr := switches[otherSwitchIndex].NodeInfo().NetAddress() books[switchIndex].AddAddress(addr, addr) } addOtherNodeAddrToAddrBook(0, 1) addOtherNodeAddrToAddrBook(1, 0) addOtherNodeAddrToAddrBook(2, 1) for i, sw := range switches { sw.AddListener(p2p.NewDefaultListener("tcp", sw.NodeInfo().ListenAddr, true, logger.With("pex", i))) err := sw.Start() // start switch and reactors require.Nil(t, err) } assertPeersWithTimeout(t, switches, 10*time.Millisecond, 10*time.Second, N-1) // stop them for _, s := range switches { s.Stop() } } func TestPEXReactorReceive(t *testing.T) { r, book := createReactor(&PEXReactorConfig{}) defer teardownReactor(book) peer := p2p.CreateRandomPeer(false) // we have to send a request to receive responses r.RequestAddrs(peer) size := book.Size() addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()} msg := cdc.MustMarshalBinary(&pexAddrsMessage{Addrs: addrs}) r.Receive(PexChannel, peer, msg) assert.Equal(t, size+1, book.Size()) msg = cdc.MustMarshalBinary(&pexRequestMessage{}) r.Receive(PexChannel, peer, msg) // should not panic. } func TestPEXReactorRequestMessageAbuse(t *testing.T) { r, book := createReactor(&PEXReactorConfig{}) defer teardownReactor(book) sw := createSwitchAndAddReactors(r) sw.SetAddrBook(book) peer := newMockPeer() p2p.AddPeerToSwitch(sw, peer) assert.True(t, sw.Peers().Has(peer.ID())) id := string(peer.ID()) msg := cdc.MustMarshalBinary(&pexRequestMessage{}) // first time creates the entry r.Receive(PexChannel, peer, msg) assert.True(t, r.lastReceivedRequests.Has(id)) assert.True(t, sw.Peers().Has(peer.ID())) // next time sets the last time value r.Receive(PexChannel, peer, msg) assert.True(t, r.lastReceivedRequests.Has(id)) assert.True(t, sw.Peers().Has(peer.ID())) // third time is too many too soon - peer is removed r.Receive(PexChannel, peer, msg) assert.False(t, r.lastReceivedRequests.Has(id)) assert.False(t, sw.Peers().Has(peer.ID())) } func TestPEXReactorAddrsMessageAbuse(t *testing.T) { r, book := createReactor(&PEXReactorConfig{}) defer teardownReactor(book) sw := createSwitchAndAddReactors(r) sw.SetAddrBook(book) peer := newMockPeer() p2p.AddPeerToSwitch(sw, peer) assert.True(t, sw.Peers().Has(peer.ID())) id := string(peer.ID()) // request addrs from the peer r.RequestAddrs(peer) assert.True(t, r.requestsSent.Has(id)) assert.True(t, sw.Peers().Has(peer.ID())) addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()} msg := cdc.MustMarshalBinary(&pexAddrsMessage{Addrs: addrs}) // receive some addrs. should clear the request r.Receive(PexChannel, peer, msg) assert.False(t, r.requestsSent.Has(id)) assert.True(t, sw.Peers().Has(peer.ID())) // receiving more addrs causes a disconnect r.Receive(PexChannel, peer, msg) assert.False(t, sw.Peers().Has(peer.ID())) } func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { // directory to store address books dir, err := ioutil.TempDir("", "pex_reactor") require.Nil(t, err) defer os.RemoveAll(dir) // nolint: errcheck // 1. create seed seed := p2p.MakeSwitch( config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { book := NewAddrBook(filepath.Join(dir, "addrbook0.json"), false) book.SetLogger(log.TestingLogger()) sw.SetAddrBook(book) sw.SetLogger(log.TestingLogger()) r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) sw.AddReactor("pex", r) return sw }, ) seed.AddListener( p2p.NewDefaultListener( "tcp", seed.NodeInfo().ListenAddr, true, log.TestingLogger(), ), ) require.Nil(t, seed.Start()) defer seed.Stop() // 2. create usual peer with only seed configured. peer := p2p.MakeSwitch( config, 1, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { book := NewAddrBook(filepath.Join(dir, "addrbook1.json"), false) book.SetLogger(log.TestingLogger()) sw.SetAddrBook(book) sw.SetLogger(log.TestingLogger()) r := NewPEXReactor( book, &PEXReactorConfig{ Seeds: []string{seed.NodeInfo().NetAddress().String()}, }, ) r.SetLogger(log.TestingLogger()) sw.AddReactor("pex", r) return sw }, ) require.Nil(t, peer.Start()) defer peer.Stop() // 3. check that the peer connects to seed immediately assertPeersWithTimeout(t, []*p2p.Switch{peer}, 10*time.Millisecond, 3*time.Second, 1) } func TestPEXReactorCrawlStatus(t *testing.T) { pexR, book := createReactor(&PEXReactorConfig{SeedMode: true}) defer teardownReactor(book) // Seed/Crawler mode uses data from the Switch sw := createSwitchAndAddReactors(pexR) sw.SetAddrBook(book) // Create a peer, add it to the peer set and the addrbook. peer := p2p.CreateRandomPeer(false) p2p.AddPeerToSwitch(pexR.Switch, peer) addr1 := peer.NodeInfo().NetAddress() pexR.book.AddAddress(addr1, addr1) // Add a non-connected address to the book. _, addr2 := p2p.CreateRoutableAddr() pexR.book.AddAddress(addr2, addr1) // Get some peerInfos to crawl peerInfos := pexR.getPeersToCrawl() // Make sure it has the proper number of elements assert.Equal(t, 2, len(peerInfos)) // TODO: test } func TestPEXReactorDoesNotAddPrivatePeersToAddrBook(t *testing.T) { peer := p2p.CreateRandomPeer(false) pexR, book := createReactor(&PEXReactorConfig{PrivatePeerIDs: []string{string(peer.NodeInfo().ID)}}) defer teardownReactor(book) // we have to send a request to receive responses pexR.RequestAddrs(peer) size := book.Size() addrs := []*p2p.NetAddress{peer.NodeInfo().NetAddress()} msg := cdc.MustMarshalBinary(&pexAddrsMessage{Addrs: addrs}) pexR.Receive(PexChannel, peer, msg) assert.Equal(t, size, book.Size()) pexR.AddPeer(peer) assert.Equal(t, size, book.Size()) } func TestPEXReactorDialPeer(t *testing.T) { pexR, book := createReactor(&PEXReactorConfig{}) defer teardownReactor(book) sw := createSwitchAndAddReactors(pexR) sw.SetAddrBook(book) peer := newMockPeer() addr := peer.NodeInfo().NetAddress() assert.Equal(t, 0, pexR.AttemptsToDial(addr)) // 1st unsuccessful attempt pexR.dialPeer(addr) assert.Equal(t, 1, pexR.AttemptsToDial(addr)) // 2nd unsuccessful attempt pexR.dialPeer(addr) // must be skipped because it is too early assert.Equal(t, 1, pexR.AttemptsToDial(addr)) if !testing.Short() { time.Sleep(3 * time.Second) // 3rd attempt pexR.dialPeer(addr) assert.Equal(t, 2, pexR.AttemptsToDial(addr)) } } type mockPeer struct { *cmn.BaseService pubKey crypto.PubKey addr *p2p.NetAddress outbound, persistent bool } func newMockPeer() mockPeer { _, netAddr := p2p.CreateRoutableAddr() mp := mockPeer{ addr: netAddr, pubKey: crypto.GenPrivKeyEd25519().PubKey(), } mp.BaseService = cmn.NewBaseService(nil, "MockPeer", mp) mp.Start() return mp } func (mp mockPeer) ID() p2p.ID { return mp.addr.ID } func (mp mockPeer) IsOutbound() bool { return mp.outbound } func (mp mockPeer) IsPersistent() bool { return mp.persistent } func (mp mockPeer) NodeInfo() p2p.NodeInfo { return p2p.NodeInfo{ ID: mp.addr.ID, ListenAddr: mp.addr.DialString(), } } func (mp mockPeer) RemoteIP() net.IP { return net.ParseIP("127.0.0.1") } func (mp mockPeer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} } func (mp mockPeer) Send(byte, []byte) bool { return false } func (mp mockPeer) TrySend(byte, []byte) bool { return false } func (mp mockPeer) Set(string, interface{}) {} func (mp mockPeer) Get(string) interface{} { return nil } func assertPeersWithTimeout( t *testing.T, switches []*p2p.Switch, checkPeriod, timeout time.Duration, nPeers int, ) { var ( ticker = time.NewTicker(checkPeriod) remaining = timeout ) for { select { case <-ticker.C: // check peers are connected allGood := true for _, s := range switches { outbound, inbound, _ := s.NumPeers() if outbound+inbound < nPeers { allGood = false } } remaining -= checkPeriod if remaining < 0 { remaining = 0 } if allGood { return } case <-time.After(remaining): numPeersStr := "" for i, s := range switches { outbound, inbound, _ := s.NumPeers() numPeersStr += fmt.Sprintf("%d => {outbound: %d, inbound: %d}, ", i, outbound, inbound) } t.Errorf( "expected all switches to be connected to at least one peer (switches: %s)", numPeersStr, ) return } } } func createReactor(config *PEXReactorConfig) (r *PEXReactor, book *addrBook) { // directory to store address book dir, err := ioutil.TempDir("", "pex_reactor") if err != nil { panic(err) } book = NewAddrBook(filepath.Join(dir, "addrbook.json"), true) book.SetLogger(log.TestingLogger()) r = NewPEXReactor(book, config) r.SetLogger(log.TestingLogger()) return } func teardownReactor(book *addrBook) { err := os.RemoveAll(filepath.Dir(book.FilePath())) if err != nil { panic(err) } } func createSwitchAndAddReactors(reactors ...p2p.Reactor) *p2p.Switch { sw := p2p.MakeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw }) sw.SetLogger(log.TestingLogger()) for _, r := range reactors { sw.AddReactor(r.String(), r) r.SetSwitch(sw) } return sw }