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 }