mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Delete eth/downloader directory
This commit is contained in:
parent
6df389dded
commit
d2a63aae86
21 changed files with 0 additions and 9277 deletions
|
|
@ -1,166 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// DownloaderAPI provides an API which gives information about the current synchronisation status.
|
||||
// It offers only methods that operates on data that can be available to anyone without security risks.
|
||||
type DownloaderAPI struct {
|
||||
d *Downloader
|
||||
mux *event.TypeMux
|
||||
installSyncSubscription chan chan interface{}
|
||||
uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest
|
||||
}
|
||||
|
||||
// NewDownloaderAPI create a new DownloaderAPI. The API has an internal event loop that
|
||||
// listens for events from the downloader through the global event mux. In case it receives one of
|
||||
// these events it broadcasts it to all syncing subscriptions that are installed through the
|
||||
// installSyncSubscription channel.
|
||||
func NewDownloaderAPI(d *Downloader, m *event.TypeMux) *DownloaderAPI {
|
||||
api := &DownloaderAPI{
|
||||
d: d,
|
||||
mux: m,
|
||||
installSyncSubscription: make(chan chan interface{}),
|
||||
uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest),
|
||||
}
|
||||
|
||||
go api.eventLoop()
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
// eventLoop runs a loop until the event mux closes. It will install and uninstall new
|
||||
// sync subscriptions and broadcasts sync status updates to the installed sync subscriptions.
|
||||
func (api *DownloaderAPI) eventLoop() {
|
||||
var (
|
||||
sub = api.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{})
|
||||
syncSubscriptions = make(map[chan interface{}]struct{})
|
||||
)
|
||||
|
||||
for {
|
||||
select {
|
||||
case i := <-api.installSyncSubscription:
|
||||
syncSubscriptions[i] = struct{}{}
|
||||
case u := <-api.uninstallSyncSubscription:
|
||||
delete(syncSubscriptions, u.c)
|
||||
close(u.uninstalled)
|
||||
case event := <-sub.Chan():
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var notification interface{}
|
||||
switch event.Data.(type) {
|
||||
case StartEvent:
|
||||
notification = &SyncingResult{
|
||||
Syncing: true,
|
||||
Status: api.d.Progress(),
|
||||
}
|
||||
case DoneEvent, FailedEvent:
|
||||
notification = false
|
||||
}
|
||||
// broadcast
|
||||
for c := range syncSubscriptions {
|
||||
c <- notification
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Syncing provides information when this nodes starts synchronising with the Ethereum network and when it's finished.
|
||||
func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) {
|
||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||
if !supported {
|
||||
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
|
||||
}
|
||||
|
||||
rpcSub := notifier.CreateSubscription()
|
||||
|
||||
go func() {
|
||||
statuses := make(chan interface{})
|
||||
sub := api.SubscribeSyncStatus(statuses)
|
||||
|
||||
for {
|
||||
select {
|
||||
case status := <-statuses:
|
||||
notifier.Notify(rpcSub.ID, status)
|
||||
case <-rpcSub.Err():
|
||||
sub.Unsubscribe()
|
||||
return
|
||||
case <-notifier.Closed():
|
||||
sub.Unsubscribe()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return rpcSub, nil
|
||||
}
|
||||
|
||||
// SyncingResult provides information about the current synchronisation status for this node.
|
||||
type SyncingResult struct {
|
||||
Syncing bool `json:"syncing"`
|
||||
Status ethereum.SyncProgress `json:"status"`
|
||||
}
|
||||
|
||||
// uninstallSyncSubscriptionRequest uninstalls a syncing subscription in the API event loop.
|
||||
type uninstallSyncSubscriptionRequest struct {
|
||||
c chan interface{}
|
||||
uninstalled chan interface{}
|
||||
}
|
||||
|
||||
// SyncStatusSubscription represents a syncing subscription.
|
||||
type SyncStatusSubscription struct {
|
||||
api *DownloaderAPI // register subscription in event loop of this api instance
|
||||
c chan interface{} // channel where events are broadcasted to
|
||||
unsubOnce sync.Once // make sure unsubscribe logic is executed once
|
||||
}
|
||||
|
||||
// Unsubscribe uninstalls the subscription from the DownloadAPI event loop.
|
||||
// The status channel that was passed to subscribeSyncStatus isn't used anymore
|
||||
// after this method returns.
|
||||
func (s *SyncStatusSubscription) Unsubscribe() {
|
||||
s.unsubOnce.Do(func() {
|
||||
req := uninstallSyncSubscriptionRequest{s.c, make(chan interface{})}
|
||||
s.api.uninstallSyncSubscription <- &req
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.c:
|
||||
// drop new status events until uninstall confirmation
|
||||
continue
|
||||
case <-req.uninstalled:
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// SubscribeSyncStatus creates a subscription that will broadcast new synchronisation updates.
|
||||
// The given channel must receive interface values, the result can either.
|
||||
func (api *DownloaderAPI) SubscribeSyncStatus(status chan interface{}) *SyncStatusSubscription {
|
||||
api.installSyncSubscription <- status
|
||||
return &SyncStatusSubscription{api: api, c: status}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// BeaconDevSync is a development helper to test synchronization by providing
|
||||
// a block hash instead of header to run the beacon sync against.
|
||||
//
|
||||
// The method will reach out to the network to retrieve the header of the sync
|
||||
// target instead of receiving it from the consensus node.
|
||||
//
|
||||
// Note, this must not be used in live code. If the forkchcoice endpoint where
|
||||
// to use this instead of giving us the payload first, then essentially nobody
|
||||
// in the network would have the block yet that we'd attempt to retrieve.
|
||||
func (d *Downloader) BeaconDevSync(mode SyncMode, hash common.Hash, stop chan struct{}) error {
|
||||
// Be very loud that this code should not be used in a live node
|
||||
log.Warn("----------------------------------")
|
||||
log.Warn("Beacon syncing with hash as target", "hash", hash)
|
||||
log.Warn("This is unhealthy for a live node!")
|
||||
log.Warn("----------------------------------")
|
||||
|
||||
log.Info("Waiting for peers to retrieve sync target")
|
||||
for {
|
||||
// If the node is going down, unblock
|
||||
select {
|
||||
case <-stop:
|
||||
return errors.New("stop requested")
|
||||
default:
|
||||
}
|
||||
// Pick a random peer to sync from and keep retrying if none are yet
|
||||
// available due to fresh startup
|
||||
d.peers.lock.RLock()
|
||||
var peer *peerConnection
|
||||
for _, peer = range d.peers.peers {
|
||||
break
|
||||
}
|
||||
d.peers.lock.RUnlock()
|
||||
|
||||
if peer == nil {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
// Found a peer, attempt to retrieve the header whilst blocking and
|
||||
// retry if it fails for whatever reason
|
||||
log.Info("Attempting to retrieve sync target", "peer", peer.id)
|
||||
headers, metas, err := d.fetchHeadersByHash(peer, hash, 1, 0, false)
|
||||
if err != nil || len(headers) != 1 {
|
||||
log.Warn("Failed to fetch sync target", "headers", len(headers), "err", err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
// Head header retrieved, if the hash matches, start the actual sync
|
||||
if metas[0] != hash {
|
||||
log.Error("Received invalid sync target", "want", hash, "have", metas[0])
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
return d.BeaconSync(mode, headers[0], headers[0])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,389 +0,0 @@
|
|||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// beaconBackfiller is the chain and state backfilling that can be commenced once
|
||||
// the skeleton syncer has successfully reverse downloaded all the headers up to
|
||||
// the genesis block or an existing header in the database. Its operation is fully
|
||||
// directed by the skeleton sync's head/tail events.
|
||||
type beaconBackfiller struct {
|
||||
downloader *Downloader // Downloader to direct via this callback implementation
|
||||
syncMode SyncMode // Sync mode to use for backfilling the skeleton chains
|
||||
success func() // Callback to run on successful sync cycle completion
|
||||
filling bool // Flag whether the downloader is backfilling or not
|
||||
filled *types.Header // Last header filled by the last terminated sync loop
|
||||
started chan struct{} // Notification channel whether the downloader inited
|
||||
lock sync.Mutex // Mutex protecting the sync lock
|
||||
}
|
||||
|
||||
// newBeaconBackfiller is a helper method to create the backfiller.
|
||||
func newBeaconBackfiller(dl *Downloader, success func()) backfiller {
|
||||
return &beaconBackfiller{
|
||||
downloader: dl,
|
||||
success: success,
|
||||
}
|
||||
}
|
||||
|
||||
// suspend cancels any background downloader threads and returns the last header
|
||||
// that has been successfully backfilled.
|
||||
func (b *beaconBackfiller) suspend() *types.Header {
|
||||
// If no filling is running, don't waste cycles
|
||||
b.lock.Lock()
|
||||
filling := b.filling
|
||||
filled := b.filled
|
||||
started := b.started
|
||||
b.lock.Unlock()
|
||||
|
||||
if !filling {
|
||||
return filled // Return the filled header on the previous sync completion
|
||||
}
|
||||
// A previous filling should be running, though it may happen that it hasn't
|
||||
// yet started (being done on a new goroutine). Many concurrent beacon head
|
||||
// announcements can lead to sync start/stop thrashing. In that case we need
|
||||
// to wait for initialization before we can safely cancel it. It is safe to
|
||||
// read this channel multiple times, it gets closed on startup.
|
||||
<-started
|
||||
|
||||
// Now that we're sure the downloader successfully started up, we can cancel
|
||||
// it safely without running the risk of data races.
|
||||
b.downloader.Cancel()
|
||||
|
||||
// Sync cycle was just terminated, retrieve and return the last filled header.
|
||||
// Can't use `filled` as that contains a stale value from before cancellation.
|
||||
return b.downloader.blockchain.CurrentSnapBlock()
|
||||
}
|
||||
|
||||
// resume starts the downloader threads for backfilling state and chain data.
|
||||
func (b *beaconBackfiller) resume() {
|
||||
b.lock.Lock()
|
||||
if b.filling {
|
||||
// If a previous filling cycle is still running, just ignore this start
|
||||
// request. // TODO(karalabe): We should make this channel driven
|
||||
b.lock.Unlock()
|
||||
return
|
||||
}
|
||||
b.filling = true
|
||||
b.filled = nil
|
||||
b.started = make(chan struct{})
|
||||
mode := b.syncMode
|
||||
b.lock.Unlock()
|
||||
|
||||
// Start the backfilling on its own thread since the downloader does not have
|
||||
// its own lifecycle runloop.
|
||||
go func() {
|
||||
// Set the backfiller to non-filling when download completes
|
||||
defer func() {
|
||||
b.lock.Lock()
|
||||
b.filling = false
|
||||
b.filled = b.downloader.blockchain.CurrentSnapBlock()
|
||||
b.lock.Unlock()
|
||||
}()
|
||||
// If the downloader fails, report an error as in beacon chain mode there
|
||||
// should be no errors as long as the chain we're syncing to is valid.
|
||||
if err := b.downloader.synchronise("", common.Hash{}, nil, nil, mode, true, b.started); err != nil {
|
||||
log.Error("Beacon backfilling failed", "err", err)
|
||||
return
|
||||
}
|
||||
// Synchronization succeeded. Since this happens async, notify the outer
|
||||
// context to disable snap syncing and enable transaction propagation.
|
||||
if b.success != nil {
|
||||
b.success()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// setMode updates the sync mode from the current one to the requested one. If
|
||||
// there's an active sync in progress, it will be cancelled and restarted.
|
||||
func (b *beaconBackfiller) setMode(mode SyncMode) {
|
||||
// Update the old sync mode and track if it was changed
|
||||
b.lock.Lock()
|
||||
updated := b.syncMode != mode
|
||||
filling := b.filling
|
||||
b.syncMode = mode
|
||||
b.lock.Unlock()
|
||||
|
||||
// If the sync mode was changed mid-sync, restart. This should never ever
|
||||
// really happen, we just handle it to detect programming errors.
|
||||
if !updated || !filling {
|
||||
return
|
||||
}
|
||||
log.Error("Downloader sync mode changed mid-run", "old", mode.String(), "new", mode.String())
|
||||
b.suspend()
|
||||
b.resume()
|
||||
}
|
||||
|
||||
// SetBadBlockCallback sets the callback to run when a bad block is hit by the
|
||||
// block processor. This method is not thread safe and should be set only once
|
||||
// on startup before system events are fired.
|
||||
func (d *Downloader) SetBadBlockCallback(onBadBlock badBlockFn) {
|
||||
d.badBlock = onBadBlock
|
||||
}
|
||||
|
||||
// BeaconSync is the post-merge version of the chain synchronization, where the
|
||||
// chain is not downloaded from genesis onward, rather from trusted head announces
|
||||
// backwards.
|
||||
//
|
||||
// Internally backfilling and state sync is done the same way, but the header
|
||||
// retrieval and scheduling is replaced.
|
||||
func (d *Downloader) BeaconSync(mode SyncMode, head *types.Header, final *types.Header) error {
|
||||
return d.beaconSync(mode, head, final, true)
|
||||
}
|
||||
|
||||
// BeaconExtend is an optimistic version of BeaconSync, where an attempt is made
|
||||
// to extend the current beacon chain with a new header, but in case of a mismatch,
|
||||
// the old sync will not be terminated and reorged, rather the new head is dropped.
|
||||
//
|
||||
// This is useful if a beacon client is feeding us large chunks of payloads to run,
|
||||
// but is not setting the head after each.
|
||||
func (d *Downloader) BeaconExtend(mode SyncMode, head *types.Header) error {
|
||||
return d.beaconSync(mode, head, nil, false)
|
||||
}
|
||||
|
||||
// beaconSync is the post-merge version of the chain synchronization, where the
|
||||
// chain is not downloaded from genesis onward, rather from trusted head announces
|
||||
// backwards.
|
||||
//
|
||||
// Internally backfilling and state sync is done the same way, but the header
|
||||
// retrieval and scheduling is replaced.
|
||||
func (d *Downloader) beaconSync(mode SyncMode, head *types.Header, final *types.Header, force bool) error {
|
||||
// When the downloader starts a sync cycle, it needs to be aware of the sync
|
||||
// mode to use (full, snap). To keep the skeleton chain oblivious, inject the
|
||||
// mode into the backfiller directly.
|
||||
//
|
||||
// Super crazy dangerous type cast. Should be fine (TM), we're only using a
|
||||
// different backfiller implementation for skeleton tests.
|
||||
d.skeleton.filler.(*beaconBackfiller).setMode(mode)
|
||||
|
||||
// Signal the skeleton sync to switch to a new head, however it wants
|
||||
if err := d.skeleton.Sync(head, final, force); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findBeaconAncestor tries to locate the common ancestor link of the local chain
|
||||
// and the beacon chain just requested. 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 ancestor.
|
||||
func (d *Downloader) findBeaconAncestor() (uint64, error) {
|
||||
// Figure out the current local head position
|
||||
var chainHead *types.Header
|
||||
|
||||
switch d.getMode() {
|
||||
case FullSync:
|
||||
chainHead = d.blockchain.CurrentBlock()
|
||||
case SnapSync:
|
||||
chainHead = d.blockchain.CurrentSnapBlock()
|
||||
default:
|
||||
chainHead = d.lightchain.CurrentHeader()
|
||||
}
|
||||
number := chainHead.Number.Uint64()
|
||||
|
||||
// Retrieve the skeleton bounds and ensure they are linked to the local chain
|
||||
beaconHead, beaconTail, _, err := d.skeleton.Bounds()
|
||||
if err != nil {
|
||||
// This is a programming error. The chain backfiller was called with an
|
||||
// invalid beacon sync state. Ideally we would panic here, but erroring
|
||||
// gives us at least a remote chance to recover. It's still a big fault!
|
||||
log.Error("Failed to retrieve beacon bounds", "err", err)
|
||||
return 0, err
|
||||
}
|
||||
var linked bool
|
||||
switch d.getMode() {
|
||||
case FullSync:
|
||||
linked = d.blockchain.HasBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
|
||||
case SnapSync:
|
||||
linked = d.blockchain.HasFastBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
|
||||
default:
|
||||
linked = d.blockchain.HasHeader(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
|
||||
}
|
||||
if !linked {
|
||||
// This is a programming error. The chain backfiller was called with a
|
||||
// tail that's not linked to the local chain. Whilst this should never
|
||||
// happen, there might be some weirdnesses if beacon sync backfilling
|
||||
// races with the user (or beacon client) calling setHead. Whilst panic
|
||||
// would be the ideal thing to do, it is safer long term to attempt a
|
||||
// recovery and fix any noticed issue after the fact.
|
||||
log.Error("Beacon sync linkup unavailable", "number", beaconTail.Number.Uint64()-1, "hash", beaconTail.ParentHash)
|
||||
return 0, fmt.Errorf("beacon linkup unavailable locally: %d [%x]", beaconTail.Number.Uint64()-1, beaconTail.ParentHash)
|
||||
}
|
||||
// Binary search to find the ancestor
|
||||
start, end := beaconTail.Number.Uint64()-1, number
|
||||
if number := beaconHead.Number.Uint64(); end > number {
|
||||
// This shouldn't really happen in a healthy network, but if the consensus
|
||||
// clients feeds us a shorter chain as the canonical, we should not attempt
|
||||
// to access non-existent skeleton items.
|
||||
log.Warn("Beacon head lower than local chain", "beacon", number, "local", end)
|
||||
end = number
|
||||
}
|
||||
for start+1 < end {
|
||||
// Split our chain interval in two, and request the hash to cross check
|
||||
check := (start + end) / 2
|
||||
|
||||
h := d.skeleton.Header(check)
|
||||
n := h.Number.Uint64()
|
||||
|
||||
var known bool
|
||||
switch d.getMode() {
|
||||
case FullSync:
|
||||
known = d.blockchain.HasBlock(h.Hash(), n)
|
||||
case SnapSync:
|
||||
known = d.blockchain.HasFastBlock(h.Hash(), n)
|
||||
default:
|
||||
known = d.lightchain.HasHeader(h.Hash(), n)
|
||||
}
|
||||
if !known {
|
||||
end = check
|
||||
continue
|
||||
}
|
||||
start = check
|
||||
}
|
||||
return start, nil
|
||||
}
|
||||
|
||||
// fetchBeaconHeaders feeds skeleton headers to the downloader queue for scheduling
|
||||
// until sync errors or is finished.
|
||||
func (d *Downloader) fetchBeaconHeaders(from uint64) error {
|
||||
var head *types.Header
|
||||
_, tail, _, err := d.skeleton.Bounds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// A part of headers are not in the skeleton space, try to resolve
|
||||
// them from the local chain. Note the range should be very short
|
||||
// and it should only happen when there are less than 64 post-merge
|
||||
// blocks in the network.
|
||||
var localHeaders []*types.Header
|
||||
if from < tail.Number.Uint64() {
|
||||
count := tail.Number.Uint64() - from
|
||||
if count > uint64(fsMinFullBlocks) {
|
||||
return fmt.Errorf("invalid origin (%d) of beacon sync (%d)", from, tail.Number)
|
||||
}
|
||||
localHeaders = d.readHeaderRange(tail, int(count))
|
||||
log.Warn("Retrieved beacon headers from local", "from", from, "count", count)
|
||||
}
|
||||
for {
|
||||
// Some beacon headers might have appeared since the last cycle, make
|
||||
// sure we're always syncing to all available ones
|
||||
head, _, _, err = d.skeleton.Bounds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If the pivot became stale (older than 2*64-8 (bit of wiggle room)),
|
||||
// move it ahead to HEAD-64
|
||||
d.pivotLock.Lock()
|
||||
if d.pivotHeader != nil {
|
||||
if head.Number.Uint64() > d.pivotHeader.Number.Uint64()+2*uint64(fsMinFullBlocks)-8 {
|
||||
// Retrieve the next pivot header, either from skeleton chain
|
||||
// or the filled chain
|
||||
number := head.Number.Uint64() - uint64(fsMinFullBlocks)
|
||||
|
||||
log.Warn("Pivot seemingly stale, moving", "old", d.pivotHeader.Number, "new", number)
|
||||
if d.pivotHeader = d.skeleton.Header(number); d.pivotHeader == nil {
|
||||
if number < tail.Number.Uint64() {
|
||||
dist := tail.Number.Uint64() - number
|
||||
if len(localHeaders) >= int(dist) {
|
||||
d.pivotHeader = localHeaders[dist-1]
|
||||
log.Warn("Retrieved pivot header from local", "number", d.pivotHeader.Number, "hash", d.pivotHeader.Hash(), "latest", head.Number, "oldest", tail.Number)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Print an error log and return directly in case the pivot header
|
||||
// is still not found. It means the skeleton chain is not linked
|
||||
// correctly with local chain.
|
||||
if d.pivotHeader == nil {
|
||||
log.Error("Pivot header is not found", "number", number)
|
||||
d.pivotLock.Unlock()
|
||||
return errNoPivotHeader
|
||||
}
|
||||
// Write out the pivot into the database so a rollback beyond
|
||||
// it will reenable snap sync and update the state root that
|
||||
// the state syncer will be downloading
|
||||
rawdb.WriteLastPivotNumber(d.stateDB, d.pivotHeader.Number.Uint64())
|
||||
}
|
||||
}
|
||||
d.pivotLock.Unlock()
|
||||
|
||||
// Retrieve a batch of headers and feed it to the header processor
|
||||
var (
|
||||
headers = make([]*types.Header, 0, maxHeadersProcess)
|
||||
hashes = make([]common.Hash, 0, maxHeadersProcess)
|
||||
)
|
||||
for i := 0; i < maxHeadersProcess && from <= head.Number.Uint64(); i++ {
|
||||
header := d.skeleton.Header(from)
|
||||
|
||||
// The header is not found in skeleton space, try to find it in local chain.
|
||||
if header == nil && from < tail.Number.Uint64() {
|
||||
dist := tail.Number.Uint64() - from
|
||||
if len(localHeaders) >= int(dist) {
|
||||
header = localHeaders[dist-1]
|
||||
}
|
||||
}
|
||||
// The header is still missing, the beacon sync is corrupted and bail out
|
||||
// the error here.
|
||||
if header == nil {
|
||||
return fmt.Errorf("missing beacon header %d", from)
|
||||
}
|
||||
headers = append(headers, header)
|
||||
hashes = append(hashes, headers[i].Hash())
|
||||
from++
|
||||
}
|
||||
if len(headers) > 0 {
|
||||
log.Trace("Scheduling new beacon headers", "count", len(headers), "from", from-uint64(len(headers)))
|
||||
select {
|
||||
case d.headerProcCh <- &headerTask{
|
||||
headers: headers,
|
||||
hashes: hashes,
|
||||
}:
|
||||
case <-d.cancelCh:
|
||||
return errCanceled
|
||||
}
|
||||
}
|
||||
// If we still have headers to import, loop and keep pushing them
|
||||
if from <= head.Number.Uint64() {
|
||||
continue
|
||||
}
|
||||
// If the pivot block is committed, signal header sync termination
|
||||
if d.committed.Load() {
|
||||
select {
|
||||
case d.headerProcCh <- nil:
|
||||
return nil
|
||||
case <-d.cancelCh:
|
||||
return errCanceled
|
||||
}
|
||||
}
|
||||
// State sync still going, wait a bit for new headers and retry
|
||||
log.Trace("Pivot not yet committed, waiting...")
|
||||
select {
|
||||
case <-time.After(fsHeaderContCheck):
|
||||
case <-d.cancelCh:
|
||||
return errCanceled
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import "github.com/ethereum/go-ethereum/core/types"
|
||||
|
||||
type DoneEvent struct {
|
||||
Latest *types.Header
|
||||
}
|
||||
type StartEvent struct{}
|
||||
type FailedEvent struct{ Err error }
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
)
|
||||
|
||||
// fetchHeadersByHash is a blocking version of Peer.RequestHeadersByHash which
|
||||
// handles all the cancellation, interruption and timeout mechanisms of a data
|
||||
// retrieval to allow blocking API calls.
|
||||
func (d *Downloader) fetchHeadersByHash(p *peerConnection, hash common.Hash, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
|
||||
// Create the response sink and send the network request
|
||||
start := time.Now()
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
req, err := p.peer.RequestHeadersByHash(hash, amount, skip, reverse, resCh)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer req.Close()
|
||||
|
||||
// Wait until the response arrives, the request is cancelled or times out
|
||||
ttl := d.peers.rates.TargetTimeout()
|
||||
|
||||
timeoutTimer := time.NewTimer(ttl)
|
||||
defer timeoutTimer.Stop()
|
||||
|
||||
select {
|
||||
case <-d.cancelCh:
|
||||
return nil, nil, errCanceled
|
||||
|
||||
case <-timeoutTimer.C:
|
||||
// Header retrieval timed out, update the metrics
|
||||
p.log.Debug("Header request timed out", "elapsed", ttl)
|
||||
headerTimeoutMeter.Mark(1)
|
||||
|
||||
return nil, nil, errTimeout
|
||||
|
||||
case res := <-resCh:
|
||||
// Headers successfully retrieved, update the metrics
|
||||
headerReqTimer.Update(time.Since(start))
|
||||
headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersRequest))))
|
||||
|
||||
// Don't reject the packet even if it turns out to be bad, downloader will
|
||||
// disconnect the peer on its own terms. Simply delivery the headers to
|
||||
// be processed by the caller
|
||||
res.Done <- nil
|
||||
|
||||
return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil
|
||||
}
|
||||
}
|
||||
|
||||
// fetchHeadersByNumber is a blocking version of Peer.RequestHeadersByNumber which
|
||||
// handles all the cancellation, interruption and timeout mechanisms of a data
|
||||
// retrieval to allow blocking API calls.
|
||||
func (d *Downloader) fetchHeadersByNumber(p *peerConnection, number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) {
|
||||
// Create the response sink and send the network request
|
||||
start := time.Now()
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
req, err := p.peer.RequestHeadersByNumber(number, amount, skip, reverse, resCh)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer req.Close()
|
||||
|
||||
// Wait until the response arrives, the request is cancelled or times out
|
||||
ttl := d.peers.rates.TargetTimeout()
|
||||
|
||||
timeoutTimer := time.NewTimer(ttl)
|
||||
defer timeoutTimer.Stop()
|
||||
|
||||
select {
|
||||
case <-d.cancelCh:
|
||||
return nil, nil, errCanceled
|
||||
|
||||
case <-timeoutTimer.C:
|
||||
// Header retrieval timed out, update the metrics
|
||||
p.log.Debug("Header request timed out", "elapsed", ttl)
|
||||
headerTimeoutMeter.Mark(1)
|
||||
|
||||
return nil, nil, errTimeout
|
||||
|
||||
case res := <-resCh:
|
||||
// Headers successfully retrieved, update the metrics
|
||||
headerReqTimer.Update(time.Since(start))
|
||||
headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersRequest))))
|
||||
|
||||
// Don't reject the packet even if it turns out to be bad, downloader will
|
||||
// disconnect the peer on its own terms. Simply delivery the headers to
|
||||
// be processed by the caller
|
||||
res.Done <- nil
|
||||
|
||||
return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil
|
||||
}
|
||||
}
|
||||
|
|
@ -1,380 +0,0 @@
|
|||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/prque"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// timeoutGracePeriod is the amount of time to allow for a peer to deliver a
|
||||
// response to a locally already timed out request. Timeouts are not penalized
|
||||
// as a peer might be temporarily overloaded, however, they still must reply
|
||||
// to each request. Failing to do so is considered a protocol violation.
|
||||
var timeoutGracePeriod = 2 * time.Minute
|
||||
|
||||
// typedQueue is an interface defining the adaptor needed to translate the type
|
||||
// specific downloader/queue schedulers into the type-agnostic general concurrent
|
||||
// fetcher algorithm calls.
|
||||
type typedQueue interface {
|
||||
// waker returns a notification channel that gets pinged in case more fetches
|
||||
// have been queued up, so the fetcher might assign it to idle peers.
|
||||
waker() chan bool
|
||||
|
||||
// pending returns the number of wrapped items that are currently queued for
|
||||
// fetching by the concurrent downloader.
|
||||
pending() int
|
||||
|
||||
// capacity is responsible for calculating how many items of the abstracted
|
||||
// type a particular peer is estimated to be able to retrieve within the
|
||||
// allotted round trip time.
|
||||
capacity(peer *peerConnection, rtt time.Duration) int
|
||||
|
||||
// updateCapacity is responsible for updating how many items of the abstracted
|
||||
// type a particular peer is estimated to be able to retrieve in a unit time.
|
||||
updateCapacity(peer *peerConnection, items int, elapsed time.Duration)
|
||||
|
||||
// reserve is responsible for allocating a requested number of pending items
|
||||
// from the download queue to the specified peer.
|
||||
reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool)
|
||||
|
||||
// unreserve is responsible for removing the current retrieval allocation
|
||||
// assigned to a specific peer and placing it back into the pool to allow
|
||||
// reassigning to some other peer.
|
||||
unreserve(peer string) int
|
||||
|
||||
// request is responsible for converting a generic fetch request into a typed
|
||||
// one and sending it to the remote peer for fulfillment.
|
||||
request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error)
|
||||
|
||||
// deliver is responsible for taking a generic response packet from the
|
||||
// concurrent fetcher, unpacking the type specific data and delivering
|
||||
// it to the downloader's queue.
|
||||
deliver(peer *peerConnection, packet *eth.Response) (int, error)
|
||||
}
|
||||
|
||||
// concurrentFetch iteratively downloads scheduled block parts, taking available
|
||||
// peers, reserving a chunk of fetch requests for each and waiting for delivery
|
||||
// or timeouts.
|
||||
func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
|
||||
// Create a delivery channel to accept responses from all peers
|
||||
responses := make(chan *eth.Response)
|
||||
|
||||
// Track the currently active requests and their timeout order
|
||||
pending := make(map[string]*eth.Request)
|
||||
defer func() {
|
||||
// Abort all requests on sync cycle cancellation. The requests may still
|
||||
// be fulfilled by the remote side, but the dispatcher will not wait to
|
||||
// deliver them since nobody's going to be listening.
|
||||
for _, req := range pending {
|
||||
req.Close()
|
||||
}
|
||||
}()
|
||||
ordering := make(map[*eth.Request]int)
|
||||
timeouts := prque.New[int64, *eth.Request](func(data *eth.Request, index int) {
|
||||
ordering[data] = index
|
||||
})
|
||||
|
||||
timeout := time.NewTimer(0)
|
||||
if !timeout.Stop() {
|
||||
<-timeout.C
|
||||
}
|
||||
defer timeout.Stop()
|
||||
|
||||
// Track the timed-out but not-yet-answered requests separately. We want to
|
||||
// keep tracking which peers are busy (potentially overloaded), so removing
|
||||
// all trace of a timed out request is not good. We also can't just cancel
|
||||
// the pending request altogether as that would prevent a late response from
|
||||
// being delivered, thus never unblocking the peer.
|
||||
stales := make(map[string]*eth.Request)
|
||||
defer func() {
|
||||
// Abort all requests on sync cycle cancellation. The requests may still
|
||||
// be fulfilled by the remote side, but the dispatcher will not wait to
|
||||
// deliver them since nobody's going to be listening.
|
||||
for _, req := range stales {
|
||||
req.Close()
|
||||
}
|
||||
}()
|
||||
// Subscribe to peer lifecycle events to schedule tasks to new joiners and
|
||||
// reschedule tasks upon disconnections. We don't care which event happened
|
||||
// for simplicity, so just use a single channel.
|
||||
peering := make(chan *peeringEvent, 64) // arbitrary buffer, just some burst protection
|
||||
|
||||
peeringSub := d.peers.SubscribeEvents(peering)
|
||||
defer peeringSub.Unsubscribe()
|
||||
|
||||
// Prepare the queue and fetch block parts until the block header fetcher's done
|
||||
finished := false
|
||||
for {
|
||||
// Short circuit if we lost all our peers
|
||||
if d.peers.Len() == 0 && !beaconMode {
|
||||
return errNoPeers
|
||||
}
|
||||
// If there's nothing more to fetch, wait or terminate
|
||||
if queue.pending() == 0 {
|
||||
if len(pending) == 0 && finished {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// Send a download request to all idle peers, until throttled
|
||||
var (
|
||||
idles []*peerConnection
|
||||
caps []int
|
||||
)
|
||||
for _, peer := range d.peers.AllPeers() {
|
||||
pending, stale := pending[peer.id], stales[peer.id]
|
||||
if pending == nil && stale == nil {
|
||||
idles = append(idles, peer)
|
||||
caps = append(caps, queue.capacity(peer, time.Second))
|
||||
} else if stale != nil {
|
||||
if waited := time.Since(stale.Sent); waited > timeoutGracePeriod {
|
||||
// Request has been in flight longer than the grace period
|
||||
// permitted it, consider the peer malicious attempting to
|
||||
// stall the sync.
|
||||
peer.log.Warn("Peer stalling, dropping", "waited", common.PrettyDuration(waited))
|
||||
d.dropPeer(peer.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(&peerCapacitySort{idles, caps})
|
||||
|
||||
var (
|
||||
progressed bool
|
||||
throttled bool
|
||||
queued = queue.pending()
|
||||
)
|
||||
for _, peer := range idles {
|
||||
// Short circuit if throttling activated or there are no more
|
||||
// queued tasks to be retrieved
|
||||
if throttled {
|
||||
break
|
||||
}
|
||||
if queued = queue.pending(); queued == 0 {
|
||||
break
|
||||
}
|
||||
// Reserve a chunk of fetches for a peer. A nil can mean either that
|
||||
// no more headers are available, or that the peer is known not to
|
||||
// have them.
|
||||
request, progress, throttle := queue.reserve(peer, queue.capacity(peer, d.peers.rates.TargetRoundTrip()))
|
||||
if progress {
|
||||
progressed = true
|
||||
}
|
||||
if throttle {
|
||||
throttled = true
|
||||
throttleCounter.Inc(1)
|
||||
}
|
||||
if request == nil {
|
||||
continue
|
||||
}
|
||||
// Fetch the chunk and make sure any errors return the hashes to the queue
|
||||
req, err := queue.request(peer, request, responses)
|
||||
if err != nil {
|
||||
// Sending the request failed, which generally means the peer
|
||||
// was disconnected in between assignment and network send.
|
||||
// Although all peer removal operations return allocated tasks
|
||||
// to the queue, that is async, and we can do better here by
|
||||
// immediately pushing the unfulfilled requests.
|
||||
queue.unreserve(peer.id) // TODO(karalabe): This needs a non-expiration method
|
||||
continue
|
||||
}
|
||||
pending[peer.id] = req
|
||||
|
||||
ttl := d.peers.rates.TargetTimeout()
|
||||
ordering[req] = timeouts.Size()
|
||||
|
||||
timeouts.Push(req, -time.Now().Add(ttl).UnixNano())
|
||||
if timeouts.Size() == 1 {
|
||||
timeout.Reset(ttl)
|
||||
}
|
||||
}
|
||||
// Make sure that we have peers available for fetching. If all peers have been tried
|
||||
// and all failed throw an error
|
||||
if !progressed && !throttled && len(pending) == 0 && len(idles) == d.peers.Len() && queued > 0 && !beaconMode {
|
||||
return errPeersUnavailable
|
||||
}
|
||||
}
|
||||
// Wait for something to happen
|
||||
select {
|
||||
case <-d.cancelCh:
|
||||
// If sync was cancelled, tear down the parallel retriever. Pending
|
||||
// requests will be cancelled locally, and the remote responses will
|
||||
// be dropped when they arrive
|
||||
return errCanceled
|
||||
|
||||
case event := <-peering:
|
||||
// A peer joined or left, the tasks queue and allocations need to be
|
||||
// checked for potential assignment or reassignment
|
||||
peerid := event.peer.id
|
||||
|
||||
if event.join {
|
||||
// Sanity check the internal state; this can be dropped later
|
||||
if _, ok := pending[peerid]; ok {
|
||||
event.peer.log.Error("Pending request exists for joining peer")
|
||||
}
|
||||
if _, ok := stales[peerid]; ok {
|
||||
event.peer.log.Error("Stale request exists for joining peer")
|
||||
}
|
||||
// Loop back to the entry point for task assignment
|
||||
continue
|
||||
}
|
||||
// A peer left, any existing requests need to be untracked, pending
|
||||
// tasks returned and possible reassignment checked
|
||||
if req, ok := pending[peerid]; ok {
|
||||
queue.unreserve(peerid) // TODO(karalabe): This needs a non-expiration method
|
||||
delete(pending, peerid)
|
||||
req.Close()
|
||||
|
||||
if index, live := ordering[req]; live {
|
||||
timeouts.Remove(index)
|
||||
if index == 0 {
|
||||
if !timeout.Stop() {
|
||||
<-timeout.C
|
||||
}
|
||||
if timeouts.Size() > 0 {
|
||||
_, exp := timeouts.Peek()
|
||||
timeout.Reset(time.Until(time.Unix(0, -exp)))
|
||||
}
|
||||
}
|
||||
delete(ordering, req)
|
||||
}
|
||||
}
|
||||
if req, ok := stales[peerid]; ok {
|
||||
delete(stales, peerid)
|
||||
req.Close()
|
||||
}
|
||||
|
||||
case <-timeout.C:
|
||||
// Retrieve the next request which should have timed out. The check
|
||||
// below is purely for to catch programming errors, given the correct
|
||||
// code, there's no possible order of events that should result in a
|
||||
// timeout firing for a non-existent event.
|
||||
req, exp := timeouts.Peek()
|
||||
if now, at := time.Now(), time.Unix(0, -exp); now.Before(at) {
|
||||
log.Error("Timeout triggered but not reached", "left", at.Sub(now))
|
||||
timeout.Reset(at.Sub(now))
|
||||
continue
|
||||
}
|
||||
// Stop tracking the timed out request from a timing perspective,
|
||||
// cancel it, so it's not considered in-flight anymore, but keep
|
||||
// the peer marked busy to prevent assigning a second request and
|
||||
// overloading it further.
|
||||
delete(pending, req.Peer)
|
||||
stales[req.Peer] = req
|
||||
|
||||
timeouts.Pop() // Popping an item will reorder indices in `ordering`, delete after, otherwise will resurrect!
|
||||
if timeouts.Size() > 0 {
|
||||
_, exp := timeouts.Peek()
|
||||
timeout.Reset(time.Until(time.Unix(0, -exp)))
|
||||
}
|
||||
delete(ordering, req)
|
||||
|
||||
// New timeout potentially set if there are more requests pending,
|
||||
// reschedule the failed one to a free peer
|
||||
fails := queue.unreserve(req.Peer)
|
||||
|
||||
// Finally, update the peer's retrieval capacity, or if it's already
|
||||
// below the minimum allowance, drop the peer. If a lot of retrieval
|
||||
// elements expired, we might have overestimated the remote peer or
|
||||
// perhaps ourselves. Only reset to minimal throughput but don't drop
|
||||
// just yet.
|
||||
//
|
||||
// The reason the minimum threshold is 2 is that the downloader tries
|
||||
// to estimate the bandwidth and latency of a peer separately, which
|
||||
// requires pushing the measured capacity a bit and seeing how response
|
||||
// times reacts, to it always requests one more than the minimum (i.e.
|
||||
// min 2).
|
||||
peer := d.peers.Peer(req.Peer)
|
||||
if peer == nil {
|
||||
// If the peer got disconnected in between, we should really have
|
||||
// short-circuited it already. Just in case there's some strange
|
||||
// codepath, leave this check in not to crash.
|
||||
log.Error("Delivery timeout from unknown peer", "peer", req.Peer)
|
||||
continue
|
||||
}
|
||||
if fails > 2 {
|
||||
queue.updateCapacity(peer, 0, 0)
|
||||
} else {
|
||||
d.dropPeer(peer.id)
|
||||
|
||||
// If this peer was the master peer, abort sync immediately
|
||||
d.cancelLock.RLock()
|
||||
master := peer.id == d.cancelPeer
|
||||
d.cancelLock.RUnlock()
|
||||
|
||||
if master {
|
||||
d.cancel()
|
||||
return errTimeout
|
||||
}
|
||||
}
|
||||
|
||||
case res := <-responses:
|
||||
// Response arrived, it may be for an existing or an already timed
|
||||
// out request. If the former, update the timeout heap and perhaps
|
||||
// reschedule the timeout timer.
|
||||
index, live := ordering[res.Req]
|
||||
if live {
|
||||
timeouts.Remove(index)
|
||||
if index == 0 {
|
||||
if !timeout.Stop() {
|
||||
<-timeout.C
|
||||
}
|
||||
if timeouts.Size() > 0 {
|
||||
_, exp := timeouts.Peek()
|
||||
timeout.Reset(time.Until(time.Unix(0, -exp)))
|
||||
}
|
||||
}
|
||||
delete(ordering, res.Req)
|
||||
}
|
||||
// Delete the pending request (if it still exists) and mark the peer idle
|
||||
delete(pending, res.Req.Peer)
|
||||
delete(stales, res.Req.Peer)
|
||||
|
||||
// Signal the dispatcher that the round trip is done. We'll drop the
|
||||
// peer if the data turns out to be junk.
|
||||
res.Done <- nil
|
||||
res.Req.Close()
|
||||
|
||||
// If the peer was previously banned and failed to deliver its pack
|
||||
// in a reasonable time frame, ignore its message.
|
||||
if peer := d.peers.Peer(res.Req.Peer); peer != nil {
|
||||
// Deliver the received chunk of data and check chain validity
|
||||
accepted, err := queue.deliver(peer, res)
|
||||
if errors.Is(err, errInvalidChain) {
|
||||
return err
|
||||
}
|
||||
// Unless a peer delivered something completely else than requested (usually
|
||||
// caused by a timed out request which came through in the end), set it to
|
||||
// idle. If the delivery's stale, the peer should have already been idled.
|
||||
if !errors.Is(err, errStaleDelivery) {
|
||||
queue.updateCapacity(peer, accepted, res.Time)
|
||||
}
|
||||
}
|
||||
|
||||
case cont := <-queue.waker():
|
||||
// The header fetcher sent a continuation flag, check if it's done
|
||||
if !cont {
|
||||
finished = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// bodyQueue implements typedQueue and is a type adapter between the generic
|
||||
// concurrent fetcher and the downloader.
|
||||
type bodyQueue Downloader
|
||||
|
||||
// waker returns a notification channel that gets pinged in case more body
|
||||
// fetches have been queued up, so the fetcher might assign it to idle peers.
|
||||
func (q *bodyQueue) waker() chan bool {
|
||||
return q.queue.blockWakeCh
|
||||
}
|
||||
|
||||
// pending returns the number of bodies that are currently queued for fetching
|
||||
// by the concurrent downloader.
|
||||
func (q *bodyQueue) pending() int {
|
||||
return q.queue.PendingBodies()
|
||||
}
|
||||
|
||||
// capacity is responsible for calculating how many bodies a particular peer is
|
||||
// estimated to be able to retrieve within the allotted round trip time.
|
||||
func (q *bodyQueue) capacity(peer *peerConnection, rtt time.Duration) int {
|
||||
return peer.BodyCapacity(rtt)
|
||||
}
|
||||
|
||||
// updateCapacity is responsible for updating how many bodies a particular peer
|
||||
// is estimated to be able to retrieve in a unit time.
|
||||
func (q *bodyQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) {
|
||||
peer.UpdateBodyRate(items, span)
|
||||
}
|
||||
|
||||
// reserve is responsible for allocating a requested number of pending bodies
|
||||
// from the download queue to the specified peer.
|
||||
func (q *bodyQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) {
|
||||
return q.queue.ReserveBodies(peer, items)
|
||||
}
|
||||
|
||||
// unreserve is responsible for removing the current body retrieval allocation
|
||||
// assigned to a specific peer and placing it back into the pool to allow
|
||||
// reassigning to some other peer.
|
||||
func (q *bodyQueue) unreserve(peer string) int {
|
||||
fails := q.queue.ExpireBodies(peer)
|
||||
if fails > 2 {
|
||||
log.Trace("Body delivery timed out", "peer", peer)
|
||||
} else {
|
||||
log.Debug("Body delivery stalling", "peer", peer)
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
// request is responsible for converting a generic fetch request into a body
|
||||
// one and sending it to the remote peer for fulfillment.
|
||||
func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) {
|
||||
peer.log.Trace("Requesting new batch of bodies", "count", len(req.Headers), "from", req.Headers[0].Number)
|
||||
if q.bodyFetchHook != nil {
|
||||
q.bodyFetchHook(req.Headers)
|
||||
}
|
||||
|
||||
hashes := make([]common.Hash, 0, len(req.Headers))
|
||||
for _, header := range req.Headers {
|
||||
hashes = append(hashes, header.Hash())
|
||||
}
|
||||
return peer.peer.RequestBodies(hashes, resCh)
|
||||
}
|
||||
|
||||
// deliver is responsible for taking a generic response packet from the concurrent
|
||||
// fetcher, unpacking the body data and delivering it to the downloader's queue.
|
||||
func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
|
||||
txs, uncles, withdrawals := packet.Res.(*eth.BlockBodiesResponse).Unpack()
|
||||
hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes, withdrawal hashes}
|
||||
|
||||
accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1], withdrawals, hashsets[2])
|
||||
switch {
|
||||
case err == nil && len(txs) == 0:
|
||||
peer.log.Trace("Requested bodies delivered")
|
||||
case err == nil:
|
||||
peer.log.Trace("Delivered new batch of bodies", "count", len(txs), "accepted", accepted)
|
||||
default:
|
||||
peer.log.Debug("Failed to deliver retrieved bodies", "err", err)
|
||||
}
|
||||
return accepted, err
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// headerQueue implements typedQueue and is a type adapter between the generic
|
||||
// concurrent fetcher and the downloader.
|
||||
type headerQueue Downloader
|
||||
|
||||
// waker returns a notification channel that gets pinged in case more header
|
||||
// fetches have been queued up, so the fetcher might assign it to idle peers.
|
||||
func (q *headerQueue) waker() chan bool {
|
||||
return q.queue.headerContCh
|
||||
}
|
||||
|
||||
// pending returns the number of headers that are currently queued for fetching
|
||||
// by the concurrent downloader.
|
||||
func (q *headerQueue) pending() int {
|
||||
return q.queue.PendingHeaders()
|
||||
}
|
||||
|
||||
// capacity is responsible for calculating how many headers a particular peer is
|
||||
// estimated to be able to retrieve within the allotted round trip time.
|
||||
func (q *headerQueue) capacity(peer *peerConnection, rtt time.Duration) int {
|
||||
return peer.HeaderCapacity(rtt)
|
||||
}
|
||||
|
||||
// updateCapacity is responsible for updating how many headers a particular peer
|
||||
// is estimated to be able to retrieve in a unit time.
|
||||
func (q *headerQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) {
|
||||
peer.UpdateHeaderRate(items, span)
|
||||
}
|
||||
|
||||
// reserve is responsible for allocating a requested number of pending headers
|
||||
// from the download queue to the specified peer.
|
||||
func (q *headerQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) {
|
||||
return q.queue.ReserveHeaders(peer, items), false, false
|
||||
}
|
||||
|
||||
// unreserve is responsible for removing the current header retrieval allocation
|
||||
// assigned to a specific peer and placing it back into the pool to allow
|
||||
// reassigning to some other peer.
|
||||
func (q *headerQueue) unreserve(peer string) int {
|
||||
fails := q.queue.ExpireHeaders(peer)
|
||||
if fails > 2 {
|
||||
log.Trace("Header delivery timed out", "peer", peer)
|
||||
} else {
|
||||
log.Debug("Header delivery stalling", "peer", peer)
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
// request is responsible for converting a generic fetch request into a header
|
||||
// one and sending it to the remote peer for fulfillment.
|
||||
func (q *headerQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) {
|
||||
peer.log.Trace("Requesting new batch of headers", "from", req.From)
|
||||
return peer.peer.RequestHeadersByNumber(req.From, MaxHeaderFetch, 0, false, resCh)
|
||||
}
|
||||
|
||||
// deliver is responsible for taking a generic response packet from the concurrent
|
||||
// fetcher, unpacking the header data and delivering it to the downloader's queue.
|
||||
func (q *headerQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
|
||||
headers := *packet.Res.(*eth.BlockHeadersRequest)
|
||||
hashes := packet.Meta.([]common.Hash)
|
||||
|
||||
accepted, err := q.queue.DeliverHeaders(peer.id, headers, hashes, q.headerProcCh)
|
||||
switch {
|
||||
case err == nil && len(headers) == 0:
|
||||
peer.log.Trace("Requested headers delivered")
|
||||
case err == nil:
|
||||
peer.log.Trace("Delivered new batch of headers", "count", len(headers), "accepted", accepted)
|
||||
default:
|
||||
peer.log.Debug("Failed to deliver retrieved headers", "err", err)
|
||||
}
|
||||
return accepted, err
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// receiptQueue implements typedQueue and is a type adapter between the generic
|
||||
// concurrent fetcher and the downloader.
|
||||
type receiptQueue Downloader
|
||||
|
||||
// waker returns a notification channel that gets pinged in case more receipt
|
||||
// fetches have been queued up, so the fetcher might assign it to idle peers.
|
||||
func (q *receiptQueue) waker() chan bool {
|
||||
return q.queue.receiptWakeCh
|
||||
}
|
||||
|
||||
// pending returns the number of receipt that are currently queued for fetching
|
||||
// by the concurrent downloader.
|
||||
func (q *receiptQueue) pending() int {
|
||||
return q.queue.PendingReceipts()
|
||||
}
|
||||
|
||||
// capacity is responsible for calculating how many receipts a particular peer is
|
||||
// estimated to be able to retrieve within the allotted round trip time.
|
||||
func (q *receiptQueue) capacity(peer *peerConnection, rtt time.Duration) int {
|
||||
return peer.ReceiptCapacity(rtt)
|
||||
}
|
||||
|
||||
// updateCapacity is responsible for updating how many receipts a particular peer
|
||||
// is estimated to be able to retrieve in a unit time.
|
||||
func (q *receiptQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) {
|
||||
peer.UpdateReceiptRate(items, span)
|
||||
}
|
||||
|
||||
// reserve is responsible for allocating a requested number of pending receipts
|
||||
// from the download queue to the specified peer.
|
||||
func (q *receiptQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) {
|
||||
return q.queue.ReserveReceipts(peer, items)
|
||||
}
|
||||
|
||||
// unreserve is responsible for removing the current receipt retrieval allocation
|
||||
// assigned to a specific peer and placing it back into the pool to allow
|
||||
// reassigning to some other peer.
|
||||
func (q *receiptQueue) unreserve(peer string) int {
|
||||
fails := q.queue.ExpireReceipts(peer)
|
||||
if fails > 2 {
|
||||
log.Trace("Receipt delivery timed out", "peer", peer)
|
||||
} else {
|
||||
log.Debug("Receipt delivery stalling", "peer", peer)
|
||||
}
|
||||
return fails
|
||||
}
|
||||
|
||||
// request is responsible for converting a generic fetch request into a receipt
|
||||
// one and sending it to the remote peer for fulfillment.
|
||||
func (q *receiptQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) {
|
||||
peer.log.Trace("Requesting new batch of receipts", "count", len(req.Headers), "from", req.Headers[0].Number)
|
||||
if q.receiptFetchHook != nil {
|
||||
q.receiptFetchHook(req.Headers)
|
||||
}
|
||||
hashes := make([]common.Hash, 0, len(req.Headers))
|
||||
for _, header := range req.Headers {
|
||||
hashes = append(hashes, header.Hash())
|
||||
}
|
||||
return peer.peer.RequestReceipts(hashes, resCh)
|
||||
}
|
||||
|
||||
// deliver is responsible for taking a generic response packet from the concurrent
|
||||
// fetcher, unpacking the receipt data and delivering it to the downloader's queue.
|
||||
func (q *receiptQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) {
|
||||
receipts := *packet.Res.(*eth.ReceiptsResponse)
|
||||
hashes := packet.Meta.([]common.Hash) // {receipt hashes}
|
||||
|
||||
accepted, err := q.queue.DeliverReceipts(peer.id, receipts, hashes)
|
||||
switch {
|
||||
case err == nil && len(receipts) == 0:
|
||||
peer.log.Trace("Requested receipts delivered")
|
||||
case err == nil:
|
||||
peer.log.Trace("Delivered new batch of receipts", "count", len(receipts), "accepted", accepted)
|
||||
default:
|
||||
peer.log.Debug("Failed to deliver retrieved receipts", "err", err)
|
||||
}
|
||||
return accepted, err
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the metrics collected by the downloader.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
headerInMeter = metrics.NewRegisteredMeter("eth/downloader/headers/in", nil)
|
||||
headerReqTimer = metrics.NewRegisteredTimer("eth/downloader/headers/req", nil)
|
||||
headerDropMeter = metrics.NewRegisteredMeter("eth/downloader/headers/drop", nil)
|
||||
headerTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/headers/timeout", nil)
|
||||
|
||||
bodyInMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/in", nil)
|
||||
bodyReqTimer = metrics.NewRegisteredTimer("eth/downloader/bodies/req", nil)
|
||||
bodyDropMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/drop", nil)
|
||||
bodyTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/timeout", nil)
|
||||
|
||||
receiptInMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/in", nil)
|
||||
receiptReqTimer = metrics.NewRegisteredTimer("eth/downloader/receipts/req", nil)
|
||||
receiptDropMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/drop", nil)
|
||||
receiptTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/timeout", nil)
|
||||
|
||||
throttleCounter = metrics.NewRegisteredCounter("eth/downloader/throttle", nil)
|
||||
)
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SyncMode represents the synchronisation mode of the downloader.
|
||||
// It is a uint32 as it is used with atomic operations.
|
||||
type SyncMode uint32
|
||||
|
||||
const (
|
||||
FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks
|
||||
SnapSync // Download the chain and the state via compact snapshots
|
||||
LightSync // Download only the headers and terminate afterwards
|
||||
)
|
||||
|
||||
func (mode SyncMode) IsValid() bool {
|
||||
return mode >= FullSync && mode <= LightSync
|
||||
}
|
||||
|
||||
// String implements the stringer interface.
|
||||
func (mode SyncMode) String() string {
|
||||
switch mode {
|
||||
case FullSync:
|
||||
return "full"
|
||||
case SnapSync:
|
||||
return "snap"
|
||||
case LightSync:
|
||||
return "light"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (mode SyncMode) MarshalText() ([]byte, error) {
|
||||
switch mode {
|
||||
case FullSync:
|
||||
return []byte("full"), nil
|
||||
case SnapSync:
|
||||
return []byte("snap"), nil
|
||||
case LightSync:
|
||||
return []byte("light"), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown sync mode %d", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (mode *SyncMode) UnmarshalText(text []byte) error {
|
||||
switch string(text) {
|
||||
case "full":
|
||||
*mode = FullSync
|
||||
case "snap":
|
||||
*mode = SnapSync
|
||||
case "light":
|
||||
*mode = LightSync
|
||||
default:
|
||||
return fmt.Errorf(`unknown sync mode %q, want "full", "snap" or "light"`, text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,290 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the active peer-set of the downloader, maintaining both failures
|
||||
// as well as reputation metrics to prioritize the block retrievals.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/msgrate"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLackingHashes = 4096 // Maximum number of entries allowed on the list or lacking items
|
||||
)
|
||||
|
||||
var (
|
||||
errAlreadyRegistered = errors.New("peer is already registered")
|
||||
errNotRegistered = errors.New("peer is not registered")
|
||||
)
|
||||
|
||||
// peerConnection represents an active peer from which hashes and blocks are retrieved.
|
||||
type peerConnection struct {
|
||||
id string // Unique identifier of the peer
|
||||
|
||||
rates *msgrate.Tracker // Tracker to hone in on the number of items retrievable per second
|
||||
lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously)
|
||||
|
||||
peer Peer
|
||||
|
||||
version uint // Eth protocol version number to switch strategies
|
||||
log log.Logger // Contextual logger to add extra infos to peer logs
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// Peer encapsulates the methods required to synchronise with a remote full peer.
|
||||
type Peer interface {
|
||||
Head() (common.Hash, *big.Int)
|
||||
RequestHeadersByHash(common.Hash, int, int, bool, chan *eth.Response) (*eth.Request, error)
|
||||
RequestHeadersByNumber(uint64, int, int, bool, chan *eth.Response) (*eth.Request, error)
|
||||
|
||||
RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
||||
RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
||||
}
|
||||
|
||||
// newPeerConnection creates a new downloader peer.
|
||||
func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection {
|
||||
return &peerConnection{
|
||||
id: id,
|
||||
lacking: make(map[common.Hash]struct{}),
|
||||
peer: peer,
|
||||
version: version,
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Reset clears the internal state of a peer entity.
|
||||
func (p *peerConnection) Reset() {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.lacking = make(map[common.Hash]struct{})
|
||||
}
|
||||
|
||||
// UpdateHeaderRate updates the peer's estimated header retrieval throughput with
|
||||
// the current measurement.
|
||||
func (p *peerConnection) UpdateHeaderRate(delivered int, elapsed time.Duration) {
|
||||
p.rates.Update(eth.BlockHeadersMsg, elapsed, delivered)
|
||||
}
|
||||
|
||||
// UpdateBodyRate updates the peer's estimated body retrieval throughput with the
|
||||
// current measurement.
|
||||
func (p *peerConnection) UpdateBodyRate(delivered int, elapsed time.Duration) {
|
||||
p.rates.Update(eth.BlockBodiesMsg, elapsed, delivered)
|
||||
}
|
||||
|
||||
// UpdateReceiptRate updates the peer's estimated receipt retrieval throughput
|
||||
// with the current measurement.
|
||||
func (p *peerConnection) UpdateReceiptRate(delivered int, elapsed time.Duration) {
|
||||
p.rates.Update(eth.ReceiptsMsg, elapsed, delivered)
|
||||
}
|
||||
|
||||
// HeaderCapacity retrieves the peer's header download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int {
|
||||
cap := p.rates.Capacity(eth.BlockHeadersMsg, targetRTT)
|
||||
if cap > MaxHeaderFetch {
|
||||
cap = MaxHeaderFetch
|
||||
}
|
||||
return cap
|
||||
}
|
||||
|
||||
// BodyCapacity retrieves the peer's body download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) BodyCapacity(targetRTT time.Duration) int {
|
||||
cap := p.rates.Capacity(eth.BlockBodiesMsg, targetRTT)
|
||||
if cap > MaxBlockFetch {
|
||||
cap = MaxBlockFetch
|
||||
}
|
||||
return cap
|
||||
}
|
||||
|
||||
// ReceiptCapacity retrieves the peers receipt download allowance based on its
|
||||
// previously discovered throughput.
|
||||
func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int {
|
||||
cap := p.rates.Capacity(eth.ReceiptsMsg, targetRTT)
|
||||
if cap > MaxReceiptFetch {
|
||||
cap = MaxReceiptFetch
|
||||
}
|
||||
return cap
|
||||
}
|
||||
|
||||
// MarkLacking appends a new entity to the set of items (blocks, receipts, states)
|
||||
// that a peer is known not to have (i.e. have been requested before). If the
|
||||
// set reaches its maximum allowed capacity, items are randomly dropped off.
|
||||
func (p *peerConnection) MarkLacking(hash common.Hash) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
for len(p.lacking) >= maxLackingHashes {
|
||||
for drop := range p.lacking {
|
||||
delete(p.lacking, drop)
|
||||
break
|
||||
}
|
||||
}
|
||||
p.lacking[hash] = struct{}{}
|
||||
}
|
||||
|
||||
// Lacks retrieves whether the hash of a blockchain item is on the peers lacking
|
||||
// list (i.e. whether we know that the peer does not have it).
|
||||
func (p *peerConnection) Lacks(hash common.Hash) bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
_, ok := p.lacking[hash]
|
||||
return ok
|
||||
}
|
||||
|
||||
// peeringEvent is sent on the peer event feed when a remote peer connects or
|
||||
// disconnects.
|
||||
type peeringEvent struct {
|
||||
peer *peerConnection
|
||||
join bool
|
||||
}
|
||||
|
||||
// peerSet represents the collection of active peer participating in the chain
|
||||
// download procedure.
|
||||
type peerSet struct {
|
||||
peers map[string]*peerConnection
|
||||
rates *msgrate.Trackers // Set of rate trackers to give the sync a common beat
|
||||
events event.Feed // Feed to publish peer lifecycle events on
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// newPeerSet creates a new peer set top track the active download sources.
|
||||
func newPeerSet() *peerSet {
|
||||
return &peerSet{
|
||||
peers: make(map[string]*peerConnection),
|
||||
rates: msgrate.NewTrackers(log.New("proto", "eth")),
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeEvents subscribes to peer arrival and departure events.
|
||||
func (ps *peerSet) SubscribeEvents(ch chan<- *peeringEvent) event.Subscription {
|
||||
return ps.events.Subscribe(ch)
|
||||
}
|
||||
|
||||
// Reset iterates over the current peer set, and resets each of the known peers
|
||||
// to prepare for a next batch of block retrieval.
|
||||
func (ps *peerSet) Reset() {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
for _, peer := range ps.peers {
|
||||
peer.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Register injects a new peer into the working set, or returns an error if the
|
||||
// peer is already known.
|
||||
//
|
||||
// The method also sets the starting throughput values of the new peer to the
|
||||
// average of all existing peers, to give it a realistic chance of being used
|
||||
// for data retrievals.
|
||||
func (ps *peerSet) Register(p *peerConnection) error {
|
||||
// Register the new peer with some meaningful defaults
|
||||
ps.lock.Lock()
|
||||
if _, ok := ps.peers[p.id]; ok {
|
||||
ps.lock.Unlock()
|
||||
return errAlreadyRegistered
|
||||
}
|
||||
p.rates = msgrate.NewTracker(ps.rates.MeanCapacities(), ps.rates.MedianRoundTrip())
|
||||
if err := ps.rates.Track(p.id, p.rates); err != nil {
|
||||
ps.lock.Unlock()
|
||||
return err
|
||||
}
|
||||
ps.peers[p.id] = p
|
||||
ps.lock.Unlock()
|
||||
|
||||
ps.events.Send(&peeringEvent{peer: p, join: true})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unregister removes a remote peer from the active set, disabling any further
|
||||
// actions to/from that particular entity.
|
||||
func (ps *peerSet) Unregister(id string) error {
|
||||
ps.lock.Lock()
|
||||
p, ok := ps.peers[id]
|
||||
if !ok {
|
||||
ps.lock.Unlock()
|
||||
return errNotRegistered
|
||||
}
|
||||
delete(ps.peers, id)
|
||||
ps.rates.Untrack(id)
|
||||
ps.lock.Unlock()
|
||||
|
||||
ps.events.Send(&peeringEvent{peer: p, join: false})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Peer retrieves the registered peer with the given id.
|
||||
func (ps *peerSet) Peer(id string) *peerConnection {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
return ps.peers[id]
|
||||
}
|
||||
|
||||
// Len returns if the current number of peers in the set.
|
||||
func (ps *peerSet) Len() int {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
return len(ps.peers)
|
||||
}
|
||||
|
||||
// AllPeers retrieves a flat list of all the peers within the set.
|
||||
func (ps *peerSet) AllPeers() []*peerConnection {
|
||||
ps.lock.RLock()
|
||||
defer ps.lock.RUnlock()
|
||||
|
||||
list := make([]*peerConnection, 0, len(ps.peers))
|
||||
for _, p := range ps.peers {
|
||||
list = append(list, p)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// peerCapacitySort implements sort.Interface.
|
||||
// It sorts peer connections by capacity (descending).
|
||||
type peerCapacitySort struct {
|
||||
peers []*peerConnection
|
||||
caps []int
|
||||
}
|
||||
|
||||
func (ps *peerCapacitySort) Len() int {
|
||||
return len(ps.peers)
|
||||
}
|
||||
|
||||
func (ps *peerCapacitySort) Less(i, j int) bool {
|
||||
return ps.caps[i] > ps.caps[j]
|
||||
}
|
||||
|
||||
func (ps *peerCapacitySort) Swap(i, j int) {
|
||||
ps.peers[i], ps.peers[j] = ps.peers[j], ps.peers[i]
|
||||
ps.caps[i], ps.caps[j] = ps.caps[j], ps.caps[i]
|
||||
}
|
||||
|
|
@ -1,956 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the block download scheduler to collect download tasks and schedule
|
||||
// them in an ordered, and throttled way.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/prque"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
const (
|
||||
bodyType = uint(0)
|
||||
receiptType = uint(1)
|
||||
)
|
||||
|
||||
var (
|
||||
blockCacheMaxItems = 8192 // Maximum number of blocks to cache before throttling the download
|
||||
blockCacheInitialItems = 2048 // Initial number of blocks to start fetching, before we know the sizes of the blocks
|
||||
blockCacheMemory = 256 * 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 (
|
||||
errNoFetchesPending = errors.New("no fetches pending")
|
||||
errStaleDelivery = errors.New("stale delivery")
|
||||
)
|
||||
|
||||
// fetchRequest is a currently running data retrieval operation.
|
||||
type fetchRequest struct {
|
||||
Peer *peerConnection // Peer to which the request was sent
|
||||
From uint64 // Requested chain element index (used for skeleton fills only)
|
||||
Headers []*types.Header // Requested headers, sorted by request order
|
||||
Time time.Time // Time when the request was made
|
||||
}
|
||||
|
||||
// fetchResult is a struct collecting partial results from data fetchers until
|
||||
// all outstanding pieces complete and the result as a whole can be processed.
|
||||
type fetchResult struct {
|
||||
pending atomic.Int32 // Flag telling what deliveries are outstanding
|
||||
|
||||
Header *types.Header
|
||||
Uncles []*types.Header
|
||||
Transactions types.Transactions
|
||||
Receipts types.Receipts
|
||||
Withdrawals types.Withdrawals
|
||||
}
|
||||
|
||||
func newFetchResult(header *types.Header, fastSync bool) *fetchResult {
|
||||
item := &fetchResult{
|
||||
Header: header,
|
||||
}
|
||||
if !header.EmptyBody() {
|
||||
item.pending.Store(item.pending.Load() | (1 << bodyType))
|
||||
} else if header.WithdrawalsHash != nil {
|
||||
item.Withdrawals = make(types.Withdrawals, 0)
|
||||
}
|
||||
if fastSync && !header.EmptyReceipts() {
|
||||
item.pending.Store(item.pending.Load() | (1 << receiptType))
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
// SetBodyDone flags the body as finished.
|
||||
func (f *fetchResult) SetBodyDone() {
|
||||
if v := f.pending.Load(); (v & (1 << bodyType)) != 0 {
|
||||
f.pending.Add(-1)
|
||||
}
|
||||
}
|
||||
|
||||
// AllDone checks if item is done.
|
||||
func (f *fetchResult) AllDone() bool {
|
||||
return f.pending.Load() == 0
|
||||
}
|
||||
|
||||
// SetReceiptsDone flags the receipts as finished.
|
||||
func (f *fetchResult) SetReceiptsDone() {
|
||||
if v := f.pending.Load(); (v & (1 << receiptType)) != 0 {
|
||||
f.pending.Add(-2)
|
||||
}
|
||||
}
|
||||
|
||||
// Done checks if the given type is done already
|
||||
func (f *fetchResult) Done(kind uint) bool {
|
||||
v := f.pending.Load()
|
||||
return v&(1<<kind) == 0
|
||||
}
|
||||
|
||||
// queue represents hashes that are either need fetching or are being fetched
|
||||
type queue struct {
|
||||
mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching
|
||||
|
||||
// Headers are "special", they download in batches, supported by a skeleton chain
|
||||
headerHead common.Hash // Hash of the last queued header to verify order
|
||||
headerTaskPool map[uint64]*types.Header // Pending header retrieval tasks, mapping starting indexes to skeleton headers
|
||||
headerTaskQueue *prque.Prque[int64, uint64] // Priority queue of the skeleton indexes to fetch the filling headers for
|
||||
headerPeerMiss map[string]map[uint64]struct{} // Set of per-peer header batches known to be unavailable
|
||||
headerPendPool map[string]*fetchRequest // Currently pending header retrieval operations
|
||||
headerResults []*types.Header // Result cache accumulating the completed headers
|
||||
headerHashes []common.Hash // Result cache accumulating the completed header hashes
|
||||
headerProced int // Number of headers already processed from the results
|
||||
headerOffset uint64 // Number of the first header in the result cache
|
||||
headerContCh chan bool // Channel to notify when header download finishes
|
||||
|
||||
// All data retrievals below are based on an already assembles header chain
|
||||
blockTaskPool map[common.Hash]*types.Header // Pending block (body) retrieval tasks, mapping hashes to headers
|
||||
blockTaskQueue *prque.Prque[int64, *types.Header] // Priority queue of the headers to fetch the blocks (bodies) for
|
||||
blockPendPool map[string]*fetchRequest // Currently pending block (body) retrieval operations
|
||||
blockWakeCh chan bool // Channel to notify the block fetcher of new tasks
|
||||
|
||||
receiptTaskPool map[common.Hash]*types.Header // Pending receipt retrieval tasks, mapping hashes to headers
|
||||
receiptTaskQueue *prque.Prque[int64, *types.Header] // Priority queue of the headers to fetch the receipts for
|
||||
receiptPendPool map[string]*fetchRequest // Currently pending receipt retrieval operations
|
||||
receiptWakeCh chan bool // Channel to notify when receipt fetcher of new tasks
|
||||
|
||||
resultCache *resultStore // Downloaded but not yet delivered fetch results
|
||||
resultSize common.StorageSize // Approximate size of a block (exponential moving average)
|
||||
|
||||
lock *sync.RWMutex
|
||||
active *sync.Cond
|
||||
closed bool
|
||||
|
||||
logTime time.Time // Time instance when status was last reported
|
||||
}
|
||||
|
||||
// newQueue creates a new download queue for scheduling block retrieval.
|
||||
func newQueue(blockCacheLimit int, thresholdInitialSize int) *queue {
|
||||
lock := new(sync.RWMutex)
|
||||
q := &queue{
|
||||
headerContCh: make(chan bool, 1),
|
||||
blockTaskQueue: prque.New[int64, *types.Header](nil),
|
||||
blockWakeCh: make(chan bool, 1),
|
||||
receiptTaskQueue: prque.New[int64, *types.Header](nil),
|
||||
receiptWakeCh: make(chan bool, 1),
|
||||
active: sync.NewCond(lock),
|
||||
lock: lock,
|
||||
}
|
||||
q.Reset(blockCacheLimit, thresholdInitialSize)
|
||||
return q
|
||||
}
|
||||
|
||||
// Reset clears out the queue contents.
|
||||
func (q *queue) Reset(blockCacheLimit int, thresholdInitialSize int) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
q.closed = false
|
||||
q.mode = FullSync
|
||||
|
||||
q.headerHead = common.Hash{}
|
||||
q.headerPendPool = make(map[string]*fetchRequest)
|
||||
|
||||
q.blockTaskPool = make(map[common.Hash]*types.Header)
|
||||
q.blockTaskQueue.Reset()
|
||||
q.blockPendPool = make(map[string]*fetchRequest)
|
||||
|
||||
q.receiptTaskPool = make(map[common.Hash]*types.Header)
|
||||
q.receiptTaskQueue.Reset()
|
||||
q.receiptPendPool = make(map[string]*fetchRequest)
|
||||
|
||||
q.resultCache = newResultStore(blockCacheLimit)
|
||||
q.resultCache.SetThrottleThreshold(uint64(thresholdInitialSize))
|
||||
}
|
||||
|
||||
// Close marks the end of the sync, unblocking Results.
|
||||
// It may be called even if the queue is already closed.
|
||||
func (q *queue) Close() {
|
||||
q.lock.Lock()
|
||||
q.closed = true
|
||||
q.active.Signal()
|
||||
q.lock.Unlock()
|
||||
}
|
||||
|
||||
// PendingHeaders retrieves the number of header requests pending for retrieval.
|
||||
func (q *queue) PendingHeaders() int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return q.headerTaskQueue.Size()
|
||||
}
|
||||
|
||||
// PendingBodies retrieves the number of block body requests pending for retrieval.
|
||||
func (q *queue) PendingBodies() int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return q.blockTaskQueue.Size()
|
||||
}
|
||||
|
||||
// PendingReceipts retrieves the number of block receipts pending for retrieval.
|
||||
func (q *queue) PendingReceipts() int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return q.receiptTaskQueue.Size()
|
||||
}
|
||||
|
||||
// InFlightBlocks retrieves whether there are block fetch requests currently in
|
||||
// flight.
|
||||
func (q *queue) InFlightBlocks() bool {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return len(q.blockPendPool) > 0
|
||||
}
|
||||
|
||||
// InFlightReceipts retrieves whether there are receipt fetch requests currently
|
||||
// in flight.
|
||||
func (q *queue) InFlightReceipts() bool {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return len(q.receiptPendPool) > 0
|
||||
}
|
||||
|
||||
// Idle returns if the queue is fully idle or has some data still inside.
|
||||
func (q *queue) Idle() bool {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
queued := q.blockTaskQueue.Size() + q.receiptTaskQueue.Size()
|
||||
pending := len(q.blockPendPool) + len(q.receiptPendPool)
|
||||
|
||||
return (queued + pending) == 0
|
||||
}
|
||||
|
||||
// ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill
|
||||
// up an already retrieved header skeleton.
|
||||
func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
// No skeleton retrieval can be in progress, fail hard if so (huge implementation bug)
|
||||
if q.headerResults != nil {
|
||||
panic("skeleton assembly already in progress")
|
||||
}
|
||||
// Schedule all the header retrieval tasks for the skeleton assembly
|
||||
q.headerTaskPool = make(map[uint64]*types.Header)
|
||||
q.headerTaskQueue = prque.New[int64, uint64](nil)
|
||||
q.headerPeerMiss = make(map[string]map[uint64]struct{}) // Reset availability to correct invalid chains
|
||||
q.headerResults = make([]*types.Header, len(skeleton)*MaxHeaderFetch)
|
||||
q.headerHashes = make([]common.Hash, len(skeleton)*MaxHeaderFetch)
|
||||
q.headerProced = 0
|
||||
q.headerOffset = from
|
||||
q.headerContCh = make(chan bool, 1)
|
||||
|
||||
for i, header := range skeleton {
|
||||
index := from + uint64(i*MaxHeaderFetch)
|
||||
|
||||
q.headerTaskPool[index] = header
|
||||
q.headerTaskQueue.Push(index, -int64(index))
|
||||
}
|
||||
}
|
||||
|
||||
// RetrieveHeaders retrieves the header chain assemble based on the scheduled
|
||||
// skeleton.
|
||||
func (q *queue) RetrieveHeaders() ([]*types.Header, []common.Hash, int) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
headers, hashes, proced := q.headerResults, q.headerHashes, q.headerProced
|
||||
q.headerResults, q.headerHashes, q.headerProced = nil, nil, 0
|
||||
|
||||
return headers, hashes, proced
|
||||
}
|
||||
|
||||
// Schedule adds a set of headers for the download queue for scheduling, returning
|
||||
// the new headers encountered.
|
||||
func (q *queue) Schedule(headers []*types.Header, hashes []common.Hash, from uint64) []*types.Header {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
// Insert all the headers prioritised by the contained block number
|
||||
inserts := make([]*types.Header, 0, len(headers))
|
||||
for i, header := range headers {
|
||||
// Make sure chain order is honoured and preserved throughout
|
||||
hash := hashes[i]
|
||||
if header.Number == nil || header.Number.Uint64() != from {
|
||||
log.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", from)
|
||||
break
|
||||
}
|
||||
if q.headerHead != (common.Hash{}) && q.headerHead != header.ParentHash {
|
||||
log.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash)
|
||||
break
|
||||
}
|
||||
// Make sure no duplicate requests are executed
|
||||
// We cannot skip this, even if the block is empty, since this is
|
||||
// what triggers the fetchResult creation.
|
||||
if _, ok := q.blockTaskPool[hash]; ok {
|
||||
log.Warn("Header already scheduled for block fetch", "number", header.Number, "hash", hash)
|
||||
} else {
|
||||
q.blockTaskPool[hash] = header
|
||||
q.blockTaskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
// Queue for receipt retrieval
|
||||
if q.mode == SnapSync && !header.EmptyReceipts() {
|
||||
if _, ok := q.receiptTaskPool[hash]; ok {
|
||||
log.Warn("Header already scheduled for receipt fetch", "number", header.Number, "hash", hash)
|
||||
} else {
|
||||
q.receiptTaskPool[hash] = header
|
||||
q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
}
|
||||
inserts = append(inserts, header)
|
||||
q.headerHead = hash
|
||||
from++
|
||||
}
|
||||
return inserts
|
||||
}
|
||||
|
||||
// Results retrieves and permanently removes a batch of fetch results from
|
||||
// the cache. the result slice will be empty if the queue has been closed.
|
||||
// Results can be called concurrently with Deliver and Schedule,
|
||||
// but assumes that there are not two simultaneous callers to Results
|
||||
func (q *queue) Results(block bool) []*fetchResult {
|
||||
// Abort early if there are no items and non-blocking requested
|
||||
if !block && !q.resultCache.HasCompletedItems() {
|
||||
return nil
|
||||
}
|
||||
closed := false
|
||||
for !closed && !q.resultCache.HasCompletedItems() {
|
||||
// In order to wait on 'active', we need to obtain the lock.
|
||||
// That may take a while, if someone is delivering at the same
|
||||
// time, so after obtaining the lock, we check again if there
|
||||
// are any results to fetch.
|
||||
// Also, in-between we ask for the lock and the lock is obtained,
|
||||
// someone can have closed the queue. In that case, we should
|
||||
// return the available results and stop blocking
|
||||
q.lock.Lock()
|
||||
if q.resultCache.HasCompletedItems() || q.closed {
|
||||
q.lock.Unlock()
|
||||
break
|
||||
}
|
||||
// No items available, and not closed
|
||||
q.active.Wait()
|
||||
closed = q.closed
|
||||
q.lock.Unlock()
|
||||
}
|
||||
// Regardless if closed or not, we can still deliver whatever we have
|
||||
results := q.resultCache.GetCompleted(maxResultsProcess)
|
||||
for _, result := range results {
|
||||
// Recalculate the result item weights to prevent memory exhaustion
|
||||
size := result.Header.Size()
|
||||
for _, uncle := range result.Uncles {
|
||||
size += uncle.Size()
|
||||
}
|
||||
for _, receipt := range result.Receipts {
|
||||
size += receipt.Size()
|
||||
}
|
||||
for _, tx := range result.Transactions {
|
||||
size += common.StorageSize(tx.Size())
|
||||
}
|
||||
q.resultSize = common.StorageSize(blockCacheSizeWeight)*size +
|
||||
(1-common.StorageSize(blockCacheSizeWeight))*q.resultSize
|
||||
}
|
||||
// Using the newly calibrated resultsize, figure out the new throttle limit
|
||||
// on the result cache
|
||||
throttleThreshold := uint64((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize)
|
||||
throttleThreshold = q.resultCache.SetThrottleThreshold(throttleThreshold)
|
||||
|
||||
// With results removed from the cache, wake throttled fetchers
|
||||
for _, ch := range []chan bool{q.blockWakeCh, q.receiptWakeCh} {
|
||||
select {
|
||||
case ch <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
// Log some info at certain times
|
||||
if time.Since(q.logTime) >= 60*time.Second {
|
||||
q.logTime = time.Now()
|
||||
|
||||
info := q.Stats()
|
||||
info = append(info, "throttle", throttleThreshold)
|
||||
log.Debug("Downloader queue stats", info...)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func (q *queue) Stats() []interface{} {
|
||||
q.lock.RLock()
|
||||
defer q.lock.RUnlock()
|
||||
|
||||
return q.stats()
|
||||
}
|
||||
|
||||
func (q *queue) stats() []interface{} {
|
||||
return []interface{}{
|
||||
"receiptTasks", q.receiptTaskQueue.Size(),
|
||||
"blockTasks", q.blockTaskQueue.Size(),
|
||||
"itemSize", q.resultSize,
|
||||
}
|
||||
}
|
||||
|
||||
// ReserveHeaders reserves a set of headers for the given peer, skipping any
|
||||
// previously failed batches.
|
||||
func (q *queue) ReserveHeaders(p *peerConnection, count int) *fetchRequest {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
// Short circuit if the peer's already downloading something (sanity check to
|
||||
// not corrupt state)
|
||||
if _, ok := q.headerPendPool[p.id]; ok {
|
||||
return nil
|
||||
}
|
||||
// Retrieve a batch of hashes, skipping previously failed ones
|
||||
send, skip := uint64(0), []uint64{}
|
||||
for send == 0 && !q.headerTaskQueue.Empty() {
|
||||
from, _ := q.headerTaskQueue.Pop()
|
||||
if q.headerPeerMiss[p.id] != nil {
|
||||
if _, ok := q.headerPeerMiss[p.id][from]; ok {
|
||||
skip = append(skip, from)
|
||||
continue
|
||||
}
|
||||
}
|
||||
send = from
|
||||
}
|
||||
// Merge all the skipped batches back
|
||||
for _, from := range skip {
|
||||
q.headerTaskQueue.Push(from, -int64(from))
|
||||
}
|
||||
// Assemble and return the block download request
|
||||
if send == 0 {
|
||||
return nil
|
||||
}
|
||||
request := &fetchRequest{
|
||||
Peer: p,
|
||||
From: send,
|
||||
Time: time.Now(),
|
||||
}
|
||||
q.headerPendPool[p.id] = request
|
||||
return request
|
||||
}
|
||||
|
||||
// ReserveBodies reserves a set of body fetches for the given peer, skipping any
|
||||
// previously failed downloads. Beside the next batch of needed fetches, it also
|
||||
// returns a flag whether empty blocks were queued requiring processing.
|
||||
func (q *queue) ReserveBodies(p *peerConnection, count int) (*fetchRequest, bool, bool) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return q.reserveHeaders(p, count, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, bodyType)
|
||||
}
|
||||
|
||||
// ReserveReceipts reserves a set of receipt fetches for the given peer, skipping
|
||||
// any previously failed downloads. Beside the next batch of needed fetches, it
|
||||
// also returns a flag whether empty receipts were queued requiring importing.
|
||||
func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bool, bool) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
return q.reserveHeaders(p, count, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, receiptType)
|
||||
}
|
||||
|
||||
// reserveHeaders reserves a set of data download operations for a given peer,
|
||||
// skipping any previously failed ones. This method is a generic version used
|
||||
// by the individual special reservation functions.
|
||||
//
|
||||
// Note, this method expects the queue lock to be already held for writing. The
|
||||
// reason the lock is not obtained in here is because the parameters already need
|
||||
// to access the queue, so they already need a lock anyway.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// item - the fetchRequest
|
||||
// progress - whether any progress was made
|
||||
// throttle - if the caller should throttle for a while
|
||||
func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque[int64, *types.Header],
|
||||
pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) {
|
||||
// Short circuit if the pool has been depleted, or if the peer's already
|
||||
// downloading something (sanity check not to corrupt state)
|
||||
if taskQueue.Empty() {
|
||||
return nil, false, true
|
||||
}
|
||||
if _, ok := pendPool[p.id]; ok {
|
||||
return nil, false, false
|
||||
}
|
||||
// Retrieve a batch of tasks, skipping previously failed ones
|
||||
send := make([]*types.Header, 0, count)
|
||||
skip := make([]*types.Header, 0)
|
||||
progress := false
|
||||
throttled := false
|
||||
for proc := 0; len(send) < count && !taskQueue.Empty(); proc++ {
|
||||
// the task queue will pop items in order, so the highest prio block
|
||||
// is also the lowest block number.
|
||||
header, _ := taskQueue.Peek()
|
||||
|
||||
// we can ask the resultcache if this header is within the
|
||||
// "prioritized" segment of blocks. If it is not, we need to throttle
|
||||
|
||||
stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == SnapSync)
|
||||
if stale {
|
||||
// Don't put back in the task queue, this item has already been
|
||||
// delivered upstream
|
||||
taskQueue.PopItem()
|
||||
progress = true
|
||||
delete(taskPool, header.Hash())
|
||||
proc = proc - 1
|
||||
log.Error("Fetch reservation already delivered", "number", header.Number.Uint64())
|
||||
continue
|
||||
}
|
||||
if throttle {
|
||||
// There are no resultslots available. Leave it in the task queue
|
||||
// However, if there are any left as 'skipped', we should not tell
|
||||
// the caller to throttle, since we still want some other
|
||||
// peer to fetch those for us
|
||||
throttled = len(skip) == 0
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// this most definitely should _not_ happen
|
||||
log.Warn("Failed to reserve headers", "err", err)
|
||||
// There are no resultslots available. Leave it in the task queue
|
||||
break
|
||||
}
|
||||
if item.Done(kind) {
|
||||
// If it's a noop, we can skip this task
|
||||
delete(taskPool, header.Hash())
|
||||
taskQueue.PopItem()
|
||||
proc = proc - 1
|
||||
progress = true
|
||||
continue
|
||||
}
|
||||
// Remove it from the task queue
|
||||
taskQueue.PopItem()
|
||||
// Otherwise unless the peer is known not to have the data, add to the retrieve list
|
||||
if p.Lacks(header.Hash()) {
|
||||
skip = append(skip, header)
|
||||
} else {
|
||||
send = append(send, header)
|
||||
}
|
||||
}
|
||||
// Merge all the skipped headers back
|
||||
for _, header := range skip {
|
||||
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
if q.resultCache.HasCompletedItems() {
|
||||
// Wake Results, resultCache was modified
|
||||
q.active.Signal()
|
||||
}
|
||||
// Assemble and return the block download request
|
||||
if len(send) == 0 {
|
||||
return nil, progress, throttled
|
||||
}
|
||||
request := &fetchRequest{
|
||||
Peer: p,
|
||||
Headers: send,
|
||||
Time: time.Now(),
|
||||
}
|
||||
pendPool[p.id] = request
|
||||
return request, progress, throttled
|
||||
}
|
||||
|
||||
// Revoke cancels all pending requests belonging to a given peer. This method is
|
||||
// meant to be called during a peer drop to quickly reassign owned data fetches
|
||||
// to remaining nodes.
|
||||
func (q *queue) Revoke(peerID string) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
if request, ok := q.headerPendPool[peerID]; ok {
|
||||
q.headerTaskQueue.Push(request.From, -int64(request.From))
|
||||
delete(q.headerPendPool, peerID)
|
||||
}
|
||||
if request, ok := q.blockPendPool[peerID]; ok {
|
||||
for _, header := range request.Headers {
|
||||
q.blockTaskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
delete(q.blockPendPool, peerID)
|
||||
}
|
||||
if request, ok := q.receiptPendPool[peerID]; ok {
|
||||
for _, header := range request.Headers {
|
||||
q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
delete(q.receiptPendPool, peerID)
|
||||
}
|
||||
}
|
||||
|
||||
// ExpireHeaders cancels a request that timed out and moves the pending fetch
|
||||
// task back into the queue for rescheduling.
|
||||
func (q *queue) ExpireHeaders(peer string) int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
headerTimeoutMeter.Mark(1)
|
||||
return q.expire(peer, q.headerPendPool, q.headerTaskQueue)
|
||||
}
|
||||
|
||||
// ExpireBodies checks for in flight block body requests that exceeded a timeout
|
||||
// allowance, canceling them and returning the responsible peers for penalisation.
|
||||
func (q *queue) ExpireBodies(peer string) int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
bodyTimeoutMeter.Mark(1)
|
||||
return q.expire(peer, q.blockPendPool, q.blockTaskQueue)
|
||||
}
|
||||
|
||||
// ExpireReceipts checks for in flight receipt requests that exceeded a timeout
|
||||
// allowance, canceling them and returning the responsible peers for penalisation.
|
||||
func (q *queue) ExpireReceipts(peer string) int {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
receiptTimeoutMeter.Mark(1)
|
||||
return q.expire(peer, q.receiptPendPool, q.receiptTaskQueue)
|
||||
}
|
||||
|
||||
// expire is the generic check that moves a specific expired task from a pending
|
||||
// pool back into a task pool. The syntax on the passed taskQueue is a bit weird
|
||||
// as we would need a generic expire method to handle both types, but that is not
|
||||
// supported at the moment at least (Go 1.19).
|
||||
//
|
||||
// Note, this method expects the queue lock to be already held. The reason the
|
||||
// lock is not obtained in here is that the parameters already need to access
|
||||
// the queue, so they already need a lock anyway.
|
||||
func (q *queue) expire(peer string, pendPool map[string]*fetchRequest, taskQueue interface{}) int {
|
||||
// Retrieve the request being expired and log an error if it's non-existent,
|
||||
// as there's no order of events that should lead to such expirations.
|
||||
req := pendPool[peer]
|
||||
if req == nil {
|
||||
log.Error("Expired request does not exist", "peer", peer)
|
||||
return 0
|
||||
}
|
||||
delete(pendPool, peer)
|
||||
|
||||
// Return any non-satisfied requests to the pool
|
||||
if req.From > 0 {
|
||||
taskQueue.(*prque.Prque[int64, uint64]).Push(req.From, -int64(req.From))
|
||||
}
|
||||
for _, header := range req.Headers {
|
||||
taskQueue.(*prque.Prque[int64, *types.Header]).Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
return len(req.Headers)
|
||||
}
|
||||
|
||||
// DeliverHeaders injects a header retrieval response into the header results
|
||||
// cache. This method either accepts all headers it received, or none of them
|
||||
// if they do not map correctly to the skeleton.
|
||||
//
|
||||
// If the headers are accepted, the method makes an attempt to deliver the set
|
||||
// of ready headers to the processor to keep the pipeline full. However, it will
|
||||
// not block to prevent stalling other pending deliveries.
|
||||
func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []common.Hash, headerProcCh chan *headerTask) (int, error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
var logger log.Logger
|
||||
if len(id) < 16 {
|
||||
// Tests use short IDs, don't choke on them
|
||||
logger = log.New("peer", id)
|
||||
} else {
|
||||
logger = log.New("peer", id[:16])
|
||||
}
|
||||
// Short circuit if the data was never requested
|
||||
request := q.headerPendPool[id]
|
||||
if request == nil {
|
||||
headerDropMeter.Mark(int64(len(headers)))
|
||||
return 0, errNoFetchesPending
|
||||
}
|
||||
delete(q.headerPendPool, id)
|
||||
|
||||
headerReqTimer.UpdateSince(request.Time)
|
||||
headerInMeter.Mark(int64(len(headers)))
|
||||
|
||||
// Ensure headers can be mapped onto the skeleton chain
|
||||
target := q.headerTaskPool[request.From].Hash()
|
||||
|
||||
accepted := len(headers) == MaxHeaderFetch
|
||||
if accepted {
|
||||
if headers[0].Number.Uint64() != request.From {
|
||||
logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", hashes[0], "expected", request.From)
|
||||
accepted = false
|
||||
} else if hashes[len(headers)-1] != target {
|
||||
logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", hashes[len(headers)-1], "expected", target)
|
||||
accepted = false
|
||||
}
|
||||
}
|
||||
if accepted {
|
||||
parentHash := hashes[0]
|
||||
for i, header := range headers[1:] {
|
||||
hash := hashes[i+1]
|
||||
if want := request.From + 1 + uint64(i); header.Number.Uint64() != want {
|
||||
logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want)
|
||||
accepted = false
|
||||
break
|
||||
}
|
||||
if parentHash != header.ParentHash {
|
||||
logger.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash)
|
||||
accepted = false
|
||||
break
|
||||
}
|
||||
// Set-up parent hash for next round
|
||||
parentHash = hash
|
||||
}
|
||||
}
|
||||
// If the batch of headers wasn't accepted, mark as unavailable
|
||||
if !accepted {
|
||||
logger.Trace("Skeleton filling not accepted", "from", request.From)
|
||||
headerDropMeter.Mark(int64(len(headers)))
|
||||
|
||||
miss := q.headerPeerMiss[id]
|
||||
if miss == nil {
|
||||
q.headerPeerMiss[id] = make(map[uint64]struct{})
|
||||
miss = q.headerPeerMiss[id]
|
||||
}
|
||||
miss[request.From] = struct{}{}
|
||||
|
||||
q.headerTaskQueue.Push(request.From, -int64(request.From))
|
||||
return 0, errors.New("delivery not accepted")
|
||||
}
|
||||
// Clean up a successful fetch and try to deliver any sub-results
|
||||
copy(q.headerResults[request.From-q.headerOffset:], headers)
|
||||
copy(q.headerHashes[request.From-q.headerOffset:], hashes)
|
||||
|
||||
delete(q.headerTaskPool, request.From)
|
||||
|
||||
ready := 0
|
||||
for q.headerProced+ready < len(q.headerResults) && q.headerResults[q.headerProced+ready] != nil {
|
||||
ready += MaxHeaderFetch
|
||||
}
|
||||
if ready > 0 {
|
||||
// Headers are ready for delivery, gather them and push forward (non blocking)
|
||||
processHeaders := make([]*types.Header, ready)
|
||||
copy(processHeaders, q.headerResults[q.headerProced:q.headerProced+ready])
|
||||
|
||||
processHashes := make([]common.Hash, ready)
|
||||
copy(processHashes, q.headerHashes[q.headerProced:q.headerProced+ready])
|
||||
|
||||
select {
|
||||
case headerProcCh <- &headerTask{
|
||||
headers: processHeaders,
|
||||
hashes: processHashes,
|
||||
}:
|
||||
logger.Trace("Pre-scheduled new headers", "count", len(processHeaders), "from", processHeaders[0].Number)
|
||||
q.headerProced += len(processHeaders)
|
||||
default:
|
||||
}
|
||||
}
|
||||
// Check for termination and return
|
||||
if len(q.headerTaskPool) == 0 {
|
||||
q.headerContCh <- false
|
||||
}
|
||||
return len(headers), nil
|
||||
}
|
||||
|
||||
// DeliverBodies injects a block body retrieval response into the results queue.
|
||||
// The method returns the number of blocks bodies accepted from the delivery and
|
||||
// also wakes any threads waiting for data delivery.
|
||||
func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListHashes []common.Hash,
|
||||
uncleLists [][]*types.Header, uncleListHashes []common.Hash,
|
||||
withdrawalLists [][]*types.Withdrawal, withdrawalListHashes []common.Hash) (int, error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
validate := func(index int, header *types.Header) error {
|
||||
if txListHashes[index] != header.TxHash {
|
||||
return errInvalidBody
|
||||
}
|
||||
if uncleListHashes[index] != header.UncleHash {
|
||||
return errInvalidBody
|
||||
}
|
||||
if header.WithdrawalsHash == nil {
|
||||
// nil hash means that withdrawals should not be present in body
|
||||
if withdrawalLists[index] != nil {
|
||||
return errInvalidBody
|
||||
}
|
||||
} else { // non-nil hash: body must have withdrawals
|
||||
if withdrawalLists[index] == nil {
|
||||
return errInvalidBody
|
||||
}
|
||||
if withdrawalListHashes[index] != *header.WithdrawalsHash {
|
||||
return errInvalidBody
|
||||
}
|
||||
}
|
||||
// Blocks must have a number of blobs corresponding to the header gas usage,
|
||||
// and zero before the Cancun hardfork.
|
||||
var blobs int
|
||||
for _, tx := range txLists[index] {
|
||||
// Count the number of blobs to validate against the header's blobGasUsed
|
||||
blobs += len(tx.BlobHashes())
|
||||
|
||||
// Validate the data blobs individually too
|
||||
if tx.Type() == types.BlobTxType {
|
||||
if len(tx.BlobHashes()) == 0 {
|
||||
return errInvalidBody
|
||||
}
|
||||
for _, hash := range tx.BlobHashes() {
|
||||
if hash[0] != params.BlobTxHashVersion {
|
||||
return errInvalidBody
|
||||
}
|
||||
}
|
||||
if tx.BlobTxSidecar() != nil {
|
||||
return errInvalidBody
|
||||
}
|
||||
}
|
||||
}
|
||||
if header.BlobGasUsed != nil {
|
||||
if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated
|
||||
return errInvalidBody
|
||||
}
|
||||
} else {
|
||||
if blobs != 0 {
|
||||
return errInvalidBody
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
reconstruct := func(index int, result *fetchResult) {
|
||||
result.Transactions = txLists[index]
|
||||
result.Uncles = uncleLists[index]
|
||||
result.Withdrawals = withdrawalLists[index]
|
||||
result.SetBodyDone()
|
||||
}
|
||||
return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool,
|
||||
bodyReqTimer, bodyInMeter, bodyDropMeter, len(txLists), validate, reconstruct)
|
||||
}
|
||||
|
||||
// DeliverReceipts injects a receipt retrieval response into the results queue.
|
||||
// The method returns the number of transaction receipts accepted from the delivery
|
||||
// and also wakes any threads waiting for data delivery.
|
||||
func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt, receiptListHashes []common.Hash) (int, error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
validate := func(index int, header *types.Header) error {
|
||||
if receiptListHashes[index] != header.ReceiptHash {
|
||||
return errInvalidReceipt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
reconstruct := func(index int, result *fetchResult) {
|
||||
result.Receipts = receiptList[index]
|
||||
result.SetReceiptsDone()
|
||||
}
|
||||
return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool,
|
||||
receiptReqTimer, receiptInMeter, receiptDropMeter, len(receiptList), validate, reconstruct)
|
||||
}
|
||||
|
||||
// deliver injects a data retrieval response into the results queue.
|
||||
//
|
||||
// Note, this method expects the queue lock to be already held for writing. The
|
||||
// reason this lock is not obtained in here is because the parameters already need
|
||||
// to access the queue, so they already need a lock anyway.
|
||||
func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
||||
taskQueue *prque.Prque[int64, *types.Header], pendPool map[string]*fetchRequest,
|
||||
reqTimer metrics.Timer, resInMeter metrics.Meter, resDropMeter metrics.Meter,
|
||||
results int, validate func(index int, header *types.Header) error,
|
||||
reconstruct func(index int, result *fetchResult)) (int, error) {
|
||||
// Short circuit if the data was never requested
|
||||
request := pendPool[id]
|
||||
if request == nil {
|
||||
resDropMeter.Mark(int64(results))
|
||||
return 0, errNoFetchesPending
|
||||
}
|
||||
delete(pendPool, id)
|
||||
|
||||
reqTimer.UpdateSince(request.Time)
|
||||
resInMeter.Mark(int64(results))
|
||||
|
||||
// If no data items were retrieved, mark them as unavailable for the origin peer
|
||||
if results == 0 {
|
||||
for _, header := range request.Headers {
|
||||
request.Peer.MarkLacking(header.Hash())
|
||||
}
|
||||
}
|
||||
// Assemble each of the results with their headers and retrieved data parts
|
||||
var (
|
||||
accepted int
|
||||
failure error
|
||||
i int
|
||||
hashes []common.Hash
|
||||
)
|
||||
for _, header := range request.Headers {
|
||||
// Short circuit assembly if no more fetch results are found
|
||||
if i >= results {
|
||||
break
|
||||
}
|
||||
// Validate the fields
|
||||
if err := validate(i, header); err != nil {
|
||||
failure = err
|
||||
break
|
||||
}
|
||||
hashes = append(hashes, header.Hash())
|
||||
i++
|
||||
}
|
||||
|
||||
for _, header := range request.Headers[:i] {
|
||||
if res, stale, err := q.resultCache.GetDeliverySlot(header.Number.Uint64()); err == nil && !stale {
|
||||
reconstruct(accepted, res)
|
||||
} else {
|
||||
// else: between here and above, some other peer filled this result,
|
||||
// or it was indeed a no-op. This should not happen, but if it does it's
|
||||
// not something to panic about
|
||||
log.Error("Delivery stale", "stale", stale, "number", header.Number.Uint64(), "err", err)
|
||||
failure = errStaleDelivery
|
||||
}
|
||||
// Clean up a successful fetch
|
||||
delete(taskPool, hashes[accepted])
|
||||
accepted++
|
||||
}
|
||||
resDropMeter.Mark(int64(results - accepted))
|
||||
|
||||
// Return all failed or missing fetches to the queue
|
||||
for _, header := range request.Headers[accepted:] {
|
||||
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
// Wake up Results
|
||||
if accepted > 0 {
|
||||
q.active.Signal()
|
||||
}
|
||||
if failure == nil {
|
||||
return accepted, nil
|
||||
}
|
||||
// If none of the data was good, it's a stale delivery
|
||||
if accepted > 0 {
|
||||
return accepted, fmt.Errorf("partial failure: %v", failure)
|
||||
}
|
||||
return accepted, fmt.Errorf("%w: %v", failure, errStaleDelivery)
|
||||
}
|
||||
|
||||
// Prepare configures the result cache to allow accepting and caching inbound
|
||||
// fetch results.
|
||||
func (q *queue) Prepare(offset uint64, mode SyncMode) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
// Prepare the queue for sync results
|
||||
q.resultCache.Prepare(offset)
|
||||
q.mode = mode
|
||||
}
|
||||
|
|
@ -1,474 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// makeChain creates a chain of n blocks starting at and including parent.
|
||||
// the returned hash chain is ordered head->parent. In addition, every 3rd block
|
||||
// contains a transaction and every 5th an uncle to allow testing correct block
|
||||
// reassembly.
|
||||
func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Block, []types.Receipts) {
|
||||
blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) {
|
||||
block.SetCoinbase(common.Address{seed})
|
||||
// Add one tx to every secondblock
|
||||
if !empty && i%2 == 0 {
|
||||
signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp())
|
||||
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
block.AddTx(tx)
|
||||
}
|
||||
})
|
||||
return blocks, receipts
|
||||
}
|
||||
|
||||
type chainData struct {
|
||||
blocks []*types.Block
|
||||
offset int
|
||||
}
|
||||
|
||||
var chain *chainData
|
||||
var emptyChain *chainData
|
||||
|
||||
func init() {
|
||||
// Create a chain of blocks to import
|
||||
targetBlocks := 128
|
||||
blocks, _ := makeChain(targetBlocks, 0, testGenesis, false)
|
||||
chain = &chainData{blocks, 0}
|
||||
|
||||
blocks, _ = makeChain(targetBlocks, 0, testGenesis, true)
|
||||
emptyChain = &chainData{blocks, 0}
|
||||
}
|
||||
|
||||
func (chain *chainData) headers() []*types.Header {
|
||||
hdrs := make([]*types.Header, len(chain.blocks))
|
||||
for i, b := range chain.blocks {
|
||||
hdrs[i] = b.Header()
|
||||
}
|
||||
return hdrs
|
||||
}
|
||||
|
||||
func (chain *chainData) Len() int {
|
||||
return len(chain.blocks)
|
||||
}
|
||||
|
||||
func dummyPeer(id string) *peerConnection {
|
||||
p := &peerConnection{
|
||||
id: id,
|
||||
lacking: make(map[common.Hash]struct{}),
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func TestBasics(t *testing.T) {
|
||||
numOfBlocks := len(emptyChain.blocks)
|
||||
numOfReceipts := len(emptyChain.blocks) / 2
|
||||
|
||||
q := newQueue(10, 10)
|
||||
if !q.Idle() {
|
||||
t.Errorf("new queue should be idle")
|
||||
}
|
||||
q.Prepare(1, SnapSync)
|
||||
if res := q.Results(false); len(res) != 0 {
|
||||
t.Fatal("new queue should have 0 results")
|
||||
}
|
||||
|
||||
// Schedule a batch of headers
|
||||
headers := chain.headers()
|
||||
hashes := make([]common.Hash, len(headers))
|
||||
for i, header := range headers {
|
||||
hashes[i] = header.Hash()
|
||||
}
|
||||
q.Schedule(headers, hashes, 1)
|
||||
if q.Idle() {
|
||||
t.Errorf("queue should not be idle")
|
||||
}
|
||||
if got, exp := q.PendingBodies(), chain.Len(); got != exp {
|
||||
t.Errorf("wrong pending block count, got %d, exp %d", got, exp)
|
||||
}
|
||||
// Only non-empty receipts get added to task-queue
|
||||
if got, exp := q.PendingReceipts(), 64; got != exp {
|
||||
t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp)
|
||||
}
|
||||
// Items are now queued for downloading, next step is that we tell the
|
||||
// queue that a certain peer will deliver them for us
|
||||
{
|
||||
peer := dummyPeer("peer-1")
|
||||
fetchReq, _, throttle := q.ReserveBodies(peer, 50)
|
||||
if !throttle {
|
||||
// queue size is only 10, so throttling should occur
|
||||
t.Fatal("should throttle")
|
||||
}
|
||||
// But we should still get the first things to fetch
|
||||
if got, exp := len(fetchReq.Headers), 5; got != exp {
|
||||
t.Fatalf("expected %d requests, got %d", exp, got)
|
||||
}
|
||||
if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp {
|
||||
t.Fatalf("expected header %d, got %d", exp, got)
|
||||
}
|
||||
}
|
||||
if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got {
|
||||
t.Errorf("expected block task queue to be %d, got %d", exp, got)
|
||||
}
|
||||
if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got {
|
||||
t.Errorf("expected receipt task queue to be %d, got %d", exp, got)
|
||||
}
|
||||
{
|
||||
peer := dummyPeer("peer-2")
|
||||
fetchReq, _, throttle := q.ReserveBodies(peer, 50)
|
||||
|
||||
// The second peer should hit throttling
|
||||
if !throttle {
|
||||
t.Fatalf("should throttle")
|
||||
}
|
||||
// And not get any fetches at all, since it was throttled to begin with
|
||||
if fetchReq != nil {
|
||||
t.Fatalf("should have no fetches, got %d", len(fetchReq.Headers))
|
||||
}
|
||||
}
|
||||
if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got {
|
||||
t.Errorf("expected block task queue to be %d, got %d", exp, got)
|
||||
}
|
||||
if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got {
|
||||
t.Errorf("expected receipt task queue to be %d, got %d", exp, got)
|
||||
}
|
||||
{
|
||||
// The receipt delivering peer should not be affected
|
||||
// by the throttling of body deliveries
|
||||
peer := dummyPeer("peer-3")
|
||||
fetchReq, _, throttle := q.ReserveReceipts(peer, 50)
|
||||
if !throttle {
|
||||
// queue size is only 10, so throttling should occur
|
||||
t.Fatal("should throttle")
|
||||
}
|
||||
// But we should still get the first things to fetch
|
||||
if got, exp := len(fetchReq.Headers), 5; got != exp {
|
||||
t.Fatalf("expected %d requests, got %d", exp, got)
|
||||
}
|
||||
if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp {
|
||||
t.Fatalf("expected header %d, got %d", exp, got)
|
||||
}
|
||||
}
|
||||
if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got {
|
||||
t.Errorf("expected block task queue to be %d, got %d", exp, got)
|
||||
}
|
||||
if exp, got := q.receiptTaskQueue.Size(), numOfReceipts-5; exp != got {
|
||||
t.Errorf("expected receipt task queue to be %d, got %d", exp, got)
|
||||
}
|
||||
if got, exp := q.resultCache.countCompleted(), 0; got != exp {
|
||||
t.Errorf("wrong processable count, got %d, exp %d", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyBlocks(t *testing.T) {
|
||||
numOfBlocks := len(emptyChain.blocks)
|
||||
|
||||
q := newQueue(10, 10)
|
||||
|
||||
q.Prepare(1, SnapSync)
|
||||
|
||||
// Schedule a batch of headers
|
||||
headers := emptyChain.headers()
|
||||
hashes := make([]common.Hash, len(headers))
|
||||
for i, header := range headers {
|
||||
hashes[i] = header.Hash()
|
||||
}
|
||||
q.Schedule(headers, hashes, 1)
|
||||
if q.Idle() {
|
||||
t.Errorf("queue should not be idle")
|
||||
}
|
||||
if got, exp := q.PendingBodies(), len(emptyChain.blocks); got != exp {
|
||||
t.Errorf("wrong pending block count, got %d, exp %d", got, exp)
|
||||
}
|
||||
if got, exp := q.PendingReceipts(), 0; got != exp {
|
||||
t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp)
|
||||
}
|
||||
// They won't be processable, because the fetchresults haven't been
|
||||
// created yet
|
||||
if got, exp := q.resultCache.countCompleted(), 0; got != exp {
|
||||
t.Errorf("wrong processable count, got %d, exp %d", got, exp)
|
||||
}
|
||||
|
||||
// Items are now queued for downloading, next step is that we tell the
|
||||
// queue that a certain peer will deliver them for us
|
||||
// That should trigger all of them to suddenly become 'done'
|
||||
{
|
||||
// Reserve blocks
|
||||
peer := dummyPeer("peer-1")
|
||||
fetchReq, _, _ := q.ReserveBodies(peer, 50)
|
||||
|
||||
// there should be nothing to fetch, blocks are empty
|
||||
if fetchReq != nil {
|
||||
t.Fatal("there should be no body fetch tasks remaining")
|
||||
}
|
||||
}
|
||||
if q.blockTaskQueue.Size() != numOfBlocks-10 {
|
||||
t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size())
|
||||
}
|
||||
if q.receiptTaskQueue.Size() != 0 {
|
||||
t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size())
|
||||
}
|
||||
{
|
||||
peer := dummyPeer("peer-3")
|
||||
fetchReq, _, _ := q.ReserveReceipts(peer, 50)
|
||||
|
||||
// there should be nothing to fetch, blocks are empty
|
||||
if fetchReq != nil {
|
||||
t.Fatal("there should be no receipt fetch tasks remaining")
|
||||
}
|
||||
}
|
||||
if q.blockTaskQueue.Size() != numOfBlocks-10 {
|
||||
t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size())
|
||||
}
|
||||
if q.receiptTaskQueue.Size() != 0 {
|
||||
t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size())
|
||||
}
|
||||
if got, exp := q.resultCache.countCompleted(), 10; got != exp {
|
||||
t.Errorf("wrong processable count, got %d, exp %d", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
// XTestDelivery does some more extensive testing of events that happen,
|
||||
// blocks that become known and peers that make reservations and deliveries.
|
||||
// disabled since it's not really a unit-test, but can be executed to test
|
||||
// some more advanced scenarios
|
||||
func XTestDelivery(t *testing.T) {
|
||||
// the outside network, holding blocks
|
||||
blo, rec := makeChain(128, 0, testGenesis, false)
|
||||
world := newNetwork()
|
||||
world.receipts = rec
|
||||
world.chain = blo
|
||||
world.progress(10)
|
||||
if false {
|
||||
log.SetDefault(log.NewLogger(slog.NewTextHandler(os.Stdout, nil)))
|
||||
}
|
||||
q := newQueue(10, 10)
|
||||
var wg sync.WaitGroup
|
||||
q.Prepare(1, SnapSync)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
// deliver headers
|
||||
defer wg.Done()
|
||||
c := 1
|
||||
for {
|
||||
//fmt.Printf("getting headers from %d\n", c)
|
||||
headers := world.headers(c)
|
||||
hashes := make([]common.Hash, len(headers))
|
||||
for i, header := range headers {
|
||||
hashes[i] = header.Hash()
|
||||
}
|
||||
l := len(headers)
|
||||
//fmt.Printf("scheduling %d headers, first %d last %d\n",
|
||||
// l, headers[0].Number.Uint64(), headers[len(headers)-1].Number.Uint64())
|
||||
q.Schedule(headers, hashes, uint64(c))
|
||||
c += l
|
||||
}
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
// collect results
|
||||
defer wg.Done()
|
||||
tot := 0
|
||||
for {
|
||||
res := q.Results(true)
|
||||
tot += len(res)
|
||||
fmt.Printf("got %d results, %d tot\n", len(res), tot)
|
||||
// Now we can forget about these
|
||||
world.forget(res[len(res)-1].Header.Number.Uint64())
|
||||
}
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
// reserve body fetch
|
||||
i := 4
|
||||
for {
|
||||
peer := dummyPeer(fmt.Sprintf("peer-%d", i))
|
||||
f, _, _ := q.ReserveBodies(peer, rand.Intn(30))
|
||||
if f != nil {
|
||||
var (
|
||||
emptyList []*types.Header
|
||||
txset [][]*types.Transaction
|
||||
uncleset [][]*types.Header
|
||||
)
|
||||
numToSkip := rand.Intn(len(f.Headers))
|
||||
for _, hdr := range f.Headers[0 : len(f.Headers)-numToSkip] {
|
||||
txset = append(txset, world.getTransactions(hdr.Number.Uint64()))
|
||||
uncleset = append(uncleset, emptyList)
|
||||
}
|
||||
var (
|
||||
txsHashes = make([]common.Hash, len(txset))
|
||||
uncleHashes = make([]common.Hash, len(uncleset))
|
||||
)
|
||||
hasher := trie.NewStackTrie(nil)
|
||||
for i, txs := range txset {
|
||||
txsHashes[i] = types.DeriveSha(types.Transactions(txs), hasher)
|
||||
}
|
||||
for i, uncles := range uncleset {
|
||||
uncleHashes[i] = types.CalcUncleHash(uncles)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, err := q.DeliverBodies(peer.id, txset, txsHashes, uncleset, uncleHashes, nil, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("delivered %d bodies %v\n", len(txset), err)
|
||||
}
|
||||
} else {
|
||||
i++
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
// reserve receiptfetch
|
||||
peer := dummyPeer("peer-3")
|
||||
for {
|
||||
f, _, _ := q.ReserveReceipts(peer, rand.Intn(50))
|
||||
if f != nil {
|
||||
var rcs [][]*types.Receipt
|
||||
for _, hdr := range f.Headers {
|
||||
rcs = append(rcs, world.getReceipts(hdr.Number.Uint64()))
|
||||
}
|
||||
hasher := trie.NewStackTrie(nil)
|
||||
hashes := make([]common.Hash, len(rcs))
|
||||
for i, receipt := range rcs {
|
||||
hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher)
|
||||
}
|
||||
_, err := q.DeliverReceipts(peer.id, rcs, hashes)
|
||||
if err != nil {
|
||||
fmt.Printf("delivered %d receipts %v\n", len(rcs), err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
} else {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 50; i++ {
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
//world.tick()
|
||||
//fmt.Printf("trying to progress\n")
|
||||
world.progress(rand.Intn(100))
|
||||
}
|
||||
for i := 0; i < 50; i++ {
|
||||
time.Sleep(2990 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
time.Sleep(990 * time.Millisecond)
|
||||
fmt.Printf("world block tip is %d\n",
|
||||
world.chain[len(world.chain)-1].Header().Number.Uint64())
|
||||
fmt.Println(q.Stats())
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func newNetwork() *network {
|
||||
var l sync.RWMutex
|
||||
return &network{
|
||||
cond: sync.NewCond(&l),
|
||||
offset: 1, // block 1 is at blocks[0]
|
||||
}
|
||||
}
|
||||
|
||||
// represents the network
|
||||
type network struct {
|
||||
offset int
|
||||
chain []*types.Block
|
||||
receipts []types.Receipts
|
||||
lock sync.RWMutex
|
||||
cond *sync.Cond
|
||||
}
|
||||
|
||||
func (n *network) getTransactions(blocknum uint64) types.Transactions {
|
||||
index := blocknum - uint64(n.offset)
|
||||
return n.chain[index].Transactions()
|
||||
}
|
||||
func (n *network) getReceipts(blocknum uint64) types.Receipts {
|
||||
index := blocknum - uint64(n.offset)
|
||||
if got := n.chain[index].Header().Number.Uint64(); got != blocknum {
|
||||
fmt.Printf("Err, got %d exp %d\n", got, blocknum)
|
||||
panic("sd")
|
||||
}
|
||||
return n.receipts[index]
|
||||
}
|
||||
|
||||
func (n *network) forget(blocknum uint64) {
|
||||
index := blocknum - uint64(n.offset)
|
||||
n.chain = n.chain[index:]
|
||||
n.receipts = n.receipts[index:]
|
||||
n.offset = int(blocknum)
|
||||
}
|
||||
func (n *network) progress(numBlocks int) {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
//fmt.Printf("progressing...\n")
|
||||
newBlocks, newR := makeChain(numBlocks, 0, n.chain[len(n.chain)-1], false)
|
||||
n.chain = append(n.chain, newBlocks...)
|
||||
n.receipts = append(n.receipts, newR...)
|
||||
n.cond.Broadcast()
|
||||
}
|
||||
|
||||
func (n *network) headers(from int) []*types.Header {
|
||||
numHeaders := 128
|
||||
var hdrs []*types.Header
|
||||
index := from - n.offset
|
||||
|
||||
for index >= len(n.chain) {
|
||||
// wait for progress
|
||||
n.cond.L.Lock()
|
||||
//fmt.Printf("header going into wait\n")
|
||||
n.cond.Wait()
|
||||
index = from - n.offset
|
||||
n.cond.L.Unlock()
|
||||
}
|
||||
n.lock.RLock()
|
||||
defer n.lock.RUnlock()
|
||||
for i, b := range n.chain[index:] {
|
||||
hdrs = append(hdrs, b.Header())
|
||||
if i >= numHeaders {
|
||||
break
|
||||
}
|
||||
}
|
||||
return hdrs
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// resultStore implements a structure for maintaining fetchResults, tracking their
|
||||
// download-progress and delivering (finished) results.
|
||||
type resultStore struct {
|
||||
items []*fetchResult // Downloaded but not yet delivered fetch results
|
||||
resultOffset uint64 // Offset of the first cached fetch result in the block chain
|
||||
|
||||
// Internal index of first non-completed entry, updated atomically when needed.
|
||||
// If all items are complete, this will equal length(items), so
|
||||
// *important* : is not safe to use for indexing without checking against length
|
||||
indexIncomplete atomic.Int32
|
||||
|
||||
// throttleThreshold is the limit up to which we _want_ to fill the
|
||||
// results. If blocks are large, we want to limit the results to less
|
||||
// than the number of available slots, and maybe only fill 1024 out of
|
||||
// 8192 possible places. The queue will, at certain times, recalibrate
|
||||
// this index.
|
||||
throttleThreshold uint64
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func newResultStore(size int) *resultStore {
|
||||
return &resultStore{
|
||||
resultOffset: 0,
|
||||
items: make([]*fetchResult, size),
|
||||
throttleThreshold: uint64(size),
|
||||
}
|
||||
}
|
||||
|
||||
// SetThrottleThreshold updates the throttling threshold based on the requested
|
||||
// limit and the total queue capacity. It returns the (possibly capped) threshold
|
||||
func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
limit := uint64(len(r.items))
|
||||
if threshold >= limit {
|
||||
threshold = limit
|
||||
}
|
||||
r.throttleThreshold = threshold
|
||||
return r.throttleThreshold
|
||||
}
|
||||
|
||||
// AddFetch adds a header for body/receipt fetching. This is used when the queue
|
||||
// wants to reserve headers for fetching.
|
||||
//
|
||||
// It returns the following:
|
||||
//
|
||||
// stale - if true, this item is already passed, and should not be requested again
|
||||
// throttled - if true, the store is at capacity, this particular header is not prio now
|
||||
// item - the result to store data into
|
||||
// err - any error that occurred
|
||||
func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
var index int
|
||||
item, index, stale, throttled, err = r.getFetchResult(header.Number.Uint64())
|
||||
if err != nil || stale || throttled {
|
||||
return stale, throttled, item, err
|
||||
}
|
||||
if item == nil {
|
||||
item = newFetchResult(header, fastSync)
|
||||
r.items[index] = item
|
||||
}
|
||||
return stale, throttled, item, err
|
||||
}
|
||||
|
||||
// GetDeliverySlot returns the fetchResult for the given header. If the 'stale' flag
|
||||
// is true, that means the header has already been delivered 'upstream'. This method
|
||||
// does not bubble up the 'throttle' flag, since it's moot at the point in time when
|
||||
// the item is downloaded and ready for delivery
|
||||
func (r *resultStore) GetDeliverySlot(headerNumber uint64) (*fetchResult, bool, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
res, _, stale, _, err := r.getFetchResult(headerNumber)
|
||||
return res, stale, err
|
||||
}
|
||||
|
||||
// getFetchResult returns the fetchResult corresponding to the given item, and
|
||||
// the index where the result is stored.
|
||||
func (r *resultStore) getFetchResult(headerNumber uint64) (item *fetchResult, index int, stale, throttle bool, err error) {
|
||||
index = int(int64(headerNumber) - int64(r.resultOffset))
|
||||
throttle = index >= int(r.throttleThreshold)
|
||||
stale = index < 0
|
||||
|
||||
if index >= len(r.items) {
|
||||
err = fmt.Errorf("%w: index allocation went beyond available resultStore space "+
|
||||
"(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d", errInvalidChain,
|
||||
index, headerNumber, r.resultOffset, len(r.items))
|
||||
return nil, index, stale, throttle, err
|
||||
}
|
||||
if stale {
|
||||
return nil, index, stale, throttle, nil
|
||||
}
|
||||
item = r.items[index]
|
||||
return item, index, stale, throttle, nil
|
||||
}
|
||||
|
||||
// HasCompletedItems returns true if there are processable items available
|
||||
// this method is cheaper than countCompleted
|
||||
func (r *resultStore) HasCompletedItems() bool {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if len(r.items) == 0 {
|
||||
return false
|
||||
}
|
||||
if item := r.items[0]; item != nil && item.AllDone() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// countCompleted returns the number of items ready for delivery, stopping at
|
||||
// the first non-complete item.
|
||||
//
|
||||
// The method assumes (at least) rlock is held.
|
||||
func (r *resultStore) countCompleted() int {
|
||||
// We iterate from the already known complete point, and see
|
||||
// if any more has completed since last count
|
||||
index := r.indexIncomplete.Load()
|
||||
for ; ; index++ {
|
||||
if index >= int32(len(r.items)) {
|
||||
break
|
||||
}
|
||||
result := r.items[index]
|
||||
if result == nil || !result.AllDone() {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.indexIncomplete.Store(index)
|
||||
return int(index)
|
||||
}
|
||||
|
||||
// GetCompleted returns the next batch of completed fetchResults
|
||||
func (r *resultStore) GetCompleted(limit int) []*fetchResult {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
completed := r.countCompleted()
|
||||
if limit > completed {
|
||||
limit = completed
|
||||
}
|
||||
results := make([]*fetchResult, limit)
|
||||
copy(results, r.items[:limit])
|
||||
|
||||
// Delete the results from the cache and clear the tail.
|
||||
copy(r.items, r.items[limit:])
|
||||
for i := len(r.items) - limit; i < len(r.items); i++ {
|
||||
r.items[i] = nil
|
||||
}
|
||||
// Advance the expected block number of the first cache entry
|
||||
r.resultOffset += uint64(limit)
|
||||
r.indexIncomplete.Add(int32(-limit))
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// Prepare initialises the offset with the given block number
|
||||
func (r *resultStore) Prepare(offset uint64) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.resultOffset < offset {
|
||||
r.resultOffset = offset
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,975 +0,0 @@
|
|||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// hookedBackfiller is a tester backfiller with all interface methods mocked and
|
||||
// hooked so tests can implement only the things they need.
|
||||
type hookedBackfiller struct {
|
||||
// suspendHook is an optional hook to be called when the filler is requested
|
||||
// to be suspended.
|
||||
suspendHook func() *types.Header
|
||||
|
||||
// resumeHook is an optional hook to be called when the filler is requested
|
||||
// to be resumed.
|
||||
resumeHook func()
|
||||
}
|
||||
|
||||
// newHookedBackfiller creates a hooked backfiller with all callbacks disabled,
|
||||
// essentially acting as a noop.
|
||||
func newHookedBackfiller() backfiller {
|
||||
return new(hookedBackfiller)
|
||||
}
|
||||
|
||||
// suspend requests the backfiller to abort any running full or snap sync
|
||||
// based on the skeleton chain as it might be invalid. The backfiller should
|
||||
// gracefully handle multiple consecutive suspends without a resume, even
|
||||
// on initial startup.
|
||||
func (hf *hookedBackfiller) suspend() *types.Header {
|
||||
if hf.suspendHook != nil {
|
||||
return hf.suspendHook()
|
||||
}
|
||||
return nil // we don't really care about header cleanups for now
|
||||
}
|
||||
|
||||
// resume requests the backfiller to start running fill or snap sync based on
|
||||
// the skeleton chain as it has successfully been linked. Appending new heads
|
||||
// to the end of the chain will not result in suspend/resume cycles.
|
||||
func (hf *hookedBackfiller) resume() {
|
||||
if hf.resumeHook != nil {
|
||||
hf.resumeHook()
|
||||
}
|
||||
}
|
||||
|
||||
// skeletonTestPeer is a mock peer that can only serve header requests from a
|
||||
// pre-perated header chain (which may be arbitrarily wrong for testing).
|
||||
//
|
||||
// Requesting anything else from these peers will hard panic. Note, do *not*
|
||||
// implement any other methods. We actually want to make sure that the skeleton
|
||||
// syncer only depends on - and will only ever do so - on header requests.
|
||||
type skeletonTestPeer struct {
|
||||
id string // Unique identifier of the mock peer
|
||||
headers []*types.Header // Headers to serve when requested
|
||||
|
||||
serve func(origin uint64) []*types.Header // Hook to allow custom responses
|
||||
|
||||
served atomic.Uint64 // Number of headers served by this peer
|
||||
dropped atomic.Uint64 // Flag whether the peer was dropped (stop responding)
|
||||
}
|
||||
|
||||
// newSkeletonTestPeer creates a new mock peer to test the skeleton sync with.
|
||||
func newSkeletonTestPeer(id string, headers []*types.Header) *skeletonTestPeer {
|
||||
return &skeletonTestPeer{
|
||||
id: id,
|
||||
headers: headers,
|
||||
}
|
||||
}
|
||||
|
||||
// newSkeletonTestPeer creates a new mock peer to test the skeleton sync with,
|
||||
// and sets an optional serve hook that can return headers for delivery instead
|
||||
// of the predefined chain. Useful for emulating malicious behavior that would
|
||||
// otherwise require dedicated peer types.
|
||||
func newSkeletonTestPeerWithHook(id string, headers []*types.Header, serve func(origin uint64) []*types.Header) *skeletonTestPeer {
|
||||
return &skeletonTestPeer{
|
||||
id: id,
|
||||
headers: headers,
|
||||
serve: serve,
|
||||
}
|
||||
}
|
||||
|
||||
// RequestHeadersByNumber constructs a GetBlockHeaders function based on a numbered
|
||||
// origin; associated with a particular peer in the download tester. The returned
|
||||
// function can be used to retrieve batches of headers from the particular peer.
|
||||
func (p *skeletonTestPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) {
|
||||
// Since skeleton test peer are in-memory mocks, dropping the does not make
|
||||
// them inaccessible. As such, check a local `dropped` field to see if the
|
||||
// peer has been dropped and should not respond any more.
|
||||
if p.dropped.Load() != 0 {
|
||||
return nil, errors.New("peer already dropped")
|
||||
}
|
||||
// Skeleton sync retrieves batches of headers going backward without gaps.
|
||||
// This ensures we can follow a clean parent progression without any reorg
|
||||
// hiccups. There is no need for any other type of header retrieval, so do
|
||||
// panic if there's such a request.
|
||||
if !reverse || skip != 0 {
|
||||
// Note, if other clients want to do these kinds of requests, it's their
|
||||
// problem, it will still work. We just don't want *us* making complicated
|
||||
// requests without a very strong reason to.
|
||||
panic(fmt.Sprintf("invalid header retrieval: reverse %v, want true; skip %d, want 0", reverse, skip))
|
||||
}
|
||||
// If the skeleton syncer requests the genesis block, panic. Whilst it could
|
||||
// be considered a valid request, our code specifically should not request it
|
||||
// ever since we want to link up headers to an existing local chain, which at
|
||||
// worse will be the genesis.
|
||||
if int64(origin)-int64(amount) < 0 {
|
||||
panic(fmt.Sprintf("headers requested before (or at) genesis: origin %d, amount %d", origin, amount))
|
||||
}
|
||||
// To make concurrency easier, the skeleton syncer always requests fixed size
|
||||
// batches of headers. Panic if the peer is requested an amount other than the
|
||||
// configured batch size (apart from the request leading to the genesis).
|
||||
if amount > requestHeaders || (amount < requestHeaders && origin > uint64(amount)) {
|
||||
panic(fmt.Sprintf("non-chunk size header batch requested: requested %d, want %d, origin %d", amount, requestHeaders, origin))
|
||||
}
|
||||
// Simple reverse header retrieval. Fill from the peer's chain and return.
|
||||
// If the tester has a serve hook set, try to use that before falling back
|
||||
// to the default behavior.
|
||||
var headers []*types.Header
|
||||
if p.serve != nil {
|
||||
headers = p.serve(origin)
|
||||
}
|
||||
if headers == nil {
|
||||
headers = make([]*types.Header, 0, amount)
|
||||
if len(p.headers) > int(origin) { // Don't serve headers if we're missing the origin
|
||||
for i := 0; i < amount; i++ {
|
||||
// Consider nil headers as a form of attack and withhold them. Nil
|
||||
// cannot be decoded from RLP, so it's not possible to produce an
|
||||
// attack by sending/receiving those over eth.
|
||||
header := p.headers[int(origin)-i]
|
||||
if header == nil {
|
||||
continue
|
||||
}
|
||||
headers = append(headers, header)
|
||||
}
|
||||
}
|
||||
}
|
||||
p.served.Add(uint64(len(headers)))
|
||||
|
||||
hashes := make([]common.Hash, len(headers))
|
||||
for i, header := range headers {
|
||||
hashes[i] = header.Hash()
|
||||
}
|
||||
// Deliver the headers to the downloader
|
||||
req := ð.Request{
|
||||
Peer: p.id,
|
||||
}
|
||||
res := ð.Response{
|
||||
Req: req,
|
||||
Res: (*eth.BlockHeadersRequest)(&headers),
|
||||
Meta: hashes,
|
||||
Time: 1,
|
||||
Done: make(chan error),
|
||||
}
|
||||
go func() {
|
||||
sink <- res
|
||||
if err := <-res.Done; err != nil {
|
||||
log.Warn("Skeleton test peer response rejected", "err", err)
|
||||
p.dropped.Add(1)
|
||||
}
|
||||
}()
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (p *skeletonTestPeer) Head() (common.Hash, *big.Int) {
|
||||
panic("skeleton sync must not request the remote head")
|
||||
}
|
||||
|
||||
func (p *skeletonTestPeer) RequestHeadersByHash(common.Hash, int, int, bool, chan *eth.Response) (*eth.Request, error) {
|
||||
panic("skeleton sync must not request headers by hash")
|
||||
}
|
||||
|
||||
func (p *skeletonTestPeer) RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error) {
|
||||
panic("skeleton sync must not request block bodies")
|
||||
}
|
||||
|
||||
func (p *skeletonTestPeer) RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error) {
|
||||
panic("skeleton sync must not request receipts")
|
||||
}
|
||||
|
||||
// Tests various sync initializations based on previous leftovers in the database
|
||||
// and announced heads.
|
||||
func TestSkeletonSyncInit(t *testing.T) {
|
||||
// Create a few key headers
|
||||
var (
|
||||
genesis = &types.Header{Number: big.NewInt(0)}
|
||||
block49 = &types.Header{Number: big.NewInt(49)}
|
||||
block49B = &types.Header{Number: big.NewInt(49), Extra: []byte("B")}
|
||||
block50 = &types.Header{Number: big.NewInt(50), ParentHash: block49.Hash()}
|
||||
)
|
||||
tests := []struct {
|
||||
headers []*types.Header // Database content (beside the genesis)
|
||||
oldstate []*subchain // Old sync state with various interrupted subchains
|
||||
head *types.Header // New head header to announce to reorg to
|
||||
newstate []*subchain // Expected sync state after the reorg
|
||||
}{
|
||||
// Completely empty database with only the genesis set. The sync is expected
|
||||
// to create a single subchain with the requested head.
|
||||
{
|
||||
head: block50,
|
||||
newstate: []*subchain{{Head: 50, Tail: 50}},
|
||||
},
|
||||
// Empty database with only the genesis set with a leftover empty sync
|
||||
// progress. This is a synthetic case, just for the sake of covering things.
|
||||
{
|
||||
oldstate: []*subchain{},
|
||||
head: block50,
|
||||
newstate: []*subchain{{Head: 50, Tail: 50}},
|
||||
},
|
||||
// A single leftover subchain is present, older than the new head. The
|
||||
// old subchain should be left as is and a new one appended to the sync
|
||||
// status.
|
||||
{
|
||||
oldstate: []*subchain{{Head: 10, Tail: 5}},
|
||||
head: block50,
|
||||
newstate: []*subchain{
|
||||
{Head: 50, Tail: 50},
|
||||
{Head: 10, Tail: 5},
|
||||
},
|
||||
},
|
||||
// Multiple leftover subchains are present, older than the new head. The
|
||||
// old subchains should be left as is and a new one appended to the sync
|
||||
// status.
|
||||
{
|
||||
oldstate: []*subchain{
|
||||
{Head: 20, Tail: 15},
|
||||
{Head: 10, Tail: 5},
|
||||
},
|
||||
head: block50,
|
||||
newstate: []*subchain{
|
||||
{Head: 50, Tail: 50},
|
||||
{Head: 20, Tail: 15},
|
||||
{Head: 10, Tail: 5},
|
||||
},
|
||||
},
|
||||
// A single leftover subchain is present, newer than the new head. The
|
||||
// newer subchain should be deleted and a fresh one created for the head.
|
||||
{
|
||||
oldstate: []*subchain{{Head: 65, Tail: 60}},
|
||||
head: block50,
|
||||
newstate: []*subchain{{Head: 50, Tail: 50}},
|
||||
},
|
||||
// Multiple leftover subchain is present, newer than the new head. The
|
||||
// newer subchains should be deleted and a fresh one created for the head.
|
||||
{
|
||||
oldstate: []*subchain{
|
||||
{Head: 75, Tail: 70},
|
||||
{Head: 65, Tail: 60},
|
||||
},
|
||||
head: block50,
|
||||
newstate: []*subchain{{Head: 50, Tail: 50}},
|
||||
},
|
||||
|
||||
// Two leftover subchains are present, one fully older and one fully
|
||||
// newer than the announced head. The head should delete the newer one,
|
||||
// keeping the older one.
|
||||
{
|
||||
oldstate: []*subchain{
|
||||
{Head: 65, Tail: 60},
|
||||
{Head: 10, Tail: 5},
|
||||
},
|
||||
head: block50,
|
||||
newstate: []*subchain{
|
||||
{Head: 50, Tail: 50},
|
||||
{Head: 10, Tail: 5},
|
||||
},
|
||||
},
|
||||
// Multiple leftover subchains are present, some fully older and some
|
||||
// fully newer than the announced head. The head should delete the newer
|
||||
// ones, keeping the older ones.
|
||||
{
|
||||
oldstate: []*subchain{
|
||||
{Head: 75, Tail: 70},
|
||||
{Head: 65, Tail: 60},
|
||||
{Head: 20, Tail: 15},
|
||||
{Head: 10, Tail: 5},
|
||||
},
|
||||
head: block50,
|
||||
newstate: []*subchain{
|
||||
{Head: 50, Tail: 50},
|
||||
{Head: 20, Tail: 15},
|
||||
{Head: 10, Tail: 5},
|
||||
},
|
||||
},
|
||||
// A single leftover subchain is present and the new head is extending
|
||||
// it with one more header. We expect the subchain head to be pushed
|
||||
// forward.
|
||||
{
|
||||
headers: []*types.Header{block49},
|
||||
oldstate: []*subchain{{Head: 49, Tail: 5}},
|
||||
head: block50,
|
||||
newstate: []*subchain{{Head: 50, Tail: 5}},
|
||||
},
|
||||
// A single leftover subchain is present and although the new head does
|
||||
// extend it number wise, the hash chain does not link up. We expect a
|
||||
// new subchain to be created for the dangling head.
|
||||
{
|
||||
headers: []*types.Header{block49B},
|
||||
oldstate: []*subchain{{Head: 49, Tail: 5}},
|
||||
head: block50,
|
||||
newstate: []*subchain{
|
||||
{Head: 50, Tail: 50},
|
||||
{Head: 49, Tail: 5},
|
||||
},
|
||||
},
|
||||
// A single leftover subchain is present. A new head is announced that
|
||||
// links into the middle of it, correctly anchoring into an existing
|
||||
// header. We expect the old subchain to be truncated and extended with
|
||||
// the new head.
|
||||
{
|
||||
headers: []*types.Header{block49},
|
||||
oldstate: []*subchain{{Head: 100, Tail: 5}},
|
||||
head: block50,
|
||||
newstate: []*subchain{{Head: 50, Tail: 5}},
|
||||
},
|
||||
// A single leftover subchain is present. A new head is announced that
|
||||
// links into the middle of it, but does not anchor into an existing
|
||||
// header. We expect the old subchain to be truncated and a new chain
|
||||
// be created for the dangling head.
|
||||
{
|
||||
headers: []*types.Header{block49B},
|
||||
oldstate: []*subchain{{Head: 100, Tail: 5}},
|
||||
head: block50,
|
||||
newstate: []*subchain{
|
||||
{Head: 50, Tail: 50},
|
||||
{Head: 49, Tail: 5},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
// Create a fresh database and initialize it with the starting state
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
|
||||
rawdb.WriteHeader(db, genesis)
|
||||
for _, header := range tt.headers {
|
||||
rawdb.WriteSkeletonHeader(db, header)
|
||||
}
|
||||
if tt.oldstate != nil {
|
||||
blob, _ := json.Marshal(&skeletonProgress{Subchains: tt.oldstate})
|
||||
rawdb.WriteSkeletonSyncStatus(db, blob)
|
||||
}
|
||||
// Create a skeleton sync and run a cycle
|
||||
wait := make(chan struct{})
|
||||
|
||||
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller())
|
||||
skeleton.syncStarting = func() { close(wait) }
|
||||
skeleton.Sync(tt.head, nil, true)
|
||||
|
||||
<-wait
|
||||
skeleton.Terminate()
|
||||
|
||||
// Ensure the correct resulting sync status
|
||||
var progress skeletonProgress
|
||||
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
||||
|
||||
if len(progress.Subchains) != len(tt.newstate) {
|
||||
t.Errorf("test %d: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.newstate))
|
||||
continue
|
||||
}
|
||||
for j := 0; j < len(progress.Subchains); j++ {
|
||||
if progress.Subchains[j].Head != tt.newstate[j].Head {
|
||||
t.Errorf("test %d: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.newstate[j].Head)
|
||||
}
|
||||
if progress.Subchains[j].Tail != tt.newstate[j].Tail {
|
||||
t.Errorf("test %d: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.newstate[j].Tail)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that a running skeleton sync can be extended with properly linked up
|
||||
// headers but not with side chains.
|
||||
func TestSkeletonSyncExtend(t *testing.T) {
|
||||
// Create a few key headers
|
||||
var (
|
||||
genesis = &types.Header{Number: big.NewInt(0)}
|
||||
block49 = &types.Header{Number: big.NewInt(49)}
|
||||
block49B = &types.Header{Number: big.NewInt(49), Extra: []byte("B")}
|
||||
block50 = &types.Header{Number: big.NewInt(50), ParentHash: block49.Hash()}
|
||||
block51 = &types.Header{Number: big.NewInt(51), ParentHash: block50.Hash()}
|
||||
)
|
||||
tests := []struct {
|
||||
head *types.Header // New head header to announce to reorg to
|
||||
extend *types.Header // New head header to announce to extend with
|
||||
newstate []*subchain // Expected sync state after the reorg
|
||||
err error // Whether extension succeeds or not
|
||||
}{
|
||||
// Initialize a sync and try to extend it with a subsequent block.
|
||||
{
|
||||
head: block49,
|
||||
extend: block50,
|
||||
newstate: []*subchain{
|
||||
{Head: 50, Tail: 49},
|
||||
},
|
||||
},
|
||||
// Initialize a sync and try to extend it with the existing head block.
|
||||
{
|
||||
head: block49,
|
||||
extend: block49,
|
||||
newstate: []*subchain{
|
||||
{Head: 49, Tail: 49},
|
||||
},
|
||||
},
|
||||
// Initialize a sync and try to extend it with a sibling block.
|
||||
{
|
||||
head: block49,
|
||||
extend: block49B,
|
||||
newstate: []*subchain{
|
||||
{Head: 49, Tail: 49},
|
||||
},
|
||||
err: errChainReorged,
|
||||
},
|
||||
// Initialize a sync and try to extend it with a number-wise sequential
|
||||
// header, but a hash wise non-linking one.
|
||||
{
|
||||
head: block49B,
|
||||
extend: block50,
|
||||
newstate: []*subchain{
|
||||
{Head: 49, Tail: 49},
|
||||
},
|
||||
err: errChainForked,
|
||||
},
|
||||
// Initialize a sync and try to extend it with a non-linking future block.
|
||||
{
|
||||
head: block49,
|
||||
extend: block51,
|
||||
newstate: []*subchain{
|
||||
{Head: 49, Tail: 49},
|
||||
},
|
||||
err: errChainGapped,
|
||||
},
|
||||
// Initialize a sync and try to extend it with a past canonical block.
|
||||
{
|
||||
head: block50,
|
||||
extend: block49,
|
||||
newstate: []*subchain{
|
||||
{Head: 50, Tail: 50},
|
||||
},
|
||||
err: errChainReorged,
|
||||
},
|
||||
// Initialize a sync and try to extend it with a past sidechain block.
|
||||
{
|
||||
head: block50,
|
||||
extend: block49B,
|
||||
newstate: []*subchain{
|
||||
{Head: 50, Tail: 50},
|
||||
},
|
||||
err: errChainReorged,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
// Create a fresh database and initialize it with the starting state
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
rawdb.WriteHeader(db, genesis)
|
||||
|
||||
// Create a skeleton sync and run a cycle
|
||||
wait := make(chan struct{})
|
||||
|
||||
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller())
|
||||
skeleton.syncStarting = func() { close(wait) }
|
||||
skeleton.Sync(tt.head, nil, true)
|
||||
|
||||
<-wait
|
||||
if err := skeleton.Sync(tt.extend, nil, false); !errors.Is(err, tt.err) {
|
||||
t.Errorf("test %d: extension failure mismatch: have %v, want %v", i, err, tt.err)
|
||||
}
|
||||
skeleton.Terminate()
|
||||
|
||||
// Ensure the correct resulting sync status
|
||||
var progress skeletonProgress
|
||||
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
||||
|
||||
if len(progress.Subchains) != len(tt.newstate) {
|
||||
t.Errorf("test %d: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.newstate))
|
||||
continue
|
||||
}
|
||||
for j := 0; j < len(progress.Subchains); j++ {
|
||||
if progress.Subchains[j].Head != tt.newstate[j].Head {
|
||||
t.Errorf("test %d: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.newstate[j].Head)
|
||||
}
|
||||
if progress.Subchains[j].Tail != tt.newstate[j].Tail {
|
||||
t.Errorf("test %d: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.newstate[j].Tail)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the skeleton sync correctly retrieves headers from one or more
|
||||
// peers without duplicates or other strange side effects.
|
||||
func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||
|
||||
// Since skeleton headers don't need to be meaningful, beyond a parent hash
|
||||
// progression, create a long fake chain to test with.
|
||||
chain := []*types.Header{{Number: big.NewInt(0)}}
|
||||
for i := 1; i < 10000; i++ {
|
||||
chain = append(chain, &types.Header{
|
||||
ParentHash: chain[i-1].Hash(),
|
||||
Number: big.NewInt(int64(i)),
|
||||
})
|
||||
}
|
||||
// Some tests require a forking side chain to trigger cornercases.
|
||||
var sidechain []*types.Header
|
||||
for i := 0; i < len(chain)/2; i++ { // Fork at block #5000
|
||||
sidechain = append(sidechain, chain[i])
|
||||
}
|
||||
for i := len(chain) / 2; i < len(chain); i++ {
|
||||
sidechain = append(sidechain, &types.Header{
|
||||
ParentHash: sidechain[i-1].Hash(),
|
||||
Number: big.NewInt(int64(i)),
|
||||
Extra: []byte("B"), // force a different hash
|
||||
})
|
||||
}
|
||||
tests := []struct {
|
||||
fill bool // Whether to run a real backfiller in this test case
|
||||
unpredictable bool // Whether to ignore drops/serves due to uncertain packet assignments
|
||||
|
||||
head *types.Header // New head header to announce to reorg to
|
||||
peers []*skeletonTestPeer // Initial peer set to start the sync with
|
||||
midstate []*subchain // Expected sync state after initial cycle
|
||||
midserve uint64 // Expected number of header retrievals after initial cycle
|
||||
middrop uint64 // Expected number of peers dropped after initial cycle
|
||||
|
||||
newHead *types.Header // New header to anoint on top of the old one
|
||||
newPeer *skeletonTestPeer // New peer to join the skeleton syncer
|
||||
endstate []*subchain // Expected sync state after the post-init event
|
||||
endserve uint64 // Expected number of header retrievals after the post-init event
|
||||
enddrop uint64 // Expected number of peers dropped after the post-init event
|
||||
}{
|
||||
// Completely empty database with only the genesis set. The sync is expected
|
||||
// to create a single subchain with the requested head. No peers however, so
|
||||
// the sync should be stuck without any progression.
|
||||
//
|
||||
// When a new peer is added, it should detect the join and fill the headers
|
||||
// to the genesis block.
|
||||
{
|
||||
head: chain[len(chain)-1],
|
||||
midstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: uint64(len(chain) - 1)}},
|
||||
|
||||
newPeer: newSkeletonTestPeer("test-peer", chain),
|
||||
endstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||
endserve: uint64(len(chain) - 2), // len - head - genesis
|
||||
},
|
||||
// Completely empty database with only the genesis set. The sync is expected
|
||||
// to create a single subchain with the requested head. With one valid peer,
|
||||
// the sync is expected to complete already in the initial round.
|
||||
//
|
||||
// Adding a second peer should not have any effect.
|
||||
{
|
||||
head: chain[len(chain)-1],
|
||||
peers: []*skeletonTestPeer{newSkeletonTestPeer("test-peer-1", chain)},
|
||||
midstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||
midserve: uint64(len(chain) - 2), // len - head - genesis
|
||||
|
||||
newPeer: newSkeletonTestPeer("test-peer-2", chain),
|
||||
endstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||
endserve: uint64(len(chain) - 2), // len - head - genesis
|
||||
},
|
||||
// Completely empty database with only the genesis set. The sync is expected
|
||||
// to create a single subchain with the requested head. With many valid peers,
|
||||
// the sync is expected to complete already in the initial round.
|
||||
//
|
||||
// Adding a new peer should not have any effect.
|
||||
{
|
||||
head: chain[len(chain)-1],
|
||||
peers: []*skeletonTestPeer{
|
||||
newSkeletonTestPeer("test-peer-1", chain),
|
||||
newSkeletonTestPeer("test-peer-2", chain),
|
||||
newSkeletonTestPeer("test-peer-3", chain),
|
||||
},
|
||||
midstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||
midserve: uint64(len(chain) - 2), // len - head - genesis
|
||||
|
||||
newPeer: newSkeletonTestPeer("test-peer-4", chain),
|
||||
endstate: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}},
|
||||
endserve: uint64(len(chain) - 2), // len - head - genesis
|
||||
},
|
||||
// This test checks if a peer tries to withhold a header - *on* the sync
|
||||
// boundary - instead of sending the requested amount. The malicious short
|
||||
// package should not be accepted.
|
||||
//
|
||||
// Joining with a new peer should however unblock the sync.
|
||||
{
|
||||
head: chain[requestHeaders+100],
|
||||
peers: []*skeletonTestPeer{
|
||||
newSkeletonTestPeer("header-skipper", append(append(append([]*types.Header{}, chain[:99]...), nil), chain[100:]...)),
|
||||
},
|
||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||
midserve: requestHeaders + 101 - 3, // len - head - genesis - missing
|
||||
middrop: 1, // penalize shortened header deliveries
|
||||
|
||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||
endserve: (requestHeaders + 101 - 3) + (100 - 1), // midserve + lenrest - genesis
|
||||
enddrop: 1, // no new drops
|
||||
},
|
||||
// This test checks if a peer tries to withhold a header - *off* the sync
|
||||
// boundary - instead of sending the requested amount. The malicious short
|
||||
// package should not be accepted.
|
||||
//
|
||||
// Joining with a new peer should however unblock the sync.
|
||||
{
|
||||
head: chain[requestHeaders+100],
|
||||
peers: []*skeletonTestPeer{
|
||||
newSkeletonTestPeer("header-skipper", append(append(append([]*types.Header{}, chain[:50]...), nil), chain[51:]...)),
|
||||
},
|
||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||
midserve: requestHeaders + 101 - 3, // len - head - genesis - missing
|
||||
middrop: 1, // penalize shortened header deliveries
|
||||
|
||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||
endserve: (requestHeaders + 101 - 3) + (100 - 1), // midserve + lenrest - genesis
|
||||
enddrop: 1, // no new drops
|
||||
},
|
||||
// This test checks if a peer tries to duplicate a header - *on* the sync
|
||||
// boundary - instead of sending the correct sequence. The malicious duped
|
||||
// package should not be accepted.
|
||||
//
|
||||
// Joining with a new peer should however unblock the sync.
|
||||
{
|
||||
head: chain[requestHeaders+100], // We want to force the 100th header to be a request boundary
|
||||
peers: []*skeletonTestPeer{
|
||||
newSkeletonTestPeer("header-duper", append(append(append([]*types.Header{}, chain[:99]...), chain[98]), chain[100:]...)),
|
||||
},
|
||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||
midserve: requestHeaders + 101 - 2, // len - head - genesis
|
||||
middrop: 1, // penalize invalid header sequences
|
||||
|
||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||
endserve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
||||
enddrop: 1, // no new drops
|
||||
},
|
||||
// This test checks if a peer tries to duplicate a header - *off* the sync
|
||||
// boundary - instead of sending the correct sequence. The malicious duped
|
||||
// package should not be accepted.
|
||||
//
|
||||
// Joining with a new peer should however unblock the sync.
|
||||
{
|
||||
head: chain[requestHeaders+100], // We want to force the 100th header to be a request boundary
|
||||
peers: []*skeletonTestPeer{
|
||||
newSkeletonTestPeer("header-duper", append(append(append([]*types.Header{}, chain[:50]...), chain[49]), chain[51:]...)),
|
||||
},
|
||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||
midserve: requestHeaders + 101 - 2, // len - head - genesis
|
||||
middrop: 1, // penalize invalid header sequences
|
||||
|
||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||
endserve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
||||
enddrop: 1, // no new drops
|
||||
},
|
||||
// This test checks if a peer tries to inject a different header - *on*
|
||||
// the sync boundary - instead of sending the correct sequence. The bad
|
||||
// package should not be accepted.
|
||||
//
|
||||
// Joining with a new peer should however unblock the sync.
|
||||
{
|
||||
head: chain[requestHeaders+100], // We want to force the 100th header to be a request boundary
|
||||
peers: []*skeletonTestPeer{
|
||||
newSkeletonTestPeer("header-changer",
|
||||
append(
|
||||
append(
|
||||
append([]*types.Header{}, chain[:99]...),
|
||||
&types.Header{
|
||||
ParentHash: chain[98].Hash(),
|
||||
Number: big.NewInt(int64(99)),
|
||||
GasLimit: 1,
|
||||
},
|
||||
), chain[100:]...,
|
||||
),
|
||||
),
|
||||
},
|
||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||
midserve: requestHeaders + 101 - 2, // len - head - genesis
|
||||
middrop: 1, // different set of headers, drop // TODO(karalabe): maybe just diff sync?
|
||||
|
||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||
endserve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
||||
enddrop: 1, // no new drops
|
||||
},
|
||||
// This test checks if a peer tries to inject a different header - *off*
|
||||
// the sync boundary - instead of sending the correct sequence. The bad
|
||||
// package should not be accepted.
|
||||
//
|
||||
// Joining with a new peer should however unblock the sync.
|
||||
{
|
||||
head: chain[requestHeaders+100], // We want to force the 100th header to be a request boundary
|
||||
peers: []*skeletonTestPeer{
|
||||
newSkeletonTestPeer("header-changer",
|
||||
append(
|
||||
append(
|
||||
append([]*types.Header{}, chain[:50]...),
|
||||
&types.Header{
|
||||
ParentHash: chain[49].Hash(),
|
||||
Number: big.NewInt(int64(50)),
|
||||
GasLimit: 1,
|
||||
},
|
||||
), chain[51:]...,
|
||||
),
|
||||
),
|
||||
},
|
||||
midstate: []*subchain{{Head: requestHeaders + 100, Tail: 100}},
|
||||
midserve: requestHeaders + 101 - 2, // len - head - genesis
|
||||
middrop: 1, // different set of headers, drop
|
||||
|
||||
newPeer: newSkeletonTestPeer("good-peer", chain),
|
||||
endstate: []*subchain{{Head: requestHeaders + 100, Tail: 1}},
|
||||
endserve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis
|
||||
enddrop: 1, // no new drops
|
||||
},
|
||||
// This test reproduces a bug caught during review (kudos to @holiman)
|
||||
// where a subchain is merged with a previously interrupted one, causing
|
||||
// pending data in the scratch space to become "invalid" (since we jump
|
||||
// ahead during subchain merge). In that case it is expected to ignore
|
||||
// the queued up data instead of trying to process on top of a shifted
|
||||
// task set.
|
||||
//
|
||||
// The test is a bit convoluted since it needs to trigger a concurrency
|
||||
// issue. First we sync up an initial chain of 2x512 items. Then announce
|
||||
// 2x512+2 as head and delay delivering the head batch to fill the scratch
|
||||
// space first. The delivery head should merge with the previous download
|
||||
// and the scratch space must not be consumed further.
|
||||
{
|
||||
head: chain[2*requestHeaders],
|
||||
peers: []*skeletonTestPeer{
|
||||
newSkeletonTestPeerWithHook("peer-1", chain, func(origin uint64) []*types.Header {
|
||||
if origin == chain[2*requestHeaders+1].Number.Uint64() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return nil // Fallback to default behavior, just delayed
|
||||
}),
|
||||
newSkeletonTestPeerWithHook("peer-2", chain, func(origin uint64) []*types.Header {
|
||||
if origin == chain[2*requestHeaders+1].Number.Uint64() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return nil // Fallback to default behavior, just delayed
|
||||
}),
|
||||
},
|
||||
midstate: []*subchain{{Head: 2 * requestHeaders, Tail: 1}},
|
||||
midserve: 2*requestHeaders - 1, // len - head - genesis
|
||||
|
||||
newHead: chain[2*requestHeaders+2],
|
||||
endstate: []*subchain{{Head: 2*requestHeaders + 2, Tail: 1}},
|
||||
endserve: 4 * requestHeaders,
|
||||
},
|
||||
// This test reproduces a bug caught by (@rjl493456442) where a skeleton
|
||||
// header goes missing, causing the sync to get stuck and/or panic.
|
||||
//
|
||||
// The setup requires a previously successfully synced chain up to a block
|
||||
// height N. That results is a single skeleton header (block N) and a single
|
||||
// subchain (head N, Tail N) being stored on disk.
|
||||
//
|
||||
// The following step requires a new sync cycle to a new side chain of a
|
||||
// height higher than N, and an ancestor lower than N (e.g. N-2, N+2).
|
||||
// In this scenario, when processing a batch of headers, a link point of
|
||||
// N-2 will be found, meaning that N-1 and N have been overwritten.
|
||||
//
|
||||
// The link event triggers an early exit, noticing that the previous sub-
|
||||
// chain is a leftover and deletes it (with it's skeleton header N). But
|
||||
// since skeleton header N has been overwritten to the new side chain, we
|
||||
// end up losing it and creating a gap.
|
||||
{
|
||||
fill: true,
|
||||
unpredictable: true, // We have good and bad peer too, bad may be dropped, test too short for certainty
|
||||
|
||||
head: chain[len(chain)/2+1], // Sync up until the sidechain common ancestor + 2
|
||||
peers: []*skeletonTestPeer{newSkeletonTestPeer("test-peer-oldchain", chain)},
|
||||
midstate: []*subchain{{Head: uint64(len(chain)/2 + 1), Tail: 1}},
|
||||
|
||||
newHead: sidechain[len(sidechain)/2+3], // Sync up until the sidechain common ancestor + 4
|
||||
newPeer: newSkeletonTestPeer("test-peer-newchain", sidechain),
|
||||
endstate: []*subchain{{Head: uint64(len(sidechain)/2 + 3), Tail: uint64(len(chain) / 2)}},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
// Create a fresh database and initialize it with the starting state
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
|
||||
rawdb.WriteBlock(db, types.NewBlockWithHeader(chain[0]))
|
||||
rawdb.WriteReceipts(db, chain[0].Hash(), chain[0].Number.Uint64(), types.Receipts{})
|
||||
|
||||
// Create a peer set to feed headers through
|
||||
peerset := newPeerSet()
|
||||
for _, peer := range tt.peers {
|
||||
peerset.Register(newPeerConnection(peer.id, eth.ETH67, peer, log.New("id", peer.id)))
|
||||
}
|
||||
// Create a peer dropper to track malicious peers
|
||||
dropped := make(map[string]int)
|
||||
drop := func(peer string) {
|
||||
if p := peerset.Peer(peer); p != nil {
|
||||
p.peer.(*skeletonTestPeer).dropped.Add(1)
|
||||
}
|
||||
peerset.Unregister(peer)
|
||||
dropped[peer]++
|
||||
}
|
||||
// Create a backfiller if we need to run more advanced tests
|
||||
filler := newHookedBackfiller()
|
||||
if tt.fill {
|
||||
var filled *types.Header
|
||||
|
||||
filler = &hookedBackfiller{
|
||||
resumeHook: func() {
|
||||
var progress skeletonProgress
|
||||
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
||||
|
||||
for progress.Subchains[0].Tail < progress.Subchains[0].Head {
|
||||
header := rawdb.ReadSkeletonHeader(db, progress.Subchains[0].Tail)
|
||||
|
||||
rawdb.WriteBlock(db, types.NewBlockWithHeader(header))
|
||||
rawdb.WriteReceipts(db, header.Hash(), header.Number.Uint64(), types.Receipts{})
|
||||
|
||||
rawdb.DeleteSkeletonHeader(db, header.Number.Uint64())
|
||||
|
||||
progress.Subchains[0].Tail++
|
||||
progress.Subchains[0].Next = header.Hash()
|
||||
}
|
||||
filled = rawdb.ReadSkeletonHeader(db, progress.Subchains[0].Tail)
|
||||
|
||||
rawdb.WriteBlock(db, types.NewBlockWithHeader(filled))
|
||||
rawdb.WriteReceipts(db, filled.Hash(), filled.Number.Uint64(), types.Receipts{})
|
||||
},
|
||||
|
||||
suspendHook: func() *types.Header {
|
||||
prev := filled
|
||||
filled = nil
|
||||
|
||||
return prev
|
||||
},
|
||||
}
|
||||
}
|
||||
// Create a skeleton sync and run a cycle
|
||||
skeleton := newSkeleton(db, peerset, drop, filler)
|
||||
skeleton.Sync(tt.head, nil, true)
|
||||
|
||||
var progress skeletonProgress
|
||||
// Wait a bit (bleah) for the initial sync loop to go to idle. This might
|
||||
// be either a finish or a never-start hence why there's no event to hook.
|
||||
check := func() error {
|
||||
if len(progress.Subchains) != len(tt.midstate) {
|
||||
return fmt.Errorf("test %d, mid state: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.midstate))
|
||||
}
|
||||
for j := 0; j < len(progress.Subchains); j++ {
|
||||
if progress.Subchains[j].Head != tt.midstate[j].Head {
|
||||
return fmt.Errorf("test %d, mid state: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.midstate[j].Head)
|
||||
}
|
||||
if progress.Subchains[j].Tail != tt.midstate[j].Tail {
|
||||
return fmt.Errorf("test %d, mid state: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.midstate[j].Tail)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
waitStart := time.Now()
|
||||
for waitTime := 20 * time.Millisecond; time.Since(waitStart) < 2*time.Second; waitTime = waitTime * 2 {
|
||||
time.Sleep(waitTime)
|
||||
// Check the post-init end state if it matches the required results
|
||||
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
||||
if err := check(); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := check(); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
if !tt.unpredictable {
|
||||
var served uint64
|
||||
for _, peer := range tt.peers {
|
||||
served += peer.served.Load()
|
||||
}
|
||||
if served != tt.midserve {
|
||||
t.Errorf("test %d, mid state: served headers mismatch: have %d, want %d", i, served, tt.midserve)
|
||||
}
|
||||
var drops uint64
|
||||
for _, peer := range tt.peers {
|
||||
drops += peer.dropped.Load()
|
||||
}
|
||||
if drops != tt.middrop {
|
||||
t.Errorf("test %d, mid state: dropped peers mismatch: have %d, want %d", i, drops, tt.middrop)
|
||||
}
|
||||
}
|
||||
// Apply the post-init events if there's any
|
||||
if tt.newHead != nil {
|
||||
skeleton.Sync(tt.newHead, nil, true)
|
||||
}
|
||||
if tt.newPeer != nil {
|
||||
if err := peerset.Register(newPeerConnection(tt.newPeer.id, eth.ETH67, tt.newPeer, log.New("id", tt.newPeer.id))); err != nil {
|
||||
t.Errorf("test %d: failed to register new peer: %v", i, err)
|
||||
}
|
||||
}
|
||||
// Wait a bit (bleah) for the second sync loop to go to idle. This might
|
||||
// be either a finish or a never-start hence why there's no event to hook.
|
||||
check = func() error {
|
||||
if len(progress.Subchains) != len(tt.endstate) {
|
||||
return fmt.Errorf("test %d, end state: subchain count mismatch: have %d, want %d", i, len(progress.Subchains), len(tt.endstate))
|
||||
}
|
||||
for j := 0; j < len(progress.Subchains); j++ {
|
||||
if progress.Subchains[j].Head != tt.endstate[j].Head {
|
||||
return fmt.Errorf("test %d, end state: subchain %d head mismatch: have %d, want %d", i, j, progress.Subchains[j].Head, tt.endstate[j].Head)
|
||||
}
|
||||
if progress.Subchains[j].Tail != tt.endstate[j].Tail {
|
||||
return fmt.Errorf("test %d, end state: subchain %d tail mismatch: have %d, want %d", i, j, progress.Subchains[j].Tail, tt.endstate[j].Tail)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
waitStart = time.Now()
|
||||
for waitTime := 20 * time.Millisecond; time.Since(waitStart) < 2*time.Second; waitTime = waitTime * 2 {
|
||||
time.Sleep(waitTime)
|
||||
// Check the post-init end state if it matches the required results
|
||||
json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress)
|
||||
if err := check(); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := check(); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
// Check that the peers served no more headers than we actually needed
|
||||
if !tt.unpredictable {
|
||||
served := uint64(0)
|
||||
for _, peer := range tt.peers {
|
||||
served += peer.served.Load()
|
||||
}
|
||||
if tt.newPeer != nil {
|
||||
served += tt.newPeer.served.Load()
|
||||
}
|
||||
if served != tt.endserve {
|
||||
t.Errorf("test %d, end state: served headers mismatch: have %d, want %d", i, served, tt.endserve)
|
||||
}
|
||||
drops := uint64(0)
|
||||
for _, peer := range tt.peers {
|
||||
drops += peer.dropped.Load()
|
||||
}
|
||||
if tt.newPeer != nil {
|
||||
drops += tt.newPeer.dropped.Load()
|
||||
}
|
||||
if drops != tt.enddrop {
|
||||
t.Errorf("test %d, end state: dropped peers mismatch: have %d, want %d", i, drops, tt.middrop)
|
||||
}
|
||||
}
|
||||
// Clean up any leftover skeleton sync resources
|
||||
skeleton.Terminate()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// syncState starts downloading state with the given root hash.
|
||||
func (d *Downloader) syncState(root common.Hash) *stateSync {
|
||||
// Create the state sync
|
||||
s := newStateSync(d, root)
|
||||
select {
|
||||
case d.stateSyncStart <- s:
|
||||
// If we tell the statesync to restart with a new root, we also need
|
||||
// to wait for it to actually also start -- when old requests have timed
|
||||
// out or been delivered
|
||||
<-s.started
|
||||
case <-d.quitCh:
|
||||
s.err = errCancelStateFetch
|
||||
close(s.done)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// stateFetcher manages the active state sync and accepts requests
|
||||
// on its behalf.
|
||||
func (d *Downloader) stateFetcher() {
|
||||
for {
|
||||
select {
|
||||
case s := <-d.stateSyncStart:
|
||||
for next := s; next != nil; {
|
||||
next = d.runStateSync(next)
|
||||
}
|
||||
case <-d.quitCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runStateSync runs a state synchronisation until it completes or another root
|
||||
// hash is requested to be switched over to.
|
||||
func (d *Downloader) runStateSync(s *stateSync) *stateSync {
|
||||
log.Trace("State sync starting", "root", s.root)
|
||||
|
||||
go s.run()
|
||||
defer s.Cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
case next := <-d.stateSyncStart:
|
||||
return next
|
||||
|
||||
case <-s.done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stateSync schedules requests for downloading a particular state trie defined
|
||||
// by a given state root.
|
||||
type stateSync struct {
|
||||
d *Downloader // Downloader instance to access and manage current peerset
|
||||
root common.Hash // State root currently being synced
|
||||
|
||||
started chan struct{} // Started is signalled once the sync loop starts
|
||||
cancel chan struct{} // Channel to signal a termination request
|
||||
cancelOnce sync.Once // Ensures cancel only ever gets called once
|
||||
done chan struct{} // Channel to signal termination completion
|
||||
err error // Any error hit during sync (set before completion)
|
||||
}
|
||||
|
||||
// newStateSync creates a new state trie download scheduler. This method does not
|
||||
// yet start the sync. The user needs to call run to initiate.
|
||||
func newStateSync(d *Downloader, root common.Hash) *stateSync {
|
||||
return &stateSync{
|
||||
d: d,
|
||||
root: root,
|
||||
cancel: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
started: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// run starts the task assignment and response processing loop, blocking until
|
||||
// it finishes, and finally notifying any goroutines waiting for the loop to
|
||||
// finish.
|
||||
func (s *stateSync) run() {
|
||||
close(s.started)
|
||||
s.err = s.d.SnapSyncer.Sync(s.root, s.cancel)
|
||||
close(s.done)
|
||||
}
|
||||
|
||||
// Wait blocks until the sync is done or canceled.
|
||||
func (s *stateSync) Wait() error {
|
||||
<-s.done
|
||||
return s.err
|
||||
}
|
||||
|
||||
// Cancel cancels the sync and waits until it has shut down.
|
||||
func (s *stateSync) Cancel() error {
|
||||
s.cancelOnce.Do(func() {
|
||||
close(s.cancel)
|
||||
})
|
||||
return s.Wait()
|
||||
}
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package downloader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// Test chain parameters.
|
||||
var (
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
testAddress = crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
testDB = rawdb.NewMemoryDatabase()
|
||||
|
||||
testGspec = &core.Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}},
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
testGenesis = testGspec.MustCommit(testDB, trie.NewDatabase(testDB, trie.HashDefaults))
|
||||
)
|
||||
|
||||
// The common prefix of all test chains:
|
||||
var testChainBase *testChain
|
||||
|
||||
// Different forks on top of the base chain:
|
||||
var testChainForkLightA, testChainForkLightB, testChainForkHeavy *testChain
|
||||
|
||||
var pregenerated bool
|
||||
|
||||
func init() {
|
||||
// Reduce some of the parameters to make the tester faster
|
||||
fullMaxForkAncestry = 10000
|
||||
lightMaxForkAncestry = 10000
|
||||
blockCacheMaxItems = 1024
|
||||
fsHeaderSafetyNet = 256
|
||||
fsHeaderContCheck = 500 * time.Millisecond
|
||||
|
||||
testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis)
|
||||
|
||||
var forkLen = int(fullMaxForkAncestry + 50)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Generate the test chains to seed the peers with
|
||||
wg.Add(3)
|
||||
go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }()
|
||||
go func() { testChainForkLightB = testChainBase.makeFork(forkLen, false, 2); wg.Done() }()
|
||||
go func() { testChainForkHeavy = testChainBase.makeFork(forkLen, true, 3); wg.Done() }()
|
||||
wg.Wait()
|
||||
|
||||
// Generate the test peers used by the tests to avoid overloading during testing.
|
||||
// These seemingly random chains are used in various downloader tests. We're just
|
||||
// pre-generating them here.
|
||||
chains := []*testChain{
|
||||
testChainBase,
|
||||
testChainForkLightA,
|
||||
testChainForkLightB,
|
||||
testChainForkHeavy,
|
||||
testChainBase.shorten(1),
|
||||
testChainBase.shorten(blockCacheMaxItems - 15),
|
||||
testChainBase.shorten((blockCacheMaxItems - 15) / 2),
|
||||
testChainBase.shorten(blockCacheMaxItems - 15 - 5),
|
||||
testChainBase.shorten(MaxHeaderFetch),
|
||||
testChainBase.shorten(800),
|
||||
testChainBase.shorten(800 / 2),
|
||||
testChainBase.shorten(800 / 3),
|
||||
testChainBase.shorten(800 / 4),
|
||||
testChainBase.shorten(800 / 5),
|
||||
testChainBase.shorten(800 / 6),
|
||||
testChainBase.shorten(800 / 7),
|
||||
testChainBase.shorten(800 / 8),
|
||||
testChainBase.shorten(3*fsHeaderSafetyNet + 256 + fsMinFullBlocks),
|
||||
testChainBase.shorten(fsMinFullBlocks + 256 - 1),
|
||||
testChainForkLightA.shorten(len(testChainBase.blocks) + 80),
|
||||
testChainForkLightB.shorten(len(testChainBase.blocks) + 81),
|
||||
testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch),
|
||||
testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch),
|
||||
testChainForkHeavy.shorten(len(testChainBase.blocks) + 79),
|
||||
}
|
||||
wg.Add(len(chains))
|
||||
for _, chain := range chains {
|
||||
go func(blocks []*types.Block) {
|
||||
newTestBlockchain(blocks)
|
||||
wg.Done()
|
||||
}(chain.blocks[1:])
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Mark the chains pregenerated. Generating a new one will lead to a panic.
|
||||
pregenerated = true
|
||||
}
|
||||
|
||||
type testChain struct {
|
||||
blocks []*types.Block
|
||||
}
|
||||
|
||||
// newTestChain creates a blockchain of the given length.
|
||||
func newTestChain(length int, genesis *types.Block) *testChain {
|
||||
tc := &testChain{
|
||||
blocks: []*types.Block{genesis},
|
||||
}
|
||||
tc.generate(length-1, 0, genesis, false)
|
||||
return tc
|
||||
}
|
||||
|
||||
// makeFork creates a fork on top of the test chain.
|
||||
func (tc *testChain) makeFork(length int, heavy bool, seed byte) *testChain {
|
||||
fork := tc.copy(len(tc.blocks) + length)
|
||||
fork.generate(length, seed, tc.blocks[len(tc.blocks)-1], heavy)
|
||||
return fork
|
||||
}
|
||||
|
||||
// shorten creates a copy of the chain with the given length. It panics if the
|
||||
// length is longer than the number of available blocks.
|
||||
func (tc *testChain) shorten(length int) *testChain {
|
||||
if length > len(tc.blocks) {
|
||||
panic(fmt.Errorf("can't shorten test chain to %d blocks, it's only %d blocks long", length, len(tc.blocks)))
|
||||
}
|
||||
return tc.copy(length)
|
||||
}
|
||||
|
||||
func (tc *testChain) copy(newlen int) *testChain {
|
||||
if newlen > len(tc.blocks) {
|
||||
newlen = len(tc.blocks)
|
||||
}
|
||||
cpy := &testChain{
|
||||
blocks: append([]*types.Block{}, tc.blocks[:newlen]...),
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// generate creates a chain of n blocks starting at and including parent.
|
||||
// the returned hash chain is ordered head->parent. In addition, every 22th block
|
||||
// contains a transaction and every 5th an uncle to allow testing correct block
|
||||
// reassembly.
|
||||
func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) {
|
||||
blocks, _ := core.GenerateChain(testGspec.Config, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) {
|
||||
block.SetCoinbase(common.Address{seed})
|
||||
// If a heavy chain is requested, delay blocks to raise difficulty
|
||||
if heavy {
|
||||
block.OffsetTime(-9)
|
||||
}
|
||||
// Include transactions to the miner to make blocks more interesting.
|
||||
if parent == tc.blocks[0] && i%22 == 0 {
|
||||
signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp())
|
||||
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
block.AddTx(tx)
|
||||
}
|
||||
// if the block number is a multiple of 5, add a bonus uncle to the block
|
||||
if i > 0 && i%5 == 0 {
|
||||
block.AddUncle(&types.Header{
|
||||
ParentHash: block.PrevBlock(i - 2).Hash(),
|
||||
Number: big.NewInt(block.Number().Int64() - 1),
|
||||
})
|
||||
}
|
||||
})
|
||||
tc.blocks = append(tc.blocks, blocks...)
|
||||
}
|
||||
|
||||
var (
|
||||
testBlockchains = make(map[common.Hash]*testBlockchain)
|
||||
testBlockchainsLock sync.Mutex
|
||||
)
|
||||
|
||||
type testBlockchain struct {
|
||||
chain *core.BlockChain
|
||||
gen sync.Once
|
||||
}
|
||||
|
||||
// newTestBlockchain creates a blockchain database built by running the given blocks,
|
||||
// either actually running them, or reusing a previously created one. The returned
|
||||
// chains are *shared*, so *do not* mutate them.
|
||||
func newTestBlockchain(blocks []*types.Block) *core.BlockChain {
|
||||
// Retrieve an existing database, or create a new one
|
||||
head := testGenesis.Hash()
|
||||
if len(blocks) > 0 {
|
||||
head = blocks[len(blocks)-1].Hash()
|
||||
}
|
||||
testBlockchainsLock.Lock()
|
||||
if _, ok := testBlockchains[head]; !ok {
|
||||
testBlockchains[head] = new(testBlockchain)
|
||||
}
|
||||
tbc := testBlockchains[head]
|
||||
testBlockchainsLock.Unlock()
|
||||
|
||||
// Ensure that the database is generated
|
||||
tbc.gen.Do(func() {
|
||||
if pregenerated {
|
||||
panic("Requested chain generation outside of init")
|
||||
}
|
||||
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, testGspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if n, err := chain.InsertChain(blocks); err != nil {
|
||||
panic(fmt.Sprintf("block %d: %v", n, err))
|
||||
}
|
||||
tbc.chain = chain
|
||||
})
|
||||
return tbc.chain
|
||||
}
|
||||
Loading…
Reference in a new issue