|
|
@ -1,7 +1,7 @@ |
|
|
|
package blockchain |
|
|
|
|
|
|
|
import ( |
|
|
|
"math/rand" |
|
|
|
"sync" |
|
|
|
"sync/atomic" |
|
|
|
"time" |
|
|
|
|
|
|
@ -10,345 +10,367 @@ import ( |
|
|
|
) |
|
|
|
|
|
|
|
const ( |
|
|
|
maxOutstandingRequestsPerPeer = 10 |
|
|
|
eventsChannelCapacity = 100 |
|
|
|
requestTimeoutSeconds = 10 |
|
|
|
maxTries = 3 |
|
|
|
requestIntervalMS = 500 |
|
|
|
requestBatchSize = 50 |
|
|
|
maxPendingRequests = 50 |
|
|
|
maxTotalRequests = 100 |
|
|
|
maxPeersPerRequest = 1 |
|
|
|
maxTries = 3 |
|
|
|
inputsChannelCapacity = 200 |
|
|
|
requestIntervalMS = 500 |
|
|
|
maxPendingRequests = 200 |
|
|
|
maxTotalRequests = 300 |
|
|
|
maxRequestsPerPeer = 300 |
|
|
|
) |
|
|
|
|
|
|
|
type BlockRequest struct { |
|
|
|
Height uint |
|
|
|
PeerId string |
|
|
|
} |
|
|
|
var ( |
|
|
|
requestTimeoutSeconds = time.Duration(1) |
|
|
|
) |
|
|
|
|
|
|
|
type BlockPool struct { |
|
|
|
peers map[string]*bpPeer |
|
|
|
blockInfos map[uint]*bpBlockInfo |
|
|
|
height uint // the lowest key in blockInfos.
|
|
|
|
started int32 // atomic
|
|
|
|
stopped int32 // atomic
|
|
|
|
numPending int32 |
|
|
|
numTotal int32 |
|
|
|
eventsCh chan interface{} // internal events.
|
|
|
|
requestsCh chan<- BlockRequest // output of new requests to make.
|
|
|
|
timeoutsCh chan<- string // output of peers that timed out.
|
|
|
|
blocksCh chan<- *types.Block // output of ordered blocks.
|
|
|
|
repeater *RepeatTimer // for requesting more bocks.
|
|
|
|
quit chan struct{} |
|
|
|
// block requests
|
|
|
|
requestsMtx sync.Mutex |
|
|
|
requests map[uint]*bpRequest |
|
|
|
height uint // the lowest key in requests.
|
|
|
|
numPending int32 |
|
|
|
numTotal int32 |
|
|
|
|
|
|
|
// peers
|
|
|
|
peersMtx sync.Mutex |
|
|
|
peers map[string]*bpPeer |
|
|
|
|
|
|
|
requestsCh chan<- BlockRequest |
|
|
|
timeoutsCh chan<- string |
|
|
|
repeater *RepeatTimer |
|
|
|
|
|
|
|
running int32 // atomic
|
|
|
|
} |
|
|
|
|
|
|
|
func NewBlockPool(start uint, timeoutsCh chan<- string, requestsCh chan<- BlockRequest, blocksCh chan<- *types.Block) *BlockPool { |
|
|
|
func NewBlockPool(start uint, requestsCh chan<- BlockRequest, timeoutsCh chan<- string) *BlockPool { |
|
|
|
return &BlockPool{ |
|
|
|
peers: make(map[string]*bpPeer), |
|
|
|
blockInfos: make(map[uint]*bpBlockInfo), |
|
|
|
peers: make(map[string]*bpPeer), |
|
|
|
|
|
|
|
requests: make(map[uint]*bpRequest), |
|
|
|
height: start, |
|
|
|
started: 0, |
|
|
|
stopped: 0, |
|
|
|
numPending: 0, |
|
|
|
numTotal: 0, |
|
|
|
quit: make(chan struct{}), |
|
|
|
|
|
|
|
eventsCh: make(chan interface{}, eventsChannelCapacity), |
|
|
|
requestsCh: requestsCh, |
|
|
|
timeoutsCh: timeoutsCh, |
|
|
|
blocksCh: blocksCh, |
|
|
|
repeater: NewRepeatTimer("", requestIntervalMS*time.Millisecond), |
|
|
|
|
|
|
|
running: 0, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (bp *BlockPool) Start() { |
|
|
|
if atomic.CompareAndSwapInt32(&bp.started, 0, 1) { |
|
|
|
func (pool *BlockPool) Start() { |
|
|
|
if atomic.CompareAndSwapInt32(&pool.running, 0, 1) { |
|
|
|
log.Info("Starting BlockPool") |
|
|
|
go bp.run() |
|
|
|
go pool.run() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (bp *BlockPool) Stop() { |
|
|
|
if atomic.CompareAndSwapInt32(&bp.stopped, 0, 1) { |
|
|
|
func (pool *BlockPool) Stop() { |
|
|
|
if atomic.CompareAndSwapInt32(&pool.running, 1, 0) { |
|
|
|
log.Info("Stopping BlockPool") |
|
|
|
close(bp.quit) |
|
|
|
close(bp.eventsCh) |
|
|
|
close(bp.requestsCh) |
|
|
|
close(bp.timeoutsCh) |
|
|
|
close(bp.blocksCh) |
|
|
|
bp.repeater.Stop() |
|
|
|
pool.repeater.Stop() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// AddBlock should be called when a block is received.
|
|
|
|
func (bp *BlockPool) AddBlock(block *types.Block, peerId string) { |
|
|
|
bp.eventsCh <- bpBlockResponse{block, peerId} |
|
|
|
func (pool *BlockPool) IsRunning() bool { |
|
|
|
return atomic.LoadInt32(&pool.running) == 1 |
|
|
|
} |
|
|
|
|
|
|
|
func (bp *BlockPool) SetPeerStatus(peerId string, height uint) { |
|
|
|
bp.eventsCh <- bpPeerStatus{peerId, height} |
|
|
|
} |
|
|
|
|
|
|
|
// Runs in a goroutine and processes messages.
|
|
|
|
func (bp *BlockPool) run() { |
|
|
|
FOR_LOOP: |
|
|
|
// Run spawns requests as needed.
|
|
|
|
func (pool *BlockPool) run() { |
|
|
|
RUN_LOOP: |
|
|
|
for { |
|
|
|
select { |
|
|
|
case msg := <-bp.eventsCh: |
|
|
|
bp.handleEvent(msg) |
|
|
|
case <-bp.repeater.Ch: |
|
|
|
bp.makeMoreBlockInfos() |
|
|
|
bp.requestBlocksFromRandomPeers(10) |
|
|
|
case <-bp.quit: |
|
|
|
break FOR_LOOP |
|
|
|
if atomic.LoadInt32(&pool.running) == 0 { |
|
|
|
break RUN_LOOP |
|
|
|
} |
|
|
|
_, numPending, numTotal := pool.GetStatus() |
|
|
|
if numPending >= maxPendingRequests { |
|
|
|
// sleep for a bit.
|
|
|
|
time.Sleep(requestIntervalMS * time.Millisecond) |
|
|
|
} else if numTotal >= maxTotalRequests { |
|
|
|
// sleep for a bit.
|
|
|
|
time.Sleep(requestIntervalMS * time.Millisecond) |
|
|
|
} else { |
|
|
|
// request for more blocks.
|
|
|
|
height := pool.nextHeight() |
|
|
|
pool.makeRequest(height) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (bp *BlockPool) handleEvent(event_ interface{}) { |
|
|
|
switch event := event_.(type) { |
|
|
|
case bpBlockResponse: |
|
|
|
peer := bp.peers[event.peerId] |
|
|
|
blockInfo := bp.blockInfos[event.block.Height] |
|
|
|
if blockInfo == nil { |
|
|
|
// block was unwanted.
|
|
|
|
if peer != nil { |
|
|
|
peer.bad++ |
|
|
|
} |
|
|
|
} else { |
|
|
|
// block was wanted.
|
|
|
|
if peer != nil { |
|
|
|
peer.good++ |
|
|
|
} |
|
|
|
delete(peer.requests, event.block.Height) |
|
|
|
if blockInfo.block == nil { |
|
|
|
// peer is the first to give it to us.
|
|
|
|
blockInfo.block = event.block |
|
|
|
blockInfo.blockBy = peer.id |
|
|
|
bp.numPending-- |
|
|
|
if event.block.Height == bp.height { |
|
|
|
go bp.pushBlocksFromStart() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
case bpPeerStatus: // updated or new status from peer
|
|
|
|
// request blocks if possible.
|
|
|
|
peer := bp.peers[event.peerId] |
|
|
|
if peer == nil { |
|
|
|
peer = bpNewPeer(event.peerId, event.height) |
|
|
|
bp.peers[peer.id] = peer |
|
|
|
} |
|
|
|
bp.requestBlocksFromPeer(peer) |
|
|
|
case bpRequestTimeout: // unconditional timeout for each peer's request.
|
|
|
|
peer := bp.peers[event.peerId] |
|
|
|
if peer == nil { |
|
|
|
// cleanup was already handled.
|
|
|
|
return |
|
|
|
} |
|
|
|
height := event.height |
|
|
|
request := peer.requests[height] |
|
|
|
if request == nil || request.block != nil { |
|
|
|
// the request was fulfilled by some peer or this peer.
|
|
|
|
return |
|
|
|
} |
|
|
|
func (pool *BlockPool) GetStatus() (uint, int32, int32) { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
// A request for peer timed out.
|
|
|
|
peer.bad++ |
|
|
|
if request.tries < maxTries { |
|
|
|
log.Warn("Timeout: Trying again.", "tries", request.tries, "peerId", peer.id) |
|
|
|
// try again.
|
|
|
|
select { |
|
|
|
case bp.requestsCh <- BlockRequest{height, peer.id}: |
|
|
|
request.startAndTimeoutTo(bp.eventsCh) // also bumps request.tries
|
|
|
|
default: |
|
|
|
// The request cannot be made because requestCh is full.
|
|
|
|
// Just delete the request.
|
|
|
|
delete(peer.requests, height) |
|
|
|
} |
|
|
|
} else { |
|
|
|
log.Warn("Timeout: Deleting request") |
|
|
|
// delete the request.
|
|
|
|
delete(peer.requests, height) |
|
|
|
blockInfo := bp.blockInfos[height] |
|
|
|
if blockInfo != nil { |
|
|
|
delete(blockInfo.requests, peer.id) |
|
|
|
} |
|
|
|
select { |
|
|
|
case bp.timeoutsCh <- peer.id: |
|
|
|
default: |
|
|
|
} |
|
|
|
return pool.height, pool.numPending, pool.numTotal |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
// We need to see the second block's Validation to validate the first block.
|
|
|
|
// So we peek two blocks at a time.
|
|
|
|
func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block) { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
if r := pool.requests[pool.height]; r != nil { |
|
|
|
first = r.block |
|
|
|
} |
|
|
|
if r := pool.requests[pool.height+1]; r != nil { |
|
|
|
second = r.block |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// NOTE: This function is sufficient, but we should find pending blocks
|
|
|
|
// and sample the peers in one go rather than the current O(n^2) impl.
|
|
|
|
func (bp *BlockPool) requestBlocksFromRandomPeers(maxPeers int) { |
|
|
|
chosen := bp.pickAvailablePeers(maxPeers) |
|
|
|
log.Debug("requestBlocksFromRandomPeers", "chosen", len(chosen)) |
|
|
|
for _, peer := range chosen { |
|
|
|
bp.requestBlocksFromPeer(peer) |
|
|
|
// Pop the first block at pool.height
|
|
|
|
// It must have been validated by 'second'.Validation from PeekTwoBlocks().
|
|
|
|
func (pool *BlockPool) PopRequest() { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
if r := pool.requests[pool.height]; r == nil || r.block == nil { |
|
|
|
panic("PopRequest() requires a valid block") |
|
|
|
} |
|
|
|
|
|
|
|
delete(pool.requests, pool.height) |
|
|
|
pool.height++ |
|
|
|
pool.numTotal-- |
|
|
|
} |
|
|
|
|
|
|
|
func (bp *BlockPool) requestBlocksFromPeer(peer *bpPeer) { |
|
|
|
// If peer is available and can provide something...
|
|
|
|
for height := bp.height; peer.available(); height++ { |
|
|
|
blockInfo := bp.blockInfos[height] |
|
|
|
if blockInfo == nil { |
|
|
|
// We're out of range.
|
|
|
|
return |
|
|
|
} |
|
|
|
needsMorePeers := blockInfo.needsMorePeers() |
|
|
|
alreadyAskedPeer := blockInfo.requests[peer.id] != nil |
|
|
|
if needsMorePeers && !alreadyAskedPeer { |
|
|
|
select { |
|
|
|
case bp.requestsCh <- BlockRequest{height, peer.id}: |
|
|
|
// Create a new request and start the timer.
|
|
|
|
request := &bpBlockRequest{ |
|
|
|
height: height, |
|
|
|
peer: peer, |
|
|
|
} |
|
|
|
blockInfo.requests[peer.id] = request |
|
|
|
peer.requests[height] = request |
|
|
|
request.startAndTimeoutTo(bp.eventsCh) // also bumps request.tries
|
|
|
|
default: |
|
|
|
// The request cannot be made because requestCh is full.
|
|
|
|
// Just stop.
|
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
// Invalidates the block at pool.height.
|
|
|
|
// Remove the peer and request from others.
|
|
|
|
func (pool *BlockPool) RedoRequest(height uint) { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
request := pool.requests[height] |
|
|
|
if request.block == nil { |
|
|
|
panic("Expected block to be non-nil") |
|
|
|
} |
|
|
|
pool.RemovePeer(request.peerId) // Lock on peersMtx.
|
|
|
|
request.block = nil |
|
|
|
request.peerId = "" |
|
|
|
pool.numPending++ |
|
|
|
|
|
|
|
go requestRoutine(pool, height) |
|
|
|
} |
|
|
|
|
|
|
|
func (bp *BlockPool) makeMoreBlockInfos() { |
|
|
|
// make more requests if necessary.
|
|
|
|
for i := 0; i < requestBatchSize; i++ { |
|
|
|
//log.Debug("Confused?",
|
|
|
|
// "numPending", bp.numPending, "maxPendingRequests", maxPendingRequests, "numtotal", bp.numTotal, "maxTotalRequests", maxTotalRequests)
|
|
|
|
if bp.numPending < maxPendingRequests && bp.numTotal < maxTotalRequests { |
|
|
|
// Make a request for the next block height
|
|
|
|
requestHeight := bp.height + uint(bp.numTotal) |
|
|
|
log.Debug("New blockInfo", "height", requestHeight) |
|
|
|
blockInfo := bpNewBlockInfo(requestHeight) |
|
|
|
bp.blockInfos[requestHeight] = blockInfo |
|
|
|
bp.numPending++ |
|
|
|
bp.numTotal++ |
|
|
|
} else { |
|
|
|
break |
|
|
|
} |
|
|
|
func (pool *BlockPool) hasBlock(height uint) bool { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
request := pool.requests[height] |
|
|
|
return request != nil && request.block != nil |
|
|
|
} |
|
|
|
|
|
|
|
func (pool *BlockPool) setPeerForRequest(height uint, peerId string) { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
request := pool.requests[height] |
|
|
|
if request == nil { |
|
|
|
return |
|
|
|
} |
|
|
|
request.peerId = peerId |
|
|
|
} |
|
|
|
|
|
|
|
func (bp *BlockPool) pickAvailablePeers(choose int) []*bpPeer { |
|
|
|
available := []*bpPeer{} |
|
|
|
for _, peer := range bp.peers { |
|
|
|
if peer.available() { |
|
|
|
available = append(available, peer) |
|
|
|
} |
|
|
|
func (pool *BlockPool) AddBlock(block *types.Block, peerId string) { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
request := pool.requests[block.Height] |
|
|
|
if request == nil { |
|
|
|
return |
|
|
|
} |
|
|
|
if request.peerId != peerId { |
|
|
|
return |
|
|
|
} |
|
|
|
perm := rand.Perm(MinInt(choose, len(available))) |
|
|
|
chosen := make([]*bpPeer, len(perm)) |
|
|
|
for i, idx := range perm { |
|
|
|
chosen[i] = available[idx] |
|
|
|
if request.block != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
return chosen |
|
|
|
request.block = block |
|
|
|
pool.numPending-- |
|
|
|
} |
|
|
|
|
|
|
|
// blocking
|
|
|
|
func (bp *BlockPool) pushBlocksFromStart() { |
|
|
|
for height := bp.height; ; height++ { |
|
|
|
// push block to blocksCh.
|
|
|
|
blockInfo := bp.blockInfos[height] |
|
|
|
if blockInfo == nil || blockInfo.block == nil { |
|
|
|
break |
|
|
|
func (pool *BlockPool) getPeer(peerId string) *bpPeer { |
|
|
|
pool.peersMtx.Lock() // Lock
|
|
|
|
defer pool.peersMtx.Unlock() |
|
|
|
|
|
|
|
peer := pool.peers[peerId] |
|
|
|
return peer |
|
|
|
} |
|
|
|
|
|
|
|
// Sets the peer's blockchain height.
|
|
|
|
func (pool *BlockPool) SetPeerHeight(peerId string, height uint) { |
|
|
|
pool.peersMtx.Lock() // Lock
|
|
|
|
defer pool.peersMtx.Unlock() |
|
|
|
|
|
|
|
peer := pool.peers[peerId] |
|
|
|
if peer != nil { |
|
|
|
peer.height = height |
|
|
|
} else { |
|
|
|
peer = &bpPeer{ |
|
|
|
height: height, |
|
|
|
id: peerId, |
|
|
|
numRequests: 0, |
|
|
|
} |
|
|
|
bp.numTotal-- |
|
|
|
bp.height++ |
|
|
|
delete(bp.blockInfos, height) |
|
|
|
bp.blocksCh <- blockInfo.block |
|
|
|
pool.peers[peerId] = peer |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
func (pool *BlockPool) RemovePeer(peerId string) { |
|
|
|
pool.peersMtx.Lock() // Lock
|
|
|
|
defer pool.peersMtx.Unlock() |
|
|
|
|
|
|
|
type bpBlockInfo struct { |
|
|
|
height uint |
|
|
|
requests map[string]*bpBlockRequest |
|
|
|
block *types.Block // first block received
|
|
|
|
blockBy string // peerId of source
|
|
|
|
delete(pool.peers, peerId) |
|
|
|
} |
|
|
|
|
|
|
|
func bpNewBlockInfo(height uint) *bpBlockInfo { |
|
|
|
return &bpBlockInfo{ |
|
|
|
height: height, |
|
|
|
requests: make(map[string]*bpBlockRequest), |
|
|
|
// Pick an available peer with at least the given minHeight.
|
|
|
|
// If no peers are available, returns nil.
|
|
|
|
func (pool *BlockPool) pickIncrAvailablePeer(minHeight uint) *bpPeer { |
|
|
|
pool.peersMtx.Lock() |
|
|
|
defer pool.peersMtx.Unlock() |
|
|
|
|
|
|
|
for _, peer := range pool.peers { |
|
|
|
if peer.numRequests >= maxRequestsPerPeer { |
|
|
|
continue |
|
|
|
} |
|
|
|
if peer.height < minHeight { |
|
|
|
continue |
|
|
|
} |
|
|
|
peer.numRequests++ |
|
|
|
return peer |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (blockInfo *bpBlockInfo) needsMorePeers() bool { |
|
|
|
return len(blockInfo.requests) < maxPeersPerRequest |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
func (pool *BlockPool) decrPeer(peerId string) { |
|
|
|
pool.peersMtx.Lock() |
|
|
|
defer pool.peersMtx.Unlock() |
|
|
|
|
|
|
|
type bpBlockRequest struct { |
|
|
|
peer *bpPeer |
|
|
|
height uint |
|
|
|
block *types.Block |
|
|
|
tries int |
|
|
|
peer := pool.peers[peerId] |
|
|
|
if peer == nil { |
|
|
|
return |
|
|
|
} |
|
|
|
peer.numRequests-- |
|
|
|
} |
|
|
|
|
|
|
|
// bump tries++ and set timeout.
|
|
|
|
// NOTE: the timer is unconditional.
|
|
|
|
func (request *bpBlockRequest) startAndTimeoutTo(eventsCh chan<- interface{}) { |
|
|
|
request.tries++ |
|
|
|
time.AfterFunc(requestTimeoutSeconds*time.Second, func() { |
|
|
|
eventsCh <- bpRequestTimeout{ |
|
|
|
peerId: request.peer.id, |
|
|
|
height: request.height, |
|
|
|
} |
|
|
|
}) |
|
|
|
func (pool *BlockPool) nextHeight() uint { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
return pool.height + uint(pool.numTotal) |
|
|
|
} |
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
func (pool *BlockPool) makeRequest(height uint) { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
type bpPeer struct { |
|
|
|
id string |
|
|
|
height uint |
|
|
|
requests map[uint]*bpBlockRequest |
|
|
|
// Count good/bad events from peer.
|
|
|
|
good uint |
|
|
|
bad uint |
|
|
|
request := &bpRequest{ |
|
|
|
height: height, |
|
|
|
peerId: "", |
|
|
|
block: nil, |
|
|
|
} |
|
|
|
pool.requests[height] = request |
|
|
|
|
|
|
|
nextHeight := pool.height + uint(pool.numTotal) |
|
|
|
if nextHeight == height { |
|
|
|
pool.numTotal++ |
|
|
|
pool.numPending++ |
|
|
|
} |
|
|
|
|
|
|
|
go requestRoutine(pool, height) |
|
|
|
} |
|
|
|
|
|
|
|
func bpNewPeer(peerId string, height uint) *bpPeer { |
|
|
|
return &bpPeer{ |
|
|
|
id: peerId, |
|
|
|
height: height, |
|
|
|
requests: make(map[uint]*bpBlockRequest), |
|
|
|
func (pool *BlockPool) sendRequest(height uint, peerId string) { |
|
|
|
if atomic.LoadInt32(&pool.running) == 0 { |
|
|
|
return |
|
|
|
} |
|
|
|
pool.requestsCh <- BlockRequest{height, peerId} |
|
|
|
} |
|
|
|
|
|
|
|
func (peer *bpPeer) available() bool { |
|
|
|
return len(peer.requests) < maxOutstandingRequestsPerPeer |
|
|
|
func (pool *BlockPool) sendTimeout(peerId string) { |
|
|
|
if atomic.LoadInt32(&pool.running) == 0 { |
|
|
|
return |
|
|
|
} |
|
|
|
pool.timeoutsCh <- peerId |
|
|
|
} |
|
|
|
|
|
|
|
func (pool *BlockPool) debug() string { |
|
|
|
pool.requestsMtx.Lock() // Lock
|
|
|
|
defer pool.requestsMtx.Unlock() |
|
|
|
|
|
|
|
str := "" |
|
|
|
for h := pool.height; h < pool.height+uint(pool.numTotal); h++ { |
|
|
|
if pool.requests[h] == nil { |
|
|
|
str += Fmt("H(%v):X ", h) |
|
|
|
} else { |
|
|
|
str += Fmt("H(%v):", h) |
|
|
|
str += Fmt("B?(%v) ", pool.requests[h].block != nil) |
|
|
|
} |
|
|
|
} |
|
|
|
return str |
|
|
|
} |
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// bp.eventsCh messages
|
|
|
|
|
|
|
|
type bpBlockResponse struct { |
|
|
|
block *types.Block |
|
|
|
peerId string |
|
|
|
type bpPeer struct { |
|
|
|
id string |
|
|
|
height uint |
|
|
|
numRequests int32 |
|
|
|
} |
|
|
|
|
|
|
|
type bpPeerStatus struct { |
|
|
|
type bpRequest struct { |
|
|
|
height uint |
|
|
|
peerId string |
|
|
|
height uint // blockchain tip of peer
|
|
|
|
block *types.Block |
|
|
|
} |
|
|
|
|
|
|
|
type bpRequestTimeout struct { |
|
|
|
peerId string |
|
|
|
height uint |
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
// Responsible for making more requests as necessary
|
|
|
|
// Returns when a block is found (e.g. AddBlock() is called)
|
|
|
|
func requestRoutine(pool *BlockPool, height uint) { |
|
|
|
for { |
|
|
|
var peer *bpPeer = nil |
|
|
|
PICK_LOOP: |
|
|
|
for { |
|
|
|
if !pool.IsRunning() { |
|
|
|
log.Debug("BlockPool not running. Stopping requestRoutine", "height", height) |
|
|
|
return |
|
|
|
} |
|
|
|
peer = pool.pickIncrAvailablePeer(height) |
|
|
|
if peer == nil { |
|
|
|
//log.Debug("No peers available", "height", height)
|
|
|
|
time.Sleep(requestIntervalMS * time.Millisecond) |
|
|
|
continue PICK_LOOP |
|
|
|
} |
|
|
|
break PICK_LOOP |
|
|
|
} |
|
|
|
|
|
|
|
pool.setPeerForRequest(height, peer.id) |
|
|
|
|
|
|
|
for try := 0; try < maxTries; try++ { |
|
|
|
pool.sendRequest(height, peer.id) |
|
|
|
time.Sleep(requestTimeoutSeconds * time.Second) |
|
|
|
if pool.hasBlock(height) { |
|
|
|
pool.decrPeer(peer.id) |
|
|
|
return |
|
|
|
} |
|
|
|
bpHeight, _, _ := pool.GetStatus() |
|
|
|
if height < bpHeight { |
|
|
|
pool.decrPeer(peer.id) |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
pool.RemovePeer(peer.id) |
|
|
|
pool.sendTimeout(peer.id) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
type BlockRequest struct { |
|
|
|
Height uint |
|
|
|
PeerId string |
|
|
|
} |