github.com/aergoio/aergo@v1.3.1/polaris/server/peerstate.go (about)

     1  /*
     2   * @file
     3   * @copyright defined in aergo/LICENSE.txt
     4   */
     5  
     6  package server
     7  
     8  import (
     9  	"bufio"
    10  	"fmt"
    11  	"github.com/aergoio/aergo/p2p/v030"
    12  	"github.com/aergoio/aergo/polaris/common"
    13  	"sync"
    14  	"sync/atomic"
    15  	"time"
    16  
    17  	"github.com/aergoio/aergo/p2p/p2pcommon"
    18  	"github.com/aergoio/aergo/p2p/p2putil"
    19  	"github.com/aergoio/aergo/types"
    20  )
    21  
    22  type PeerHealth int
    23  
    24  // PeersState
    25  const (
    26  	PeerHealth_GOOD PeerHealth = 0
    27  	PeerHealth_MID  PeerHealth = 1
    28  	PeerHealth_BAD  PeerHealth = 2
    29  )
    30  
    31  type peerState struct {
    32  	*PeerMapService
    33  
    34  	meta      p2pcommon.PeerMeta
    35  	addr      types.PeerAddress
    36  	connected time.Time
    37  
    38  	// temporary means it does not affect current peerregistry. TODO refactor more pretty way
    39  	temporary bool
    40  
    41  	bestHash   []byte
    42  	bestNo     int64
    43  	lCheckTime time.Time
    44  	contFail   int32
    45  }
    46  
    47  func (hc *peerState) health() PeerHealth {
    48  	// TODO make more robust if needed
    49  	switch {
    50  	case atomic.LoadInt32(&hc.contFail) == 0:
    51  		return PeerHealth_GOOD
    52  	default:
    53  		return PeerHealth_BAD
    54  	}
    55  }
    56  
    57  func (hc *peerState) lastCheck() time.Time {
    58  	return hc.lCheckTime
    59  }
    60  
    61  func (hc *peerState) check(wg *sync.WaitGroup, timeout time.Duration) {
    62  	defer wg.Done()
    63  	success, err := hc.checkConnect(timeout)
    64  
    65  	if !hc.temporary {
    66  		if success == nil || err != nil {
    67  			hc.unregisterPeer(hc.meta.ID)
    68  		} else if hc.health() == PeerHealth_BAD {
    69  			hc.unregisterPeer(hc.meta.ID)
    70  		}
    71  	}
    72  }
    73  
    74  func (hc *peerState) checkConnect(timeout time.Duration) (*types.Ping, error) {
    75  	hc.Logger.Debug().Str(p2putil.LogPeerID, p2putil.ShortForm(hc.meta.ID)).Msg("staring up healthcheck")
    76  	hc.lCheckTime = time.Now()
    77  	s, err := hc.nt.GetOrCreateStreamWithTTL(hc.meta, PolarisPingTTL, common.PolarisPingSub)
    78  	if err != nil {
    79  		hc.contFail++
    80  		hc.Logger.Debug().Err(err).Msg("Healthcheck failed to get network stream")
    81  		hc.unregisterPeer(hc.meta.ID)
    82  		return nil, err
    83  	}
    84  	defer s.Close()
    85  
    86  	rw := v030.NewV030ReadWriter(bufio.NewReader(s), bufio.NewWriter(s), nil)
    87  	pc := &pingChecker{peerState: hc, rw: rw}
    88  	pingResp, err := p2putil.InvokeWithTimer(pc, time.NewTimer(timeout))
    89  	if pingResp.(*types.Ping) == nil {
    90  		return nil, fmt.Errorf("ping error")
    91  	}
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	return pingResp.(*types.Ping), nil
    96  }
    97  
    98  // this method MUST be called in same go routine as AergoPeer.RunPeer()
    99  func (hc *peerState) sendPing(wt p2pcommon.MsgReadWriter) (p2pcommon.MsgID, error) {
   100  	// find my best block
   101  	ping := &types.Ping{}
   102  	msgID := p2pcommon.NewMsgID()
   103  	pingMsg, err := createV030Message(msgID, EmptyMsgID, p2pcommon.PingRequest, ping)
   104  	if err != nil {
   105  		hc.Logger.Warn().Err(err).Msg("failed to create ping message")
   106  		return EmptyMsgID, err
   107  	}
   108  
   109  	err = wt.WriteMsg(pingMsg)
   110  	if err != nil {
   111  		hc.Logger.Warn().Err(err).Msg("failed to write ping message")
   112  		return EmptyMsgID, err
   113  	}
   114  
   115  	return msgID, nil
   116  }
   117  
   118  // tryAddPeer will do check connecting peer and add. it will return peer meta information received from
   119  // remote peer setup some
   120  func (hc *peerState) receivePingResp(reqID p2pcommon.MsgID, rd p2pcommon.MsgReadWriter) (p2pcommon.Message, *types.Ping, error) {
   121  	resp, err := rd.ReadMsg()
   122  	if err != nil {
   123  		return nil, nil, err
   124  	}
   125  	if resp.Subprotocol() != p2pcommon.PingResponse || reqID != resp.OriginalID() {
   126  		return nil, nil, fmt.Errorf("Not expected response %s : req_id=%s", resp.Subprotocol().String(), resp.OriginalID().String())
   127  	}
   128  	pingResp := &types.Ping{}
   129  	err = p2putil.UnmarshalMessageBody(resp.Payload(), pingResp)
   130  	if err != nil {
   131  		return resp, nil, err
   132  	}
   133  
   134  	return resp, pingResp, nil
   135  }
   136  
   137  // pingChecker has ttl and will try to
   138  type pingChecker struct {
   139  	*peerState
   140  	rw     p2pcommon.MsgReadWriter
   141  	cancel int32
   142  }
   143  
   144  func (pc *pingChecker) DoCall(done chan<- interface{}) {
   145  	var pingResp *types.Ping = nil
   146  	defer func() {
   147  		if pingResp != nil {
   148  			atomic.StoreInt32(&pc.contFail, 0)
   149  		} else {
   150  			atomic.AddInt32(&pc.contFail, 1)
   151  		}
   152  		done <- pingResp
   153  	}()
   154  
   155  	reqID, err := pc.sendPing(pc.rw)
   156  	if err != nil {
   157  		pc.Logger.Debug().Err(err).Msg("Healthcheck failed to send ping message")
   158  		return
   159  	}
   160  	if atomic.LoadInt32(&pc.cancel) != 0 {
   161  		return
   162  	}
   163  	_, pingResp, err = pc.receivePingResp(reqID, pc.rw)
   164  	if err != nil {
   165  		pc.Logger.Debug().Err(err).Msg("Healthcheck failed to receive ping response")
   166  		return
   167  	}
   168  	if atomic.LoadInt32(&pc.cancel) != 0 {
   169  		pingResp = nil
   170  		return
   171  	}
   172  
   173  	pc.Logger.Debug().Str(p2putil.LogPeerID, p2putil.ShortForm(pc.meta.ID)).Interface("ping_resp", pingResp).Msg("Healthcheck finished successful")
   174  	return
   175  
   176  }
   177  
   178  func (pc *pingChecker) Cancel() {
   179  	atomic.StoreInt32(&pc.cancel, 1)
   180  }