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 }