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 }