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 }