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  }