github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/blockchain/v0/pool.go (about)

     1  package v0
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	flow "github.com/tendermint/tendermint/libs/flowrate"
    11  	"github.com/tendermint/tendermint/libs/log"
    12  	"github.com/tendermint/tendermint/libs/service"
    13  	tmsync "github.com/tendermint/tendermint/libs/sync"
    14  	"github.com/tendermint/tendermint/p2p"
    15  	"github.com/tendermint/tendermint/types"
    16  )
    17  
    18  /*
    19  eg, L = latency = 0.1s
    20  	P = num peers = 10
    21  	FN = num full nodes
    22  	BS = 1kB block size
    23  	CB = 1 Mbit/s = 128 kB/s
    24  	CB/P = 12.8 kB
    25  	B/S = CB/P/BS = 12.8 blocks/s
    26  
    27  	12.8 * 0.1 = 1.28 blocks on conn
    28  */
    29  
    30  const (
    31  	requestIntervalMS         = 2
    32  	maxTotalRequesters        = 600
    33  	maxPendingRequests        = maxTotalRequesters
    34  	maxPendingRequestsPerPeer = 20
    35  
    36  	// Minimum recv rate to ensure we're receiving blocks from a peer fast
    37  	// enough. If a peer is not sending us data at at least that rate, we
    38  	// consider them to have timedout and we disconnect.
    39  	//
    40  	// Assuming a DSL connection (not a good choice) 128 Kbps (upload) ~ 15 KB/s,
    41  	// sending data across atlantic ~ 7.5 KB/s.
    42  	minRecvRate = 7680
    43  
    44  	// Maximum difference between current and new block's height.
    45  	maxDiffBetweenCurrentAndReceivedBlockHeight = 100
    46  )
    47  
    48  var peerTimeout = 15 * time.Second // not const so we can override with tests
    49  
    50  /*
    51  	Peers self report their heights when we join the block pool.
    52  	Starting from our latest pool.height, we request blocks
    53  	in sequence from peers that reported higher heights than ours.
    54  	Every so often we ask peers what height they're on so we can keep going.
    55  
    56  	Requests are continuously made for blocks of higher heights until
    57  	the limit is reached. If most of the requests have no available peers, and we
    58  	are not at peer limits, we can probably switch to consensus reactor
    59  */
    60  
    61  // BlockPool keeps track of the fast sync peers, block requests and block responses.
    62  type BlockPool struct {
    63  	service.BaseService
    64  	startTime time.Time
    65  
    66  	mtx tmsync.Mutex
    67  	// block requests
    68  	requesters map[int64]*bpRequester
    69  	height     int64 // the lowest key in requesters.
    70  	// peers
    71  	peers         map[p2p.ID]*bpPeer
    72  	maxPeerHeight int64 // the biggest reported height
    73  
    74  	// atomic
    75  	numPending int32 // number of requests pending assignment or block response
    76  
    77  	requestsCh chan<- BlockRequest
    78  	errorsCh   chan<- peerError
    79  }
    80  
    81  // NewBlockPool returns a new BlockPool with the height equal to start. Block
    82  // requests and errors will be sent to requestsCh and errorsCh accordingly.
    83  func NewBlockPool(start int64, requestsCh chan<- BlockRequest, errorsCh chan<- peerError) *BlockPool {
    84  	bp := &BlockPool{
    85  		peers: make(map[p2p.ID]*bpPeer),
    86  
    87  		requesters: make(map[int64]*bpRequester),
    88  		height:     start,
    89  		numPending: 0,
    90  
    91  		requestsCh: requestsCh,
    92  		errorsCh:   errorsCh,
    93  	}
    94  	bp.BaseService = *service.NewBaseService(nil, "BlockPool", bp)
    95  	return bp
    96  }
    97  
    98  // OnStart implements service.Service by spawning requesters routine and recording
    99  // pool's start time.
   100  func (pool *BlockPool) OnStart() error {
   101  	go pool.makeRequestersRoutine()
   102  	pool.startTime = time.Now()
   103  	return nil
   104  }
   105  
   106  // spawns requesters as needed
   107  func (pool *BlockPool) makeRequestersRoutine() {
   108  	for {
   109  		if !pool.IsRunning() {
   110  			break
   111  		}
   112  
   113  		_, numPending, lenRequesters := pool.GetStatus()
   114  		switch {
   115  		case numPending >= maxPendingRequests:
   116  			// sleep for a bit.
   117  			time.Sleep(requestIntervalMS * time.Millisecond)
   118  			// check for timed out peers
   119  			pool.removeTimedoutPeers()
   120  		case lenRequesters >= maxTotalRequesters:
   121  			// sleep for a bit.
   122  			time.Sleep(requestIntervalMS * time.Millisecond)
   123  			// check for timed out peers
   124  			pool.removeTimedoutPeers()
   125  		default:
   126  			// request for more blocks.
   127  			pool.makeNextRequester()
   128  		}
   129  	}
   130  }
   131  
   132  func (pool *BlockPool) removeTimedoutPeers() {
   133  	pool.mtx.Lock()
   134  	defer pool.mtx.Unlock()
   135  
   136  	for _, peer := range pool.peers {
   137  		if !peer.didTimeout && peer.numPending > 0 {
   138  			curRate := peer.recvMonitor.Status().CurRate
   139  			// curRate can be 0 on start
   140  			if curRate != 0 && curRate < minRecvRate {
   141  				err := errors.New("peer is not sending us data fast enough")
   142  				pool.sendError(err, peer.id)
   143  				pool.Logger.Error("SendTimeout", "peer", peer.id,
   144  					"reason", err,
   145  					"curRate", fmt.Sprintf("%d KB/s", curRate/1024),
   146  					"minRate", fmt.Sprintf("%d KB/s", minRecvRate/1024))
   147  				peer.didTimeout = true
   148  			}
   149  		}
   150  		if peer.didTimeout {
   151  			pool.removePeer(peer.id)
   152  		}
   153  	}
   154  }
   155  
   156  // GetStatus returns pool's height, numPending requests and the number of
   157  // requesters.
   158  func (pool *BlockPool) GetStatus() (height int64, numPending int32, lenRequesters int) {
   159  	pool.mtx.Lock()
   160  	defer pool.mtx.Unlock()
   161  
   162  	return pool.height, atomic.LoadInt32(&pool.numPending), len(pool.requesters)
   163  }
   164  
   165  // IsCaughtUp returns true if this node is caught up, false - otherwise.
   166  // TODO: relax conditions, prevent abuse.
   167  func (pool *BlockPool) IsCaughtUp() bool {
   168  	pool.mtx.Lock()
   169  	defer pool.mtx.Unlock()
   170  
   171  	// Need at least 1 peer to be considered caught up.
   172  	if len(pool.peers) == 0 {
   173  		pool.Logger.Debug("Blockpool has no peers")
   174  		return false
   175  	}
   176  
   177  	// Some conditions to determine if we're caught up.
   178  	// Ensures we've either received a block or waited some amount of time,
   179  	// and that we're synced to the highest known height.
   180  	// Note we use maxPeerHeight - 1 because to sync block H requires block H+1
   181  	// to verify the LastCommit.
   182  	receivedBlockOrTimedOut := pool.height > 0 || time.Since(pool.startTime) > 5*time.Second
   183  	ourChainIsLongestAmongPeers := pool.maxPeerHeight == 0 || pool.height >= (pool.maxPeerHeight-1)
   184  	isCaughtUp := receivedBlockOrTimedOut && ourChainIsLongestAmongPeers
   185  	return isCaughtUp
   186  }
   187  
   188  // PeekTwoBlocks returns blocks at pool.height and pool.height+1.
   189  // We need to see the second block's Commit to validate the first block.
   190  // So we peek two blocks at a time.
   191  // The caller will verify the commit.
   192  func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block) {
   193  	pool.mtx.Lock()
   194  	defer pool.mtx.Unlock()
   195  
   196  	if r := pool.requesters[pool.height]; r != nil {
   197  		first = r.getBlock()
   198  	}
   199  	if r := pool.requesters[pool.height+1]; r != nil {
   200  		second = r.getBlock()
   201  	}
   202  	return
   203  }
   204  
   205  // PopRequest pops the first block at pool.height.
   206  // It must have been validated by 'second'.Commit from PeekTwoBlocks().
   207  func (pool *BlockPool) PopRequest() {
   208  	pool.mtx.Lock()
   209  	defer pool.mtx.Unlock()
   210  
   211  	if r := pool.requesters[pool.height]; r != nil {
   212  		/*  The block can disappear at any time, due to removePeer().
   213  		if r := pool.requesters[pool.height]; r == nil || r.block == nil {
   214  			PanicSanity("PopRequest() requires a valid block")
   215  		}
   216  		*/
   217  		r.Stop()
   218  		delete(pool.requesters, pool.height)
   219  		pool.height++
   220  	} else {
   221  		panic(fmt.Sprintf("Expected requester to pop, got nothing at height %v", pool.height))
   222  	}
   223  }
   224  
   225  // RedoRequest invalidates the block at pool.height,
   226  // Remove the peer and redo request from others.
   227  // Returns the ID of the removed peer.
   228  func (pool *BlockPool) RedoRequest(height int64) p2p.ID {
   229  	pool.mtx.Lock()
   230  	defer pool.mtx.Unlock()
   231  
   232  	request := pool.requesters[height]
   233  	peerID := request.getPeerID()
   234  	if peerID != p2p.ID("") {
   235  		// RemovePeer will redo all requesters associated with this peer.
   236  		pool.removePeer(peerID)
   237  	}
   238  	return peerID
   239  }
   240  
   241  // AddBlock validates that the block comes from the peer it was expected from and calls the requester to store it.
   242  // TODO: ensure that blocks come in order for each peer.
   243  func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int) {
   244  	pool.mtx.Lock()
   245  	defer pool.mtx.Unlock()
   246  
   247  	requester := pool.requesters[block.Height]
   248  	if requester == nil {
   249  		pool.Logger.Info(
   250  			"peer sent us a block we didn't expect",
   251  			"peer",
   252  			peerID,
   253  			"curHeight",
   254  			pool.height,
   255  			"blockHeight",
   256  			block.Height)
   257  		diff := pool.height - block.Height
   258  		if diff < 0 {
   259  			diff *= -1
   260  		}
   261  		if diff > maxDiffBetweenCurrentAndReceivedBlockHeight {
   262  			pool.sendError(errors.New("peer sent us a block we didn't expect with a height too far ahead/behind"), peerID)
   263  		}
   264  		return
   265  	}
   266  
   267  	if requester.setBlock(block, peerID) {
   268  		atomic.AddInt32(&pool.numPending, -1)
   269  		peer := pool.peers[peerID]
   270  		if peer != nil {
   271  			peer.decrPending(blockSize)
   272  		}
   273  	} else {
   274  		pool.Logger.Info("invalid peer", "peer", peerID, "blockHeight", block.Height)
   275  		pool.sendError(errors.New("invalid peer"), peerID)
   276  	}
   277  }
   278  
   279  // MaxPeerHeight returns the highest reported height.
   280  func (pool *BlockPool) MaxPeerHeight() int64 {
   281  	pool.mtx.Lock()
   282  	defer pool.mtx.Unlock()
   283  	return pool.maxPeerHeight
   284  }
   285  
   286  // SetPeerRange sets the peer's alleged blockchain base and height.
   287  func (pool *BlockPool) SetPeerRange(peerID p2p.ID, base int64, height int64) {
   288  	pool.mtx.Lock()
   289  	defer pool.mtx.Unlock()
   290  
   291  	peer := pool.peers[peerID]
   292  	if peer != nil {
   293  		peer.base = base
   294  		peer.height = height
   295  	} else {
   296  		peer = newBPPeer(pool, peerID, base, height)
   297  		peer.setLogger(pool.Logger.With("peer", peerID))
   298  		pool.peers[peerID] = peer
   299  	}
   300  
   301  	if height > pool.maxPeerHeight {
   302  		pool.maxPeerHeight = height
   303  	}
   304  }
   305  
   306  // RemovePeer removes the peer with peerID from the pool. If there's no peer
   307  // with peerID, function is a no-op.
   308  func (pool *BlockPool) RemovePeer(peerID p2p.ID) {
   309  	pool.mtx.Lock()
   310  	defer pool.mtx.Unlock()
   311  
   312  	pool.removePeer(peerID)
   313  }
   314  
   315  func (pool *BlockPool) removePeer(peerID p2p.ID) {
   316  	for _, requester := range pool.requesters {
   317  		if requester.getPeerID() == peerID {
   318  			requester.redo(peerID)
   319  		}
   320  	}
   321  
   322  	peer, ok := pool.peers[peerID]
   323  	if ok {
   324  		if peer.timeout != nil {
   325  			peer.timeout.Stop()
   326  		}
   327  
   328  		delete(pool.peers, peerID)
   329  
   330  		// Find a new peer with the biggest height and update maxPeerHeight if the
   331  		// peer's height was the biggest.
   332  		if peer.height == pool.maxPeerHeight {
   333  			pool.updateMaxPeerHeight()
   334  		}
   335  	}
   336  }
   337  
   338  // If no peers are left, maxPeerHeight is set to 0.
   339  func (pool *BlockPool) updateMaxPeerHeight() {
   340  	var max int64
   341  	for _, peer := range pool.peers {
   342  		if peer.height > max {
   343  			max = peer.height
   344  		}
   345  	}
   346  	pool.maxPeerHeight = max
   347  }
   348  
   349  // Pick an available peer with the given height available.
   350  // If no peers are available, returns nil.
   351  func (pool *BlockPool) pickIncrAvailablePeer(height int64) *bpPeer {
   352  	pool.mtx.Lock()
   353  	defer pool.mtx.Unlock()
   354  
   355  	for _, peer := range pool.peers {
   356  		if peer.didTimeout {
   357  			pool.removePeer(peer.id)
   358  			continue
   359  		}
   360  		if peer.numPending >= maxPendingRequestsPerPeer {
   361  			continue
   362  		}
   363  		if height < peer.base || height > peer.height {
   364  			continue
   365  		}
   366  		peer.incrPending()
   367  		return peer
   368  	}
   369  	return nil
   370  }
   371  
   372  func (pool *BlockPool) makeNextRequester() {
   373  	pool.mtx.Lock()
   374  	defer pool.mtx.Unlock()
   375  
   376  	nextHeight := pool.height + pool.requestersLen()
   377  	if nextHeight > pool.maxPeerHeight {
   378  		return
   379  	}
   380  
   381  	request := newBPRequester(pool, nextHeight)
   382  
   383  	pool.requesters[nextHeight] = request
   384  	atomic.AddInt32(&pool.numPending, 1)
   385  
   386  	err := request.Start()
   387  	if err != nil {
   388  		request.Logger.Error("Error starting request", "err", err)
   389  	}
   390  }
   391  
   392  func (pool *BlockPool) requestersLen() int64 {
   393  	return int64(len(pool.requesters))
   394  }
   395  
   396  func (pool *BlockPool) sendRequest(height int64, peerID p2p.ID) {
   397  	if !pool.IsRunning() {
   398  		return
   399  	}
   400  	pool.requestsCh <- BlockRequest{height, peerID}
   401  }
   402  
   403  func (pool *BlockPool) sendError(err error, peerID p2p.ID) {
   404  	if !pool.IsRunning() {
   405  		return
   406  	}
   407  	pool.errorsCh <- peerError{err, peerID}
   408  }
   409  
   410  // for debugging purposes
   411  //nolint:unused
   412  func (pool *BlockPool) debug() string {
   413  	pool.mtx.Lock()
   414  	defer pool.mtx.Unlock()
   415  
   416  	str := ""
   417  	nextHeight := pool.height + pool.requestersLen()
   418  	for h := pool.height; h < nextHeight; h++ {
   419  		if pool.requesters[h] == nil {
   420  			str += fmt.Sprintf("H(%v):X ", h)
   421  		} else {
   422  			str += fmt.Sprintf("H(%v):", h)
   423  			str += fmt.Sprintf("B?(%v) ", pool.requesters[h].block != nil)
   424  		}
   425  	}
   426  	return str
   427  }
   428  
   429  //-------------------------------------
   430  
   431  type bpPeer struct {
   432  	didTimeout  bool
   433  	numPending  int32
   434  	height      int64
   435  	base        int64
   436  	pool        *BlockPool
   437  	id          p2p.ID
   438  	recvMonitor *flow.Monitor
   439  
   440  	timeout *time.Timer
   441  
   442  	logger log.Logger
   443  }
   444  
   445  func newBPPeer(pool *BlockPool, peerID p2p.ID, base int64, height int64) *bpPeer {
   446  	peer := &bpPeer{
   447  		pool:       pool,
   448  		id:         peerID,
   449  		base:       base,
   450  		height:     height,
   451  		numPending: 0,
   452  		logger:     log.NewNopLogger(),
   453  	}
   454  	return peer
   455  }
   456  
   457  func (peer *bpPeer) setLogger(l log.Logger) {
   458  	peer.logger = l
   459  }
   460  
   461  func (peer *bpPeer) resetMonitor() {
   462  	peer.recvMonitor = flow.New(time.Second, time.Second*40)
   463  	initialValue := float64(minRecvRate) * math.E
   464  	peer.recvMonitor.SetREMA(initialValue)
   465  }
   466  
   467  func (peer *bpPeer) resetTimeout() {
   468  	if peer.timeout == nil {
   469  		peer.timeout = time.AfterFunc(peerTimeout, peer.onTimeout)
   470  	} else {
   471  		peer.timeout.Reset(peerTimeout)
   472  	}
   473  }
   474  
   475  func (peer *bpPeer) incrPending() {
   476  	if peer.numPending == 0 {
   477  		peer.resetMonitor()
   478  		peer.resetTimeout()
   479  	}
   480  	peer.numPending++
   481  }
   482  
   483  func (peer *bpPeer) decrPending(recvSize int) {
   484  	peer.numPending--
   485  	if peer.numPending == 0 {
   486  		peer.timeout.Stop()
   487  	} else {
   488  		peer.recvMonitor.Update(recvSize)
   489  		peer.resetTimeout()
   490  	}
   491  }
   492  
   493  func (peer *bpPeer) onTimeout() {
   494  	peer.pool.mtx.Lock()
   495  	defer peer.pool.mtx.Unlock()
   496  
   497  	err := errors.New("peer did not send us anything")
   498  	peer.pool.sendError(err, peer.id)
   499  	peer.logger.Error("SendTimeout", "reason", err, "timeout", peerTimeout)
   500  	peer.didTimeout = true
   501  }
   502  
   503  //-------------------------------------
   504  
   505  type bpRequester struct {
   506  	service.BaseService
   507  	pool       *BlockPool
   508  	height     int64
   509  	gotBlockCh chan struct{}
   510  	redoCh     chan p2p.ID //redo may send multitime, add peerId to identify repeat
   511  
   512  	mtx    tmsync.Mutex
   513  	peerID p2p.ID
   514  	block  *types.Block
   515  }
   516  
   517  func newBPRequester(pool *BlockPool, height int64) *bpRequester {
   518  	bpr := &bpRequester{
   519  		pool:       pool,
   520  		height:     height,
   521  		gotBlockCh: make(chan struct{}, 1),
   522  		redoCh:     make(chan p2p.ID, 1),
   523  
   524  		peerID: "",
   525  		block:  nil,
   526  	}
   527  	bpr.BaseService = *service.NewBaseService(nil, "bpRequester", bpr)
   528  	return bpr
   529  }
   530  
   531  func (bpr *bpRequester) OnStart() error {
   532  	go bpr.requestRoutine()
   533  	return nil
   534  }
   535  
   536  // Returns true if the peer matches and block doesn't already exist.
   537  func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool {
   538  	bpr.mtx.Lock()
   539  	if bpr.block != nil || bpr.peerID != peerID {
   540  		bpr.mtx.Unlock()
   541  		return false
   542  	}
   543  	bpr.block = block
   544  	bpr.mtx.Unlock()
   545  
   546  	select {
   547  	case bpr.gotBlockCh <- struct{}{}:
   548  	default:
   549  	}
   550  	return true
   551  }
   552  
   553  func (bpr *bpRequester) getBlock() *types.Block {
   554  	bpr.mtx.Lock()
   555  	defer bpr.mtx.Unlock()
   556  	return bpr.block
   557  }
   558  
   559  func (bpr *bpRequester) getPeerID() p2p.ID {
   560  	bpr.mtx.Lock()
   561  	defer bpr.mtx.Unlock()
   562  	return bpr.peerID
   563  }
   564  
   565  // This is called from the requestRoutine, upon redo().
   566  func (bpr *bpRequester) reset() {
   567  	bpr.mtx.Lock()
   568  	defer bpr.mtx.Unlock()
   569  
   570  	if bpr.block != nil {
   571  		atomic.AddInt32(&bpr.pool.numPending, 1)
   572  	}
   573  
   574  	bpr.peerID = ""
   575  	bpr.block = nil
   576  }
   577  
   578  // Tells bpRequester to pick another peer and try again.
   579  // NOTE: Nonblocking, and does nothing if another redo
   580  // was already requested.
   581  func (bpr *bpRequester) redo(peerID p2p.ID) {
   582  	select {
   583  	case bpr.redoCh <- peerID:
   584  	default:
   585  	}
   586  }
   587  
   588  // Responsible for making more requests as necessary
   589  // Returns only when a block is found (e.g. AddBlock() is called)
   590  func (bpr *bpRequester) requestRoutine() {
   591  OUTER_LOOP:
   592  	for {
   593  		// Pick a peer to send request to.
   594  		var peer *bpPeer
   595  	PICK_PEER_LOOP:
   596  		for {
   597  			if !bpr.IsRunning() || !bpr.pool.IsRunning() {
   598  				return
   599  			}
   600  			peer = bpr.pool.pickIncrAvailablePeer(bpr.height)
   601  			if peer == nil {
   602  				//log.Info("No peers available", "height", height)
   603  				time.Sleep(requestIntervalMS * time.Millisecond)
   604  				continue PICK_PEER_LOOP
   605  			}
   606  			break PICK_PEER_LOOP
   607  		}
   608  		bpr.mtx.Lock()
   609  		bpr.peerID = peer.id
   610  		bpr.mtx.Unlock()
   611  
   612  		// Send request and wait.
   613  		bpr.pool.sendRequest(bpr.height, peer.id)
   614  	WAIT_LOOP:
   615  		for {
   616  			select {
   617  			case <-bpr.pool.Quit():
   618  				bpr.Stop()
   619  				return
   620  			case <-bpr.Quit():
   621  				return
   622  			case peerID := <-bpr.redoCh:
   623  				if peerID == bpr.peerID {
   624  					bpr.reset()
   625  					continue OUTER_LOOP
   626  				} else {
   627  					continue WAIT_LOOP
   628  				}
   629  			case <-bpr.gotBlockCh:
   630  				// We got a block!
   631  				// Continue the for-loop and wait til Quit.
   632  				continue WAIT_LOOP
   633  			}
   634  		}
   635  	}
   636  }
   637  
   638  // BlockRequest stores a block request identified by the block Height and the PeerID responsible for
   639  // delivering the block
   640  type BlockRequest struct {
   641  	Height int64
   642  	PeerID p2p.ID
   643  }