downloader: different sync strategy
This commit is contained in:
parent
68be45e5f8
commit
51b2f1620c
@ -430,7 +430,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
|
||||
}
|
||||
height := latest.Number.Uint64()
|
||||
|
||||
origin, err := d.findAncestor(p, height)
|
||||
origin, err := d.findAncestor(p, latest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -587,41 +587,85 @@ func (d *Downloader) fetchHeight(p *peerConnection) (*types.Header, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// calculateRequestSpan calculates what headers to request from a peer when trying to determine the
|
||||
// common ancestor.
|
||||
// It returns parameters to be used for peer.RequestHeadersByNumber:
|
||||
// from - starting block number
|
||||
// count - number of headers to request
|
||||
// skip - number of headers to skip
|
||||
// and also returns 'max', the last block which is expected to be returned by the remote peers,
|
||||
// given the (from,count,skip)
|
||||
func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) {
|
||||
var (
|
||||
from int
|
||||
count int
|
||||
MaxCount = MaxHeaderFetch / 16
|
||||
)
|
||||
// requestHead is the highest block that we will ask for. If requestHead is not offset,
|
||||
// the highest block that we will get is 16 blocks back from head, which means we
|
||||
// will fetch 14 or 15 blocks unnecessarily in the case the height difference
|
||||
// between us and the peer is 1-2 blocks, which is most common
|
||||
requestHead := int(remoteHeight) - 1
|
||||
if requestHead < 0 {
|
||||
requestHead = 0
|
||||
}
|
||||
// requestBottom is the lowest block we want included in the query
|
||||
// Ideally, we want to include just below own head
|
||||
requestBottom := int(localHeight - 1)
|
||||
if requestBottom < 0 {
|
||||
requestBottom = 0
|
||||
}
|
||||
totalSpan := requestHead - requestBottom
|
||||
span := 1 + totalSpan/MaxCount
|
||||
if span < 2 {
|
||||
span = 2
|
||||
}
|
||||
if span > 16 {
|
||||
span = 16
|
||||
}
|
||||
|
||||
count = 1 + totalSpan/span
|
||||
if count > MaxCount {
|
||||
count = MaxCount
|
||||
}
|
||||
if count < 2 {
|
||||
count = 2
|
||||
}
|
||||
from = requestHead - (count-1)*span
|
||||
if from < 0 {
|
||||
from = 0
|
||||
}
|
||||
max := from + (count-1)*span
|
||||
return int64(from), count, span - 1, uint64(max)
|
||||
}
|
||||
|
||||
// findAncestor tries to locate the common ancestor link of the local chain and
|
||||
// a remote peers blockchain. In the general case when our node was in sync and
|
||||
// on the correct chain, checking the top N links should already get us a match.
|
||||
// In the rare scenario when we ended up on a long reorganisation (i.e. none of
|
||||
// the head links match), we do a binary search to find the common ancestor.
|
||||
func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, error) {
|
||||
func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) (uint64, error) {
|
||||
// Figure out the valid ancestor range to prevent rewrite attacks
|
||||
floor, ceil := int64(-1), d.lightchain.CurrentHeader().Number.Uint64()
|
||||
var (
|
||||
floor = int64(-1)
|
||||
localHeight uint64
|
||||
remoteHeight = remoteHeader.Number.Uint64()
|
||||
)
|
||||
switch d.mode {
|
||||
case FullSync:
|
||||
localHeight = d.blockchain.CurrentBlock().NumberU64()
|
||||
case FastSync:
|
||||
localHeight = d.blockchain.CurrentFastBlock().NumberU64()
|
||||
default:
|
||||
localHeight = d.lightchain.CurrentHeader().Number.Uint64()
|
||||
}
|
||||
p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight)
|
||||
if localHeight >= MaxForkAncestry {
|
||||
floor = int64(localHeight - MaxForkAncestry)
|
||||
}
|
||||
|
||||
if d.mode == FullSync {
|
||||
ceil = d.blockchain.CurrentBlock().NumberU64()
|
||||
} else if d.mode == FastSync {
|
||||
ceil = d.blockchain.CurrentFastBlock().NumberU64()
|
||||
}
|
||||
if ceil >= MaxForkAncestry {
|
||||
floor = int64(ceil - MaxForkAncestry)
|
||||
}
|
||||
p.log.Debug("Looking for common ancestor", "local", ceil, "remote", height)
|
||||
|
||||
// Request the topmost blocks to short circuit binary ancestor lookup
|
||||
head := ceil
|
||||
if head > height {
|
||||
head = height
|
||||
}
|
||||
from := int64(head) - int64(MaxHeaderFetch)
|
||||
if from < 0 {
|
||||
from = 0
|
||||
}
|
||||
// Span out with 15 block gaps into the future to catch bad head reports
|
||||
limit := 2 * MaxHeaderFetch / 16
|
||||
count := 1 + int((int64(ceil)-from)/16)
|
||||
if count > limit {
|
||||
count = limit
|
||||
}
|
||||
go p.peer.RequestHeadersByNumber(uint64(from), count, 15, false)
|
||||
from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight)
|
||||
go p.peer.RequestHeadersByNumber(uint64(from), count, skip, false)
|
||||
|
||||
// Wait for the remote response to the head fetch
|
||||
number, hash := uint64(0), common.Hash{}
|
||||
@ -647,9 +691,10 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
||||
return 0, errEmptyHeaderSet
|
||||
}
|
||||
// Make sure the peer's reply conforms to the request
|
||||
for i := 0; i < len(headers); i++ {
|
||||
if number := headers[i].Number.Int64(); number != from+int64(i)*16 {
|
||||
p.log.Warn("Head headers broke chain ordering", "index", i, "requested", from+int64(i)*16, "received", number)
|
||||
for i, header := range headers {
|
||||
expectNumber := from + int64(i)*int64((skip+1))
|
||||
if number := header.Number.Int64(); number != expectNumber {
|
||||
p.log.Warn("Head headers broke chain ordering", "index", i, "requested", expectNumber, "received", number)
|
||||
return 0, errInvalidChain
|
||||
}
|
||||
}
|
||||
@ -657,20 +702,15 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
||||
finished = true
|
||||
for i := len(headers) - 1; i >= 0; i-- {
|
||||
// Skip any headers that underflow/overflow our requested set
|
||||
if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > ceil {
|
||||
if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > max {
|
||||
continue
|
||||
}
|
||||
// Otherwise check if we already know the header or not
|
||||
h := headers[i].Hash()
|
||||
n := headers[i].Number.Uint64()
|
||||
if (d.mode == FullSync && d.blockchain.HasBlock(h, n)) || (d.mode != FullSync && d.lightchain.HasHeader(h, n)) {
|
||||
if (d.mode == FullSync && d.blockchain.HasBlock(h, n)) ||
|
||||
(d.mode != FullSync && d.lightchain.HasHeader(h, n)) {
|
||||
number, hash = n, h
|
||||
|
||||
// If every header is known, even future ones, the peer straight out lied about its head
|
||||
if number > height && i == limit-1 {
|
||||
p.log.Warn("Lied about chain head", "reported", height, "found", number)
|
||||
return 0, errStallingPeer
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -694,7 +734,7 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
||||
return number, nil
|
||||
}
|
||||
// Ancestor not found, we need to binary search over our chain
|
||||
start, end := uint64(0), head
|
||||
start, end := uint64(0), remoteHeight
|
||||
if floor > 0 {
|
||||
start = uint64(floor)
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@ -1479,3 +1480,78 @@ func (ftp *floodingTestPeer) RequestHeadersByNumber(from uint64, count, skip int
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRemoteHeaderRequestSpan(t *testing.T) {
|
||||
testCases := []struct {
|
||||
remoteHeight uint64
|
||||
localHeight uint64
|
||||
expected []int
|
||||
}{
|
||||
// Remote is way higher. We should ask for the remote head and go backwards
|
||||
{1500, 1000,
|
||||
[]int{1323, 1339, 1355, 1371, 1387, 1403, 1419, 1435, 1451, 1467, 1483, 1499},
|
||||
},
|
||||
{15000, 13006,
|
||||
[]int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999},
|
||||
},
|
||||
//Remote is pretty close to us. We don't have to fetch as many
|
||||
{1200, 1150,
|
||||
[]int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199},
|
||||
},
|
||||
// Remote is equal to us (so on a fork with higher td)
|
||||
// We should get the closest couple of ancestors
|
||||
{1500, 1500,
|
||||
[]int{1497, 1499},
|
||||
},
|
||||
// We're higher than the remote! Odd
|
||||
{1000, 1500,
|
||||
[]int{997, 999},
|
||||
},
|
||||
// Check some weird edgecases that it behaves somewhat rationally
|
||||
{0, 1500,
|
||||
[]int{0, 2},
|
||||
},
|
||||
{6000000, 0,
|
||||
[]int{5999823, 5999839, 5999855, 5999871, 5999887, 5999903, 5999919, 5999935, 5999951, 5999967, 5999983, 5999999},
|
||||
},
|
||||
{0, 0,
|
||||
[]int{0, 2},
|
||||
},
|
||||
}
|
||||
reqs := func(from, count, span int) []int {
|
||||
var r []int
|
||||
num := from
|
||||
for len(r) < count {
|
||||
r = append(r, num)
|
||||
num += span + 1
|
||||
}
|
||||
return r
|
||||
}
|
||||
for i, tt := range testCases {
|
||||
from, count, span, max := calculateRequestSpan(tt.remoteHeight, tt.localHeight)
|
||||
data := reqs(int(from), count, span)
|
||||
|
||||
if max != uint64(data[len(data)-1]) {
|
||||
t.Errorf("test %d: wrong last value %d != %d", i, data[len(data)-1], max)
|
||||
}
|
||||
failed := false
|
||||
if len(data) != len(tt.expected) {
|
||||
failed = true
|
||||
t.Errorf("test %d: length wrong, expected %d got %d", i, len(tt.expected), len(data))
|
||||
} else {
|
||||
for j, n := range data {
|
||||
if n != tt.expected[j] {
|
||||
failed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
res := strings.Replace(fmt.Sprint(data), " ", ",", -1)
|
||||
exp := strings.Replace(fmt.Sprint(tt.expected), " ", ",", -1)
|
||||
fmt.Printf("got: %v\n", res)
|
||||
fmt.Printf("exp: %v\n", exp)
|
||||
t.Errorf("test %d: wrong values", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user