mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 09:53:48 +00:00
eth/dwonloader: moving pivot and capped memory usage
This commit is contained in:
parent
cfb510c20d
commit
abd99834db
5 changed files with 285 additions and 385 deletions
|
|
@ -203,6 +203,9 @@ func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainE
|
||||||
if header.ParentHash != prevHash {
|
if header.ParentHash != prevHash {
|
||||||
// Reorg to the common ancestor (might not exist in light sync mode, skip reorg then)
|
// Reorg to the common ancestor (might not exist in light sync mode, skip reorg then)
|
||||||
// TODO(karalabe, zsfelfoldi): This seems a bit brittle, can we detect this case explicitly?
|
// TODO(karalabe, zsfelfoldi): This seems a bit brittle, can we detect this case explicitly?
|
||||||
|
|
||||||
|
// TODO(karalabe): This operation is expensive and might block, causing the event system to
|
||||||
|
// potentially also lock up. We need to do with on a different thread somehow.
|
||||||
if h := FindCommonAncestor(c.chainDb, prevHeader, header); h != nil {
|
if h := FindCommonAncestor(c.chainDb, prevHeader, header); h != nil {
|
||||||
c.newHead(h.Number.Uint64(), true)
|
c.newHead(h.Number.Uint64(), true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,8 @@
|
||||||
package downloader
|
package downloader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -64,9 +62,8 @@ var (
|
||||||
fsHeaderCheckFrequency = 100 // Verification frequency of the downloaded headers during fast sync
|
fsHeaderCheckFrequency = 100 // Verification frequency of the downloaded headers during fast sync
|
||||||
fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected
|
fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected
|
||||||
fsHeaderForceVerify = 24 // Number of headers to verify before and after the pivot to accept it
|
fsHeaderForceVerify = 24 // Number of headers to verify before and after the pivot to accept it
|
||||||
fsPivotInterval = 256 // Number of headers out of which to randomize the pivot point
|
fsHeaderContCheck = 3 * time.Second // Time interval to check for header continuations during state download
|
||||||
fsMinFullBlocks = 64 // Number of blocks to retrieve fully even in fast sync
|
fsMinFullBlocks = 64 // Number of blocks to retrieve fully even in fast sync
|
||||||
fsCriticalTrials = uint32(32) // Number of times to retry in the cricical section before bailing
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -102,9 +99,6 @@ type Downloader struct {
|
||||||
peers *peerSet // Set of active peers from which download can proceed
|
peers *peerSet // Set of active peers from which download can proceed
|
||||||
stateDB ethdb.Database
|
stateDB ethdb.Database
|
||||||
|
|
||||||
fsPivotLock *types.Header // Pivot header on critical section entry (cannot change between retries)
|
|
||||||
fsPivotFails uint32 // Number of subsequent fast sync failures in the critical section
|
|
||||||
|
|
||||||
rttEstimate uint64 // Round trip time to target for download requests
|
rttEstimate uint64 // Round trip time to target for download requests
|
||||||
rttConfidence uint64 // Confidence in the estimated RTT (unit: millionths to allow atomic ops)
|
rttConfidence uint64 // Confidence in the estimated RTT (unit: millionths to allow atomic ops)
|
||||||
|
|
||||||
|
|
@ -124,6 +118,7 @@ type Downloader struct {
|
||||||
synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing
|
synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing
|
||||||
synchronising int32
|
synchronising int32
|
||||||
notified int32
|
notified int32
|
||||||
|
committed int32
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
headerCh chan dataPack // [eth/62] Channel receiving inbound block headers
|
headerCh chan dataPack // [eth/62] Channel receiving inbound block headers
|
||||||
|
|
@ -391,9 +386,7 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode
|
||||||
|
|
||||||
// Set the requested sync mode, unless it's forbidden
|
// Set the requested sync mode, unless it's forbidden
|
||||||
d.mode = mode
|
d.mode = mode
|
||||||
if d.mode == FastSync && atomic.LoadUint32(&d.fsPivotFails) >= fsCriticalTrials {
|
|
||||||
d.mode = FullSync
|
|
||||||
}
|
|
||||||
// Retrieve the origin peer and initiate the downloading process
|
// Retrieve the origin peer and initiate the downloading process
|
||||||
p := d.peers.Peer(id)
|
p := d.peers.Peer(id)
|
||||||
if p == nil {
|
if p == nil {
|
||||||
|
|
@ -441,57 +434,40 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
|
||||||
d.syncStatsChainHeight = height
|
d.syncStatsChainHeight = height
|
||||||
d.syncStatsLock.Unlock()
|
d.syncStatsLock.Unlock()
|
||||||
|
|
||||||
// Initiate the sync using a concurrent header and content retrieval algorithm
|
// Ensure our origin point is below any fast sync pivot point
|
||||||
pivot := uint64(0)
|
pivot := uint64(0)
|
||||||
switch d.mode {
|
if d.mode == FastSync {
|
||||||
case LightSync:
|
if height <= uint64(fsMinFullBlocks) {
|
||||||
pivot = height
|
|
||||||
case FastSync:
|
|
||||||
// Calculate the new fast/slow sync pivot point
|
|
||||||
if d.fsPivotLock == nil {
|
|
||||||
pivotOffset, err := rand.Int(rand.Reader, big.NewInt(int64(fsPivotInterval)))
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Failed to access crypto random source: %v", err))
|
|
||||||
}
|
|
||||||
if height > uint64(fsMinFullBlocks)+pivotOffset.Uint64() {
|
|
||||||
pivot = height - uint64(fsMinFullBlocks) - pivotOffset.Uint64()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Pivot point locked in, use this and do not pick a new one!
|
|
||||||
pivot = d.fsPivotLock.Number.Uint64()
|
|
||||||
}
|
|
||||||
// If the point is below the origin, move origin back to ensure state download
|
|
||||||
if pivot < origin {
|
|
||||||
if pivot > 0 {
|
|
||||||
origin = pivot - 1
|
|
||||||
} else {
|
|
||||||
origin = 0
|
origin = 0
|
||||||
|
} else {
|
||||||
|
pivot = height - uint64(fsMinFullBlocks)
|
||||||
|
if pivot <= origin {
|
||||||
|
origin = pivot - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debug("Fast syncing until pivot block", "pivot", pivot)
|
|
||||||
}
|
}
|
||||||
d.queue.Prepare(origin+1, d.mode, pivot, latest)
|
d.committed = 1
|
||||||
|
if d.mode == FastSync && pivot != 0 {
|
||||||
|
d.committed = 0
|
||||||
|
}
|
||||||
|
// Initiate the sync using a concurrent header and content retrieval algorithm
|
||||||
|
d.queue.Prepare(origin+1, d.mode)
|
||||||
if d.syncInitHook != nil {
|
if d.syncInitHook != nil {
|
||||||
d.syncInitHook(origin, height)
|
d.syncInitHook(origin, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchers := []func() error{
|
fetchers := []func() error{
|
||||||
func() error { return d.fetchHeaders(p, origin+1) }, // Headers are always retrieved
|
func() error { return d.fetchHeaders(p, origin+1, pivot) }, // Headers are always retrieved
|
||||||
func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and fast sync
|
func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and fast sync
|
||||||
func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync
|
func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync
|
||||||
func() error { return d.processHeaders(origin+1, td) },
|
func() error { return d.processHeaders(origin+1, pivot, td) },
|
||||||
}
|
}
|
||||||
if d.mode == FastSync {
|
if d.mode == FastSync {
|
||||||
fetchers = append(fetchers, func() error { return d.processFastSyncContent(latest) })
|
fetchers = append(fetchers, func() error { return d.processFastSyncContent(latest) })
|
||||||
} else if d.mode == FullSync {
|
} else if d.mode == FullSync {
|
||||||
fetchers = append(fetchers, d.processFullSyncContent)
|
fetchers = append(fetchers, d.processFullSyncContent)
|
||||||
}
|
}
|
||||||
err = d.spawnSync(fetchers)
|
return d.spawnSync(fetchers)
|
||||||
if err != nil && d.mode == FastSync && d.fsPivotLock != nil {
|
|
||||||
// If sync failed in the critical section, bump the fail counter.
|
|
||||||
atomic.AddUint32(&d.fsPivotFails, 1)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// spawnSync runs d.process and all given fetcher functions to completion in
|
// spawnSync runs d.process and all given fetcher functions to completion in
|
||||||
|
|
@ -774,7 +750,7 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
||||||
// other peers are only accepted if they map cleanly to the skeleton. If no one
|
// other peers are only accepted if they map cleanly to the skeleton. If no one
|
||||||
// can fill in the skeleton - not even the origin peer - it's assumed invalid and
|
// can fill in the skeleton - not even the origin peer - it's assumed invalid and
|
||||||
// the origin is dropped.
|
// the origin is dropped.
|
||||||
func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error {
|
func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64) error {
|
||||||
p.log.Debug("Directing header downloads", "origin", from)
|
p.log.Debug("Directing header downloads", "origin", from)
|
||||||
defer p.log.Debug("Header download terminated")
|
defer p.log.Debug("Header download terminated")
|
||||||
|
|
||||||
|
|
@ -825,7 +801,19 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error {
|
||||||
}
|
}
|
||||||
// If no more headers are inbound, notify the content fetchers and return
|
// If no more headers are inbound, notify the content fetchers and return
|
||||||
if packet.Items() == 0 {
|
if packet.Items() == 0 {
|
||||||
p.log.Debug("No more headers available")
|
// Don't abort header fetches while the pivot is downloading
|
||||||
|
if atomic.LoadInt32(&d.committed) == 0 && pivot <= from {
|
||||||
|
p.log.Error("No headers, waiting for pivot commit")
|
||||||
|
select {
|
||||||
|
case <-time.After(fsHeaderContCheck):
|
||||||
|
getHeaders(from)
|
||||||
|
continue
|
||||||
|
case <-d.cancelCh:
|
||||||
|
return errCancelHeaderFetch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Pivot done (or not in fast sync) and no more headers, terminate the process
|
||||||
|
p.log.Error("No more headers available")
|
||||||
select {
|
select {
|
||||||
case d.headerProcCh <- nil:
|
case d.headerProcCh <- nil:
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -1129,10 +1117,8 @@ func (d *Downloader) fetchParts(errCancel error, deliveryCh chan dataPack, deliv
|
||||||
}
|
}
|
||||||
if request.From > 0 {
|
if request.From > 0 {
|
||||||
peer.log.Trace("Requesting new batch of data", "type", kind, "from", request.From)
|
peer.log.Trace("Requesting new batch of data", "type", kind, "from", request.From)
|
||||||
} else if len(request.Headers) > 0 {
|
|
||||||
peer.log.Trace("Requesting new batch of data", "type", kind, "count", len(request.Headers), "from", request.Headers[0].Number)
|
|
||||||
} else {
|
} else {
|
||||||
peer.log.Trace("Requesting new batch of data", "type", kind, "count", len(request.Hashes))
|
peer.log.Trace("Requesting new batch of data", "type", kind, "count", len(request.Headers), "from", request.Headers[0].Number)
|
||||||
}
|
}
|
||||||
// Fetch the chunk and make sure any errors return the hashes to the queue
|
// Fetch the chunk and make sure any errors return the hashes to the queue
|
||||||
if fetchHook != nil {
|
if fetchHook != nil {
|
||||||
|
|
@ -1160,10 +1146,7 @@ func (d *Downloader) fetchParts(errCancel error, deliveryCh chan dataPack, deliv
|
||||||
// processHeaders takes batches of retrieved headers from an input channel and
|
// processHeaders takes batches of retrieved headers from an input channel and
|
||||||
// keeps processing and scheduling them into the header chain and downloader's
|
// keeps processing and scheduling them into the header chain and downloader's
|
||||||
// queue until the stream ends or a failure occurs.
|
// queue until the stream ends or a failure occurs.
|
||||||
func (d *Downloader) processHeaders(origin uint64, td *big.Int) error {
|
func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) error {
|
||||||
// Calculate the pivoting point for switching from fast to slow sync
|
|
||||||
pivot := d.queue.FastSyncPivot()
|
|
||||||
|
|
||||||
// Keep a count of uncertain headers to roll back
|
// Keep a count of uncertain headers to roll back
|
||||||
rollback := []*types.Header{}
|
rollback := []*types.Header{}
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
@ -1188,19 +1171,6 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error {
|
||||||
"header", fmt.Sprintf("%d->%d", lastHeader, d.lightchain.CurrentHeader().Number),
|
"header", fmt.Sprintf("%d->%d", lastHeader, d.lightchain.CurrentHeader().Number),
|
||||||
"fast", fmt.Sprintf("%d->%d", lastFastBlock, curFastBlock),
|
"fast", fmt.Sprintf("%d->%d", lastFastBlock, curFastBlock),
|
||||||
"block", fmt.Sprintf("%d->%d", lastBlock, curBlock))
|
"block", fmt.Sprintf("%d->%d", lastBlock, curBlock))
|
||||||
|
|
||||||
// If we're already past the pivot point, this could be an attack, thread carefully
|
|
||||||
if rollback[len(rollback)-1].Number.Uint64() > pivot {
|
|
||||||
// If we didn't ever fail, lock in the pivot header (must! not! change!)
|
|
||||||
if atomic.LoadUint32(&d.fsPivotFails) == 0 {
|
|
||||||
for _, header := range rollback {
|
|
||||||
if header.Number.Uint64() == pivot {
|
|
||||||
log.Warn("Fast-sync pivot locked in", "number", pivot, "hash", header.Hash())
|
|
||||||
d.fsPivotLock = header
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -1302,13 +1272,6 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error {
|
||||||
rollback = append(rollback[:0], rollback[len(rollback)-fsHeaderSafetyNet:]...)
|
rollback = append(rollback[:0], rollback[len(rollback)-fsHeaderSafetyNet:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we're fast syncing and just pulled in the pivot, make sure it's the one locked in
|
|
||||||
if d.mode == FastSync && d.fsPivotLock != nil && chunk[0].Number.Uint64() <= pivot && chunk[len(chunk)-1].Number.Uint64() >= pivot {
|
|
||||||
if pivot := chunk[int(pivot-chunk[0].Number.Uint64())]; pivot.Hash() != d.fsPivotLock.Hash() {
|
|
||||||
log.Warn("Pivot doesn't match locked in one", "remoteNumber", pivot.Number, "remoteHash", pivot.Hash(), "localNumber", d.fsPivotLock.Number, "localHash", d.fsPivotLock.Hash())
|
|
||||||
return errInvalidChain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Unless we're doing light chains, schedule the headers for associated content retrieval
|
// Unless we're doing light chains, schedule the headers for associated content retrieval
|
||||||
if d.mode == FullSync || d.mode == FastSync {
|
if d.mode == FullSync || d.mode == FastSync {
|
||||||
// If we've reached the allowed number of pending headers, stall a bit
|
// If we've reached the allowed number of pending headers, stall a bit
|
||||||
|
|
@ -1343,7 +1306,7 @@ func (d *Downloader) processHeaders(origin uint64, td *big.Int) error {
|
||||||
// processFullSyncContent takes fetch results from the queue and imports them into the chain.
|
// processFullSyncContent takes fetch results from the queue and imports them into the chain.
|
||||||
func (d *Downloader) processFullSyncContent() error {
|
func (d *Downloader) processFullSyncContent() error {
|
||||||
for {
|
for {
|
||||||
results := d.queue.WaitResults()
|
results := d.queue.Results(true)
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -1357,31 +1320,29 @@ func (d *Downloader) processFullSyncContent() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) importBlockResults(results []*fetchResult) error {
|
func (d *Downloader) importBlockResults(results []*fetchResult) error {
|
||||||
for len(results) != 0 {
|
// Check for any early termination requests
|
||||||
// Check for any termination requests. This makes clean shutdown faster.
|
if len(results) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-d.quitCh:
|
case <-d.quitCh:
|
||||||
return errCancelContentProcessing
|
return errCancelContentProcessing
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
// Retrieve the a batch of results to import
|
// Retrieve the a batch of results to import
|
||||||
items := int(math.Min(float64(len(results)), float64(maxResultsProcess)))
|
first, last := results[0].Header, results[len(results)-1].Header
|
||||||
first, last := results[0].Header, results[items-1].Header
|
|
||||||
log.Debug("Inserting downloaded chain", "items", len(results),
|
log.Debug("Inserting downloaded chain", "items", len(results),
|
||||||
"firstnum", first.Number, "firsthash", first.Hash(),
|
"firstnum", first.Number, "firsthash", first.Hash(),
|
||||||
"lastnum", last.Number, "lasthash", last.Hash(),
|
"lastnum", last.Number, "lasthash", last.Hash(),
|
||||||
)
|
)
|
||||||
blocks := make([]*types.Block, items)
|
blocks := make([]*types.Block, len(results))
|
||||||
for i, result := range results[:items] {
|
for i, result := range results {
|
||||||
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles)
|
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles)
|
||||||
}
|
}
|
||||||
if index, err := d.blockchain.InsertChain(blocks); err != nil {
|
if index, err := d.blockchain.InsertChain(blocks); err != nil {
|
||||||
log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err)
|
log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err)
|
||||||
return errInvalidChain
|
return errInvalidChain
|
||||||
}
|
}
|
||||||
// Shift the results to the next batch
|
|
||||||
results = results[items:]
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1397,26 +1358,74 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error {
|
||||||
d.queue.Close() // wake up WaitResults
|
d.queue.Close() // wake up WaitResults
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
// Figure out the ideal pivot block. Note, that this goalpost may move if the
|
||||||
pivot := d.queue.FastSyncPivot()
|
// sync takes long enough for the chain head to move significantly.
|
||||||
|
pivot := uint64(0)
|
||||||
|
if height := latest.Number.Uint64(); height > uint64(fsMinFullBlocks) {
|
||||||
|
pivot = height - uint64(fsMinFullBlocks)
|
||||||
|
}
|
||||||
|
// To cater for moving pivot points, track the pivot block and subsequently
|
||||||
|
// accumulated download results separatey.
|
||||||
|
var (
|
||||||
|
oldPivot *fetchResult // Locked in pivot block, might change eventually
|
||||||
|
oldTail []*fetchResult // Downloaded content after the pivot
|
||||||
|
)
|
||||||
for {
|
for {
|
||||||
results := d.queue.WaitResults()
|
// Wait for the next batch of downloaded data to be available, and if the pivot
|
||||||
if len(results) == 0 {
|
// block became stale, move the goalpost.
|
||||||
|
results := d.queue.Results(oldPivot == nil) // Block if we're not monitoring pivot staleness
|
||||||
|
if len(results) == 0 && oldPivot == nil {
|
||||||
return stateSync.Cancel()
|
return stateSync.Cancel()
|
||||||
}
|
}
|
||||||
if d.chainInsertHook != nil {
|
if d.chainInsertHook != nil {
|
||||||
d.chainInsertHook(results)
|
d.chainInsertHook(results)
|
||||||
}
|
}
|
||||||
|
if oldPivot != nil {
|
||||||
|
results = append(append([]*fetchResult{oldPivot}, oldTail...), results...)
|
||||||
|
}
|
||||||
|
// Split around the pivot block and process the two sides via fast/full sync
|
||||||
|
if atomic.LoadInt32(&d.committed) == 0 {
|
||||||
|
latest = results[len(results)-1].Header
|
||||||
|
if height := latest.Number.Uint64(); height > pivot+2*uint64(fsMinFullBlocks) {
|
||||||
|
log.Warn("Pivot became stale, moving", "old", pivot, "new", height-uint64(fsMinFullBlocks))
|
||||||
|
pivot = height - uint64(fsMinFullBlocks)
|
||||||
|
}
|
||||||
|
}
|
||||||
P, beforeP, afterP := splitAroundPivot(pivot, results)
|
P, beforeP, afterP := splitAroundPivot(pivot, results)
|
||||||
if err := d.commitFastSyncData(beforeP, stateSync); err != nil {
|
if err := d.commitFastSyncData(beforeP, stateSync); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if P != nil {
|
if P != nil {
|
||||||
|
// If new pivot block found, cancel old state retrieval and restart
|
||||||
|
if oldPivot != P {
|
||||||
stateSync.Cancel()
|
stateSync.Cancel()
|
||||||
|
|
||||||
|
stateSync = d.syncState(P.Header.Root)
|
||||||
|
defer stateSync.Cancel()
|
||||||
|
go func() {
|
||||||
|
if err := stateSync.Wait(); err != nil {
|
||||||
|
d.queue.Close() // wake up WaitResults
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
oldPivot = P
|
||||||
|
}
|
||||||
|
// Wait for completion, occasionally checking for pivot staleness
|
||||||
|
select {
|
||||||
|
case <-stateSync.done:
|
||||||
|
if stateSync.err != nil {
|
||||||
|
return stateSync.err
|
||||||
|
}
|
||||||
if err := d.commitPivotBlock(P); err != nil {
|
if err := d.commitPivotBlock(P); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
oldPivot = nil
|
||||||
|
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
oldTail = afterP
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Fast sync done, pivot commit done, full import
|
||||||
if err := d.importBlockResults(afterP); err != nil {
|
if err := d.importBlockResults(afterP); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -1439,8 +1448,10 @@ func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, bef
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) commitFastSyncData(results []*fetchResult, stateSync *stateSync) error {
|
func (d *Downloader) commitFastSyncData(results []*fetchResult, stateSync *stateSync) error {
|
||||||
for len(results) != 0 {
|
// Check for any early termination requests
|
||||||
// Check for any termination requests.
|
if len(results) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-d.quitCh:
|
case <-d.quitCh:
|
||||||
return errCancelContentProcessing
|
return errCancelContentProcessing
|
||||||
|
|
@ -1451,15 +1462,14 @@ func (d *Downloader) commitFastSyncData(results []*fetchResult, stateSync *state
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
// Retrieve the a batch of results to import
|
// Retrieve the a batch of results to import
|
||||||
items := int(math.Min(float64(len(results)), float64(maxResultsProcess)))
|
first, last := results[0].Header, results[len(results)-1].Header
|
||||||
first, last := results[0].Header, results[items-1].Header
|
|
||||||
log.Debug("Inserting fast-sync blocks", "items", len(results),
|
log.Debug("Inserting fast-sync blocks", "items", len(results),
|
||||||
"firstnum", first.Number, "firsthash", first.Hash(),
|
"firstnum", first.Number, "firsthash", first.Hash(),
|
||||||
"lastnumn", last.Number, "lasthash", last.Hash(),
|
"lastnumn", last.Number, "lasthash", last.Hash(),
|
||||||
)
|
)
|
||||||
blocks := make([]*types.Block, items)
|
blocks := make([]*types.Block, len(results))
|
||||||
receipts := make([]types.Receipts, items)
|
receipts := make([]types.Receipts, len(results))
|
||||||
for i, result := range results[:items] {
|
for i, result := range results {
|
||||||
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles)
|
blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles)
|
||||||
receipts[i] = result.Receipts
|
receipts[i] = result.Receipts
|
||||||
}
|
}
|
||||||
|
|
@ -1467,24 +1477,20 @@ func (d *Downloader) commitFastSyncData(results []*fetchResult, stateSync *state
|
||||||
log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err)
|
log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err)
|
||||||
return errInvalidChain
|
return errInvalidChain
|
||||||
}
|
}
|
||||||
// Shift the results to the next batch
|
|
||||||
results = results[items:]
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Downloader) commitPivotBlock(result *fetchResult) error {
|
func (d *Downloader) commitPivotBlock(result *fetchResult) error {
|
||||||
b := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles)
|
block := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles)
|
||||||
// Sync the pivot block state. This should complete reasonably quickly because
|
log.Debug("Committing fast sync pivot as new head", "number", block.Number(), "hash", block.Hash())
|
||||||
// we've already synced up to the reported head block state earlier.
|
if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{result.Receipts}); err != nil {
|
||||||
if err := d.syncState(b.Root()).Wait(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debug("Committing fast sync pivot as new head", "number", b.Number(), "hash", b.Hash())
|
if err := d.blockchain.FastSyncCommitHead(block.Hash()); err != nil {
|
||||||
if _, err := d.blockchain.InsertReceiptChain([]*types.Block{b}, []types.Receipts{result.Receipts}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return d.blockchain.FastSyncCommitHead(b.Hash())
|
atomic.StoreInt32(&d.committed, 1)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliverHeaders injects a new batch of block headers received from a remote
|
// DeliverHeaders injects a new batch of block headers received from a remote
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
|
@ -45,8 +44,8 @@ var (
|
||||||
// Reduce some of the parameters to make the tester faster.
|
// Reduce some of the parameters to make the tester faster.
|
||||||
func init() {
|
func init() {
|
||||||
MaxForkAncestry = uint64(10000)
|
MaxForkAncestry = uint64(10000)
|
||||||
blockCacheLimit = 1024
|
blockCacheItems = 1024
|
||||||
fsCriticalTrials = 10
|
fsHeaderContCheck = 500 * time.Millisecond
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadTester is a test simulator for mocking out local block chain.
|
// downloadTester is a test simulator for mocking out local block chain.
|
||||||
|
|
@ -619,28 +618,22 @@ func assertOwnChain(t *testing.T, tester *downloadTester, length int) {
|
||||||
// number of items of the various chain components.
|
// number of items of the various chain components.
|
||||||
func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, lengths []int) {
|
func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, lengths []int) {
|
||||||
// Initialize the counters for the first fork
|
// Initialize the counters for the first fork
|
||||||
headers, blocks := lengths[0], lengths[0]
|
headers, blocks, receipts := lengths[0], lengths[0], lengths[0]-fsMinFullBlocks
|
||||||
|
|
||||||
minReceipts, maxReceipts := lengths[0]-fsMinFullBlocks-fsPivotInterval, lengths[0]-fsMinFullBlocks
|
if receipts < 0 {
|
||||||
if minReceipts < 0 {
|
receipts = 1
|
||||||
minReceipts = 1
|
|
||||||
}
|
|
||||||
if maxReceipts < 0 {
|
|
||||||
maxReceipts = 1
|
|
||||||
}
|
}
|
||||||
// Update the counters for each subsequent fork
|
// Update the counters for each subsequent fork
|
||||||
for _, length := range lengths[1:] {
|
for _, length := range lengths[1:] {
|
||||||
headers += length - common
|
headers += length - common
|
||||||
blocks += length - common
|
blocks += length - common
|
||||||
|
receipts += length - common - fsMinFullBlocks
|
||||||
minReceipts += length - common - fsMinFullBlocks - fsPivotInterval
|
|
||||||
maxReceipts += length - common - fsMinFullBlocks
|
|
||||||
}
|
}
|
||||||
switch tester.downloader.mode {
|
switch tester.downloader.mode {
|
||||||
case FullSync:
|
case FullSync:
|
||||||
minReceipts, maxReceipts = 1, 1
|
receipts = 1
|
||||||
case LightSync:
|
case LightSync:
|
||||||
blocks, minReceipts, maxReceipts = 1, 1, 1
|
blocks, receipts = 1, 1
|
||||||
}
|
}
|
||||||
if hs := len(tester.ownHeaders); hs != headers {
|
if hs := len(tester.ownHeaders); hs != headers {
|
||||||
t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers)
|
t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers)
|
||||||
|
|
@ -648,11 +641,12 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng
|
||||||
if bs := len(tester.ownBlocks); bs != blocks {
|
if bs := len(tester.ownBlocks); bs != blocks {
|
||||||
t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, blocks)
|
t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, blocks)
|
||||||
}
|
}
|
||||||
if rs := len(tester.ownReceipts); rs < minReceipts || rs > maxReceipts {
|
if rs := len(tester.ownReceipts); rs != receipts {
|
||||||
t.Fatalf("synchronised receipts mismatch: have %v, want between [%v, %v]", rs, minReceipts, maxReceipts)
|
t.Fatalf("synchronised receipts mismatch: have %v, want %v", rs, receipts)
|
||||||
}
|
}
|
||||||
// Verify the state trie too for fast syncs
|
// Verify the state trie too for fast syncs
|
||||||
if tester.downloader.mode == FastSync {
|
/*if tester.downloader.mode == FastSync {
|
||||||
|
pivot := uint64(0)
|
||||||
var index int
|
var index int
|
||||||
if pivot := int(tester.downloader.queue.fastSyncPivot); pivot < common {
|
if pivot := int(tester.downloader.queue.fastSyncPivot); pivot < common {
|
||||||
index = pivot
|
index = pivot
|
||||||
|
|
@ -664,7 +658,7 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng
|
||||||
t.Fatalf("state reconstruction failed: %v", err)
|
t.Fatalf("state reconstruction failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that simple synchronization against a canonical chain works correctly.
|
// Tests that simple synchronization against a canonical chain works correctly.
|
||||||
|
|
@ -684,7 +678,7 @@ func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download
|
// Create a small enough block chain to download
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheItems - 15
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
tester.newPeer("peer", protocol, hashes, headers, blocks, receipts)
|
tester.newPeer("peer", protocol, hashes, headers, blocks, receipts)
|
||||||
|
|
@ -710,7 +704,7 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a long block chain to download and the tester
|
// Create a long block chain to download and the tester
|
||||||
targetBlocks := 8 * blockCacheLimit
|
targetBlocks := 8 * blockCacheItems
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
tester.newPeer("peer", protocol, hashes, headers, blocks, receipts)
|
tester.newPeer("peer", protocol, hashes, headers, blocks, receipts)
|
||||||
|
|
@ -745,9 +739,9 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) {
|
||||||
cached = len(tester.downloader.queue.blockDonePool)
|
cached = len(tester.downloader.queue.blockDonePool)
|
||||||
if mode == FastSync {
|
if mode == FastSync {
|
||||||
if receipts := len(tester.downloader.queue.receiptDonePool); receipts < cached {
|
if receipts := len(tester.downloader.queue.receiptDonePool); receipts < cached {
|
||||||
if tester.downloader.queue.resultCache[receipts].Header.Number.Uint64() < tester.downloader.queue.fastSyncPivot {
|
//if tester.downloader.queue.resultCache[receipts].Header.Number.Uint64() < tester.downloader.queue.fastSyncPivot {
|
||||||
cached = receipts
|
cached = receipts
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
frozen = int(atomic.LoadUint32(&blocked))
|
frozen = int(atomic.LoadUint32(&blocked))
|
||||||
|
|
@ -755,7 +749,7 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) {
|
||||||
tester.downloader.queue.lock.Unlock()
|
tester.downloader.queue.lock.Unlock()
|
||||||
tester.lock.Unlock()
|
tester.lock.Unlock()
|
||||||
|
|
||||||
if cached == blockCacheLimit || retrieved+cached+frozen == targetBlocks+1 {
|
if cached == blockCacheItems || retrieved+cached+frozen == targetBlocks+1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -765,8 +759,8 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) {
|
||||||
tester.lock.RLock()
|
tester.lock.RLock()
|
||||||
retrieved = len(tester.ownBlocks)
|
retrieved = len(tester.ownBlocks)
|
||||||
tester.lock.RUnlock()
|
tester.lock.RUnlock()
|
||||||
if cached != blockCacheLimit && retrieved+cached+frozen != targetBlocks+1 {
|
if cached != blockCacheItems && retrieved+cached+frozen != targetBlocks+1 {
|
||||||
t.Fatalf("block count mismatch: have %v, want %v (owned %v, blocked %v, target %v)", cached, blockCacheLimit, retrieved, frozen, targetBlocks+1)
|
t.Fatalf("block count mismatch: have %v, want %v (owned %v, blocked %v, target %v)", cached, blockCacheItems, retrieved, frozen, targetBlocks+1)
|
||||||
}
|
}
|
||||||
// Permit the blocked blocks to import
|
// Permit the blocked blocks to import
|
||||||
if atomic.LoadUint32(&blocked) > 0 {
|
if atomic.LoadUint32(&blocked) > 0 {
|
||||||
|
|
@ -974,7 +968,7 @@ func testCancel(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download and the tester
|
// Create a small enough block chain to download and the tester
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheItems - 15
|
||||||
if targetBlocks >= MaxHashFetch {
|
if targetBlocks >= MaxHashFetch {
|
||||||
targetBlocks = MaxHashFetch - 15
|
targetBlocks = MaxHashFetch - 15
|
||||||
}
|
}
|
||||||
|
|
@ -1016,12 +1010,12 @@ func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) {
|
||||||
|
|
||||||
// Create various peers with various parts of the chain
|
// Create various peers with various parts of the chain
|
||||||
targetPeers := 8
|
targetPeers := 8
|
||||||
targetBlocks := targetPeers*blockCacheLimit - 15
|
targetBlocks := targetPeers*blockCacheItems - 15
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
for i := 0; i < targetPeers; i++ {
|
for i := 0; i < targetPeers; i++ {
|
||||||
id := fmt.Sprintf("peer #%d", i)
|
id := fmt.Sprintf("peer #%d", i)
|
||||||
tester.newPeer(id, protocol, hashes[i*blockCacheLimit:], headers, blocks, receipts)
|
tester.newPeer(id, protocol, hashes[i*blockCacheItems:], headers, blocks, receipts)
|
||||||
}
|
}
|
||||||
if err := tester.sync("peer #0", nil, mode); err != nil {
|
if err := tester.sync("peer #0", nil, mode); err != nil {
|
||||||
t.Fatalf("failed to synchronise blocks: %v", err)
|
t.Fatalf("failed to synchronise blocks: %v", err)
|
||||||
|
|
@ -1045,7 +1039,7 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download
|
// Create a small enough block chain to download
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheItems - 15
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
// Create peers of every type
|
// Create peers of every type
|
||||||
|
|
@ -1084,7 +1078,7 @@ func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a block chain to download
|
// Create a block chain to download
|
||||||
targetBlocks := 2*blockCacheLimit - 15
|
targetBlocks := 2*blockCacheItems - 15
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
tester.newPeer("peer", protocol, hashes, headers, blocks, receipts)
|
tester.newPeer("peer", protocol, hashes, headers, blocks, receipts)
|
||||||
|
|
@ -1110,8 +1104,8 @@ func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) {
|
||||||
bodiesNeeded++
|
bodiesNeeded++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for hash, receipt := range receipts {
|
for _, receipt := range receipts {
|
||||||
if mode == FastSync && len(receipt) > 0 && headers[hash].Number.Uint64() <= tester.downloader.queue.fastSyncPivot {
|
if mode == FastSync && len(receipt) > 0 {
|
||||||
receiptsNeeded++
|
receiptsNeeded++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1139,7 +1133,7 @@ func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download
|
// Create a small enough block chain to download
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheItems - 15
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
// Attempt a full sync with an attacker feeding gapped headers
|
// Attempt a full sync with an attacker feeding gapped headers
|
||||||
|
|
@ -1174,7 +1168,7 @@ func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download
|
// Create a small enough block chain to download
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheItems - 15
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
// Attempt a full sync with an attacker feeding shifted headers
|
// Attempt a full sync with an attacker feeding shifted headers
|
||||||
|
|
@ -1208,7 +1202,7 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download
|
// Create a small enough block chain to download
|
||||||
targetBlocks := 3*fsHeaderSafetyNet + fsPivotInterval + fsMinFullBlocks
|
targetBlocks := 3*fsHeaderSafetyNet + 256 + fsMinFullBlocks
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
// Attempt to sync with an attacker that feeds junk during the fast sync phase.
|
// Attempt to sync with an attacker that feeds junk during the fast sync phase.
|
||||||
|
|
@ -1248,7 +1242,6 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
|
||||||
tester.newPeer("withhold-attack", protocol, hashes, headers, blocks, receipts)
|
tester.newPeer("withhold-attack", protocol, hashes, headers, blocks, receipts)
|
||||||
missing = 3*fsHeaderSafetyNet + MaxHeaderFetch + 1
|
missing = 3*fsHeaderSafetyNet + MaxHeaderFetch + 1
|
||||||
|
|
||||||
tester.downloader.fsPivotFails = 0
|
|
||||||
tester.downloader.syncInitHook = func(uint64, uint64) {
|
tester.downloader.syncInitHook = func(uint64, uint64) {
|
||||||
for i := missing; i <= len(hashes); i++ {
|
for i := missing; i <= len(hashes); i++ {
|
||||||
delete(tester.peerHeaders["withhold-attack"], hashes[len(hashes)-i])
|
delete(tester.peerHeaders["withhold-attack"], hashes[len(hashes)-i])
|
||||||
|
|
@ -1267,8 +1260,6 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
|
||||||
t.Errorf("fast sync pivot block #%d not rolled back", head)
|
t.Errorf("fast sync pivot block #%d not rolled back", head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tester.downloader.fsPivotFails = fsCriticalTrials
|
|
||||||
|
|
||||||
// Synchronise with the valid peer and make sure sync succeeds. Since the last
|
// Synchronise with the valid peer and make sure sync succeeds. Since the last
|
||||||
// rollback should also disable fast syncing for this process, verify that we
|
// rollback should also disable fast syncing for this process, verify that we
|
||||||
// did a fresh full sync. Note, we can't assert anything about the receipts
|
// did a fresh full sync. Note, we can't assert anything about the receipts
|
||||||
|
|
@ -1383,7 +1374,7 @@ func testSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download
|
// Create a small enough block chain to download
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheItems - 15
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
// Set a sync init hook to catch progress changes
|
// Set a sync init hook to catch progress changes
|
||||||
|
|
@ -1532,7 +1523,7 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small enough block chain to download
|
// Create a small enough block chain to download
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheItems - 15
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
// Set a sync init hook to catch progress changes
|
// Set a sync init hook to catch progress changes
|
||||||
|
|
@ -1609,7 +1600,7 @@ func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
|
||||||
defer tester.terminate()
|
defer tester.terminate()
|
||||||
|
|
||||||
// Create a small block chain
|
// Create a small block chain
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheItems - 15
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks+3, 0, tester.genesis, nil, false)
|
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks+3, 0, tester.genesis, nil, false)
|
||||||
|
|
||||||
// Set a sync init hook to catch progress changes
|
// Set a sync init hook to catch progress changes
|
||||||
|
|
@ -1760,101 +1751,3 @@ func testDeliverHeadersHang(t *testing.T, protocol int, mode SyncMode) {
|
||||||
tester.terminate()
|
tester.terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that if fast sync aborts in the critical section, it can restart a few
|
|
||||||
// times before giving up.
|
|
||||||
// We use data driven subtests to manage this so that it will be parallel on its own
|
|
||||||
// and not with the other tests, avoiding intermittent failures.
|
|
||||||
func TestFastCriticalRestarts(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
protocol int
|
|
||||||
progress bool
|
|
||||||
}{
|
|
||||||
{63, false},
|
|
||||||
{64, false},
|
|
||||||
{63, true},
|
|
||||||
{64, true},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(fmt.Sprintf("protocol %d progress %v", tc.protocol, tc.progress), func(t *testing.T) {
|
|
||||||
testFastCriticalRestarts(t, tc.protocol, tc.progress)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFastCriticalRestarts(t *testing.T, protocol int, progress bool) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tester := newTester()
|
|
||||||
defer tester.terminate()
|
|
||||||
|
|
||||||
// Create a large enough blockchin to actually fast sync on
|
|
||||||
targetBlocks := fsMinFullBlocks + 2*fsPivotInterval - 15
|
|
||||||
hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false)
|
|
||||||
|
|
||||||
// Create a tester peer with a critical section header missing (force failures)
|
|
||||||
tester.newPeer("peer", protocol, hashes, headers, blocks, receipts)
|
|
||||||
delete(tester.peerHeaders["peer"], hashes[fsMinFullBlocks-1])
|
|
||||||
tester.downloader.dropPeer = func(id string) {} // We reuse the same "faulty" peer throughout the test
|
|
||||||
|
|
||||||
// Remove all possible pivot state roots and slow down replies (test failure resets later)
|
|
||||||
for i := 0; i < fsPivotInterval; i++ {
|
|
||||||
tester.peerMissingStates["peer"][headers[hashes[fsMinFullBlocks+i]].Root] = true
|
|
||||||
}
|
|
||||||
(tester.downloader.peers.peers["peer"].peer).(*downloadTesterPeer).setDelay(500 * time.Millisecond) // Enough to reach the critical section
|
|
||||||
|
|
||||||
// Synchronise with the peer a few times and make sure they fail until the retry limit
|
|
||||||
for i := 0; i < int(fsCriticalTrials)-1; i++ {
|
|
||||||
// Attempt a sync and ensure it fails properly
|
|
||||||
if err := tester.sync("peer", nil, FastSync); err == nil {
|
|
||||||
t.Fatalf("failing fast sync succeeded: %v", err)
|
|
||||||
}
|
|
||||||
time.Sleep(150 * time.Millisecond) // Make sure no in-flight requests remain
|
|
||||||
|
|
||||||
// If it's the first failure, pivot should be locked => reenable all others to detect pivot changes
|
|
||||||
if i == 0 {
|
|
||||||
time.Sleep(150 * time.Millisecond) // Make sure no in-flight requests remain
|
|
||||||
if tester.downloader.fsPivotLock == nil {
|
|
||||||
time.Sleep(400 * time.Millisecond) // Make sure the first huge timeout expires too
|
|
||||||
t.Fatalf("pivot block not locked in after critical section failure")
|
|
||||||
}
|
|
||||||
tester.lock.Lock()
|
|
||||||
tester.peerHeaders["peer"][hashes[fsMinFullBlocks-1]] = headers[hashes[fsMinFullBlocks-1]]
|
|
||||||
tester.peerMissingStates["peer"] = map[common.Hash]bool{tester.downloader.fsPivotLock.Root: true}
|
|
||||||
(tester.downloader.peers.peers["peer"].peer).(*downloadTesterPeer).setDelay(0)
|
|
||||||
tester.lock.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return all nodes if we're testing fast sync progression
|
|
||||||
if progress {
|
|
||||||
tester.lock.Lock()
|
|
||||||
tester.peerMissingStates["peer"] = map[common.Hash]bool{}
|
|
||||||
tester.lock.Unlock()
|
|
||||||
|
|
||||||
if err := tester.sync("peer", nil, FastSync); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks in progressed fast sync: %v", err)
|
|
||||||
}
|
|
||||||
time.Sleep(150 * time.Millisecond) // Make sure no in-flight requests remain
|
|
||||||
|
|
||||||
if fails := atomic.LoadUint32(&tester.downloader.fsPivotFails); fails != 1 {
|
|
||||||
t.Fatalf("progressed pivot trial count mismatch: have %v, want %v", fails, 1)
|
|
||||||
}
|
|
||||||
assertOwnChain(t, tester, targetBlocks+1)
|
|
||||||
} else {
|
|
||||||
if err := tester.sync("peer", nil, FastSync); err == nil {
|
|
||||||
t.Fatalf("succeeded to synchronise blocks in failed fast sync")
|
|
||||||
}
|
|
||||||
time.Sleep(150 * time.Millisecond) // Make sure no in-flight requests remain
|
|
||||||
|
|
||||||
if fails := atomic.LoadUint32(&tester.downloader.fsPivotFails); fails != fsCriticalTrials {
|
|
||||||
t.Fatalf("failed pivot trial count mismatch: have %v, want %v", fails, fsCriticalTrials)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Retry limit exhausted, downloader will switch to full sync, should succeed
|
|
||||||
if err := tester.sync("peer", nil, FastSync); err != nil {
|
|
||||||
t.Fatalf("failed to synchronise blocks in slow sync: %v", err)
|
|
||||||
}
|
|
||||||
// Note, we can't assert the chain here because the test asserter assumes sync
|
|
||||||
// completed using a single mode of operation, whereas fast-then-slow can result
|
|
||||||
// in arbitrary intermediate state that's not cleanly verifiable.
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,16 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
"gopkg.in/karalabe/cookiejar.v2/collections/prque"
|
"gopkg.in/karalabe/cookiejar.v2/collections/prque"
|
||||||
)
|
)
|
||||||
|
|
||||||
var blockCacheLimit = 8192 // Maximum number of blocks to cache before throttling the download
|
var (
|
||||||
|
blockCacheItems = 8192 // Maximum number of blocks to cache before throttling the download
|
||||||
|
blockCacheMemory = 64 * 1024 * 1024 // Maximum amount of memory to use for block caching
|
||||||
|
blockCacheSizeWeight = 0.1 // Multiplier to approximate the average block size based on past ones
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNoFetchesPending = errors.New("no fetches pending")
|
errNoFetchesPending = errors.New("no fetches pending")
|
||||||
|
|
@ -43,7 +48,6 @@ var (
|
||||||
type fetchRequest struct {
|
type fetchRequest struct {
|
||||||
Peer *peerConnection // Peer to which the request was sent
|
Peer *peerConnection // Peer to which the request was sent
|
||||||
From uint64 // [eth/62] Requested chain element index (used for skeleton fills only)
|
From uint64 // [eth/62] Requested chain element index (used for skeleton fills only)
|
||||||
Hashes map[common.Hash]int // [eth/61] Requested hashes with their insertion index (priority)
|
|
||||||
Headers []*types.Header // [eth/62] Requested headers, sorted by request order
|
Headers []*types.Header // [eth/62] Requested headers, sorted by request order
|
||||||
Time time.Time // Time when the request was made
|
Time time.Time // Time when the request was made
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +56,7 @@ type fetchRequest struct {
|
||||||
// all outstanding pieces complete and the result as a whole can be processed.
|
// all outstanding pieces complete and the result as a whole can be processed.
|
||||||
type fetchResult struct {
|
type fetchResult struct {
|
||||||
Pending int // Number of data fetches still pending
|
Pending int // Number of data fetches still pending
|
||||||
|
Hash common.Hash // Hash of the header to prevent recalculating
|
||||||
|
|
||||||
Header *types.Header
|
Header *types.Header
|
||||||
Uncles []*types.Header
|
Uncles []*types.Header
|
||||||
|
|
@ -62,11 +67,9 @@ type fetchResult struct {
|
||||||
// queue represents hashes that are either need fetching or are being fetched
|
// queue represents hashes that are either need fetching or are being fetched
|
||||||
type queue struct {
|
type queue struct {
|
||||||
mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching
|
mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching
|
||||||
fastSyncPivot uint64 // Block number where the fast sync pivots into archive synchronisation mode
|
|
||||||
|
|
||||||
headerHead common.Hash // [eth/62] Hash of the last queued header to verify order
|
|
||||||
|
|
||||||
// Headers are "special", they download in batches, supported by a skeleton chain
|
// Headers are "special", they download in batches, supported by a skeleton chain
|
||||||
|
headerHead common.Hash // [eth/62] Hash of the last queued header to verify order
|
||||||
headerTaskPool map[uint64]*types.Header // [eth/62] Pending header retrieval tasks, mapping starting indexes to skeleton headers
|
headerTaskPool map[uint64]*types.Header // [eth/62] Pending header retrieval tasks, mapping starting indexes to skeleton headers
|
||||||
headerTaskQueue *prque.Prque // [eth/62] Priority queue of the skeleton indexes to fetch the filling headers for
|
headerTaskQueue *prque.Prque // [eth/62] Priority queue of the skeleton indexes to fetch the filling headers for
|
||||||
headerPeerMiss map[string]map[uint64]struct{} // [eth/62] Set of per-peer header batches known to be unavailable
|
headerPeerMiss map[string]map[uint64]struct{} // [eth/62] Set of per-peer header batches known to be unavailable
|
||||||
|
|
@ -89,6 +92,7 @@ type queue struct {
|
||||||
|
|
||||||
resultCache []*fetchResult // Downloaded but not yet delivered fetch results
|
resultCache []*fetchResult // Downloaded but not yet delivered fetch results
|
||||||
resultOffset uint64 // Offset of the first cached fetch result in the block chain
|
resultOffset uint64 // Offset of the first cached fetch result in the block chain
|
||||||
|
resultSize common.StorageSize // Approximate size of a block (exponential moving average)
|
||||||
|
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
active *sync.Cond
|
active *sync.Cond
|
||||||
|
|
@ -109,7 +113,7 @@ func newQueue() *queue {
|
||||||
receiptTaskQueue: prque.New(),
|
receiptTaskQueue: prque.New(),
|
||||||
receiptPendPool: make(map[string]*fetchRequest),
|
receiptPendPool: make(map[string]*fetchRequest),
|
||||||
receiptDonePool: make(map[common.Hash]struct{}),
|
receiptDonePool: make(map[common.Hash]struct{}),
|
||||||
resultCache: make([]*fetchResult, blockCacheLimit),
|
resultCache: make([]*fetchResult, blockCacheItems),
|
||||||
active: sync.NewCond(lock),
|
active: sync.NewCond(lock),
|
||||||
lock: lock,
|
lock: lock,
|
||||||
}
|
}
|
||||||
|
|
@ -122,10 +126,8 @@ func (q *queue) Reset() {
|
||||||
|
|
||||||
q.closed = false
|
q.closed = false
|
||||||
q.mode = FullSync
|
q.mode = FullSync
|
||||||
q.fastSyncPivot = 0
|
|
||||||
|
|
||||||
q.headerHead = common.Hash{}
|
q.headerHead = common.Hash{}
|
||||||
|
|
||||||
q.headerPendPool = make(map[string]*fetchRequest)
|
q.headerPendPool = make(map[string]*fetchRequest)
|
||||||
|
|
||||||
q.blockTaskPool = make(map[common.Hash]*types.Header)
|
q.blockTaskPool = make(map[common.Hash]*types.Header)
|
||||||
|
|
@ -138,7 +140,7 @@ func (q *queue) Reset() {
|
||||||
q.receiptPendPool = make(map[string]*fetchRequest)
|
q.receiptPendPool = make(map[string]*fetchRequest)
|
||||||
q.receiptDonePool = make(map[common.Hash]struct{})
|
q.receiptDonePool = make(map[common.Hash]struct{})
|
||||||
|
|
||||||
q.resultCache = make([]*fetchResult, blockCacheLimit)
|
q.resultCache = make([]*fetchResult, blockCacheItems)
|
||||||
q.resultOffset = 0
|
q.resultOffset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,27 +216,13 @@ func (q *queue) Idle() bool {
|
||||||
return (queued + pending + cached) == 0
|
return (queued + pending + cached) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// FastSyncPivot retrieves the currently used fast sync pivot point.
|
|
||||||
func (q *queue) FastSyncPivot() uint64 {
|
|
||||||
q.lock.Lock()
|
|
||||||
defer q.lock.Unlock()
|
|
||||||
|
|
||||||
return q.fastSyncPivot
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldThrottleBlocks checks if the download should be throttled (active block (body)
|
// ShouldThrottleBlocks checks if the download should be throttled (active block (body)
|
||||||
// fetches exceed block cache).
|
// fetches exceed block cache).
|
||||||
func (q *queue) ShouldThrottleBlocks() bool {
|
func (q *queue) ShouldThrottleBlocks() bool {
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
defer q.lock.Unlock()
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
// Calculate the currently in-flight block (body) requests
|
return q.resultSlots(q.blockPendPool, q.blockDonePool) <= 0
|
||||||
pending := 0
|
|
||||||
for _, request := range q.blockPendPool {
|
|
||||||
pending += len(request.Hashes) + len(request.Headers)
|
|
||||||
}
|
|
||||||
// Throttle if more blocks (bodies) are in-flight than free space in the cache
|
|
||||||
return pending >= len(q.resultCache)-len(q.blockDonePool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldThrottleReceipts checks if the download should be throttled (active receipt
|
// ShouldThrottleReceipts checks if the download should be throttled (active receipt
|
||||||
|
|
@ -243,13 +231,39 @@ func (q *queue) ShouldThrottleReceipts() bool {
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
defer q.lock.Unlock()
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
// Calculate the currently in-flight receipt requests
|
return q.resultSlots(q.receiptPendPool, q.receiptDonePool) <= 0
|
||||||
pending := 0
|
}
|
||||||
for _, request := range q.receiptPendPool {
|
|
||||||
pending += len(request.Headers)
|
// resultSlots calculates the number of results slots available for requests
|
||||||
|
// whilst adhering to both the item and the memory limit too of the results
|
||||||
|
// cache.
|
||||||
|
func (q *queue) resultSlots(pendPool map[string]*fetchRequest, donePool map[common.Hash]struct{}) int {
|
||||||
|
// Calculate the maximum length capped by the memory limit
|
||||||
|
limit := len(q.resultCache)
|
||||||
|
if common.StorageSize(len(q.resultCache))*q.resultSize > common.StorageSize(blockCacheMemory) {
|
||||||
|
limit = int((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize)
|
||||||
}
|
}
|
||||||
// Throttle if more receipts are in-flight than free space in the cache
|
// Calculate the number of slots already finished
|
||||||
return pending >= len(q.resultCache)-len(q.receiptDonePool)
|
finished := 0
|
||||||
|
for _, result := range q.resultCache[:limit] {
|
||||||
|
if result == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := donePool[result.Hash]; ok {
|
||||||
|
finished++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Calculate the number of slots currently downloading
|
||||||
|
pending := 0
|
||||||
|
for _, request := range pendPool {
|
||||||
|
for _, header := range request.Headers {
|
||||||
|
if header.Number.Uint64() < q.resultOffset+uint64(limit) {
|
||||||
|
pending++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Return the free slots to distribute
|
||||||
|
return limit - finished - pending
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill
|
// ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill
|
||||||
|
|
@ -323,8 +337,7 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header {
|
||||||
q.blockTaskPool[hash] = header
|
q.blockTaskPool[hash] = header
|
||||||
q.blockTaskQueue.Push(header, -float32(header.Number.Uint64()))
|
q.blockTaskQueue.Push(header, -float32(header.Number.Uint64()))
|
||||||
|
|
||||||
if q.mode == FastSync && header.Number.Uint64() <= q.fastSyncPivot {
|
if q.mode == FastSync {
|
||||||
// Fast phase of the fast sync, retrieve receipts too
|
|
||||||
q.receiptTaskPool[hash] = header
|
q.receiptTaskPool[hash] = header
|
||||||
q.receiptTaskQueue.Push(header, -float32(header.Number.Uint64()))
|
q.receiptTaskQueue.Push(header, -float32(header.Number.Uint64()))
|
||||||
}
|
}
|
||||||
|
|
@ -335,18 +348,25 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header {
|
||||||
return inserts
|
return inserts
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitResults retrieves and permanently removes a batch of fetch
|
// Results retrieves and permanently removes a batch of fetch results from
|
||||||
// results from the cache. the result slice will be empty if the queue
|
// the cache. the result slice will be empty if the queue has been closed.
|
||||||
// has been closed.
|
func (q *queue) Results(block bool) []*fetchResult {
|
||||||
func (q *queue) WaitResults() []*fetchResult {
|
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
defer q.lock.Unlock()
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
|
// Count the number of items available for processing
|
||||||
nproc := q.countProcessableItems()
|
nproc := q.countProcessableItems()
|
||||||
for nproc == 0 && !q.closed {
|
for nproc == 0 && !q.closed {
|
||||||
|
if !block {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
q.active.Wait()
|
q.active.Wait()
|
||||||
nproc = q.countProcessableItems()
|
nproc = q.countProcessableItems()
|
||||||
}
|
}
|
||||||
|
// Since we have a batch limit, don't pull more into "dangling" memory
|
||||||
|
if nproc > maxResultsProcess {
|
||||||
|
nproc = maxResultsProcess
|
||||||
|
}
|
||||||
results := make([]*fetchResult, nproc)
|
results := make([]*fetchResult, nproc)
|
||||||
copy(results, q.resultCache[:nproc])
|
copy(results, q.resultCache[:nproc])
|
||||||
if len(results) > 0 {
|
if len(results) > 0 {
|
||||||
|
|
@ -363,6 +383,18 @@ func (q *queue) WaitResults() []*fetchResult {
|
||||||
}
|
}
|
||||||
// Advance the expected block number of the first cache entry.
|
// Advance the expected block number of the first cache entry.
|
||||||
q.resultOffset += uint64(nproc)
|
q.resultOffset += uint64(nproc)
|
||||||
|
|
||||||
|
// Recalculate the result item weights to prevent memory exhaustion
|
||||||
|
for _, result := range results {
|
||||||
|
var size common.StorageSize
|
||||||
|
|
||||||
|
blob, _ := rlp.EncodeToBytes(result.Receipts) // TODO(karalabe): Whoah, nice hack, super optimal
|
||||||
|
size += common.StorageSize(len(blob))
|
||||||
|
blob, _ = rlp.EncodeToBytes(result.Transactions)
|
||||||
|
size += common.StorageSize(len(blob))
|
||||||
|
|
||||||
|
q.resultSize = common.StorageSize(blockCacheSizeWeight)*size + (1-common.StorageSize(blockCacheSizeWeight))*q.resultSize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
@ -370,21 +402,9 @@ func (q *queue) WaitResults() []*fetchResult {
|
||||||
// countProcessableItems counts the processable items.
|
// countProcessableItems counts the processable items.
|
||||||
func (q *queue) countProcessableItems() int {
|
func (q *queue) countProcessableItems() int {
|
||||||
for i, result := range q.resultCache {
|
for i, result := range q.resultCache {
|
||||||
// Don't process incomplete or unavailable items.
|
|
||||||
if result == nil || result.Pending > 0 {
|
if result == nil || result.Pending > 0 {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
// Stop before processing the pivot block to ensure that
|
|
||||||
// resultCache has space for fsHeaderForceVerify items. Not
|
|
||||||
// doing this could leave us unable to download the required
|
|
||||||
// amount of headers.
|
|
||||||
if q.mode == FastSync && result.Header.Number.Uint64() == q.fastSyncPivot {
|
|
||||||
for j := 0; j < fsHeaderForceVerify; j++ {
|
|
||||||
if i+j+1 >= len(q.resultCache) || q.resultCache[i+j+1] == nil {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return len(q.resultCache)
|
return len(q.resultCache)
|
||||||
}
|
}
|
||||||
|
|
@ -473,10 +493,8 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
// Calculate an upper limit on the items we might fetch (i.e. throttling)
|
// Calculate an upper limit on the items we might fetch (i.e. throttling)
|
||||||
space := len(q.resultCache) - len(donePool)
|
space := q.resultSlots(pendPool, donePool)
|
||||||
for _, request := range pendPool {
|
|
||||||
space -= len(request.Headers)
|
|
||||||
}
|
|
||||||
// Retrieve a batch of tasks, skipping previously failed ones
|
// Retrieve a batch of tasks, skipping previously failed ones
|
||||||
send := make([]*types.Header, 0, count)
|
send := make([]*types.Header, 0, count)
|
||||||
skip := make([]*types.Header, 0)
|
skip := make([]*types.Header, 0)
|
||||||
|
|
@ -484,6 +502,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
|
||||||
progress := false
|
progress := false
|
||||||
for proc := 0; proc < space && len(send) < count && !taskQueue.Empty(); proc++ {
|
for proc := 0; proc < space && len(send) < count && !taskQueue.Empty(); proc++ {
|
||||||
header := taskQueue.PopItem().(*types.Header)
|
header := taskQueue.PopItem().(*types.Header)
|
||||||
|
hash := header.Hash()
|
||||||
|
|
||||||
// If we're the first to request this task, initialise the result container
|
// If we're the first to request this task, initialise the result container
|
||||||
index := int(header.Number.Int64() - int64(q.resultOffset))
|
index := int(header.Number.Int64() - int64(q.resultOffset))
|
||||||
|
|
@ -493,18 +512,19 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
|
||||||
}
|
}
|
||||||
if q.resultCache[index] == nil {
|
if q.resultCache[index] == nil {
|
||||||
components := 1
|
components := 1
|
||||||
if q.mode == FastSync && header.Number.Uint64() <= q.fastSyncPivot {
|
if q.mode == FastSync {
|
||||||
components = 2
|
components = 2
|
||||||
}
|
}
|
||||||
q.resultCache[index] = &fetchResult{
|
q.resultCache[index] = &fetchResult{
|
||||||
Pending: components,
|
Pending: components,
|
||||||
|
Hash: hash,
|
||||||
Header: header,
|
Header: header,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If this fetch task is a noop, skip this fetch operation
|
// If this fetch task is a noop, skip this fetch operation
|
||||||
if isNoop(header) {
|
if isNoop(header) {
|
||||||
donePool[header.Hash()] = struct{}{}
|
donePool[hash] = struct{}{}
|
||||||
delete(taskPool, header.Hash())
|
delete(taskPool, hash)
|
||||||
|
|
||||||
space, proc = space-1, proc-1
|
space, proc = space-1, proc-1
|
||||||
q.resultCache[index].Pending--
|
q.resultCache[index].Pending--
|
||||||
|
|
@ -512,7 +532,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Otherwise unless the peer is known not to have the data, add to the retrieve list
|
// Otherwise unless the peer is known not to have the data, add to the retrieve list
|
||||||
if p.Lacks(header.Hash()) {
|
if p.Lacks(hash) {
|
||||||
skip = append(skip, header)
|
skip = append(skip, header)
|
||||||
} else {
|
} else {
|
||||||
send = append(send, header)
|
send = append(send, header)
|
||||||
|
|
@ -565,9 +585,6 @@ func (q *queue) cancel(request *fetchRequest, taskQueue *prque.Prque, pendPool m
|
||||||
if request.From > 0 {
|
if request.From > 0 {
|
||||||
taskQueue.Push(request.From, -float32(request.From))
|
taskQueue.Push(request.From, -float32(request.From))
|
||||||
}
|
}
|
||||||
for hash, index := range request.Hashes {
|
|
||||||
taskQueue.Push(hash, float32(index))
|
|
||||||
}
|
|
||||||
for _, header := range request.Headers {
|
for _, header := range request.Headers {
|
||||||
taskQueue.Push(header, -float32(header.Number.Uint64()))
|
taskQueue.Push(header, -float32(header.Number.Uint64()))
|
||||||
}
|
}
|
||||||
|
|
@ -640,18 +657,11 @@ func (q *queue) expire(timeout time.Duration, pendPool map[string]*fetchRequest,
|
||||||
if request.From > 0 {
|
if request.From > 0 {
|
||||||
taskQueue.Push(request.From, -float32(request.From))
|
taskQueue.Push(request.From, -float32(request.From))
|
||||||
}
|
}
|
||||||
for hash, index := range request.Hashes {
|
|
||||||
taskQueue.Push(hash, float32(index))
|
|
||||||
}
|
|
||||||
for _, header := range request.Headers {
|
for _, header := range request.Headers {
|
||||||
taskQueue.Push(header, -float32(header.Number.Uint64()))
|
taskQueue.Push(header, -float32(header.Number.Uint64()))
|
||||||
}
|
}
|
||||||
// Add the peer to the expiry report along the the number of failed requests
|
// Add the peer to the expiry report along the the number of failed requests
|
||||||
expirations := len(request.Hashes)
|
expiries[id] = len(request.Headers)
|
||||||
if expirations < len(request.Headers) {
|
|
||||||
expirations = len(request.Headers)
|
|
||||||
}
|
|
||||||
expiries[id] = expirations
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove the expired requests from the pending pool
|
// Remove the expired requests from the pending pool
|
||||||
|
|
@ -828,14 +838,16 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, taskQ
|
||||||
failure = err
|
failure = err
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
donePool[header.Hash()] = struct{}{}
|
hash := header.Hash()
|
||||||
|
|
||||||
|
donePool[hash] = struct{}{}
|
||||||
q.resultCache[index].Pending--
|
q.resultCache[index].Pending--
|
||||||
useful = true
|
useful = true
|
||||||
accepted++
|
accepted++
|
||||||
|
|
||||||
// Clean up a successful fetch
|
// Clean up a successful fetch
|
||||||
request.Headers[i] = nil
|
request.Headers[i] = nil
|
||||||
delete(taskPool, header.Hash())
|
delete(taskPool, hash)
|
||||||
}
|
}
|
||||||
// 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 {
|
||||||
|
|
@ -860,7 +872,7 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, taskQ
|
||||||
|
|
||||||
// Prepare configures the result cache to allow accepting and caching inbound
|
// Prepare configures the result cache to allow accepting and caching inbound
|
||||||
// fetch results.
|
// fetch results.
|
||||||
func (q *queue) Prepare(offset uint64, mode SyncMode, pivot uint64, head *types.Header) {
|
func (q *queue) Prepare(offset uint64, mode SyncMode) {
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
defer q.lock.Unlock()
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -868,6 +880,5 @@ func (q *queue) Prepare(offset uint64, mode SyncMode, pivot uint64, head *types.
|
||||||
if q.resultOffset < offset {
|
if q.resultOffset < offset {
|
||||||
q.resultOffset = offset
|
q.resultOffset = offset
|
||||||
}
|
}
|
||||||
q.fastSyncPivot = pivot
|
|
||||||
q.mode = mode
|
q.mode = mode
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -304,17 +303,13 @@ func (s *stateSync) loop() error {
|
||||||
s.d.dropPeer(req.peer.id)
|
s.d.dropPeer(req.peer.id)
|
||||||
}
|
}
|
||||||
// Process all the received blobs and check for stale delivery
|
// Process all the received blobs and check for stale delivery
|
||||||
stale, err := s.process(req)
|
if err := s.process(req); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Warn("Node data write error", "err", err)
|
log.Warn("Node data write error", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// The the delivery contains requested data, mark the node idle (otherwise it's a timed out delivery)
|
|
||||||
if !stale {
|
|
||||||
req.peer.SetNodeDataIdle(len(req.response))
|
req.peer.SetNodeDataIdle(len(req.response))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return s.commit(true)
|
return s.commit(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -390,7 +385,7 @@ func (s *stateSync) fillTasks(n int, req *stateReq) {
|
||||||
// process iterates over a batch of delivered state data, injecting each item
|
// process iterates over a batch of delivered state data, injecting each item
|
||||||
// into a running state sync, re-queuing any items that were requested but not
|
// into a running state sync, re-queuing any items that were requested but not
|
||||||
// delivered.
|
// delivered.
|
||||||
func (s *stateSync) process(req *stateReq) (bool, error) {
|
func (s *stateSync) process(req *stateReq) error {
|
||||||
// Collect processing stats and update progress if valid data was received
|
// Collect processing stats and update progress if valid data was received
|
||||||
duplicate, unexpected := 0, 0
|
duplicate, unexpected := 0, 0
|
||||||
|
|
||||||
|
|
@ -401,7 +396,7 @@ func (s *stateSync) process(req *stateReq) (bool, error) {
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
// Iterate over all the delivered data and inject one-by-one into the trie
|
// Iterate over all the delivered data and inject one-by-one into the trie
|
||||||
progress, stale := false, len(req.response) > 0
|
progress := false
|
||||||
|
|
||||||
for _, blob := range req.response {
|
for _, blob := range req.response {
|
||||||
prog, hash, err := s.processNodeData(blob)
|
prog, hash, err := s.processNodeData(blob)
|
||||||
|
|
@ -415,20 +410,12 @@ func (s *stateSync) process(req *stateReq) (bool, error) {
|
||||||
case trie.ErrAlreadyProcessed:
|
case trie.ErrAlreadyProcessed:
|
||||||
duplicate++
|
duplicate++
|
||||||
default:
|
default:
|
||||||
return stale, fmt.Errorf("invalid state node %s: %v", hash.TerminalString(), err)
|
return fmt.Errorf("invalid state node %s: %v", hash.TerminalString(), err)
|
||||||
}
|
}
|
||||||
// If the node delivered a requested item, mark the delivery non-stale
|
|
||||||
if _, ok := req.tasks[hash]; ok {
|
if _, ok := req.tasks[hash]; ok {
|
||||||
delete(req.tasks, hash)
|
delete(req.tasks, hash)
|
||||||
stale = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we're inside the critical section, reset fail counter since we progressed.
|
|
||||||
if progress && atomic.LoadUint32(&s.d.fsPivotFails) > 1 {
|
|
||||||
log.Trace("Fast-sync progressed, resetting fail counter", "previous", atomic.LoadUint32(&s.d.fsPivotFails))
|
|
||||||
atomic.StoreUint32(&s.d.fsPivotFails, 1) // Don't ever reset to 0, as that will unlock the pivot block
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put unfulfilled tasks back into the retry queue
|
// Put unfulfilled tasks back into the retry queue
|
||||||
npeers := s.d.peers.Len()
|
npeers := s.d.peers.Len()
|
||||||
for hash, task := range req.tasks {
|
for hash, task := range req.tasks {
|
||||||
|
|
@ -441,12 +428,12 @@ func (s *stateSync) process(req *stateReq) (bool, error) {
|
||||||
// If we've requested the node too many times already, it may be a malicious
|
// If we've requested the node too many times already, it may be a malicious
|
||||||
// sync where nobody has the right data. Abort.
|
// sync where nobody has the right data. Abort.
|
||||||
if len(task.attempts) >= npeers {
|
if len(task.attempts) >= npeers {
|
||||||
return stale, fmt.Errorf("state node %s failed with all peers (%d tries, %d peers)", hash.TerminalString(), len(task.attempts), npeers)
|
return fmt.Errorf("state node %s failed with all peers (%d tries, %d peers)", hash.TerminalString(), len(task.attempts), npeers)
|
||||||
}
|
}
|
||||||
// Missing item, place into the retry queue.
|
// Missing item, place into the retry queue.
|
||||||
s.tasks[hash] = task
|
s.tasks[hash] = task
|
||||||
}
|
}
|
||||||
return stale, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processNodeData tries to inject a trie node data blob delivered from a remote
|
// processNodeData tries to inject a trie node data blob delivered from a remote
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue