downloader: refactor common ancestor algos and logic

This breaks the common ancestor search functions
into SpanSearch and BinarySearch functions, ensuring
that a negotiated common ancestor is never
above our local head.

It makes switch cases conditional on sync mode
use LightSync explicitly, which makes the code more
readable (instead of relying on LightSync as tacit
default).

Signed-off-by: meows <b5c6@protonmail.com>
This commit is contained in:
meows 2020-05-11 15:28:44 -05:00
parent 069a7e1f8a
commit 12f6c7642c
No known key found for this signature in database
GPG key ID: 5786AEA5C8D5A520

View file

@ -81,6 +81,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")
@ -711,8 +712,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 +744,36 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
}
}
// Only attempt span search if local head is "close" to reported remote
if remoteHeight-localHeight < uint64(MaxHeaderFetch) {
a, err := d.findAncestorSpanSearch(p, remoteHeight, localHeight, floor)
if err == nil {
if a > localHeight {
a = localHeight
}
return a, nil
} else 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 +824,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 +853,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 +905,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