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

     1  package v1
     2  
     3  import (
     4  	"sort"
     5  
     6  	"github.com/DFWallet/tendermint-cosmos/libs/log"
     7  	"github.com/DFWallet/tendermint-cosmos/p2p"
     8  	"github.com/DFWallet/tendermint-cosmos/types"
     9  )
    10  
    11  // BlockPool keeps track of the fast sync peers, block requests and block responses.
    12  type BlockPool struct {
    13  	logger log.Logger
    14  	// Set of peers that have sent status responses, with height bigger than pool.Height
    15  	peers map[p2p.ID]*BpPeer
    16  	// Set of block heights and the corresponding peers from where a block response is expected or has been received.
    17  	blocks map[int64]p2p.ID
    18  
    19  	plannedRequests   map[int64]struct{} // list of blocks to be assigned peers for blockRequest
    20  	nextRequestHeight int64              // next height to be added to plannedRequests
    21  
    22  	Height        int64 // height of next block to execute
    23  	MaxPeerHeight int64 // maximum height of all peers
    24  	toBcR         bcReactor
    25  }
    26  
    27  // NewBlockPool creates a new BlockPool.
    28  func NewBlockPool(height int64, toBcR bcReactor) *BlockPool {
    29  	return &BlockPool{
    30  		Height:            height,
    31  		MaxPeerHeight:     0,
    32  		peers:             make(map[p2p.ID]*BpPeer),
    33  		blocks:            make(map[int64]p2p.ID),
    34  		plannedRequests:   make(map[int64]struct{}),
    35  		nextRequestHeight: height,
    36  		toBcR:             toBcR,
    37  	}
    38  }
    39  
    40  // SetLogger sets the logger of the pool.
    41  func (pool *BlockPool) SetLogger(l log.Logger) {
    42  	pool.logger = l
    43  }
    44  
    45  // ReachedMaxHeight check if the pool has reached the maximum peer height.
    46  func (pool *BlockPool) ReachedMaxHeight() bool {
    47  	return pool.Height >= pool.MaxPeerHeight
    48  }
    49  
    50  func (pool *BlockPool) rescheduleRequest(peerID p2p.ID, height int64) {
    51  	pool.logger.Info("reschedule requests made to peer for height ", "peerID", peerID, "height", height)
    52  	pool.plannedRequests[height] = struct{}{}
    53  	delete(pool.blocks, height)
    54  	pool.peers[peerID].RemoveBlock(height)
    55  }
    56  
    57  // Updates the pool's max height. If no peers are left MaxPeerHeight is set to 0.
    58  func (pool *BlockPool) updateMaxPeerHeight() {
    59  	var newMax int64
    60  	for _, peer := range pool.peers {
    61  		peerHeight := peer.Height
    62  		if peerHeight > newMax {
    63  			newMax = peerHeight
    64  		}
    65  	}
    66  	pool.MaxPeerHeight = newMax
    67  }
    68  
    69  // UpdatePeer adds a new peer or updates an existing peer with a new base and height.
    70  // If a peer is short it is not added.
    71  func (pool *BlockPool) UpdatePeer(peerID p2p.ID, base int64, height int64) error {
    72  
    73  	peer := pool.peers[peerID]
    74  
    75  	if peer == nil {
    76  		if height < pool.Height {
    77  			pool.logger.Info("Peer height too small",
    78  				"peer", peerID, "height", height, "fsm_height", pool.Height)
    79  			return errPeerTooShort
    80  		}
    81  		// Add new peer.
    82  		peer = NewBpPeer(peerID, base, height, pool.toBcR.sendPeerError, nil)
    83  		peer.SetLogger(pool.logger.With("peer", peerID))
    84  		pool.peers[peerID] = peer
    85  		pool.logger.Info("added peer", "peerID", peerID, "base", base, "height", height, "num_peers", len(pool.peers))
    86  	} else {
    87  		// Check if peer is lowering its height. This is not allowed.
    88  		if height < peer.Height {
    89  			pool.RemovePeer(peerID, errPeerLowersItsHeight)
    90  			return errPeerLowersItsHeight
    91  		}
    92  		// Update existing peer.
    93  		peer.Base = base
    94  		peer.Height = height
    95  	}
    96  
    97  	// Update the pool's MaxPeerHeight if needed.
    98  	pool.updateMaxPeerHeight()
    99  
   100  	return nil
   101  }
   102  
   103  // Cleans and deletes the peer. Recomputes the max peer height.
   104  func (pool *BlockPool) deletePeer(peer *BpPeer) {
   105  	if peer == nil {
   106  		return
   107  	}
   108  	peer.Cleanup()
   109  	delete(pool.peers, peer.ID)
   110  
   111  	if peer.Height == pool.MaxPeerHeight {
   112  		pool.updateMaxPeerHeight()
   113  	}
   114  }
   115  
   116  // RemovePeer removes the blocks and requests from the peer, reschedules them and deletes the peer.
   117  func (pool *BlockPool) RemovePeer(peerID p2p.ID, err error) {
   118  	peer := pool.peers[peerID]
   119  	if peer == nil {
   120  		return
   121  	}
   122  	pool.logger.Info("removing peer", "peerID", peerID, "error", err)
   123  
   124  	// Reschedule the block requests made to the peer, or received and not processed yet.
   125  	// Note that some of the requests may be removed further down.
   126  	for h := range pool.peers[peerID].blocks {
   127  		pool.rescheduleRequest(peerID, h)
   128  	}
   129  
   130  	oldMaxPeerHeight := pool.MaxPeerHeight
   131  	// Delete the peer. This operation may result in the pool's MaxPeerHeight being lowered.
   132  	pool.deletePeer(peer)
   133  
   134  	// Check if the pool's MaxPeerHeight has been lowered.
   135  	// This may happen if the tallest peer has been removed.
   136  	if oldMaxPeerHeight > pool.MaxPeerHeight {
   137  		// Remove any planned requests for heights over the new MaxPeerHeight.
   138  		for h := range pool.plannedRequests {
   139  			if h > pool.MaxPeerHeight {
   140  				delete(pool.plannedRequests, h)
   141  			}
   142  		}
   143  		// Adjust the nextRequestHeight to the new max plus one.
   144  		if pool.nextRequestHeight > pool.MaxPeerHeight {
   145  			pool.nextRequestHeight = pool.MaxPeerHeight + 1
   146  		}
   147  	}
   148  }
   149  
   150  func (pool *BlockPool) removeShortPeers() {
   151  	for _, peer := range pool.peers {
   152  		if peer.Height < pool.Height {
   153  			pool.RemovePeer(peer.ID, nil)
   154  		}
   155  	}
   156  }
   157  
   158  func (pool *BlockPool) removeBadPeers() {
   159  	pool.removeShortPeers()
   160  	for _, peer := range pool.peers {
   161  		if err := peer.CheckRate(); err != nil {
   162  			pool.RemovePeer(peer.ID, err)
   163  			pool.toBcR.sendPeerError(err, peer.ID)
   164  		}
   165  	}
   166  }
   167  
   168  // MakeNextRequests creates more requests if the block pool is running low.
   169  func (pool *BlockPool) MakeNextRequests(maxNumRequests int) {
   170  	heights := pool.makeRequestBatch(maxNumRequests)
   171  	if len(heights) != 0 {
   172  		pool.logger.Info("makeNextRequests will make following requests",
   173  			"number", len(heights), "heights", heights)
   174  	}
   175  
   176  	for _, height := range heights {
   177  		h := int64(height)
   178  		if !pool.sendRequest(h) {
   179  			// If a good peer was not found for sending the request at height h then return,
   180  			// as it shouldn't be possible to find a peer for h+1.
   181  			return
   182  		}
   183  		delete(pool.plannedRequests, h)
   184  	}
   185  }
   186  
   187  // Makes a batch of requests sorted by height such that the block pool has up to maxNumRequests entries.
   188  func (pool *BlockPool) makeRequestBatch(maxNumRequests int) []int {
   189  	pool.removeBadPeers()
   190  	// At this point pool.requests may include heights for requests to be redone due to removal of peers:
   191  	// - peers timed out or were removed by switch
   192  	// - FSM timed out on waiting to advance the block execution due to missing blocks at h or h+1
   193  	// Determine the number of requests needed by subtracting the number of requests already made from the maximum
   194  	// allowed
   195  	numNeeded := maxNumRequests - len(pool.blocks)
   196  	for len(pool.plannedRequests) < numNeeded {
   197  		if pool.nextRequestHeight > pool.MaxPeerHeight {
   198  			break
   199  		}
   200  		pool.plannedRequests[pool.nextRequestHeight] = struct{}{}
   201  		pool.nextRequestHeight++
   202  	}
   203  
   204  	heights := make([]int, 0, len(pool.plannedRequests))
   205  	for k := range pool.plannedRequests {
   206  		heights = append(heights, int(k))
   207  	}
   208  	sort.Ints(heights)
   209  	return heights
   210  }
   211  
   212  func (pool *BlockPool) sendRequest(height int64) bool {
   213  	for _, peer := range pool.peers {
   214  		if peer.NumPendingBlockRequests >= maxRequestsPerPeer {
   215  			continue
   216  		}
   217  		if peer.Base > height || peer.Height < height {
   218  			continue
   219  		}
   220  
   221  		err := pool.toBcR.sendBlockRequest(peer.ID, height)
   222  		if err == errNilPeerForBlockRequest {
   223  			// Switch does not have this peer, remove it and continue to look for another peer.
   224  			pool.logger.Error("switch does not have peer..removing peer selected for height", "peer",
   225  				peer.ID, "height", height)
   226  			pool.RemovePeer(peer.ID, err)
   227  			continue
   228  		}
   229  
   230  		if err == errSendQueueFull {
   231  			pool.logger.Error("peer queue is full", "peer", peer.ID, "height", height)
   232  			continue
   233  		}
   234  
   235  		pool.logger.Info("assigned request to peer", "peer", peer.ID, "height", height)
   236  
   237  		pool.blocks[height] = peer.ID
   238  		peer.RequestSent(height)
   239  
   240  		return true
   241  	}
   242  	pool.logger.Error("could not find peer to send request for block at height", "height", height)
   243  	return false
   244  }
   245  
   246  // AddBlock validates that the block comes from the peer it was expected from and stores it in the 'blocks' map.
   247  func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int) error {
   248  	peer, ok := pool.peers[peerID]
   249  	if !ok {
   250  		pool.logger.Error("block from unknown peer", "height", block.Height, "peer", peerID)
   251  		return errBadDataFromPeer
   252  	}
   253  	if wantPeerID, ok := pool.blocks[block.Height]; ok && wantPeerID != peerID {
   254  		pool.logger.Error("block received from wrong peer", "height", block.Height,
   255  			"peer", peerID, "expected_peer", wantPeerID)
   256  		return errBadDataFromPeer
   257  	}
   258  
   259  	return peer.AddBlock(block, blockSize)
   260  }
   261  
   262  // BlockData stores the peer responsible to deliver a block and the actual block if delivered.
   263  type BlockData struct {
   264  	block *types.Block
   265  	peer  *BpPeer
   266  }
   267  
   268  // BlockAndPeerAtHeight retrieves the block and delivery peer at specified height.
   269  // Returns errMissingBlock if a block was not found
   270  func (pool *BlockPool) BlockAndPeerAtHeight(height int64) (bData *BlockData, err error) {
   271  	peerID := pool.blocks[height]
   272  	peer := pool.peers[peerID]
   273  	if peer == nil {
   274  		return nil, errMissingBlock
   275  	}
   276  
   277  	block, err := peer.BlockAtHeight(height)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	return &BlockData{peer: peer, block: block}, nil
   283  
   284  }
   285  
   286  // FirstTwoBlocksAndPeers returns the blocks and the delivery peers at pool's height H and H+1.
   287  func (pool *BlockPool) FirstTwoBlocksAndPeers() (first, second *BlockData, err error) {
   288  	first, err = pool.BlockAndPeerAtHeight(pool.Height)
   289  	second, err2 := pool.BlockAndPeerAtHeight(pool.Height + 1)
   290  	if err == nil {
   291  		err = err2
   292  	}
   293  	return
   294  }
   295  
   296  // InvalidateFirstTwoBlocks removes the peers that sent us the first two blocks, blocks are removed by RemovePeer().
   297  func (pool *BlockPool) InvalidateFirstTwoBlocks(err error) {
   298  	first, err1 := pool.BlockAndPeerAtHeight(pool.Height)
   299  	second, err2 := pool.BlockAndPeerAtHeight(pool.Height + 1)
   300  
   301  	if err1 == nil {
   302  		pool.RemovePeer(first.peer.ID, err)
   303  	}
   304  	if err2 == nil {
   305  		pool.RemovePeer(second.peer.ID, err)
   306  	}
   307  }
   308  
   309  // ProcessedCurrentHeightBlock performs cleanup after a block is processed. It removes block at pool height and
   310  // the peers that are now short.
   311  func (pool *BlockPool) ProcessedCurrentHeightBlock() {
   312  	peerID, peerOk := pool.blocks[pool.Height]
   313  	if peerOk {
   314  		pool.peers[peerID].RemoveBlock(pool.Height)
   315  	}
   316  	delete(pool.blocks, pool.Height)
   317  	pool.logger.Debug("removed block at height", "height", pool.Height)
   318  	pool.Height++
   319  	pool.removeShortPeers()
   320  }
   321  
   322  // RemovePeerAtCurrentHeights checks if a block at pool's height H exists and if not, it removes the
   323  // delivery peer and returns. If a block at height H exists then the check and peer removal is done for H+1.
   324  // This function is called when the FSM is not able to make progress for some time.
   325  // This happens if either the block H or H+1 have not been delivered.
   326  func (pool *BlockPool) RemovePeerAtCurrentHeights(err error) {
   327  	peerID := pool.blocks[pool.Height]
   328  	peer, ok := pool.peers[peerID]
   329  	if ok {
   330  		if _, err := peer.BlockAtHeight(pool.Height); err != nil {
   331  			pool.logger.Info("remove peer that hasn't sent block at pool.Height",
   332  				"peer", peerID, "height", pool.Height)
   333  			pool.RemovePeer(peerID, err)
   334  			return
   335  		}
   336  	}
   337  	peerID = pool.blocks[pool.Height+1]
   338  	peer, ok = pool.peers[peerID]
   339  	if ok {
   340  		if _, err := peer.BlockAtHeight(pool.Height + 1); err != nil {
   341  			pool.logger.Info("remove peer that hasn't sent block at pool.Height+1",
   342  				"peer", peerID, "height", pool.Height+1)
   343  			pool.RemovePeer(peerID, err)
   344  			return
   345  		}
   346  	}
   347  }
   348  
   349  // Cleanup performs pool and peer cleanup
   350  func (pool *BlockPool) Cleanup() {
   351  	for id, peer := range pool.peers {
   352  		peer.Cleanup()
   353  		delete(pool.peers, id)
   354  	}
   355  	pool.plannedRequests = make(map[int64]struct{})
   356  	pool.blocks = make(map[int64]p2p.ID)
   357  	pool.nextRequestHeight = 0
   358  	pool.Height = 0
   359  	pool.MaxPeerHeight = 0
   360  }
   361  
   362  // NumPeers returns the number of peers in the pool
   363  func (pool *BlockPool) NumPeers() int {
   364  	return len(pool.peers)
   365  }
   366  
   367  // NeedsBlocks returns true if more blocks are required.
   368  func (pool *BlockPool) NeedsBlocks() bool {
   369  	return len(pool.blocks) < maxNumRequests
   370  }