github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/connection/peerManager.go (about)

     1  package connection
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/libp2p/go-libp2p/core/peer"
    10  	"github.com/rs/zerolog"
    11  
    12  	"github.com/onflow/flow-go/module/component"
    13  	"github.com/onflow/flow-go/module/irrecoverable"
    14  	"github.com/onflow/flow-go/network/p2p"
    15  	p2plogging "github.com/onflow/flow-go/network/p2p/logging"
    16  	"github.com/onflow/flow-go/utils/logging"
    17  	"github.com/onflow/flow-go/utils/rand"
    18  )
    19  
    20  // DefaultPeerUpdateInterval is default duration for which the peer manager waits in between attempts to update peer connections.
    21  // We set it to 1 second to be aligned with the heartbeat intervals of libp2p, alsp, and gossipsub.
    22  var DefaultPeerUpdateInterval = time.Second
    23  
    24  var _ p2p.PeerManager = (*PeerManager)(nil)
    25  var _ component.Component = (*PeerManager)(nil)
    26  var _ p2p.RateLimiterConsumer = (*PeerManager)(nil)
    27  
    28  // PeerManager adds and removes connections to peers periodically and on request
    29  type PeerManager struct {
    30  	component.Component
    31  
    32  	logger             zerolog.Logger
    33  	peersProvider      p2p.PeersProvider // callback to retrieve list of peers to connect to
    34  	peerRequestQ       chan struct{}     // a channel to queue a peer update request
    35  	connector          p2p.PeerUpdater   // connector to connect or disconnect from peers
    36  	peerUpdateInterval time.Duration     // interval the peer manager runs on
    37  
    38  	peersProviderMu sync.RWMutex
    39  }
    40  
    41  // NewPeerManager creates a new peer manager which calls the peersProvider callback to get a list of peers to connect to
    42  // and it uses the connector to actually connect or disconnect from peers.
    43  func NewPeerManager(logger zerolog.Logger, updateInterval time.Duration, connector p2p.PeerUpdater) *PeerManager {
    44  	pm := &PeerManager{
    45  		logger:             logger.With().Str("component", "peer-manager").Logger(),
    46  		connector:          connector,
    47  		peerRequestQ:       make(chan struct{}, 1),
    48  		peerUpdateInterval: updateInterval,
    49  	}
    50  
    51  	pm.Component = component.NewComponentManagerBuilder().
    52  		AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
    53  			// makes sure that peer update request is invoked once before returning
    54  			pm.RequestPeerUpdate()
    55  
    56  			ready()
    57  			pm.updateLoop(ctx)
    58  		}).
    59  		AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
    60  			ready()
    61  			pm.periodicLoop(ctx)
    62  		}).
    63  		Build()
    64  
    65  	return pm
    66  }
    67  
    68  // updateLoop triggers an update peer request when it has been requested
    69  func (pm *PeerManager) updateLoop(ctx irrecoverable.SignalerContext) {
    70  	for {
    71  		select {
    72  		case <-ctx.Done():
    73  			return
    74  		default:
    75  		}
    76  
    77  		select {
    78  		case <-ctx.Done():
    79  			return
    80  		case <-pm.peerRequestQ:
    81  			pm.updatePeers(ctx)
    82  		}
    83  	}
    84  }
    85  
    86  // periodicLoop periodically triggers an update peer request
    87  func (pm *PeerManager) periodicLoop(ctx irrecoverable.SignalerContext) {
    88  	// add a random delay to initial launch to avoid synchronizing this
    89  	// potentially expensive operation across the network
    90  	r, err := rand.Uint64n(uint64(pm.peerUpdateInterval.Nanoseconds()))
    91  	if err != nil {
    92  		ctx.Throw(fmt.Errorf("unable to generate random interval: %w", err))
    93  	}
    94  	delay := time.Duration(r)
    95  
    96  	ticker := time.NewTicker(pm.peerUpdateInterval)
    97  	defer ticker.Stop()
    98  
    99  	select {
   100  	case <-ctx.Done():
   101  		return
   102  	case <-time.After(delay):
   103  	}
   104  
   105  	for {
   106  		select {
   107  		case <-ctx.Done():
   108  			return
   109  		case <-ticker.C:
   110  			pm.RequestPeerUpdate()
   111  		}
   112  	}
   113  }
   114  
   115  // RequestPeerUpdate requests an update to the peer connections of this node.
   116  // If a peer update has already been requested (either as a periodic request or an on-demand request) and is outstanding,
   117  // then this call is a no-op.
   118  func (pm *PeerManager) RequestPeerUpdate() {
   119  	select {
   120  	case pm.peerRequestQ <- struct{}{}:
   121  	default:
   122  	}
   123  }
   124  
   125  // updatePeers updates the peers by connecting to all the nodes provided by the peersProvider callback and disconnecting from
   126  // previous nodes that are no longer in the new list of nodes.
   127  func (pm *PeerManager) updatePeers(ctx context.Context) {
   128  	pm.peersProviderMu.RLock()
   129  	defer pm.peersProviderMu.RUnlock()
   130  
   131  	if pm.peersProvider == nil {
   132  		pm.logger.Error().Msg("peers provider not set")
   133  		return
   134  	}
   135  
   136  	// get all the peer ids to connect to
   137  	peers := pm.peersProvider()
   138  
   139  	pm.logger.Trace().
   140  		Str("peers", fmt.Sprintf("%v", peers)).
   141  		Msg("connecting to peers")
   142  
   143  	// ask the connector to connect to all peers in the list
   144  	pm.connector.UpdatePeers(ctx, peers)
   145  }
   146  
   147  // ForceUpdatePeers initiates an update to the peer connections of this node immediately
   148  func (pm *PeerManager) ForceUpdatePeers(ctx context.Context) {
   149  	pm.updatePeers(ctx)
   150  }
   151  
   152  // SetPeersProvider sets the peers provider
   153  // SetPeersProvider may be called at most once
   154  func (pm *PeerManager) SetPeersProvider(peersProvider p2p.PeersProvider) {
   155  	pm.peersProviderMu.Lock()
   156  	defer pm.peersProviderMu.Unlock()
   157  
   158  	if pm.peersProvider != nil {
   159  		pm.logger.Fatal().Msg("peers provider already set")
   160  	}
   161  
   162  	pm.peersProvider = peersProvider
   163  }
   164  
   165  // OnRateLimitedPeer rate limiter distributor consumer func that will be called when a peer is rate limited, the rate limited peer
   166  // is disconnected immediately after being rate limited.
   167  func (pm *PeerManager) OnRateLimitedPeer(pid peer.ID, role, msgType, topic, reason string) {
   168  	pm.logger.Warn().
   169  		Str("peer_id", p2plogging.PeerId(pid)).
   170  		Str("role", role).
   171  		Str("message_type", msgType).
   172  		Str("topic", topic).
   173  		Str("reason", reason).
   174  		Bool(logging.KeySuspicious, true).
   175  		Msg("pruning connection to rate-limited peer")
   176  	pm.RequestPeerUpdate()
   177  }