github.com/aergoio/aergo@v1.3.1/p2p/blkreceiver.go (about)

     1  /*
     2   * @file
     3   * @copyright defined in aergo/LICENSE.txt
     4   */
     5  
     6  package p2p
     7  
     8  import (
     9  	"bytes"
    10  	"time"
    11  
    12  	"github.com/aergoio/aergo/chain"
    13  
    14  	"github.com/aergoio/aergo/message"
    15  	"github.com/aergoio/aergo/p2p/p2pcommon"
    16  	"github.com/aergoio/aergo/types"
    17  )
    18  
    19  // BlocksChunkReceiver is send p2p getBlocksRequest to target peer and receive p2p responses till all requests blocks are received
    20  // It will send response actor message if all blocks are received or failed to receive, but not send response if timeout expired, since
    21  // syncer actor already dropped wait before.
    22  type BlocksChunkReceiver struct {
    23  	syncerSeq uint64
    24  	requestID p2pcommon.MsgID
    25  
    26  	peer  p2pcommon.RemotePeer
    27  	actor p2pcommon.ActorService
    28  
    29  	blockHashes []message.BlockHash
    30  	timeout     time.Time
    31  	finished    bool
    32  	status      receiverStatus
    33  
    34  	got            []*types.Block
    35  	offset         int
    36  	senderFinished chan interface{}
    37  }
    38  
    39  type receiverStatus int32
    40  
    41  const (
    42  	receiverStatusWaiting receiverStatus = iota
    43  	receiverStatusCanceled
    44  	receiverStatusFinished
    45  )
    46  
    47  func NewBlockReceiver(actor p2pcommon.ActorService, peer p2pcommon.RemotePeer, seq uint64, blockHashes []message.BlockHash, ttl time.Duration) *BlocksChunkReceiver {
    48  	timeout := time.Now().Add(ttl)
    49  	return &BlocksChunkReceiver{syncerSeq: seq, actor: actor, peer: peer, blockHashes: blockHashes, timeout: timeout, got: make([]*types.Block, len(blockHashes))}
    50  }
    51  
    52  func (br *BlocksChunkReceiver) StartGet() {
    53  	hashes := make([][]byte, len(br.blockHashes))
    54  	for i, hash := range br.blockHashes {
    55  		hashes[i] = ([]byte)(hash)
    56  	}
    57  	// create message data
    58  	req := &types.GetBlockRequest{Hashes: hashes}
    59  	mo := br.peer.MF().NewMsgBlockRequestOrder(br.ReceiveResp, p2pcommon.GetBlocksRequest, req)
    60  	br.peer.SendMessage(mo)
    61  	br.requestID = mo.GetMsgID()
    62  }
    63  
    64  // ReceiveResp must be called just in read go routine
    65  func (br *BlocksChunkReceiver) ReceiveResp(msg p2pcommon.Message, msgBody p2pcommon.MessageBody) (ret bool) {
    66  	// cases in waiting
    67  	//   normal not status => wait
    68  	//   normal status (last response)  => finish
    69  	//   abnormal resp (no following resp expected): hasNext is true => cancel
    70  	//   abnormal resp (following resp expected): hasNext is false, or invalid resp data type (maybe remote peer is totally broken) => cancel finish
    71  	// case in status or status
    72  	ret = true
    73  	switch br.status {
    74  	case receiverStatusWaiting:
    75  		br.handleInWaiting(msg, msgBody)
    76  	case receiverStatusCanceled:
    77  		br.ignoreMsg(msg, msgBody)
    78  		return
    79  	case receiverStatusFinished:
    80  		fallthrough
    81  	default:
    82  		return
    83  	}
    84  	return
    85  }
    86  
    87  func (br *BlocksChunkReceiver) handleInWaiting(msg p2pcommon.Message, msgBody p2pcommon.MessageBody) {
    88  	// consuming request id when timeout, no more resp expected (i.e. hasNext == false ) or malformed body.
    89  	// timeout
    90  	if br.timeout.Before(time.Now()) {
    91  		// silently ignore already status job
    92  		br.finishReceiver()
    93  		return
    94  	}
    95  	// malformed responses means that later responses will be also malformed..
    96  	respBody, ok := msgBody.(types.ResponseMessage)
    97  	if !ok || respBody.GetStatus() != types.ResultStatus_OK {
    98  		br.cancelReceiving(message.RemotePeerFailError, false)
    99  		return
   100  	}
   101  	// remote peer response malformed data.
   102  	body, ok := msgBody.(*types.GetBlockResponse)
   103  	if !ok || len(body.Blocks) == 0 {
   104  		br.cancelReceiving(message.MissingHashError, false)
   105  		return
   106  	}
   107  
   108  	// add to Got
   109  	for _, block := range body.Blocks {
   110  		// It also error that response has more blocks than expected(=requested).
   111  		if br.offset >= len(br.got) {
   112  			br.cancelReceiving(message.TooManyBlocksError, body.HasNext)
   113  			return
   114  		}
   115  		// unexpected block
   116  		if !bytes.Equal(br.blockHashes[br.offset], block.Hash) {
   117  			br.cancelReceiving(message.UnexpectedBlockError, body.HasNext)
   118  			return
   119  		}
   120  		if block.Size() > int(chain.MaxBlockSize()) {
   121  			br.cancelReceiving(message.TooBigBlockError, body.HasNext)
   122  			return
   123  		}
   124  		br.got[br.offset] = block
   125  		br.offset++
   126  	}
   127  	// remote peer hopefully sent last chunk
   128  	if !body.HasNext {
   129  		if br.offset < len(br.got) {
   130  			// not all blocks were filled. this is error
   131  			br.cancelReceiving(message.TooFewBlocksError, body.HasNext)
   132  		} else {
   133  			br.actor.TellRequest(message.SyncerSvc, &message.GetBlockChunksRsp{Seq: br.syncerSeq, ToWhom: br.peer.ID(), Blocks: br.got, Err: nil})
   134  			br.finishReceiver()
   135  		}
   136  	}
   137  	return
   138  }
   139  
   140  // cancelReceiving is cancel wait for receiving and send syncer the failure result.
   141  // not all part of response is received, it wait remaining (and useless) response. It is assumed cancelling is not frequently occur
   142  func (br *BlocksChunkReceiver) cancelReceiving(err error, hasNext bool) {
   143  	br.status = receiverStatusCanceled
   144  	br.actor.TellRequest(message.SyncerSvc,
   145  		&message.GetBlockChunksRsp{Seq: br.syncerSeq, ToWhom: br.peer.ID(), Err: err})
   146  
   147  	// check time again. since negative duration of timer will not fire channel.
   148  	interval := br.timeout.Sub(time.Now())
   149  	if !hasNext || interval <= 0 {
   150  		// if remote peer will not send partial response anymore. it it actually same as finish.
   151  		br.finishReceiver()
   152  	} else {
   153  		// canceling in the middle of responses
   154  		br.senderFinished = make(chan interface{})
   155  		go func() {
   156  			timer := time.NewTimer(interval)
   157  			select {
   158  			case <-timer.C:
   159  				break
   160  			case <-br.senderFinished:
   161  				break
   162  			}
   163  			br.peer.ConsumeRequest(br.requestID)
   164  		}()
   165  	}
   166  }
   167  
   168  // finishReceiver is to cancel works, assuming cancellations are not frequently occur
   169  func (br *BlocksChunkReceiver) finishReceiver() {
   170  	br.status = receiverStatusFinished
   171  	br.peer.ConsumeRequest(br.requestID)
   172  }
   173  
   174  // ignoreMsg is silently ignore following responses, which is not useless anymore.
   175  func (br *BlocksChunkReceiver) ignoreMsg(msg p2pcommon.Message, msgBody p2pcommon.MessageBody) {
   176  	body, ok := msgBody.(*types.GetBlockResponse)
   177  	if !ok {
   178  		return
   179  	}
   180  	if !body.HasNext {
   181  		// really status from remote peer
   182  		select {
   183  		case br.senderFinished <- struct{}{}:
   184  		default:
   185  		}
   186  	}
   187  }