github.com/aergoio/aergo@v1.3.1/p2p/waitpeermanager.go (about) 1 /* 2 * @file 3 * @copyright defined in aergo/LICENSE.txt 4 */ 5 6 package p2p 7 8 import ( 9 "errors" 10 "fmt" 11 "github.com/aergoio/aergo-lib/log" 12 "github.com/aergoio/aergo/p2p/p2pcommon" 13 "github.com/aergoio/aergo/p2p/p2putil" 14 "github.com/aergoio/aergo/types" 15 "github.com/libp2p/go-libp2p-core/network" 16 "sort" 17 "time" 18 ) 19 20 type handshakeResult struct { 21 s network.Stream 22 msgRW p2pcommon.MsgReadWriter 23 24 meta p2pcommon.PeerMeta 25 status *types.Status 26 } 27 28 func NewWaitingPeerManager(logger *log.Logger, pm *peerManager, lm p2pcommon.ListManager, maxCap int, useDiscover bool) p2pcommon.WaitingPeerManager { 29 var wpm p2pcommon.WaitingPeerManager 30 if !useDiscover { 31 sp := &staticWPManager{basePeerManager: basePeerManager{pm: pm, lm:lm, logger: logger, workingJobs: make(map[types.PeerID]ConnWork)}} 32 wpm = sp 33 } else { 34 dp := &dynamicWPManager{basePeerManager: basePeerManager{pm: pm, lm:lm, logger: logger, workingJobs: make(map[types.PeerID]ConnWork)}, maxPeers: maxCap} 35 wpm = dp 36 } 37 38 return wpm 39 } 40 41 type basePeerManager struct { 42 pm *peerManager 43 lm p2pcommon.ListManager 44 45 logger *log.Logger 46 workingJobs map[types.PeerID]ConnWork 47 } 48 49 func (dpm *basePeerManager) OnInboundConn(s network.Stream) { 50 peerID := s.Conn().RemotePeer() 51 tempMeta := p2pcommon.PeerMeta{ID: peerID} 52 addr := s.Conn().RemoteMultiaddr() 53 54 dpm.logger.Info().Str(p2putil.LogFullID, peerID.Pretty()).Str("multiaddr", addr.String()).Msg("new inbound peer arrived") 55 ip := p2putil.ExtractIPAddress(addr) 56 if ip == nil { 57 dpm.logger.Info().Str(p2putil.LogPeerID, p2putil.ShortForm(peerID)).Msg("Can't extract ip address from inbound peer") 58 s.Close() 59 return 60 } 61 if banned, _ := dpm.lm.IsBanned(ip.String(), peerID); banned { 62 dpm.logger.Info().Str(p2putil.LogPeerID, p2putil.ShortForm(peerID)).Str("multiaddr", addr.String()).Msg("inbound peer is banned by list manager") 63 s.Close() 64 return 65 } 66 tempMeta.IPAddress = ip.String() 67 68 query := inboundConnEvent{meta: tempMeta, p2pVer: p2pcommon.P2PVersionUnknown, foundC: make(chan bool)} 69 dpm.pm.inboundConnChan <- query 70 if exist := <-query.foundC; exist { 71 dpm.logger.Debug().Str(p2putil.LogPeerID, p2putil.ShortForm(peerID)).Msg("same peer as inbound peer already exists.") 72 s.Close() 73 return 74 } 75 76 h := dpm.pm.hsFactory.CreateHSHandler(false, false, peerID) 77 // check if remote peer is connected (already handshaked) 78 completeMeta, added := dpm.tryAddPeer(false, tempMeta, s, h) 79 if !added { 80 s.Close() 81 } else { 82 if tempMeta.IPAddress != completeMeta.IPAddress { 83 dpm.logger.Debug().Str("after", completeMeta.IPAddress).Msg("Update IP address of inbound remote peer") 84 } 85 } 86 } 87 88 func (dpm *basePeerManager) OnInboundConnLegacy(s network.Stream) { 89 version := p2pcommon.P2PVersion030 90 peerID := s.Conn().RemotePeer() 91 tempMeta := p2pcommon.PeerMeta{ID: peerID} 92 addr := s.Conn().RemoteMultiaddr() 93 94 dpm.logger.Info().Str(p2putil.LogFullID, peerID.Pretty()).Str("multiaddr", addr.String()).Msg("new legacy inbound peer arrived") 95 query := inboundConnEvent{meta: tempMeta, p2pVer: version, foundC: make(chan bool)} 96 dpm.pm.inboundConnChan <- query 97 if exist := <-query.foundC; exist { 98 dpm.logger.Debug().Str(p2putil.LogPeerID, p2putil.ShortForm(peerID)).Msg("same peer as inbound peer already exists.") 99 s.Close() 100 return 101 } 102 103 h := dpm.pm.hsFactory.CreateHSHandler(true, false, peerID) 104 // check if remote peer is connected (already handshaked) 105 completeMeta, added := dpm.tryAddPeer(false, tempMeta, s, h) 106 if !added { 107 s.Close() 108 } else { 109 if tempMeta.IPAddress != completeMeta.IPAddress { 110 dpm.logger.Debug().Str("after", completeMeta.IPAddress).Msg("Update IP address of inbound remote peer") 111 } 112 } 113 } 114 115 func (dpm *basePeerManager) CheckAndConnect() { 116 dpm.logger.Debug().Msg("checking space to connect more peers") 117 maxJobs := dpm.getRemainingSpaces() 118 if maxJobs == 0 { 119 return 120 } 121 dpm.connectWaitingPeers(maxJobs) 122 } 123 124 func (dpm *basePeerManager) InstantConnect(meta p2pcommon.PeerMeta) { 125 if _, ok := dpm.pm.remotePeers[meta.ID]; ok { 126 // skip if peer is already connected 127 return 128 } else if wp, ok := dpm.pm.waitingPeers[meta.ID]; ok { 129 // reset next trial to try connect 130 wp.NextTrial = time.Now().Add(-time.Hour) 131 wp.TrialCnt = 0 132 } else { 133 // add to waiting peer 134 dpm.pm.waitingPeers[meta.ID] = &p2pcommon.WaitingPeer{Meta: meta, NextTrial: time.Now().Add(-time.Hour)} 135 } 136 dpm.connectWaitingPeers(1) 137 } 138 139 func (dpm *basePeerManager) connectWaitingPeers(maxJob int) { 140 // do try to connection at most maxJobs cnt, 141 peers := make([]*p2pcommon.WaitingPeer, 0, len(dpm.pm.waitingPeers)) 142 for _, wp := range dpm.pm.waitingPeers { 143 peers = append(peers, wp) 144 } 145 sort.Sort(byNextTrial(peers)) 146 147 added := 0 148 now := time.Now() 149 for _, wp := range peers { 150 if added >= maxJob { 151 break 152 } 153 if wp.NextTrial.Before(now) { 154 // check if peer is currently working now 155 if _, exist := dpm.workingJobs[wp.Meta.ID]; exist { 156 continue 157 } 158 // 2019.09.02 connecting to outbound peer is not affected by whitelist. inbound peer will block 159 //if banned, _ := dpm.lm.IsBanned(wp.Meta.IPAddress, wp.Meta.ID); banned { 160 // dpm.logger.Info().Str(p2putil.LogPeerName, p2putil.ShortMetaForm(wp.Meta)).Msg("Skipping banned peer") 161 // continue 162 //} 163 dpm.logger.Info().Int("trial", wp.TrialCnt).Str(p2putil.LogPeerID, p2putil.ShortForm(wp.Meta.ID)).Msg("Starting scheduled try to connect peer") 164 165 dpm.workingJobs[wp.Meta.ID] = ConnWork{Meta: wp.Meta, PeerID: wp.Meta.ID, StartTime: time.Now()} 166 go dpm.runTryOutboundConnect(wp) 167 added++ 168 } else { 169 continue 170 } 171 } 172 } 173 174 // getRemainingSpaces check and return the number that can do connection work. 175 // the number depends on the number of current works and the number of waiting peers 176 func (dpm *basePeerManager) getRemainingSpaces() int { 177 // simpler version. just check total count 178 // has space to add more connection 179 if len(dpm.pm.waitingPeers) <= 0 { 180 return 0 181 } 182 affordWorker := p2pcommon.MaxConcurrentHandshake - len(dpm.workingJobs) 183 if affordWorker <= 0 { 184 return 0 185 } 186 return affordWorker 187 } 188 189 func (dpm *basePeerManager) runTryOutboundConnect(wp *p2pcommon.WaitingPeer) { 190 workResult := p2pcommon.ConnWorkResult{Meta: wp.Meta, TargetPeer: wp} 191 defer func() { 192 dpm.pm.workDoneChannel <- workResult 193 }() 194 195 meta := wp.Meta 196 legacy, s, err := dpm.getStream(meta) 197 if err != nil { 198 dpm.logger.Info().Err(err).Str(p2putil.LogPeerID, p2putil.ShortForm(meta.ID)).Msg("Failed to get stream.") 199 workResult.Result = err 200 return 201 } 202 h := dpm.pm.hsFactory.CreateHSHandler(legacy, true, meta.ID) 203 // handshake 204 completeMeta, added := dpm.tryAddPeer(true, meta, s, h) 205 if !added { 206 s.Close() 207 workResult.Result = errors.New("handshake failed") 208 return 209 } else { 210 if meta.IPAddress != completeMeta.IPAddress { 211 dpm.logger.Debug().Str(p2putil.LogPeerID, p2putil.ShortForm(completeMeta.ID)).Str("before", meta.IPAddress).Str("after", completeMeta.IPAddress).Msg("IP address of remote peer is changed to ") 212 } 213 } 214 } 215 216 // getStream returns is wire handshake is legacy or newer 217 func (dpm *basePeerManager) getStream(meta p2pcommon.PeerMeta) (bool, network.Stream, error) { 218 // try connect peer with possible versions 219 s, err := dpm.pm.nt.GetOrCreateStream(meta, p2pcommon.P2PSubAddr, p2pcommon.LegacyP2PSubAddr) 220 if err != nil { 221 return false, nil, err 222 } 223 switch s.Protocol() { 224 case p2pcommon.P2PSubAddr: 225 return false, s, nil 226 case p2pcommon.LegacyP2PSubAddr: 227 return true, s, nil 228 default: 229 return false, nil, fmt.Errorf("unknown p2p wire protocol %v", s.Protocol()) 230 } 231 } 232 233 // tryAddPeer will do check connecting peer and add. it will return peer meta information received from 234 // remote peer. stream s will be owned to remotePeer if succeed to add peer. 235 func (dpm *basePeerManager) tryAddPeer(outbound bool, meta p2pcommon.PeerMeta, s network.Stream, h p2pcommon.HSHandler) (p2pcommon.PeerMeta, bool) { 236 msgRW, remoteStatus, err := h.Handle(s, defaultHandshakeTTL) 237 if err != nil { 238 dpm.logger.Debug().Err(err).Bool("outbound", outbound).Str(p2putil.LogPeerID, p2putil.ShortForm(meta.ID)).Msg("Failed to handshake") 239 return meta, false 240 } 241 // update peer meta info using sent information from remote peer 242 receivedMeta := p2pcommon.NewMetaFromStatus(remoteStatus, outbound) 243 244 dpm.pm.peerHandshaked <- handshakeResult{meta: receivedMeta, status: remoteStatus, msgRW: msgRW, s: s} 245 return receivedMeta, true 246 } 247 248 func (dpm *basePeerManager) OnWorkDone(result p2pcommon.ConnWorkResult) { 249 meta := result.Meta 250 delete(dpm.workingJobs, meta.ID) 251 wp, ok := dpm.pm.waitingPeers[meta.ID] 252 if !ok { 253 dpm.logger.Debug().Str(p2putil.LogPeerName, p2putil.ShortMetaForm(meta)).Err(result.Result).Msg("Connection job finished") 254 return 255 } else { 256 dpm.logger.Debug().Str(p2putil.LogPeerName, p2putil.ShortMetaForm(meta)).Int("trial", wp.TrialCnt).Err(result.Result).Msg("Connection job finished") 257 } 258 wp.LastResult = result.Result 259 // success to connect 260 if result.Result == nil { 261 dpm.logger.Debug().Str(p2putil.LogPeerName, p2putil.ShortMetaForm(meta)).Msg("Deleting unimportant failed peer.") 262 delete(dpm.pm.waitingPeers, meta.ID) 263 } else { 264 // leave waitingpeer if needed to reconnect 265 if !setNextTrial(wp) { 266 dpm.logger.Debug().Str(p2putil.LogPeerName, p2putil.ShortMetaForm(meta)).Time("next_time", wp.NextTrial).Msg("Failed Connection will be retried") 267 delete(dpm.pm.waitingPeers, meta.ID) 268 } 269 } 270 271 } 272 273 type staticWPManager struct { 274 basePeerManager 275 } 276 277 func (spm *staticWPManager) OnPeerConnect(pid types.PeerID) { 278 delete(spm.pm.waitingPeers, pid) 279 } 280 281 func (spm *staticWPManager) OnPeerDisconnect(peer p2pcommon.RemotePeer) { 282 // if peer is designated peer , try reconnect by add peermeta to waiting peer 283 if _, ok := spm.pm.designatedPeers[peer.ID()]; ok { 284 spm.logger.Debug().Str(p2putil.LogPeerID, peer.Name()).Msg("server will try to reconnect designated peer after cooltime") 285 // These peers must have cool time. 286 spm.pm.waitingPeers[peer.ID()] = &p2pcommon.WaitingPeer{Meta: peer.Meta(), NextTrial: time.Now().Add(firstReconnectCoolTime)} 287 } 288 } 289 290 func (spm *staticWPManager) OnDiscoveredPeers(metas []p2pcommon.PeerMeta) int { 291 // static manager don't need to discovered peer. 292 return 0 293 } 294 295 type dynamicWPManager struct { 296 basePeerManager 297 298 maxPeers int 299 } 300 301 func (dpm *dynamicWPManager) OnPeerConnect(pid types.PeerID) { 302 // remove peer from wait pool 303 delete(dpm.pm.waitingPeers, pid) 304 } 305 306 func (dpm *dynamicWPManager) OnPeerDisconnect(peer p2pcommon.RemotePeer) { 307 // if peer is designated peer or trusted enough , try reconnect by add peermeta to waiting peer 308 // TODO check by trust level is not implemented yet. 309 if _, ok := dpm.pm.designatedPeers[peer.ID()]; ok { 310 dpm.logger.Debug().Str(p2putil.LogPeerID, peer.Name()).Msg("server will try to reconnect designated peer after cooltime") 311 // These peers must have cool time. 312 dpm.pm.waitingPeers[peer.ID()] = &p2pcommon.WaitingPeer{Meta: peer.Meta(), NextTrial: time.Now().Add(firstReconnectCoolTime)} 313 //dpm.pm.addAwait(peer.Meta()) 314 } 315 } 316 317 func (dpm *dynamicWPManager) OnDiscoveredPeers(metas []p2pcommon.PeerMeta) int { 318 addedWP := 0 319 for _, meta := range metas { 320 if _, ok := dpm.pm.remotePeers[meta.ID]; ok { 321 // skip connected peer 322 continue 323 } else if _, ok := dpm.pm.waitingPeers[meta.ID]; ok { 324 // skip already waiting peer 325 continue 326 } 327 328 // TODO check blacklist later. 329 dpm.pm.waitingPeers[meta.ID] = &p2pcommon.WaitingPeer{Meta: meta, NextTrial: time.Now()} 330 addedWP++ 331 } 332 return addedWP 333 } 334 335 func (dpm *dynamicWPManager) CheckAndConnect() { 336 dpm.logger.Debug().Msg("checking space to connect more peers") 337 maxJobs := dpm.getRemainingSpaces() 338 if maxJobs == 0 { 339 return 340 } 341 dpm.connectWaitingPeers(maxJobs) 342 } 343 344 func (dpm *dynamicWPManager) getRemainingSpaces() int { 345 // simpler version. just check total count 346 // has space to add more connection 347 affordCnt := dpm.maxPeers - len(dpm.pm.remotePeers) - len(dpm.workingJobs) 348 if affordCnt <= 0 { 349 return 0 350 } 351 affordWorker := dpm.basePeerManager.getRemainingSpaces() 352 if affordCnt < affordWorker { 353 return affordCnt 354 } else { 355 return affordWorker 356 } 357 } 358 359 type inboundConnEvent struct { 360 meta p2pcommon.PeerMeta 361 p2pVer p2pcommon.P2PVersion 362 foundC chan bool 363 } 364 365 type byNextTrial []*p2pcommon.WaitingPeer 366 367 func (a byNextTrial) Len() int { return len(a) } 368 func (a byNextTrial) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 369 func (a byNextTrial) Less(i, j int) bool { return a[i].NextTrial.Before(a[j].NextTrial) } 370 371 type ConnWork struct { 372 PeerID types.PeerID 373 Meta p2pcommon.PeerMeta 374 StartTime time.Time 375 } 376 377 // setNextTrial check if peer is worthy to connect, and set time when the server try to connect next time. 378 // It will true if this node is worth to try connect again, or return false if not. 379 func setNextTrial(wp *p2pcommon.WaitingPeer) bool { 380 if wp.Meta.Designated { 381 wp.TrialCnt++ 382 wp.NextTrial = time.Now().Add(getNextInterval(wp.TrialCnt)) 383 return true 384 } else { 385 return false 386 } 387 }