eth/downloader: detect and skip fetching empty blocks

This commit is contained in:
Péter Szilágyi 2015-08-21 10:29:22 +03:00
parent 7fd2bf5208
commit 8016efccee
2 changed files with 56 additions and 29 deletions

View file

@ -319,7 +319,9 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e
} }
}() }()
glog.V(logger.Debug).Infof("Synchronizing with the network using: %s, eth/%d", p.id, p.version) glog.V(logger.Debug).Infof("Synchronising with the network using: %s [eth/%d]", p.id, p.version)
defer glog.V(logger.Debug).Infof("Synchronisation terminated")
switch { switch {
case p.version == eth61: case p.version == eth61:
// Old eth/61, use forward, concurrent hash and block retrieval algorithm // Old eth/61, use forward, concurrent hash and block retrieval algorithm
@ -887,6 +889,7 @@ func (d *Downloader) findAncestor(p *peer) (uint64, error) {
// are returned, potentially throttling on the way. // are returned, potentially throttling on the way.
func (d *Downloader) fetchHeaders(p *peer, td *big.Int, from uint64) error { func (d *Downloader) fetchHeaders(p *peer, td *big.Int, from uint64) error {
glog.V(logger.Debug).Infof("%v: downloading headers from #%d", p, from) glog.V(logger.Debug).Infof("%v: downloading headers from #%d", p, from)
defer glog.V(logger.Debug).Infof("%v: header download terminated", p)
// Create a timeout timer, and the associated hash fetcher // Create a timeout timer, and the associated hash fetcher
timeout := time.NewTimer(0) // timer to dump a non-responsive active peer timeout := time.NewTimer(0) // timer to dump a non-responsive active peer
@ -1103,6 +1106,7 @@ func (d *Downloader) fetchBodies(from uint64) error {
break break
} }
// Send a download request to all idle peers, until throttled // Send a download request to all idle peers, until throttled
queuedEmptyBlocks := false
for _, peer := range d.peers.IdlePeers() { for _, peer := range d.peers.IdlePeers() {
// Short circuit if throttling activated // Short circuit if throttling activated
if d.queue.Throttle() { if d.queue.Throttle() {
@ -1111,7 +1115,14 @@ func (d *Downloader) fetchBodies(from uint64) error {
// Reserve a chunk of hashes for a peer. A nil can mean either that // Reserve a chunk of hashes for a peer. A nil can mean either that
// no more hashes are available, or that the peer is known not to // no more hashes are available, or that the peer is known not to
// have them. // have them.
request := d.queue.Reserve(peer, peer.Capacity()) request, process, err := d.queue.Reserve(peer, peer.Capacity())
if err != nil {
return err
}
if process {
queuedEmptyBlocks = true
go d.process()
}
if request == nil { if request == nil {
continue continue
} }
@ -1126,7 +1137,7 @@ func (d *Downloader) fetchBodies(from uint64) error {
} }
// Make sure that we have peers available for fetching. If all peers have been tried // Make sure that we have peers available for fetching. If all peers have been tried
// and all failed throw an error // and all failed throw an error
if !d.queue.Throttle() && d.queue.InFlight() == 0 { if !queuedEmptyBlocks && !d.queue.Throttle() && d.queue.InFlight() == 0 {
return errPeersUnavailable return errPeersUnavailable
} }
} }

View file

@ -312,18 +312,19 @@ func (q *queue) Reserve61(p *peer, count int) *fetchRequest {
} }
// Reserve reserves a set of headers for the given peer, skipping any previously // Reserve reserves a set of headers for the given peer, skipping any previously
// failed download. // failed download. Beside the next batch of needed fetches, it also returns a
func (q *queue) Reserve(p *peer, count int) *fetchRequest { // flag whether empty blocks were queued requiring processing.
func (q *queue) Reserve(p *peer, count int) (*fetchRequest, bool, error) {
q.lock.Lock() q.lock.Lock()
defer q.lock.Unlock() defer q.lock.Unlock()
// Short circuit if the pool has been depleted, or if the peer's already // Short circuit if the pool has been depleted, or if the peer's already
// downloading something (sanity check not to corrupt state) // downloading something (sanity check not to corrupt state)
if q.headerQueue.Empty() { if q.headerQueue.Empty() {
return nil return nil, false, nil
} }
if _, ok := q.pendPool[p.id]; ok { if _, ok := q.pendPool[p.id]; ok {
return nil return nil, false, nil
} }
// Calculate an upper limit on the bodies we might fetch (i.e. throttling) // Calculate an upper limit on the bodies we might fetch (i.e. throttling)
space := len(q.blockCache) - len(q.blockPool) space := len(q.blockCache) - len(q.blockPool)
@ -334,8 +335,20 @@ func (q *queue) Reserve(p *peer, count int) *fetchRequest {
send := make([]*types.Header, 0, count) send := make([]*types.Header, 0, count)
skip := make([]*types.Header, 0) skip := make([]*types.Header, 0)
process := false
for proc := 0; proc < space && len(send) < count && !q.headerQueue.Empty(); proc++ { for proc := 0; proc < space && len(send) < count && !q.headerQueue.Empty(); proc++ {
header := q.headerQueue.PopItem().(*types.Header) header := q.headerQueue.PopItem().(*types.Header)
// If the header defines an empty block, deliver straight
if header.TxHash == types.DeriveSha(types.Transactions{}) && header.UncleHash == types.CalcUncleHash([]*types.Header{}) {
if err := q.enqueue("", types.NewBlockWithHeader(header)); err != nil {
return nil, false, errInvalidChain
}
delete(q.headerPool, header.Hash())
process, space, proc = true, space-1, proc-1
continue
}
// If it's a content block, add to the body fetch request
if p.ignored.Has(header.Hash()) { if p.ignored.Has(header.Hash()) {
skip = append(skip, header) skip = append(skip, header)
} else { } else {
@ -348,7 +361,7 @@ func (q *queue) Reserve(p *peer, count int) *fetchRequest {
} }
// Assemble and return the block download request // Assemble and return the block download request
if len(send) == 0 { if len(send) == 0 {
return nil return nil, process, nil
} }
request := &fetchRequest{ request := &fetchRequest{
Peer: p, Peer: p,
@ -357,7 +370,7 @@ func (q *queue) Reserve(p *peer, count int) *fetchRequest {
} }
q.pendPool[p.id] = request q.pendPool[p.id] = request
return request return request, process, nil
} }
// Cancel aborts a fetch request, returning all pending hashes to the queue. // Cancel aborts a fetch request, returning all pending hashes to the queue.
@ -424,19 +437,12 @@ func (q *queue) Deliver61(id string, blocks []*types.Block) (err error) {
errs = append(errs, fmt.Errorf("non-requested block %x", hash)) errs = append(errs, fmt.Errorf("non-requested block %x", hash))
continue continue
} }
// If a requested block falls out of the range, the hash chain is invalid // Queue the block up for processing
index := int(int64(block.NumberU64()) - int64(q.blockOffset)) if err := q.enqueue(id, block); err != nil {
if index >= len(q.blockCache) || index < 0 { return err
return errInvalidChain
}
// Otherwise merge the block and mark the hash block
q.blockCache[index] = &Block{
RawBlock: block,
OriginPeer: id,
} }
delete(request.Hashes, hash) delete(request.Hashes, hash)
delete(q.hashPool, hash) delete(q.hashPool, hash)
q.blockPool[hash] = block.NumberU64()
} }
// Return all failed or missing fetches to the queue // Return all failed or missing fetches to the queue
for hash, index := range request.Hashes { for hash, index := range request.Hashes {
@ -483,19 +489,12 @@ func (q *queue) Deliver(id string, transactions [][]*types.Transaction, uncles [
} }
block := types.NewBlockWithHeader(header).WithBody(transactions[i], uncles[i]) block := types.NewBlockWithHeader(header).WithBody(transactions[i], uncles[i])
// If a requested block falls out of the range, the hash chain is invalid // Queue the block up for processing
index := int(int64(block.NumberU64()) - int64(q.blockOffset)) if err := q.enqueue(id, block); err != nil {
if index >= len(q.blockCache) || index < 0 { return err
return errInvalidChain
}
// Otherwise merge the block and mark the hash done
q.blockCache[index] = &Block{
RawBlock: block,
OriginPeer: id,
} }
request.Headers[i] = nil request.Headers[i] = nil
delete(q.headerPool, header.Hash()) delete(q.headerPool, header.Hash())
q.blockPool[header.Hash()] = block.NumberU64()
} }
// Return all failed or missing fetches to the queue // Return all failed or missing fetches to the queue
for _, header := range request.Headers { for _, header := range request.Headers {
@ -513,6 +512,23 @@ func (q *queue) Deliver(id string, transactions [][]*types.Transaction, uncles [
return nil return nil
} }
// enqueue inserts a new block into the final delivery queue, waiting for pickup
// by the processor.
func (q *queue) enqueue(origin string, block *types.Block) error {
// If a requested block falls out of the range, the hash chain is invalid
index := int(int64(block.NumberU64()) - int64(q.blockOffset))
if index >= len(q.blockCache) || index < 0 {
return errInvalidChain
}
// Otherwise merge the block and mark the hash done
q.blockCache[index] = &Block{
RawBlock: block,
OriginPeer: origin,
}
q.blockPool[block.Header().Hash()] = block.NumberU64()
return nil
}
// Prepare configures the block cache offset to allow accepting inbound blocks. // Prepare configures the block cache offset to allow accepting inbound blocks.
func (q *queue) Prepare(offset uint64) { func (q *queue) Prepare(offset uint64) {
q.lock.Lock() q.lock.Lock()