github.com/DFWallet/tendermint-cosmos@v0.0.2/blockchain/v1/peer.go (about)

     1  package v1
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"time"
     7  
     8  	flow "github.com/DFWallet/tendermint-cosmos/libs/flowrate"
     9  	"github.com/DFWallet/tendermint-cosmos/libs/log"
    10  	"github.com/DFWallet/tendermint-cosmos/p2p"
    11  	"github.com/DFWallet/tendermint-cosmos/types"
    12  )
    13  
    14  //--------
    15  // Peer
    16  
    17  // BpPeerParams stores the peer parameters that are used when creating a peer.
    18  type BpPeerParams struct {
    19  	timeout     time.Duration
    20  	minRecvRate int64
    21  	sampleRate  time.Duration
    22  	windowSize  time.Duration
    23  }
    24  
    25  // BpPeer is the datastructure associated with a fast sync peer.
    26  type BpPeer struct {
    27  	logger log.Logger
    28  	ID     p2p.ID
    29  
    30  	Base                    int64                  // the peer reported base
    31  	Height                  int64                  // the peer reported height
    32  	NumPendingBlockRequests int                    // number of requests still waiting for block responses
    33  	blocks                  map[int64]*types.Block // blocks received or expected to be received from this peer
    34  	blockResponseTimer      *time.Timer
    35  	recvMonitor             *flow.Monitor
    36  	params                  *BpPeerParams // parameters for timer and monitor
    37  
    38  	onErr func(err error, peerID p2p.ID) // function to call on error
    39  }
    40  
    41  // NewBpPeer creates a new peer.
    42  func NewBpPeer(peerID p2p.ID, base int64, height int64,
    43  	onErr func(err error, peerID p2p.ID), params *BpPeerParams) *BpPeer {
    44  
    45  	if params == nil {
    46  		params = BpPeerDefaultParams()
    47  	}
    48  	return &BpPeer{
    49  		ID:     peerID,
    50  		Base:   base,
    51  		Height: height,
    52  		blocks: make(map[int64]*types.Block, maxRequestsPerPeer),
    53  		logger: log.NewNopLogger(),
    54  		onErr:  onErr,
    55  		params: params,
    56  	}
    57  }
    58  
    59  // String returns a string representation of a peer.
    60  func (peer *BpPeer) String() string {
    61  	return fmt.Sprintf("peer: %v height: %v pending: %v", peer.ID, peer.Height, peer.NumPendingBlockRequests)
    62  }
    63  
    64  // SetLogger sets the logger of the peer.
    65  func (peer *BpPeer) SetLogger(l log.Logger) {
    66  	peer.logger = l
    67  }
    68  
    69  // Cleanup performs cleanup of the peer, removes blocks, requests, stops timer and monitor.
    70  func (peer *BpPeer) Cleanup() {
    71  	if peer.blockResponseTimer != nil {
    72  		peer.blockResponseTimer.Stop()
    73  	}
    74  	if peer.NumPendingBlockRequests != 0 {
    75  		peer.logger.Info("peer with pending requests is being cleaned", "peer", peer.ID)
    76  	}
    77  	if len(peer.blocks)-peer.NumPendingBlockRequests != 0 {
    78  		peer.logger.Info("peer with pending blocks is being cleaned", "peer", peer.ID)
    79  	}
    80  	for h := range peer.blocks {
    81  		delete(peer.blocks, h)
    82  	}
    83  	peer.NumPendingBlockRequests = 0
    84  	peer.recvMonitor = nil
    85  }
    86  
    87  // BlockAtHeight returns the block at a given height if available and errMissingBlock otherwise.
    88  func (peer *BpPeer) BlockAtHeight(height int64) (*types.Block, error) {
    89  	block, ok := peer.blocks[height]
    90  	if !ok {
    91  		return nil, errMissingBlock
    92  	}
    93  	if block == nil {
    94  		return nil, errMissingBlock
    95  	}
    96  	return peer.blocks[height], nil
    97  }
    98  
    99  // AddBlock adds a block at peer level. Block must be non-nil and recvSize a positive integer
   100  // The peer must have a pending request for this block.
   101  func (peer *BpPeer) AddBlock(block *types.Block, recvSize int) error {
   102  	if block == nil || recvSize < 0 {
   103  		panic("bad parameters")
   104  	}
   105  	existingBlock, ok := peer.blocks[block.Height]
   106  	if !ok {
   107  		peer.logger.Error("unsolicited block", "blockHeight", block.Height, "peer", peer.ID)
   108  		return errMissingBlock
   109  	}
   110  	if existingBlock != nil {
   111  		peer.logger.Error("already have a block for height", "height", block.Height)
   112  		return errDuplicateBlock
   113  	}
   114  	if peer.NumPendingBlockRequests == 0 {
   115  		panic("peer does not have pending requests")
   116  	}
   117  	peer.blocks[block.Height] = block
   118  	peer.NumPendingBlockRequests--
   119  	if peer.NumPendingBlockRequests == 0 {
   120  		peer.stopMonitor()
   121  		peer.stopBlockResponseTimer()
   122  	} else {
   123  		peer.recvMonitor.Update(recvSize)
   124  		peer.resetBlockResponseTimer()
   125  	}
   126  	return nil
   127  }
   128  
   129  // RemoveBlock removes the block of given height
   130  func (peer *BpPeer) RemoveBlock(height int64) {
   131  	delete(peer.blocks, height)
   132  }
   133  
   134  // RequestSent records that a request was sent, and starts the peer timer and monitor if needed.
   135  func (peer *BpPeer) RequestSent(height int64) {
   136  	peer.blocks[height] = nil
   137  
   138  	if peer.NumPendingBlockRequests == 0 {
   139  		peer.startMonitor()
   140  		peer.resetBlockResponseTimer()
   141  	}
   142  	peer.NumPendingBlockRequests++
   143  }
   144  
   145  // CheckRate verifies that the response rate of the peer is acceptable (higher than the minimum allowed).
   146  func (peer *BpPeer) CheckRate() error {
   147  	if peer.NumPendingBlockRequests == 0 {
   148  		return nil
   149  	}
   150  	curRate := peer.recvMonitor.Status().CurRate
   151  	// curRate can be 0 on start
   152  	if curRate != 0 && curRate < peer.params.minRecvRate {
   153  		err := errSlowPeer
   154  		peer.logger.Error("SendTimeout", "peer", peer,
   155  			"reason", err,
   156  			"curRate", fmt.Sprintf("%d KB/s", curRate/1024),
   157  			"minRate", fmt.Sprintf("%d KB/s", peer.params.minRecvRate/1024))
   158  		return err
   159  	}
   160  	return nil
   161  }
   162  
   163  func (peer *BpPeer) onTimeout() {
   164  	peer.onErr(errNoPeerResponse, peer.ID)
   165  }
   166  
   167  func (peer *BpPeer) stopMonitor() {
   168  	peer.recvMonitor.Done()
   169  	peer.recvMonitor = nil
   170  }
   171  
   172  func (peer *BpPeer) startMonitor() {
   173  	peer.recvMonitor = flow.New(peer.params.sampleRate, peer.params.windowSize)
   174  	initialValue := float64(peer.params.minRecvRate) * math.E
   175  	peer.recvMonitor.SetREMA(initialValue)
   176  }
   177  
   178  func (peer *BpPeer) resetBlockResponseTimer() {
   179  	if peer.blockResponseTimer == nil {
   180  		peer.blockResponseTimer = time.AfterFunc(peer.params.timeout, peer.onTimeout)
   181  	} else {
   182  		peer.blockResponseTimer.Reset(peer.params.timeout)
   183  	}
   184  }
   185  
   186  func (peer *BpPeer) stopBlockResponseTimer() bool {
   187  	if peer.blockResponseTimer == nil {
   188  		return false
   189  	}
   190  	return peer.blockResponseTimer.Stop()
   191  }
   192  
   193  // BpPeerDefaultParams returns the default peer parameters.
   194  func BpPeerDefaultParams() *BpPeerParams {
   195  	return &BpPeerParams{
   196  		// Timeout for a peer to respond to a block request.
   197  		timeout: 15 * time.Second,
   198  
   199  		// Minimum recv rate to ensure we're receiving blocks from a peer fast
   200  		// enough. If a peer is not sending data at at least that rate, we
   201  		// consider them to have timedout and we disconnect.
   202  		//
   203  		// Assuming a DSL connection (not a good choice) 128 Kbps (upload) ~ 15 KB/s,
   204  		// sending data across atlantic ~ 7.5 KB/s.
   205  		minRecvRate: int64(7680),
   206  
   207  		// Monitor parameters
   208  		sampleRate: time.Second,
   209  		windowSize: 40 * time.Second,
   210  	}
   211  }