-
Notifications
You must be signed in to change notification settings - Fork 20.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor downloader common ancestor span/binary search fns #21063
Closed
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
12f6c76
downloader: refactor common ancestor algos and logic
meowsbits db63f1e
downloader: only attempt common ancestor negotiation if > 0 local head
meowsbits ca59775
downloader: attempt span search if <= (+or equal) to maxheaderfetch
meowsbits 9a9d2aa
downloader: use Go conventional if (remove 'else')
meowsbits 58dd36b
downloader: use absolute value of remote vs. local height
meowsbits 4122416
downloader: handle light sync case as findAncestor condition
meowsbits File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ package downloader | |
import ( | ||
"errors" | ||
"fmt" | ||
"math" | ||
"math/big" | ||
"sync" | ||
"sync/atomic" | ||
|
@@ -81,6 +82,7 @@ var ( | |
errTimeout = errors.New("timeout") | ||
errEmptyHeaderSet = errors.New("empty header set by peer") | ||
errPeersUnavailable = errors.New("no peers available or all tried for download") | ||
errNoAncestor = errors.New("no common ancestor") | ||
errInvalidAncestor = errors.New("retrieved ancestor is invalid") | ||
errInvalidChain = errors.New("retrieved hash chain is invalid") | ||
errInvalidBody = errors.New("retrieved block body is invalid") | ||
|
@@ -440,10 +442,16 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I | |
} | ||
height := latest.Number.Uint64() | ||
|
||
origin, err := d.findAncestor(p, latest) | ||
if err != nil { | ||
return err | ||
var origin = uint64(0) | ||
|
||
if (d.mode != LightSync && d.blockchain.CurrentHeader().Number.Uint64() > 0) || | ||
d.lightchain.CurrentHeader().Number.Uint64() > 0 { | ||
origin, err = d.findAncestor(p, latest) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
d.syncStatsLock.Lock() | ||
if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin { | ||
d.syncStatsChainOrigin = origin | ||
|
@@ -711,8 +719,10 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) | |
localHeight = d.blockchain.CurrentBlock().NumberU64() | ||
case FastSync: | ||
localHeight = d.blockchain.CurrentFastBlock().NumberU64() | ||
default: | ||
case LightSync: | ||
localHeight = d.lightchain.CurrentHeader().Number.Uint64() | ||
default: | ||
log.Crit("unknown sync mode", "mode", d.mode) | ||
} | ||
p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight) | ||
|
||
|
@@ -741,9 +751,37 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) | |
} | ||
} | ||
|
||
// Only attempt span search if local head is "close" to reported remote | ||
if math.Abs(float64(remoteHeight-localHeight)) <= float64(MaxHeaderFetch) { | ||
a, err := d.findAncestorSpanSearch(p, remoteHeight, localHeight, floor) | ||
if err == nil { | ||
if a > localHeight { | ||
a = localHeight | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, what happens here? The found ancestor is above our local height? |
||
} | ||
return a, nil | ||
} | ||
if err != errNoAncestor { | ||
return 0, err | ||
} | ||
} | ||
|
||
// Ancestor not found, we need to binary search over our chain | ||
a, err := d.findAncestorBinarySearch(p, remoteHeight, floor) | ||
if err != nil { | ||
return 0, err | ||
} | ||
if a > localHeight { | ||
a = localHeight | ||
} | ||
return a, nil | ||
} | ||
|
||
// findAncestorSpanSearch looks for a common ancestor by requesting a spaced set of headers between ours and theirs. | ||
func (d *Downloader) findAncestorSpanSearch(p *peerConnection, remoteHeight, localHeight uint64, floor int64) (uint64, error) { | ||
from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight) | ||
|
||
p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip) | ||
|
||
go p.peer.RequestHeadersByNumber(uint64(from), count, skip, false) | ||
|
||
// Wait for the remote response to the head fetch | ||
|
@@ -794,8 +832,10 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) | |
known = d.blockchain.HasBlock(h, n) | ||
case FastSync: | ||
known = d.blockchain.HasFastBlock(h, n) | ||
default: | ||
case LightSync: | ||
known = d.lightchain.HasHeader(h, n) | ||
default: | ||
log.Crit("unknown sync mode", "mode", d.mode) | ||
} | ||
if known { | ||
number, hash = n, h | ||
|
@@ -821,13 +861,19 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) | |
p.log.Debug("Found common ancestor", "number", number, "hash", hash) | ||
return number, nil | ||
} | ||
// Ancestor not found, we need to binary search over our chain | ||
return 0, errNoAncestor | ||
} | ||
|
||
// findAncestorBinarySearch negotiates a binary search using their reported chain. | ||
func (d *Downloader) findAncestorBinarySearch(p *peerConnection, remoteHeight uint64, floor int64) (uint64, error) { | ||
start, end := uint64(0), remoteHeight | ||
if floor > 0 { | ||
start = uint64(floor) | ||
} | ||
p.log.Trace("Binary searching for common ancestor", "start", start, "end", end) | ||
|
||
// Wait for the remote response to the head fetch | ||
hash := common.Hash{} | ||
for start+1 < end { | ||
// Split our chain interval in two, and request the hash to cross check | ||
check := (start + end) / 2 | ||
|
@@ -867,8 +913,10 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) | |
known = d.blockchain.HasBlock(h, n) | ||
case FastSync: | ||
known = d.blockchain.HasFastBlock(h, n) | ||
default: | ||
case LightSync: | ||
known = d.lightchain.HasHeader(h, n) | ||
default: | ||
log.Crit("unknown sync mode", "mode", d.mode) | ||
} | ||
if !known { | ||
end = check | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've added these in three places, I can't say I think it's a worthwhile improvement, really. Mind reverting that and make the diff smaller?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I like this is that it removes what I would call "incidental" logic, where a reader/developer has to know and use an implicit context; ie. that there are 3 and only 3 downloader sync modes and what they are. And then read between the lines to understand that the
d.lightchain
assignment happens tacitly in the case of the downloader'sLightSync
configuration (despite thatLightSync
isn't the application default). IMO It's taking advantage of a terse logic pattern at the price of readability, and I would prefer explicit readability at the cost of a few lines.Hypothetically, with the proposed change, if a developer wants to try adding a 4th downloader sync mode on a given weekend, they won't have to spend any time working to understand a mysterious bug that will turn out to be the switch statement not complaining about an unhandled case.
Of course, you're the owner here, and I'm willing to concede the change with no hard feelings, but those are my two cents.