github.com/aergoio/aergo@v1.3.1/p2p/hashreceiver.go (about) 1 /* 2 * @file 3 * @copyright defined in aergo/LICENSE.txt 4 */ 5 6 package p2p 7 8 import ( 9 "time" 10 11 "github.com/aergoio/aergo/message" 12 "github.com/aergoio/aergo/p2p/p2pcommon" 13 "github.com/aergoio/aergo/types" 14 ) 15 16 // BlockHashesReceiver is send p2p GetHashesRequest to target peer and receive p2p responses till all requested hashes are received 17 // It will send response actor message if all hashes are received or failed to receive, but not send response if timeout expired. 18 type BlockHashesReceiver struct { 19 syncerSeq uint64 20 requestID p2pcommon.MsgID 21 22 peer p2pcommon.RemotePeer 23 actor p2pcommon.ActorService 24 25 prevBlock *types.BlockInfo 26 count int 27 timeout time.Time 28 finished bool 29 status receiverStatus 30 31 got []message.BlockHash 32 offset int 33 senderFinished chan interface{} 34 } 35 36 func NewBlockHashesReceiver(actor p2pcommon.ActorService, peer p2pcommon.RemotePeer, seq uint64, req *message.GetHashes, ttl time.Duration) *BlockHashesReceiver { 37 timeout := time.Now().Add(ttl) 38 return &BlockHashesReceiver{syncerSeq:seq, actor: actor, peer: peer, prevBlock: req.PrevInfo, count: int(req.Count), timeout: timeout, got: make([]message.BlockHash, int(req.Count))} 39 } 40 41 func (br *BlockHashesReceiver) StartGet() { 42 // create message data 43 req := &types.GetHashesRequest{PrevHash: br.prevBlock.Hash, PrevNumber: br.prevBlock.No, Size: uint64(br.count)} 44 mo := br.peer.MF().NewMsgBlockRequestOrder(br.ReceiveResp, p2pcommon.GetHashesRequest, req) 45 br.peer.SendMessage(mo) 46 br.requestID = mo.GetMsgID() 47 } 48 49 // ReceiveResp must be called just in read go routine 50 func (br *BlockHashesReceiver) ReceiveResp(msg p2pcommon.Message, msgBody p2pcommon.MessageBody) (ret bool) { 51 // TODO this code is exact copy of BlocksChunkReceiver, so be lots of other codes in this file. consider refactoring 52 ret = true 53 switch br.status { 54 case receiverStatusWaiting: 55 br.handleInWaiting(msg, msgBody) 56 case receiverStatusCanceled: 57 br.ignoreMsg(msg, msgBody) 58 return 59 case receiverStatusFinished: 60 fallthrough 61 default: 62 return 63 } 64 return 65 } 66 67 func (br *BlockHashesReceiver) handleInWaiting(msg p2pcommon.Message, msgBody p2pcommon.MessageBody) { 68 // consuming request id when timeout, no more resp expected (i.e. hasNext == false ) or malformed body. 69 // timeout 70 if br.timeout.Before(time.Now()) { 71 // silently ignore already status job 72 br.finishReceiver() 73 return 74 } 75 // malformed responses means that later responses will be also malformed.. 76 respBody, ok := msgBody.(types.ResponseMessage) 77 if !ok || respBody.GetStatus() != types.ResultStatus_OK { 78 br.cancelReceiving(message.RemotePeerFailError, false) 79 return 80 } 81 82 // remote peer response failure 83 body, ok := msgBody.(*types.GetHashesResponse) 84 if !ok || len(body.Hashes) == 0 { 85 br.cancelReceiving(message.MissingHashError, false) 86 return 87 } 88 89 // add to Got 90 for _, block := range body.Hashes { 91 // It also error that response has more hashes than expected(=requested). 92 if br.offset >= len(br.got) { 93 br.cancelReceiving(message.TooManyBlocksError, body.HasNext) 94 return 95 } 96 br.got[br.offset] = block 97 br.offset++ 98 } 99 // remote peer hopefully sent last part 100 if !body.HasNext { 101 br.actor.TellRequest(message.SyncerSvc, &message.GetHashesRsp{Seq:br.syncerSeq, Hashes: br.got, PrevInfo: br.prevBlock, Count: uint64(len(br.got))}) 102 br.finishReceiver() 103 } 104 return 105 } 106 107 // cancelReceiving is cancel wait for receiving and send syncer the failure result. 108 // not all part of response is received, it wait remaining (and useless) response. It is assumed canceling is not frequently occur 109 func (br *BlockHashesReceiver) cancelReceiving(err error, hasNext bool) { 110 br.status = receiverStatusCanceled 111 br.actor.TellRequest(message.SyncerSvc, 112 &message.GetHashesRsp{Seq: br.syncerSeq, PrevInfo:br.prevBlock, Err: err}) 113 114 // check time again. since negative duration of timer will not fire channel. 115 interval := br.timeout.Sub(time.Now()) 116 if !hasNext || interval <= 0 { 117 // if remote peer will not send partial response anymore. it it actually same as finish. 118 br.finishReceiver() 119 } else { 120 // canceling in the middle of responses 121 br.senderFinished = make(chan interface{}) 122 go func() { 123 timer := time.NewTimer(interval) 124 select { 125 case <-timer.C: 126 break 127 case <-br.senderFinished: 128 break 129 } 130 br.peer.ConsumeRequest(br.requestID) 131 }() 132 } 133 } 134 135 // finishReceiver is to cancel works, assuming cancellations are not frequently occur 136 func (br *BlockHashesReceiver) finishReceiver() { 137 br.status = receiverStatusFinished 138 br.peer.ConsumeRequest(br.requestID) 139 } 140 141 // ignoreMsg is silently ignore following responses, which is not useless anymore. 142 func (br *BlockHashesReceiver) ignoreMsg(msg p2pcommon.Message, msgBody p2pcommon.MessageBody) { 143 body, ok := msgBody.(*types.GetBlockResponse) 144 if !ok { 145 return 146 } 147 if !body.HasNext { 148 // really status from remote peer 149 select { 150 case br.senderFinished <- struct{}{}: 151 default: 152 } 153 } 154 }