github.com/KYVENetwork/cometbft/v38@v38.0.3/p2p/pex/pex_reactor.go (about)

     1  package pex
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/KYVENetwork/cometbft/v38/libs/cmap"
    10  	cmtmath "github.com/KYVENetwork/cometbft/v38/libs/math"
    11  	cmtrand "github.com/KYVENetwork/cometbft/v38/libs/rand"
    12  	"github.com/KYVENetwork/cometbft/v38/libs/service"
    13  	"github.com/KYVENetwork/cometbft/v38/p2p"
    14  	"github.com/KYVENetwork/cometbft/v38/p2p/conn"
    15  	tmp2p "github.com/KYVENetwork/cometbft/v38/proto/cometbft/v38/p2p"
    16  )
    17  
    18  type Peer = p2p.Peer
    19  
    20  const (
    21  	// PexChannel is a channel for PEX messages
    22  	PexChannel = byte(0x00)
    23  
    24  	// over-estimate of max NetAddress size
    25  	// hexID (40) + IP (16) + Port (2) + Name (100) ...
    26  	// NOTE: dont use massive DNS name ..
    27  	maxAddressSize = 256
    28  
    29  	// NOTE: amplificaiton factor!
    30  	// small request results in up to maxMsgSize response
    31  	maxMsgSize = maxAddressSize * maxGetSelection
    32  
    33  	// ensure we have enough peers
    34  	defaultEnsurePeersPeriod = 30 * time.Second
    35  
    36  	// Seed/Crawler constants
    37  
    38  	// minTimeBetweenCrawls is a minimum time between attempts to crawl a peer.
    39  	minTimeBetweenCrawls = 2 * time.Minute
    40  
    41  	// check some peers every this
    42  	crawlPeerPeriod = 30 * time.Second
    43  
    44  	maxAttemptsToDial = 16 // ~ 35h in total (last attempt - 18h)
    45  
    46  	// if node connects to seed, it does not have any trusted peers.
    47  	// Especially in the beginning, node should have more trusted peers than
    48  	// untrusted.
    49  	biasToSelectNewPeers = 30 // 70 to select good peers
    50  
    51  	// if a peer is marked bad, it will be banned for at least this time period
    52  	defaultBanTime = 24 * time.Hour
    53  )
    54  
    55  type errMaxAttemptsToDial struct{}
    56  
    57  func (e errMaxAttemptsToDial) Error() string {
    58  	return fmt.Sprintf("reached max attempts %d to dial", maxAttemptsToDial)
    59  }
    60  
    61  type errTooEarlyToDial struct {
    62  	backoffDuration time.Duration
    63  	lastDialed      time.Time
    64  }
    65  
    66  func (e errTooEarlyToDial) Error() string {
    67  	return fmt.Sprintf(
    68  		"too early to dial (backoff duration: %d, last dialed: %v, time since: %v)",
    69  		e.backoffDuration, e.lastDialed, time.Since(e.lastDialed))
    70  }
    71  
    72  // Reactor handles PEX (peer exchange) and ensures that an
    73  // adequate number of peers are connected to the switch.
    74  //
    75  // It uses `AddrBook` (address book) to store `NetAddress`es of the peers.
    76  //
    77  // ## Preventing abuse
    78  //
    79  // Only accept pexAddrsMsg from peers we sent a corresponding pexRequestMsg too.
    80  // Only accept one pexRequestMsg every ~defaultEnsurePeersPeriod.
    81  type Reactor struct {
    82  	p2p.BaseReactor
    83  
    84  	book              AddrBook
    85  	config            *ReactorConfig
    86  	ensurePeersPeriod time.Duration // TODO: should go in the config
    87  
    88  	// maps to prevent abuse
    89  	requestsSent         *cmap.CMap // ID->struct{}: unanswered send requests
    90  	lastReceivedRequests *cmap.CMap // ID->time.Time: last time peer requested from us
    91  
    92  	seedAddrs []*p2p.NetAddress
    93  
    94  	attemptsToDial sync.Map // address (string) -> {number of attempts (int), last time dialed (time.Time)}
    95  
    96  	// seed/crawled mode fields
    97  	crawlPeerInfos map[p2p.ID]crawlPeerInfo
    98  }
    99  
   100  func (r *Reactor) minReceiveRequestInterval() time.Duration {
   101  	// NOTE: must be less than ensurePeersPeriod, otherwise we'll request
   102  	// peers too quickly from others and they'll think we're bad!
   103  	return r.ensurePeersPeriod / 3
   104  }
   105  
   106  // ReactorConfig holds reactor specific configuration data.
   107  type ReactorConfig struct {
   108  	// Seed/Crawler mode
   109  	SeedMode bool
   110  
   111  	// We want seeds to only advertise good peers. Therefore they should wait at
   112  	// least as long as we expect it to take for a peer to become good before
   113  	// disconnecting.
   114  	SeedDisconnectWaitPeriod time.Duration
   115  
   116  	// Maximum pause when redialing a persistent peer (if zero, exponential backoff is used)
   117  	PersistentPeersMaxDialPeriod time.Duration
   118  
   119  	// Seeds is a list of addresses reactor may use
   120  	// if it can't connect to peers in the addrbook.
   121  	Seeds []string
   122  }
   123  
   124  type _attemptsToDial struct {
   125  	number     int
   126  	lastDialed time.Time
   127  }
   128  
   129  // NewReactor creates new PEX reactor.
   130  func NewReactor(b AddrBook, config *ReactorConfig) *Reactor {
   131  	r := &Reactor{
   132  		book:                 b,
   133  		config:               config,
   134  		ensurePeersPeriod:    defaultEnsurePeersPeriod,
   135  		requestsSent:         cmap.NewCMap(),
   136  		lastReceivedRequests: cmap.NewCMap(),
   137  		crawlPeerInfos:       make(map[p2p.ID]crawlPeerInfo),
   138  	}
   139  	r.BaseReactor = *p2p.NewBaseReactor("PEX", r)
   140  	return r
   141  }
   142  
   143  // OnStart implements BaseService
   144  func (r *Reactor) OnStart() error {
   145  	err := r.book.Start()
   146  	if err != nil && err != service.ErrAlreadyStarted {
   147  		return err
   148  	}
   149  
   150  	numOnline, seedAddrs, err := r.checkSeeds()
   151  	if err != nil {
   152  		return err
   153  	} else if numOnline == 0 && r.book.Empty() {
   154  		return errors.New("address book is empty and couldn't resolve any seed nodes")
   155  	}
   156  
   157  	r.seedAddrs = seedAddrs
   158  
   159  	// Check if this node should run
   160  	// in seed/crawler mode
   161  	if r.config.SeedMode {
   162  		go r.crawlPeersRoutine()
   163  	} else {
   164  		go r.ensurePeersRoutine()
   165  	}
   166  	return nil
   167  }
   168  
   169  // OnStop implements BaseService
   170  func (r *Reactor) OnStop() {
   171  	if err := r.book.Stop(); err != nil {
   172  		r.Logger.Error("Error stopping address book", "err", err)
   173  	}
   174  }
   175  
   176  // GetChannels implements Reactor
   177  func (r *Reactor) GetChannels() []*conn.ChannelDescriptor {
   178  	return []*conn.ChannelDescriptor{
   179  		{
   180  			ID:                  PexChannel,
   181  			Priority:            1,
   182  			SendQueueCapacity:   10,
   183  			RecvMessageCapacity: maxMsgSize,
   184  			MessageType:         &tmp2p.Message{},
   185  		},
   186  	}
   187  }
   188  
   189  // AddPeer implements Reactor by adding peer to the address book (if inbound)
   190  // or by requesting more addresses (if outbound).
   191  func (r *Reactor) AddPeer(p Peer) {
   192  	if p.IsOutbound() {
   193  		// For outbound peers, the address is already in the books -
   194  		// either via DialPeersAsync or r.Receive.
   195  		// Ask it for more peers if we need.
   196  		if r.book.NeedMoreAddrs() {
   197  			r.RequestAddrs(p)
   198  		}
   199  	} else {
   200  		// inbound peer is its own source
   201  		addr, err := p.NodeInfo().NetAddress()
   202  		if err != nil {
   203  			r.Logger.Error("Failed to get peer NetAddress", "err", err, "peer", p)
   204  			return
   205  		}
   206  
   207  		// Make it explicit that addr and src are the same for an inbound peer.
   208  		src := addr
   209  
   210  		// add to book. dont RequestAddrs right away because
   211  		// we don't trust inbound as much - let ensurePeersRoutine handle it.
   212  		err = r.book.AddAddress(addr, src)
   213  		r.logErrAddrBook(err)
   214  	}
   215  }
   216  
   217  // RemovePeer implements Reactor by resetting peer's requests info.
   218  func (r *Reactor) RemovePeer(p Peer, _ interface{}) {
   219  	id := string(p.ID())
   220  	r.requestsSent.Delete(id)
   221  	r.lastReceivedRequests.Delete(id)
   222  }
   223  
   224  func (r *Reactor) logErrAddrBook(err error) {
   225  	if err != nil {
   226  		switch err.(type) {
   227  		case ErrAddrBookNilAddr:
   228  			r.Logger.Error("Failed to add new address", "err", err)
   229  		default:
   230  			// non-routable, self, full book, private, etc.
   231  			r.Logger.Debug("Failed to add new address", "err", err)
   232  		}
   233  	}
   234  }
   235  
   236  // Receive implements Reactor by handling incoming PEX messages.
   237  func (r *Reactor) Receive(e p2p.Envelope) {
   238  	r.Logger.Debug("Received message", "src", e.Src, "chId", e.ChannelID, "msg", e.Message)
   239  
   240  	switch msg := e.Message.(type) {
   241  	case *tmp2p.PexRequest:
   242  
   243  		// NOTE: this is a prime candidate for amplification attacks,
   244  		// so it's important we
   245  		// 1) restrict how frequently peers can request
   246  		// 2) limit the output size
   247  
   248  		// If we're a seed and this is an inbound peer,
   249  		// respond once and disconnect.
   250  		if r.config.SeedMode && !e.Src.IsOutbound() {
   251  			id := string(e.Src.ID())
   252  			v := r.lastReceivedRequests.Get(id)
   253  			if v != nil {
   254  				// FlushStop/StopPeer are already
   255  				// running in a go-routine.
   256  				return
   257  			}
   258  			r.lastReceivedRequests.Set(id, time.Now())
   259  
   260  			// Send addrs and disconnect
   261  			r.SendAddrs(e.Src, r.book.GetSelectionWithBias(biasToSelectNewPeers))
   262  			go func() {
   263  				// In a go-routine so it doesn't block .Receive.
   264  				e.Src.FlushStop()
   265  				r.Switch.StopPeerGracefully(e.Src)
   266  			}()
   267  
   268  		} else {
   269  			// Check we're not receiving requests too frequently.
   270  			if err := r.receiveRequest(e.Src); err != nil {
   271  				r.Switch.StopPeerForError(e.Src, err)
   272  				r.book.MarkBad(e.Src.SocketAddr(), defaultBanTime)
   273  				return
   274  			}
   275  			r.SendAddrs(e.Src, r.book.GetSelection())
   276  		}
   277  
   278  	case *tmp2p.PexAddrs:
   279  		// If we asked for addresses, add them to the book
   280  		addrs, err := p2p.NetAddressesFromProto(msg.Addrs)
   281  		if err != nil {
   282  			r.Switch.StopPeerForError(e.Src, err)
   283  			r.book.MarkBad(e.Src.SocketAddr(), defaultBanTime)
   284  			return
   285  		}
   286  		err = r.ReceiveAddrs(addrs, e.Src)
   287  		if err != nil {
   288  			r.Switch.StopPeerForError(e.Src, err)
   289  			if err == ErrUnsolicitedList {
   290  				r.book.MarkBad(e.Src.SocketAddr(), defaultBanTime)
   291  			}
   292  			return
   293  		}
   294  
   295  	default:
   296  		r.Logger.Error(fmt.Sprintf("Unknown message type %T", msg))
   297  	}
   298  }
   299  
   300  // enforces a minimum amount of time between requests
   301  func (r *Reactor) receiveRequest(src Peer) error {
   302  	id := string(src.ID())
   303  	v := r.lastReceivedRequests.Get(id)
   304  	if v == nil {
   305  		// initialize with empty time
   306  		lastReceived := time.Time{}
   307  		r.lastReceivedRequests.Set(id, lastReceived)
   308  		return nil
   309  	}
   310  
   311  	lastReceived := v.(time.Time)
   312  	if lastReceived.Equal(time.Time{}) {
   313  		// first time gets a free pass. then we start tracking the time
   314  		lastReceived = time.Now()
   315  		r.lastReceivedRequests.Set(id, lastReceived)
   316  		return nil
   317  	}
   318  
   319  	now := time.Now()
   320  	minInterval := r.minReceiveRequestInterval()
   321  	if now.Sub(lastReceived) < minInterval {
   322  		return fmt.Errorf(
   323  			"peer (%v) sent next PEX request too soon. lastReceived: %v, now: %v, minInterval: %v. Disconnecting",
   324  			src.ID(),
   325  			lastReceived,
   326  			now,
   327  			minInterval,
   328  		)
   329  	}
   330  	r.lastReceivedRequests.Set(id, now)
   331  	return nil
   332  }
   333  
   334  // RequestAddrs asks peer for more addresses if we do not already have a
   335  // request out for this peer.
   336  func (r *Reactor) RequestAddrs(p Peer) {
   337  	id := string(p.ID())
   338  	if r.requestsSent.Has(id) {
   339  		return
   340  	}
   341  	r.Logger.Debug("Request addrs", "from", p)
   342  	r.requestsSent.Set(id, struct{}{})
   343  	p.Send(p2p.Envelope{
   344  		ChannelID: PexChannel,
   345  		Message:   &tmp2p.PexRequest{},
   346  	})
   347  }
   348  
   349  // ReceiveAddrs adds the given addrs to the addrbook if theres an open
   350  // request for this peer and deletes the open request.
   351  // If there's no open request for the src peer, it returns an error.
   352  func (r *Reactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error {
   353  	id := string(src.ID())
   354  	if !r.requestsSent.Has(id) {
   355  		return ErrUnsolicitedList
   356  	}
   357  	r.requestsSent.Delete(id)
   358  
   359  	srcAddr, err := src.NodeInfo().NetAddress()
   360  	if err != nil {
   361  		return err
   362  	}
   363  
   364  	srcIsSeed := false
   365  	for _, seedAddr := range r.seedAddrs {
   366  		if seedAddr.Equals(srcAddr) {
   367  			srcIsSeed = true
   368  			break
   369  		}
   370  	}
   371  
   372  	for _, netAddr := range addrs {
   373  		// NOTE: we check netAddr validity and routability in book#AddAddress.
   374  		err = r.book.AddAddress(netAddr, srcAddr)
   375  		if err != nil {
   376  			r.logErrAddrBook(err)
   377  			// XXX: should we be strict about incoming data and disconnect from a
   378  			// peer here too?
   379  			continue
   380  		}
   381  
   382  		// If this address came from a seed node, try to connect to it without
   383  		// waiting (#2093)
   384  		if srcIsSeed {
   385  			go func(addr *p2p.NetAddress) {
   386  				err := r.dialPeer(addr)
   387  				if err != nil {
   388  					switch err.(type) {
   389  					case errMaxAttemptsToDial, errTooEarlyToDial, p2p.ErrCurrentlyDialingOrExistingAddress:
   390  						r.Logger.Debug(err.Error(), "addr", addr)
   391  					default:
   392  						r.Logger.Debug(err.Error(), "addr", addr)
   393  					}
   394  				}
   395  			}(netAddr)
   396  		}
   397  	}
   398  
   399  	return nil
   400  }
   401  
   402  // SendAddrs sends addrs to the peer.
   403  func (r *Reactor) SendAddrs(p Peer, netAddrs []*p2p.NetAddress) {
   404  	e := p2p.Envelope{
   405  		ChannelID: PexChannel,
   406  		Message:   &tmp2p.PexAddrs{Addrs: p2p.NetAddressesToProto(netAddrs)},
   407  	}
   408  	p.Send(e)
   409  }
   410  
   411  // SetEnsurePeersPeriod sets period to ensure peers connected.
   412  func (r *Reactor) SetEnsurePeersPeriod(d time.Duration) {
   413  	r.ensurePeersPeriod = d
   414  }
   415  
   416  // Ensures that sufficient peers are connected. (continuous)
   417  func (r *Reactor) ensurePeersRoutine() {
   418  	var (
   419  		seed   = cmtrand.NewRand()
   420  		jitter = seed.Int63n(r.ensurePeersPeriod.Nanoseconds())
   421  	)
   422  
   423  	// Randomize first round of communication to avoid thundering herd.
   424  	// If no peers are present directly start connecting so we guarantee swift
   425  	// setup with the help of configured seeds.
   426  	if r.nodeHasSomePeersOrDialingAny() {
   427  		time.Sleep(time.Duration(jitter))
   428  	}
   429  
   430  	// fire once immediately.
   431  	// ensures we dial the seeds right away if the book is empty
   432  	r.ensurePeers()
   433  
   434  	// fire periodically
   435  	ticker := time.NewTicker(r.ensurePeersPeriod)
   436  	for {
   437  		select {
   438  		case <-ticker.C:
   439  			r.ensurePeers()
   440  		case <-r.Quit():
   441  			ticker.Stop()
   442  			return
   443  		}
   444  	}
   445  }
   446  
   447  // ensurePeers ensures that sufficient peers are connected. (once)
   448  //
   449  // heuristic that we haven't perfected yet, or, perhaps is manually edited by
   450  // the node operator. It should not be used to compute what addresses are
   451  // already connected or not.
   452  func (r *Reactor) ensurePeers() {
   453  	var (
   454  		out, in, dial = r.Switch.NumPeers()
   455  		numToDial     = r.Switch.MaxNumOutboundPeers() - (out + dial)
   456  	)
   457  	r.Logger.Info(
   458  		"Ensure peers",
   459  		"numOutPeers", out,
   460  		"numInPeers", in,
   461  		"numDialing", dial,
   462  		"numToDial", numToDial,
   463  	)
   464  
   465  	if numToDial <= 0 {
   466  		return
   467  	}
   468  
   469  	// bias to prefer more vetted peers when we have fewer connections.
   470  	// not perfect, but somewhate ensures that we prioritize connecting to more-vetted
   471  	// NOTE: range here is [10, 90]. Too high ?
   472  	newBias := cmtmath.MinInt(out, 8)*10 + 10
   473  
   474  	toDial := make(map[p2p.ID]*p2p.NetAddress)
   475  	// Try maxAttempts times to pick numToDial addresses to dial
   476  	maxAttempts := numToDial * 3
   477  
   478  	for i := 0; i < maxAttempts && len(toDial) < numToDial; i++ {
   479  		try := r.book.PickAddress(newBias)
   480  		if try == nil {
   481  			continue
   482  		}
   483  		if _, selected := toDial[try.ID]; selected {
   484  			continue
   485  		}
   486  		if r.Switch.IsDialingOrExistingAddress(try) {
   487  			continue
   488  		}
   489  		// TODO: consider moving some checks from toDial into here
   490  		// so we don't even consider dialing peers that we want to wait
   491  		// before dialing again, or have dialed too many times already
   492  		toDial[try.ID] = try
   493  	}
   494  
   495  	// Dial picked addresses
   496  	for _, addr := range toDial {
   497  		go func(addr *p2p.NetAddress) {
   498  			err := r.dialPeer(addr)
   499  			if err != nil {
   500  				switch err.(type) {
   501  				case errMaxAttemptsToDial, errTooEarlyToDial:
   502  					r.Logger.Debug(err.Error(), "addr", addr)
   503  				default:
   504  					r.Logger.Debug(err.Error(), "addr", addr)
   505  				}
   506  			}
   507  		}(addr)
   508  	}
   509  
   510  	if r.book.NeedMoreAddrs() {
   511  		// Check if banned nodes can be reinstated
   512  		r.book.ReinstateBadPeers()
   513  	}
   514  
   515  	if r.book.NeedMoreAddrs() {
   516  
   517  		// 1) Pick a random peer and ask for more.
   518  		peers := r.Switch.Peers().List()
   519  		peersCount := len(peers)
   520  		if peersCount > 0 {
   521  			peer := peers[cmtrand.Int()%peersCount]
   522  			r.Logger.Info("We need more addresses. Sending pexRequest to random peer", "peer", peer)
   523  			r.RequestAddrs(peer)
   524  		}
   525  
   526  		// 2) Dial seeds if we are not dialing anyone.
   527  		// This is done in addition to asking a peer for addresses to work-around
   528  		// peers not participating in PEX.
   529  		if len(toDial) == 0 {
   530  			r.Logger.Info("No addresses to dial. Falling back to seeds")
   531  			r.dialSeeds()
   532  		}
   533  	}
   534  }
   535  
   536  func (r *Reactor) dialAttemptsInfo(addr *p2p.NetAddress) (attempts int, lastDialed time.Time) {
   537  	_attempts, ok := r.attemptsToDial.Load(addr.DialString())
   538  	if !ok {
   539  		return
   540  	}
   541  	atd := _attempts.(_attemptsToDial)
   542  	return atd.number, atd.lastDialed
   543  }
   544  
   545  func (r *Reactor) dialPeer(addr *p2p.NetAddress) error {
   546  	attempts, lastDialed := r.dialAttemptsInfo(addr)
   547  	if !r.Switch.IsPeerPersistent(addr) && attempts > maxAttemptsToDial {
   548  		r.book.MarkBad(addr, defaultBanTime)
   549  		return errMaxAttemptsToDial{}
   550  	}
   551  
   552  	// exponential backoff if it's not our first attempt to dial given address
   553  	if attempts > 0 {
   554  		jitter := time.Duration(cmtrand.Float64() * float64(time.Second)) // 1s == (1e9 ns)
   555  		backoffDuration := jitter + ((1 << uint(attempts)) * time.Second)
   556  		backoffDuration = r.maxBackoffDurationForPeer(addr, backoffDuration)
   557  		sinceLastDialed := time.Since(lastDialed)
   558  		if sinceLastDialed < backoffDuration {
   559  			return errTooEarlyToDial{backoffDuration, lastDialed}
   560  		}
   561  	}
   562  
   563  	err := r.Switch.DialPeerWithAddress(addr)
   564  	if err != nil {
   565  		if _, ok := err.(p2p.ErrCurrentlyDialingOrExistingAddress); ok {
   566  			return err
   567  		}
   568  
   569  		markAddrInBookBasedOnErr(addr, r.book, err)
   570  		switch err.(type) {
   571  		case p2p.ErrSwitchAuthenticationFailure:
   572  			// NOTE: addr is removed from addrbook in markAddrInBookBasedOnErr
   573  			r.attemptsToDial.Delete(addr.DialString())
   574  		default:
   575  			r.attemptsToDial.Store(addr.DialString(), _attemptsToDial{attempts + 1, time.Now()})
   576  		}
   577  		return fmt.Errorf("dialing failed (attempts: %d): %w", attempts+1, err)
   578  	}
   579  
   580  	// cleanup any history
   581  	r.attemptsToDial.Delete(addr.DialString())
   582  	return nil
   583  }
   584  
   585  // maxBackoffDurationForPeer caps the backoff duration for persistent peers.
   586  func (r *Reactor) maxBackoffDurationForPeer(addr *p2p.NetAddress, planned time.Duration) time.Duration {
   587  	if r.config.PersistentPeersMaxDialPeriod > 0 &&
   588  		planned > r.config.PersistentPeersMaxDialPeriod &&
   589  		r.Switch.IsPeerPersistent(addr) {
   590  		return r.config.PersistentPeersMaxDialPeriod
   591  	}
   592  	return planned
   593  }
   594  
   595  // checkSeeds checks that addresses are well formed.
   596  // Returns number of seeds we can connect to, along with all seeds addrs.
   597  // return err if user provided any badly formatted seed addresses.
   598  // Doesn't error if the seed node can't be reached.
   599  // numOnline returns -1 if no seed nodes were in the initial configuration.
   600  func (r *Reactor) checkSeeds() (numOnline int, netAddrs []*p2p.NetAddress, err error) {
   601  	lSeeds := len(r.config.Seeds)
   602  	if lSeeds == 0 {
   603  		return -1, nil, nil
   604  	}
   605  	netAddrs, errs := p2p.NewNetAddressStrings(r.config.Seeds)
   606  	numOnline = lSeeds - len(errs)
   607  	for _, err := range errs {
   608  		switch e := err.(type) {
   609  		case p2p.ErrNetAddressLookup:
   610  			r.Logger.Error("Connecting to seed failed", "err", e)
   611  		default:
   612  			return 0, nil, fmt.Errorf("seed node configuration has error: %w", e)
   613  		}
   614  	}
   615  	return numOnline, netAddrs, nil
   616  }
   617  
   618  // randomly dial seeds until we connect to one or exhaust them
   619  func (r *Reactor) dialSeeds() {
   620  	perm := cmtrand.Perm(len(r.seedAddrs))
   621  	// perm := r.Switch.rng.Perm(lSeeds)
   622  	for _, i := range perm {
   623  		// dial a random seed
   624  		seedAddr := r.seedAddrs[i]
   625  		err := r.Switch.DialPeerWithAddress(seedAddr)
   626  
   627  		switch err.(type) {
   628  		case nil, p2p.ErrCurrentlyDialingOrExistingAddress:
   629  			return
   630  		}
   631  		r.Switch.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr)
   632  	}
   633  	// do not write error message if there were no seeds specified in config
   634  	if len(r.seedAddrs) > 0 {
   635  		r.Switch.Logger.Error("Couldn't connect to any seeds")
   636  	}
   637  }
   638  
   639  // AttemptsToDial returns the number of attempts to dial specific address. It
   640  // returns 0 if never attempted or successfully connected.
   641  func (r *Reactor) AttemptsToDial(addr *p2p.NetAddress) int {
   642  	lAttempts, attempted := r.attemptsToDial.Load(addr.DialString())
   643  	if attempted {
   644  		return lAttempts.(_attemptsToDial).number
   645  	}
   646  	return 0
   647  }
   648  
   649  //----------------------------------------------------------
   650  
   651  // Explores the network searching for more peers. (continuous)
   652  // Seed/Crawler Mode causes this node to quickly disconnect
   653  // from peers, except other seed nodes.
   654  func (r *Reactor) crawlPeersRoutine() {
   655  	// If we have any seed nodes, consult them first
   656  	if len(r.seedAddrs) > 0 {
   657  		r.dialSeeds()
   658  	} else {
   659  		// Do an initial crawl
   660  		r.crawlPeers(r.book.GetSelection())
   661  	}
   662  
   663  	// Fire periodically
   664  	ticker := time.NewTicker(crawlPeerPeriod)
   665  
   666  	for {
   667  		select {
   668  		case <-ticker.C:
   669  			r.attemptDisconnects()
   670  			r.crawlPeers(r.book.GetSelection())
   671  			r.cleanupCrawlPeerInfos()
   672  		case <-r.Quit():
   673  			return
   674  		}
   675  	}
   676  }
   677  
   678  // nodeHasSomePeersOrDialingAny returns true if the node is connected to some
   679  // peers or dialing them currently.
   680  func (r *Reactor) nodeHasSomePeersOrDialingAny() bool {
   681  	out, in, dial := r.Switch.NumPeers()
   682  	return out+in+dial > 0
   683  }
   684  
   685  // crawlPeerInfo handles temporary data needed for the network crawling
   686  // performed during seed/crawler mode.
   687  type crawlPeerInfo struct {
   688  	Addr *p2p.NetAddress `json:"addr"`
   689  	// The last time we crawled the peer or attempted to do so.
   690  	LastCrawled time.Time `json:"last_crawled"`
   691  }
   692  
   693  // crawlPeers will crawl the network looking for new peer addresses.
   694  func (r *Reactor) crawlPeers(addrs []*p2p.NetAddress) {
   695  	now := time.Now()
   696  
   697  	for _, addr := range addrs {
   698  		peerInfo, ok := r.crawlPeerInfos[addr.ID]
   699  
   700  		// Do not attempt to connect with peers we recently crawled.
   701  		if ok && now.Sub(peerInfo.LastCrawled) < minTimeBetweenCrawls {
   702  			continue
   703  		}
   704  
   705  		// Record crawling attempt.
   706  		r.crawlPeerInfos[addr.ID] = crawlPeerInfo{
   707  			Addr:        addr,
   708  			LastCrawled: now,
   709  		}
   710  
   711  		err := r.dialPeer(addr)
   712  		if err != nil {
   713  			switch err.(type) {
   714  			case errMaxAttemptsToDial, errTooEarlyToDial, p2p.ErrCurrentlyDialingOrExistingAddress:
   715  				r.Logger.Debug(err.Error(), "addr", addr)
   716  			default:
   717  				r.Logger.Debug(err.Error(), "addr", addr)
   718  			}
   719  			continue
   720  		}
   721  
   722  		peer := r.Switch.Peers().Get(addr.ID)
   723  		if peer != nil {
   724  			r.RequestAddrs(peer)
   725  		}
   726  	}
   727  }
   728  
   729  func (r *Reactor) cleanupCrawlPeerInfos() {
   730  	for id, info := range r.crawlPeerInfos {
   731  		// If we did not crawl a peer for 24 hours, it means the peer was removed
   732  		// from the addrbook => remove
   733  		//
   734  		// 10000 addresses / maxGetSelection = 40 cycles to get all addresses in
   735  		// the ideal case,
   736  		// 40 * crawlPeerPeriod ~ 20 minutes
   737  		if time.Since(info.LastCrawled) > 24*time.Hour {
   738  			delete(r.crawlPeerInfos, id)
   739  		}
   740  	}
   741  }
   742  
   743  // attemptDisconnects checks if we've been with each peer long enough to disconnect
   744  func (r *Reactor) attemptDisconnects() {
   745  	for _, peer := range r.Switch.Peers().List() {
   746  		if peer.Status().Duration < r.config.SeedDisconnectWaitPeriod {
   747  			continue
   748  		}
   749  		if peer.IsPersistent() {
   750  			continue
   751  		}
   752  		r.Switch.StopPeerGracefully(peer)
   753  	}
   754  }
   755  
   756  func markAddrInBookBasedOnErr(addr *p2p.NetAddress, book AddrBook, err error) {
   757  	// TODO: detect more "bad peer" scenarios
   758  	switch err.(type) {
   759  	case p2p.ErrSwitchAuthenticationFailure:
   760  		book.MarkBad(addr, defaultBanTime)
   761  	default:
   762  		book.MarkAttempt(addr)
   763  	}
   764  }