github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/eth/downloader/downloader.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package downloader contains the manual full chain synchronisation. 18 package downloader 19 20 import ( 21 "errors" 22 "fmt" 23 "math/big" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 electroneum "github.com/electroneum/electroneum-sc" 29 "github.com/electroneum/electroneum-sc/common" 30 "github.com/electroneum/electroneum-sc/core/rawdb" 31 "github.com/electroneum/electroneum-sc/core/state/snapshot" 32 "github.com/electroneum/electroneum-sc/core/types" 33 "github.com/electroneum/electroneum-sc/eth/protocols/snap" 34 "github.com/electroneum/electroneum-sc/ethdb" 35 "github.com/electroneum/electroneum-sc/event" 36 "github.com/electroneum/electroneum-sc/log" 37 "github.com/electroneum/electroneum-sc/params" 38 ) 39 40 var ( 41 MaxBlockFetch = 128 // Amount of blocks to be fetched per retrieval request 42 MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request 43 MaxSkeletonSize = 128 // Number of header fetches to need for a skeleton assembly 44 MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request 45 46 maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) 47 maxHeadersProcess = 2048 // Number of header download results to import at once into the chain 48 maxResultsProcess = 2048 // Number of content download results to import at once into the chain 49 fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) 50 lightMaxForkAncestry uint64 = params.LightImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) 51 52 reorgProtThreshold = 48 // Threshold number of recent blocks to disable mini reorg protection 53 reorgProtHeaderDelay = 2 // Number of headers to delay delivering to cover mini reorgs 54 55 fsHeaderCheckFrequency = 100 // Verification frequency of the downloaded headers during snap sync 56 fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected 57 fsHeaderForceVerify = 24 // Number of headers to verify before and after the pivot to accept it 58 fsHeaderContCheck = 3 * time.Second // Time interval to check for header continuations during state download 59 fsMinFullBlocks = 64 // Number of blocks to retrieve fully even in snap sync 60 ) 61 62 var ( 63 errBusy = errors.New("busy") 64 errUnknownPeer = errors.New("peer is unknown or unhealthy") 65 errBadPeer = errors.New("action from bad peer ignored") 66 errStallingPeer = errors.New("peer is stalling") 67 errUnsyncedPeer = errors.New("unsynced peer") 68 errNoPeers = errors.New("no peers to keep download active") 69 errTimeout = errors.New("timeout") 70 errEmptyHeaderSet = errors.New("empty header set by peer") 71 errPeersUnavailable = errors.New("no peers available or all tried for download") 72 errInvalidAncestor = errors.New("retrieved ancestor is invalid") 73 errInvalidChain = errors.New("retrieved hash chain is invalid") 74 errInvalidBody = errors.New("retrieved block body is invalid") 75 errInvalidReceipt = errors.New("retrieved receipt is invalid") 76 errCancelStateFetch = errors.New("state data download canceled (requested)") 77 errCancelContentProcessing = errors.New("content processing canceled (requested)") 78 errCanceled = errors.New("syncing canceled (requested)") 79 errTooOld = errors.New("peer's protocol version too old") 80 errNoAncestorFound = errors.New("no common ancestor found") 81 errNoPivotHeader = errors.New("pivot header is not found") 82 ErrMergeTransition = errors.New("legacy sync reached the merge") 83 ) 84 85 // peerDropFn is a callback type for dropping a peer detected as malicious. 86 type peerDropFn func(id string) 87 88 // headerTask is a set of downloaded headers to queue along with their precomputed 89 // hashes to avoid constant rehashing. 90 type headerTask struct { 91 headers []*types.Header 92 hashes []common.Hash 93 } 94 95 type Downloader struct { 96 mode uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode 97 mux *event.TypeMux // Event multiplexer to announce sync operation events 98 99 checkpoint uint64 // Checkpoint block number to enforce head against (e.g. snap sync) 100 genesis uint64 // Genesis block number to limit sync to (e.g. light client CHT) 101 queue *queue // Scheduler for selecting the hashes to download 102 peers *peerSet // Set of active peers from which download can proceed 103 104 stateDB ethdb.Database // Database to state sync into (and deduplicate via) 105 106 // Statistics 107 syncStatsChainOrigin uint64 // Origin block number where syncing started at 108 syncStatsChainHeight uint64 // Highest block number known when syncing started 109 syncStatsLock sync.RWMutex // Lock protecting the sync stats fields 110 111 lightchain LightChain 112 blockchain BlockChain 113 114 // Callbacks 115 dropPeer peerDropFn // Drops a peer for misbehaving 116 117 // Status 118 synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing 119 synchronising int32 120 notified int32 121 committed int32 122 ancientLimit uint64 // The maximum block number which can be regarded as ancient data. 123 124 // Channels 125 headerProcCh chan *headerTask // Channel to feed the header processor new tasks 126 127 // Skeleton sync 128 skeleton *skeleton // Header skeleton to backfill the chain with (eth2 mode) 129 130 // State sync 131 pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root 132 pivotLock sync.RWMutex // Lock protecting pivot header reads from updates 133 134 SnapSyncer *snap.Syncer // TODO(karalabe): make private! hack for now 135 stateSyncStart chan *stateSync 136 137 // Cancellation and termination 138 cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop) 139 cancelCh chan struct{} // Channel to cancel mid-flight syncs 140 cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers 141 cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited. 142 143 quitCh chan struct{} // Quit channel to signal termination 144 quitLock sync.Mutex // Lock to prevent double closes 145 146 // Testing hooks 147 syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run 148 bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch 149 receiptFetchHook func([]*types.Header) // Method to call upon starting a receipt fetch 150 chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) 151 } 152 153 // LightChain encapsulates functions required to synchronise a light chain. 154 type LightChain interface { 155 // HasHeader verifies a header's presence in the local chain. 156 HasHeader(common.Hash, uint64) bool 157 158 // GetHeaderByHash retrieves a header from the local chain. 159 GetHeaderByHash(common.Hash) *types.Header 160 161 // CurrentHeader retrieves the head header from the local chain. 162 CurrentHeader() *types.Header 163 164 // GetTd returns the total difficulty of a local block. 165 GetTd(common.Hash, uint64) *big.Int 166 167 // InsertHeaderChain inserts a batch of headers into the local chain. 168 InsertHeaderChain([]*types.Header, int) (int, error) 169 170 // SetHead rewinds the local chain to a new head. 171 SetHead(uint64) error 172 } 173 174 // BlockChain encapsulates functions required to sync a (full or snap) blockchain. 175 type BlockChain interface { 176 LightChain 177 178 // HasBlock verifies a block's presence in the local chain. 179 HasBlock(common.Hash, uint64) bool 180 181 // HasFastBlock verifies a snap block's presence in the local chain. 182 HasFastBlock(common.Hash, uint64) bool 183 184 // GetBlockByHash retrieves a block from the local chain. 185 GetBlockByHash(common.Hash) *types.Block 186 187 // CurrentBlock retrieves the head block from the local chain. 188 CurrentBlock() *types.Block 189 190 // CurrentFastBlock retrieves the head snap block from the local chain. 191 CurrentFastBlock() *types.Block 192 193 // SnapSyncCommitHead directly commits the head block to a certain entity. 194 SnapSyncCommitHead(common.Hash) error 195 196 // InsertChain inserts a batch of blocks into the local chain. 197 InsertChain(types.Blocks) (int, error) 198 199 // InsertReceiptChain inserts a batch of receipts into the local chain. 200 InsertReceiptChain(types.Blocks, []types.Receipts, uint64) (int, error) 201 202 // Snapshots returns the blockchain snapshot tree to paused it during sync. 203 Snapshots() *snapshot.Tree 204 } 205 206 // New creates a new downloader to fetch hashes and blocks from remote peers. 207 func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func()) *Downloader { 208 if lightchain == nil { 209 lightchain = chain 210 } 211 dl := &Downloader{ 212 stateDB: stateDb, 213 mux: mux, 214 checkpoint: checkpoint, 215 queue: newQueue(blockCacheMaxItems, blockCacheInitialItems), 216 peers: newPeerSet(), 217 blockchain: chain, 218 lightchain: lightchain, 219 dropPeer: dropPeer, 220 headerProcCh: make(chan *headerTask, 1), 221 quitCh: make(chan struct{}), 222 SnapSyncer: snap.NewSyncer(stateDb), 223 stateSyncStart: make(chan *stateSync), 224 } 225 dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success)) 226 227 go dl.stateFetcher() 228 return dl 229 } 230 231 // Progress retrieves the synchronisation boundaries, specifically the origin 232 // block where synchronisation started at (may have failed/suspended); the block 233 // or header sync is currently at; and the latest known block which the sync targets. 234 // 235 // In addition, during the state download phase of snap synchronisation the number 236 // of processed and the total number of known states are also returned. Otherwise 237 // these are zero. 238 func (d *Downloader) Progress() electroneum.SyncProgress { 239 // Lock the current stats and return the progress 240 d.syncStatsLock.RLock() 241 defer d.syncStatsLock.RUnlock() 242 243 current := uint64(0) 244 mode := d.getMode() 245 switch { 246 case d.blockchain != nil && mode == FullSync: 247 current = d.blockchain.CurrentBlock().NumberU64() 248 case d.blockchain != nil && mode == SnapSync: 249 current = d.blockchain.CurrentFastBlock().NumberU64() 250 case d.lightchain != nil: 251 current = d.lightchain.CurrentHeader().Number.Uint64() 252 default: 253 log.Error("Unknown downloader chain/mode combo", "light", d.lightchain != nil, "full", d.blockchain != nil, "mode", mode) 254 } 255 progress, pending := d.SnapSyncer.Progress() 256 257 return electroneum.SyncProgress{ 258 StartingBlock: d.syncStatsChainOrigin, 259 CurrentBlock: current, 260 HighestBlock: d.syncStatsChainHeight, 261 SyncedAccounts: progress.AccountSynced, 262 SyncedAccountBytes: uint64(progress.AccountBytes), 263 SyncedBytecodes: progress.BytecodeSynced, 264 SyncedBytecodeBytes: uint64(progress.BytecodeBytes), 265 SyncedStorage: progress.StorageSynced, 266 SyncedStorageBytes: uint64(progress.StorageBytes), 267 HealedTrienodes: progress.TrienodeHealSynced, 268 HealedTrienodeBytes: uint64(progress.TrienodeHealBytes), 269 HealedBytecodes: progress.BytecodeHealSynced, 270 HealedBytecodeBytes: uint64(progress.BytecodeHealBytes), 271 HealingTrienodes: pending.TrienodeHeal, 272 HealingBytecode: pending.BytecodeHeal, 273 } 274 } 275 276 // Synchronising returns whether the downloader is currently retrieving blocks. 277 func (d *Downloader) Synchronising() bool { 278 return atomic.LoadInt32(&d.synchronising) > 0 279 } 280 281 // RegisterPeer injects a new download peer into the set of block source to be 282 // used for fetching hashes and blocks from. 283 func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { 284 var logger log.Logger 285 if len(id) < 16 { 286 // Tests use short IDs, don't choke on them 287 logger = log.New("peer", id) 288 } else { 289 logger = log.New("peer", id[:8]) 290 } 291 logger.Trace("Registering sync peer") 292 if err := d.peers.Register(newPeerConnection(id, version, peer, logger)); err != nil { 293 logger.Error("Failed to register sync peer", "err", err) 294 return err 295 } 296 return nil 297 } 298 299 // RegisterLightPeer injects a light client peer, wrapping it so it appears as a regular peer. 300 func (d *Downloader) RegisterLightPeer(id string, version uint, peer LightPeer) error { 301 return d.RegisterPeer(id, version, &lightPeerWrapper{peer}) 302 } 303 304 // UnregisterPeer remove a peer from the known list, preventing any action from 305 // the specified peer. An effort is also made to return any pending fetches into 306 // the queue. 307 func (d *Downloader) UnregisterPeer(id string) error { 308 // Unregister the peer from the active peer set and revoke any fetch tasks 309 var logger log.Logger 310 if len(id) < 16 { 311 // Tests use short IDs, don't choke on them 312 logger = log.New("peer", id) 313 } else { 314 logger = log.New("peer", id[:8]) 315 } 316 logger.Trace("Unregistering sync peer") 317 if err := d.peers.Unregister(id); err != nil { 318 logger.Error("Failed to unregister sync peer", "err", err) 319 return err 320 } 321 d.queue.Revoke(id) 322 323 return nil 324 } 325 326 // LegacySync tries to sync up our local block chain with a remote peer, both 327 // adding various sanity checks as well as wrapping it with various log entries. 328 func (d *Downloader) LegacySync(id string, head common.Hash, td, ttd *big.Int, mode SyncMode) error { 329 err := d.synchronise(id, head, td, ttd, mode, false, nil) 330 331 switch err { 332 case nil, errBusy, errCanceled: 333 return err 334 } 335 if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) || 336 errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) || 337 errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) { 338 log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err) 339 if d.dropPeer == nil { 340 // The dropPeer method is nil when `--copydb` is used for a local copy. 341 // Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored 342 log.Warn("Downloader wants to drop peer, but peerdrop-function is not set", "peer", id) 343 } else { 344 d.dropPeer(id) 345 } 346 return err 347 } 348 if errors.Is(err, ErrMergeTransition) { 349 return err // This is an expected fault, don't keep printing it in a spin-loop 350 } 351 log.Warn("Synchronisation failed, retrying", "err", err) 352 return err 353 } 354 355 // synchronise will select the peer and use it for synchronising. If an empty string is given 356 // it will use the best peer possible and synchronize if its TD is higher than our own. If any of the 357 // checks fail an error will be returned. This method is synchronous 358 func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int, mode SyncMode, beaconMode bool, beaconPing chan struct{}) error { 359 // The beacon header syncer is async. It will start this synchronization and 360 // will continue doing other tasks. However, if synchronization needs to be 361 // cancelled, the syncer needs to know if we reached the startup point (and 362 // inited the cancel cannel) or not yet. Make sure that we'll signal even in 363 // case of a failure. 364 if beaconPing != nil { 365 defer func() { 366 select { 367 case <-beaconPing: // already notified 368 default: 369 close(beaconPing) // weird exit condition, notify that it's safe to cancel (the nothing) 370 } 371 }() 372 } 373 // Mock out the synchronisation if testing 374 if d.synchroniseMock != nil { 375 return d.synchroniseMock(id, hash) 376 } 377 // Make sure only one goroutine is ever allowed past this point at once 378 if !atomic.CompareAndSwapInt32(&d.synchronising, 0, 1) { 379 return errBusy 380 } 381 defer atomic.StoreInt32(&d.synchronising, 0) 382 383 // Post a user notification of the sync (only once per session) 384 if atomic.CompareAndSwapInt32(&d.notified, 0, 1) { 385 log.Info("Block synchronisation started") 386 } 387 if mode == SnapSync { 388 // Snap sync uses the snapshot namespace to store potentially flakey data until 389 // sync completely heals and finishes. Pause snapshot maintenance in the mean- 390 // time to prevent access. 391 if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests 392 snapshots.Disable() 393 } 394 } 395 // Reset the queue, peer set and wake channels to clean any internal leftover state 396 d.queue.Reset(blockCacheMaxItems, blockCacheInitialItems) 397 d.peers.Reset() 398 399 for _, ch := range []chan bool{d.queue.blockWakeCh, d.queue.receiptWakeCh} { 400 select { 401 case <-ch: 402 default: 403 } 404 } 405 for empty := false; !empty; { 406 select { 407 case <-d.headerProcCh: 408 default: 409 empty = true 410 } 411 } 412 // Create cancel channel for aborting mid-flight and mark the master peer 413 d.cancelLock.Lock() 414 d.cancelCh = make(chan struct{}) 415 d.cancelPeer = id 416 d.cancelLock.Unlock() 417 418 defer d.Cancel() // No matter what, we can't leave the cancel channel open 419 420 // Atomically set the requested sync mode 421 atomic.StoreUint32(&d.mode, uint32(mode)) 422 423 // Retrieve the origin peer and initiate the downloading process 424 var p *peerConnection 425 if !beaconMode { // Beacon mode doesn't need a peer to sync from 426 p = d.peers.Peer(id) 427 if p == nil { 428 return errUnknownPeer 429 } 430 } 431 if beaconPing != nil { 432 close(beaconPing) 433 } 434 return d.syncWithPeer(p, hash, td, ttd, beaconMode) 435 } 436 437 func (d *Downloader) getMode() SyncMode { 438 return SyncMode(atomic.LoadUint32(&d.mode)) 439 } 440 441 // syncWithPeer starts a block synchronization based on the hash chain from the 442 // specified peer and head hash. 443 func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *big.Int, beaconMode bool) (err error) { 444 d.mux.Post(StartEvent{}) 445 defer func() { 446 // reset on error 447 if err != nil { 448 d.mux.Post(FailedEvent{err}) 449 } else { 450 latest := d.lightchain.CurrentHeader() 451 d.mux.Post(DoneEvent{latest}) 452 } 453 }() 454 mode := d.getMode() 455 456 if !beaconMode { 457 log.Debug("Synchronising with the network", "peer", p.id, "eth", p.version, "head", hash, "td", td, "mode", mode) 458 } else { 459 log.Debug("Backfilling with the network", "mode", mode) 460 } 461 defer func(start time.Time) { 462 log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start))) 463 }(time.Now()) 464 465 // Look up the sync boundaries: the common ancestor and the target block 466 var latest, pivot *types.Header 467 if !beaconMode { 468 // In legacy mode, use the master peer to retrieve the headers from 469 latest, pivot, err = d.fetchHead(p) 470 if err != nil { 471 return err 472 } 473 } else { 474 // In beacon mode, user the skeleton chain to retrieve the headers from 475 latest, _, err = d.skeleton.Bounds() 476 if err != nil { 477 return err 478 } 479 if latest.Number.Uint64() > uint64(fsMinFullBlocks) { 480 number := latest.Number.Uint64() - uint64(fsMinFullBlocks) 481 482 // Retrieve the pivot header from the skeleton chain segment but 483 // fallback to local chain if it's not found in skeleton space. 484 if pivot = d.skeleton.Header(number); pivot == nil { 485 _, oldest, _ := d.skeleton.Bounds() // error is already checked 486 if number < oldest.Number.Uint64() { 487 count := int(oldest.Number.Uint64() - number) // it's capped by fsMinFullBlocks 488 headers := d.readHeaderRange(oldest, count) 489 if len(headers) == count { 490 pivot = headers[len(headers)-1] 491 log.Warn("Retrieved pivot header from local", "number", pivot.Number, "hash", pivot.Hash(), "latest", latest.Number, "oldest", oldest.Number) 492 } 493 } 494 } 495 // Print an error log and return directly in case the pivot header 496 // is still not found. It means the skeleton chain is not linked 497 // correctly with local chain. 498 if pivot == nil { 499 log.Error("Pivot header is not found", "number", number) 500 return errNoPivotHeader 501 } 502 } 503 } 504 // If no pivot block was returned, the head is below the min full block 505 // threshold (i.e. new chain). In that case we won't really snap sync 506 // anyway, but still need a valid pivot block to avoid some code hitting 507 // nil panics on access. 508 if mode == SnapSync && pivot == nil { 509 pivot = d.blockchain.CurrentBlock().Header() 510 } 511 height := latest.Number.Uint64() 512 513 var origin uint64 514 if !beaconMode { 515 // In legacy mode, reach out to the network and find the ancestor 516 origin, err = d.findAncestor(p, latest) 517 if err != nil { 518 return err 519 } 520 } else { 521 // In beacon mode, use the skeleton chain for the ancestor lookup 522 origin, err = d.findBeaconAncestor() 523 if err != nil { 524 return err 525 } 526 } 527 d.syncStatsLock.Lock() 528 if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin { 529 d.syncStatsChainOrigin = origin 530 } 531 d.syncStatsChainHeight = height 532 d.syncStatsLock.Unlock() 533 534 // Ensure our origin point is below any snap sync pivot point 535 if mode == SnapSync { 536 if height <= uint64(fsMinFullBlocks) { 537 origin = 0 538 } else { 539 pivotNumber := pivot.Number.Uint64() 540 if pivotNumber <= origin { 541 origin = pivotNumber - 1 542 } 543 // Write out the pivot into the database so a rollback beyond it will 544 // reenable snap sync 545 rawdb.WriteLastPivotNumber(d.stateDB, pivotNumber) 546 } 547 } 548 d.committed = 1 549 if mode == SnapSync && pivot.Number.Uint64() != 0 { 550 d.committed = 0 551 } 552 if mode == SnapSync { 553 // Set the ancient data limitation. 554 // If we are running snap sync, all block data older than ancientLimit will be 555 // written to the ancient store. More recent data will be written to the active 556 // database and will wait for the freezer to migrate. 557 // 558 // If there is a checkpoint available, then calculate the ancientLimit through 559 // that. Otherwise calculate the ancient limit through the advertised height 560 // of the remote peer. 561 // 562 // The reason for picking checkpoint first is that a malicious peer can give us 563 // a fake (very high) height, forcing the ancient limit to also be very high. 564 // The peer would start to feed us valid blocks until head, resulting in all of 565 // the blocks might be written into the ancient store. A following mini-reorg 566 // could cause issues. 567 if d.checkpoint != 0 && d.checkpoint > fullMaxForkAncestry+1 { 568 d.ancientLimit = d.checkpoint 569 } else if height > fullMaxForkAncestry+1 { 570 d.ancientLimit = height - fullMaxForkAncestry - 1 571 } else { 572 d.ancientLimit = 0 573 } 574 frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. 575 576 // If a part of blockchain data has already been written into active store, 577 // disable the ancient style insertion explicitly. 578 if origin >= frozen && frozen != 0 { 579 d.ancientLimit = 0 580 log.Info("Disabling direct-ancient mode", "origin", origin, "ancient", frozen-1) 581 } else if d.ancientLimit > 0 { 582 log.Debug("Enabling direct-ancient mode", "ancient", d.ancientLimit) 583 } 584 // Rewind the ancient store and blockchain if reorg happens. 585 if origin+1 < frozen { 586 if err := d.lightchain.SetHead(origin); err != nil { 587 return err 588 } 589 } 590 } 591 // Initiate the sync using a concurrent header and content retrieval algorithm 592 d.queue.Prepare(origin+1, mode) 593 if d.syncInitHook != nil { 594 d.syncInitHook(origin, height) 595 } 596 var headerFetcher func() error 597 if !beaconMode { 598 // In legacy mode, headers are retrieved from the network 599 headerFetcher = func() error { return d.fetchHeaders(p, origin+1, latest.Number.Uint64()) } 600 } else { 601 // In beacon mode, headers are served by the skeleton syncer 602 headerFetcher = func() error { return d.fetchBeaconHeaders(origin + 1) } 603 } 604 fetchers := []func() error{ 605 headerFetcher, // Headers are always retrieved 606 func() error { return d.fetchBodies(origin+1, beaconMode) }, // Bodies are retrieved during normal and snap sync 607 func() error { return d.fetchReceipts(origin+1, beaconMode) }, // Receipts are retrieved during snap sync 608 func() error { return d.processHeaders(origin+1, td, ttd, beaconMode) }, 609 } 610 if mode == SnapSync { 611 d.pivotLock.Lock() 612 d.pivotHeader = pivot 613 d.pivotLock.Unlock() 614 615 fetchers = append(fetchers, func() error { return d.processSnapSyncContent() }) 616 } else if mode == FullSync { 617 fetchers = append(fetchers, func() error { return d.processFullSyncContent(ttd, beaconMode) }) 618 } 619 return d.spawnSync(fetchers) 620 } 621 622 // spawnSync runs d.process and all given fetcher functions to completion in 623 // separate goroutines, returning the first error that appears. 624 func (d *Downloader) spawnSync(fetchers []func() error) error { 625 errc := make(chan error, len(fetchers)) 626 d.cancelWg.Add(len(fetchers)) 627 for _, fn := range fetchers { 628 fn := fn 629 go func() { defer d.cancelWg.Done(); errc <- fn() }() 630 } 631 // Wait for the first error, then terminate the others. 632 var err error 633 for i := 0; i < len(fetchers); i++ { 634 if i == len(fetchers)-1 { 635 // Close the queue when all fetchers have exited. 636 // This will cause the block processor to end when 637 // it has processed the queue. 638 d.queue.Close() 639 } 640 if err = <-errc; err != nil && err != errCanceled { 641 break 642 } 643 } 644 d.queue.Close() 645 d.Cancel() 646 return err 647 } 648 649 // cancel aborts all of the operations and resets the queue. However, cancel does 650 // not wait for the running download goroutines to finish. This method should be 651 // used when cancelling the downloads from inside the downloader. 652 func (d *Downloader) cancel() { 653 // Close the current cancel channel 654 d.cancelLock.Lock() 655 defer d.cancelLock.Unlock() 656 657 if d.cancelCh != nil { 658 select { 659 case <-d.cancelCh: 660 // Channel was already closed 661 default: 662 close(d.cancelCh) 663 } 664 } 665 } 666 667 // Cancel aborts all of the operations and waits for all download goroutines to 668 // finish before returning. 669 func (d *Downloader) Cancel() { 670 d.cancel() 671 d.cancelWg.Wait() 672 } 673 674 // Terminate interrupts the downloader, canceling all pending operations. 675 // The downloader cannot be reused after calling Terminate. 676 func (d *Downloader) Terminate() { 677 // Close the termination channel (make sure double close is allowed) 678 d.quitLock.Lock() 679 select { 680 case <-d.quitCh: 681 default: 682 close(d.quitCh) 683 684 // Terminate the internal beacon syncer 685 d.skeleton.Terminate() 686 } 687 d.quitLock.Unlock() 688 689 // Cancel any pending download requests 690 d.Cancel() 691 } 692 693 // fetchHead retrieves the head header and prior pivot block (if available) from 694 // a remote peer. 695 func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *types.Header, err error) { 696 p.log.Debug("Retrieving remote chain head") 697 mode := d.getMode() 698 699 // Request the advertised remote head block and wait for the response 700 latest, _ := p.peer.Head() 701 fetch := 1 702 if mode == SnapSync { 703 fetch = 2 // head + pivot headers 704 } 705 headers, hashes, err := d.fetchHeadersByHash(p, latest, fetch, fsMinFullBlocks-1, true) 706 if err != nil { 707 return nil, nil, err 708 } 709 // Make sure the peer gave us at least one and at most the requested headers 710 if len(headers) == 0 || len(headers) > fetch { 711 return nil, nil, fmt.Errorf("%w: returned headers %d != requested %d", errBadPeer, len(headers), fetch) 712 } 713 // The first header needs to be the head, validate against the checkpoint 714 // and request. If only 1 header was returned, make sure there's no pivot 715 // or there was not one requested. 716 head = headers[0] 717 if (mode == SnapSync || mode == LightSync) && head.Number.Uint64() < d.checkpoint { 718 return nil, nil, fmt.Errorf("%w: remote head %d below checkpoint %d", errUnsyncedPeer, head.Number, d.checkpoint) 719 } 720 if len(headers) == 1 { 721 if mode == SnapSync && head.Number.Uint64() > uint64(fsMinFullBlocks) { 722 return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer) 723 } 724 p.log.Debug("Remote head identified, no pivot", "number", head.Number, "hash", hashes[0]) 725 return head, nil, nil 726 } 727 // At this point we have 2 headers in total and the first is the 728 // validated head of the chain. Check the pivot number and return, 729 pivot = headers[1] 730 if pivot.Number.Uint64() != head.Number.Uint64()-uint64(fsMinFullBlocks) { 731 return nil, nil, fmt.Errorf("%w: remote pivot %d != requested %d", errInvalidChain, pivot.Number, head.Number.Uint64()-uint64(fsMinFullBlocks)) 732 } 733 return head, pivot, nil 734 } 735 736 // calculateRequestSpan calculates what headers to request from a peer when trying to determine the 737 // common ancestor. 738 // It returns parameters to be used for peer.RequestHeadersByNumber: 739 // 740 // from - starting block number 741 // count - number of headers to request 742 // skip - number of headers to skip 743 // 744 // and also returns 'max', the last block which is expected to be returned by the remote peers, 745 // given the (from,count,skip) 746 func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) { 747 var ( 748 from int 749 count int 750 MaxCount = MaxHeaderFetch / 16 751 ) 752 // requestHead is the highest block that we will ask for. If requestHead is not offset, 753 // the highest block that we will get is 16 blocks back from head, which means we 754 // will fetch 14 or 15 blocks unnecessarily in the case the height difference 755 // between us and the peer is 1-2 blocks, which is most common 756 requestHead := int(remoteHeight) - 1 757 if requestHead < 0 { 758 requestHead = 0 759 } 760 // requestBottom is the lowest block we want included in the query 761 // Ideally, we want to include the one just below our own head 762 requestBottom := int(localHeight - 1) 763 if requestBottom < 0 { 764 requestBottom = 0 765 } 766 totalSpan := requestHead - requestBottom 767 span := 1 + totalSpan/MaxCount 768 if span < 2 { 769 span = 2 770 } 771 if span > 16 { 772 span = 16 773 } 774 775 count = 1 + totalSpan/span 776 if count > MaxCount { 777 count = MaxCount 778 } 779 if count < 2 { 780 count = 2 781 } 782 from = requestHead - (count-1)*span 783 if from < 0 { 784 from = 0 785 } 786 max := from + (count-1)*span 787 return int64(from), count, span - 1, uint64(max) 788 } 789 790 // findAncestor tries to locate the common ancestor link of the local chain and 791 // a remote peers blockchain. In the general case when our node was in sync and 792 // on the correct chain, checking the top N links should already get us a match. 793 // In the rare scenario when we ended up on a long reorganisation (i.e. none of 794 // the head links match), we do a binary search to find the common ancestor. 795 func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) (uint64, error) { 796 // Figure out the valid ancestor range to prevent rewrite attacks 797 var ( 798 floor = int64(-1) 799 localHeight uint64 800 remoteHeight = remoteHeader.Number.Uint64() 801 ) 802 mode := d.getMode() 803 switch mode { 804 case FullSync: 805 localHeight = d.blockchain.CurrentBlock().NumberU64() 806 case SnapSync: 807 localHeight = d.blockchain.CurrentFastBlock().NumberU64() 808 default: 809 localHeight = d.lightchain.CurrentHeader().Number.Uint64() 810 } 811 p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight) 812 813 // Recap floor value for binary search 814 maxForkAncestry := fullMaxForkAncestry 815 if d.getMode() == LightSync { 816 maxForkAncestry = lightMaxForkAncestry 817 } 818 if localHeight >= maxForkAncestry { 819 // We're above the max reorg threshold, find the earliest fork point 820 floor = int64(localHeight - maxForkAncestry) 821 } 822 // If we're doing a light sync, ensure the floor doesn't go below the CHT, as 823 // all headers before that point will be missing. 824 if mode == LightSync { 825 // If we don't know the current CHT position, find it 826 if d.genesis == 0 { 827 header := d.lightchain.CurrentHeader() 828 for header != nil { 829 d.genesis = header.Number.Uint64() 830 if floor >= int64(d.genesis)-1 { 831 break 832 } 833 header = d.lightchain.GetHeaderByHash(header.ParentHash) 834 } 835 } 836 // We already know the "genesis" block number, cap floor to that 837 if floor < int64(d.genesis)-1 { 838 floor = int64(d.genesis) - 1 839 } 840 } 841 842 ancestor, err := d.findAncestorSpanSearch(p, mode, remoteHeight, localHeight, floor) 843 if err == nil { 844 return ancestor, nil 845 } 846 // The returned error was not nil. 847 // If the error returned does not reflect that a common ancestor was not found, return it. 848 // If the error reflects that a common ancestor was not found, continue to binary search, 849 // where the error value will be reassigned. 850 if !errors.Is(err, errNoAncestorFound) { 851 return 0, err 852 } 853 854 ancestor, err = d.findAncestorBinarySearch(p, mode, remoteHeight, floor) 855 if err != nil { 856 return 0, err 857 } 858 return ancestor, nil 859 } 860 861 func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, remoteHeight, localHeight uint64, floor int64) (uint64, error) { 862 from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight) 863 864 p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip) 865 headers, hashes, err := d.fetchHeadersByNumber(p, uint64(from), count, skip, false) 866 if err != nil { 867 return 0, err 868 } 869 // Wait for the remote response to the head fetch 870 number, hash := uint64(0), common.Hash{} 871 872 // Make sure the peer actually gave something valid 873 if len(headers) == 0 { 874 p.log.Warn("Empty head header set") 875 return 0, errEmptyHeaderSet 876 } 877 // Make sure the peer's reply conforms to the request 878 for i, header := range headers { 879 expectNumber := from + int64(i)*int64(skip+1) 880 if number := header.Number.Int64(); number != expectNumber { 881 p.log.Warn("Head headers broke chain ordering", "index", i, "requested", expectNumber, "received", number) 882 return 0, fmt.Errorf("%w: %v", errInvalidChain, errors.New("head headers broke chain ordering")) 883 } 884 } 885 // Check if a common ancestor was found 886 for i := len(headers) - 1; i >= 0; i-- { 887 // Skip any headers that underflow/overflow our requested set 888 if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > max { 889 continue 890 } 891 // Otherwise check if we already know the header or not 892 h := hashes[i] 893 n := headers[i].Number.Uint64() 894 895 var known bool 896 switch mode { 897 case FullSync: 898 known = d.blockchain.HasBlock(h, n) 899 case SnapSync: 900 known = d.blockchain.HasFastBlock(h, n) 901 default: 902 known = d.lightchain.HasHeader(h, n) 903 } 904 if known { 905 number, hash = n, h 906 break 907 } 908 } 909 // If the head fetch already found an ancestor, return 910 if hash != (common.Hash{}) { 911 if int64(number) <= floor { 912 p.log.Warn("Ancestor below allowance", "number", number, "hash", hash, "allowance", floor) 913 return 0, errInvalidAncestor 914 } 915 p.log.Debug("Found common ancestor", "number", number, "hash", hash) 916 return number, nil 917 } 918 return 0, errNoAncestorFound 919 } 920 921 func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, remoteHeight uint64, floor int64) (uint64, error) { 922 hash := common.Hash{} 923 924 // Ancestor not found, we need to binary search over our chain 925 start, end := uint64(0), remoteHeight 926 if floor > 0 { 927 start = uint64(floor) 928 } 929 p.log.Trace("Binary searching for common ancestor", "start", start, "end", end) 930 931 for start+1 < end { 932 // Split our chain interval in two, and request the hash to cross check 933 check := (start + end) / 2 934 935 headers, hashes, err := d.fetchHeadersByNumber(p, check, 1, 0, false) 936 if err != nil { 937 return 0, err 938 } 939 // Make sure the peer actually gave something valid 940 if len(headers) != 1 { 941 p.log.Warn("Multiple headers for single request", "headers", len(headers)) 942 return 0, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers)) 943 } 944 // Modify the search interval based on the response 945 h := hashes[0] 946 n := headers[0].Number.Uint64() 947 948 var known bool 949 switch mode { 950 case FullSync: 951 known = d.blockchain.HasBlock(h, n) 952 case SnapSync: 953 known = d.blockchain.HasFastBlock(h, n) 954 default: 955 known = d.lightchain.HasHeader(h, n) 956 } 957 if !known { 958 end = check 959 continue 960 } 961 header := d.lightchain.GetHeaderByHash(h) // Independent of sync mode, header surely exists 962 if header.Number.Uint64() != check { 963 p.log.Warn("Received non requested header", "number", header.Number, "hash", header.Hash(), "request", check) 964 return 0, fmt.Errorf("%w: non-requested header (%d)", errBadPeer, header.Number) 965 } 966 start = check 967 hash = h 968 } 969 // Ensure valid ancestry and return 970 if int64(start) <= floor { 971 p.log.Warn("Ancestor below allowance", "number", start, "hash", hash, "allowance", floor) 972 return 0, errInvalidAncestor 973 } 974 p.log.Debug("Found common ancestor", "number", start, "hash", hash) 975 return start, nil 976 } 977 978 // fetchHeaders keeps retrieving headers concurrently from the number 979 // requested, until no more are returned, potentially throttling on the way. To 980 // facilitate concurrency but still protect against malicious nodes sending bad 981 // headers, we construct a header chain skeleton using the "origin" peer we are 982 // syncing with, and fill in the missing headers using anyone else. Headers from 983 // other peers are only accepted if they map cleanly to the skeleton. If no one 984 // can fill in the skeleton - not even the origin peer - it's assumed invalid and 985 // the origin is dropped. 986 func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, head uint64) error { 987 p.log.Debug("Directing header downloads", "origin", from) 988 defer p.log.Debug("Header download terminated") 989 990 // Start pulling the header chain skeleton until all is done 991 var ( 992 skeleton = true // Skeleton assembly phase or finishing up 993 pivoting = false // Whether the next request is pivot verification 994 ancestor = from 995 mode = d.getMode() 996 ) 997 for { 998 // Pull the next batch of headers, it either: 999 // - Pivot check to see if the chain moved too far 1000 // - Skeleton retrieval to permit concurrent header fetches 1001 // - Full header retrieval if we're near the chain head 1002 var ( 1003 headers []*types.Header 1004 hashes []common.Hash 1005 err error 1006 ) 1007 switch { 1008 case pivoting: 1009 d.pivotLock.RLock() 1010 pivot := d.pivotHeader.Number.Uint64() 1011 d.pivotLock.RUnlock() 1012 1013 p.log.Trace("Fetching next pivot header", "number", pivot+uint64(fsMinFullBlocks)) 1014 headers, hashes, err = d.fetchHeadersByNumber(p, pivot+uint64(fsMinFullBlocks), 2, fsMinFullBlocks-9, false) // move +64 when it's 2x64-8 deep 1015 1016 case skeleton: 1017 p.log.Trace("Fetching skeleton headers", "count", MaxHeaderFetch, "from", from) 1018 headers, hashes, err = d.fetchHeadersByNumber(p, from+uint64(MaxHeaderFetch)-1, MaxSkeletonSize, MaxHeaderFetch-1, false) 1019 1020 default: 1021 p.log.Trace("Fetching full headers", "count", MaxHeaderFetch, "from", from) 1022 headers, hashes, err = d.fetchHeadersByNumber(p, from, MaxHeaderFetch, 0, false) 1023 } 1024 switch err { 1025 case nil: 1026 // Headers retrieved, continue with processing 1027 1028 case errCanceled: 1029 // Sync cancelled, no issue, propagate up 1030 return err 1031 1032 default: 1033 // Header retrieval either timed out, or the peer failed in some strange way 1034 // (e.g. disconnect). Consider the master peer bad and drop 1035 d.dropPeer(p.id) 1036 1037 // Finish the sync gracefully instead of dumping the gathered data though 1038 for _, ch := range []chan bool{d.queue.blockWakeCh, d.queue.receiptWakeCh} { 1039 select { 1040 case ch <- false: 1041 case <-d.cancelCh: 1042 } 1043 } 1044 select { 1045 case d.headerProcCh <- nil: 1046 case <-d.cancelCh: 1047 } 1048 return fmt.Errorf("%w: header request failed: %v", errBadPeer, err) 1049 } 1050 // If the pivot is being checked, move if it became stale and run the real retrieval 1051 var pivot uint64 1052 1053 d.pivotLock.RLock() 1054 if d.pivotHeader != nil { 1055 pivot = d.pivotHeader.Number.Uint64() 1056 } 1057 d.pivotLock.RUnlock() 1058 1059 if pivoting { 1060 if len(headers) == 2 { 1061 if have, want := headers[0].Number.Uint64(), pivot+uint64(fsMinFullBlocks); have != want { 1062 log.Warn("Peer sent invalid next pivot", "have", have, "want", want) 1063 return fmt.Errorf("%w: next pivot number %d != requested %d", errInvalidChain, have, want) 1064 } 1065 if have, want := headers[1].Number.Uint64(), pivot+2*uint64(fsMinFullBlocks)-8; have != want { 1066 log.Warn("Peer sent invalid pivot confirmer", "have", have, "want", want) 1067 return fmt.Errorf("%w: next pivot confirmer number %d != requested %d", errInvalidChain, have, want) 1068 } 1069 log.Warn("Pivot seemingly stale, moving", "old", pivot, "new", headers[0].Number) 1070 pivot = headers[0].Number.Uint64() 1071 1072 d.pivotLock.Lock() 1073 d.pivotHeader = headers[0] 1074 d.pivotLock.Unlock() 1075 1076 // Write out the pivot into the database so a rollback beyond 1077 // it will reenable snap sync and update the state root that 1078 // the state syncer will be downloading. 1079 rawdb.WriteLastPivotNumber(d.stateDB, pivot) 1080 } 1081 // Disable the pivot check and fetch the next batch of headers 1082 pivoting = false 1083 continue 1084 } 1085 // If the skeleton's finished, pull any remaining head headers directly from the origin 1086 if skeleton && len(headers) == 0 { 1087 // A malicious node might withhold advertised headers indefinitely 1088 if from+uint64(MaxHeaderFetch)-1 <= head { 1089 p.log.Warn("Peer withheld skeleton headers", "advertised", head, "withheld", from+uint64(MaxHeaderFetch)-1) 1090 return fmt.Errorf("%w: withheld skeleton headers: advertised %d, withheld #%d", errStallingPeer, head, from+uint64(MaxHeaderFetch)-1) 1091 } 1092 p.log.Debug("No skeleton, fetching headers directly") 1093 skeleton = false 1094 continue 1095 } 1096 // If no more headers are inbound, notify the content fetchers and return 1097 if len(headers) == 0 { 1098 // Don't abort header fetches while the pivot is downloading 1099 if atomic.LoadInt32(&d.committed) == 0 && pivot <= from { 1100 p.log.Debug("No headers, waiting for pivot commit") 1101 select { 1102 case <-time.After(fsHeaderContCheck): 1103 continue 1104 case <-d.cancelCh: 1105 return errCanceled 1106 } 1107 } 1108 // Pivot done (or not in snap sync) and no more headers, terminate the process 1109 p.log.Debug("No more headers available") 1110 select { 1111 case d.headerProcCh <- nil: 1112 return nil 1113 case <-d.cancelCh: 1114 return errCanceled 1115 } 1116 } 1117 // If we received a skeleton batch, resolve internals concurrently 1118 var progressed bool 1119 if skeleton { 1120 filled, hashset, proced, err := d.fillHeaderSkeleton(from, headers) 1121 if err != nil { 1122 p.log.Debug("Skeleton chain invalid", "err", err) 1123 return fmt.Errorf("%w: %v", errInvalidChain, err) 1124 } 1125 headers = filled[proced:] 1126 hashes = hashset[proced:] 1127 1128 progressed = proced > 0 1129 from += uint64(proced) 1130 } else { 1131 // A malicious node might withhold advertised headers indefinitely 1132 if n := len(headers); n < MaxHeaderFetch && headers[n-1].Number.Uint64() < head { 1133 p.log.Warn("Peer withheld headers", "advertised", head, "delivered", headers[n-1].Number.Uint64()) 1134 return fmt.Errorf("%w: withheld headers: advertised %d, delivered %d", errStallingPeer, head, headers[n-1].Number.Uint64()) 1135 } 1136 // If we're closing in on the chain head, but haven't yet reached it, delay 1137 // the last few headers so mini reorgs on the head don't cause invalid hash 1138 // chain errors. 1139 if n := len(headers); n > 0 { 1140 // Retrieve the current head we're at 1141 var head uint64 1142 if mode == LightSync { 1143 head = d.lightchain.CurrentHeader().Number.Uint64() 1144 } else { 1145 head = d.blockchain.CurrentFastBlock().NumberU64() 1146 if full := d.blockchain.CurrentBlock().NumberU64(); head < full { 1147 head = full 1148 } 1149 } 1150 // If the head is below the common ancestor, we're actually deduplicating 1151 // already existing chain segments, so use the ancestor as the fake head. 1152 // Otherwise, we might end up delaying header deliveries pointlessly. 1153 if head < ancestor { 1154 head = ancestor 1155 } 1156 // If the head is way older than this batch, delay the last few headers 1157 if head+uint64(reorgProtThreshold) < headers[n-1].Number.Uint64() { 1158 delay := reorgProtHeaderDelay 1159 if delay > n { 1160 delay = n 1161 } 1162 headers = headers[:n-delay] 1163 hashes = hashes[:n-delay] 1164 } 1165 } 1166 } 1167 // If no headers have bene delivered, or all of them have been delayed, 1168 // sleep a bit and retry. Take care with headers already consumed during 1169 // skeleton filling 1170 if len(headers) == 0 && !progressed { 1171 p.log.Trace("All headers delayed, waiting") 1172 select { 1173 case <-time.After(fsHeaderContCheck): 1174 continue 1175 case <-d.cancelCh: 1176 return errCanceled 1177 } 1178 } 1179 // Insert any remaining new headers and fetch the next batch 1180 if len(headers) > 0 { 1181 p.log.Trace("Scheduling new headers", "count", len(headers), "from", from) 1182 select { 1183 case d.headerProcCh <- &headerTask{ 1184 headers: headers, 1185 hashes: hashes, 1186 }: 1187 case <-d.cancelCh: 1188 return errCanceled 1189 } 1190 from += uint64(len(headers)) 1191 } 1192 // If we're still skeleton filling snap sync, check pivot staleness 1193 // before continuing to the next skeleton filling 1194 if skeleton && pivot > 0 { 1195 pivoting = true 1196 } 1197 } 1198 } 1199 1200 // fillHeaderSkeleton concurrently retrieves headers from all our available peers 1201 // and maps them to the provided skeleton header chain. 1202 // 1203 // Any partial results from the beginning of the skeleton is (if possible) forwarded 1204 // immediately to the header processor to keep the rest of the pipeline full even 1205 // in the case of header stalls. 1206 // 1207 // The method returns the entire filled skeleton and also the number of headers 1208 // already forwarded for processing. 1209 func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ([]*types.Header, []common.Hash, int, error) { 1210 log.Debug("Filling up skeleton", "from", from) 1211 d.queue.ScheduleSkeleton(from, skeleton) 1212 1213 err := d.concurrentFetch((*headerQueue)(d), false) 1214 if err != nil { 1215 log.Debug("Skeleton fill failed", "err", err) 1216 } 1217 filled, hashes, proced := d.queue.RetrieveHeaders() 1218 if err == nil { 1219 log.Debug("Skeleton fill succeeded", "filled", len(filled), "processed", proced) 1220 } 1221 return filled, hashes, proced, err 1222 } 1223 1224 // fetchBodies iteratively downloads the scheduled block bodies, taking any 1225 // available peers, reserving a chunk of blocks for each, waiting for delivery 1226 // and also periodically checking for timeouts. 1227 func (d *Downloader) fetchBodies(from uint64, beaconMode bool) error { 1228 log.Debug("Downloading block bodies", "origin", from) 1229 err := d.concurrentFetch((*bodyQueue)(d), beaconMode) 1230 1231 log.Debug("Block body download terminated", "err", err) 1232 return err 1233 } 1234 1235 // fetchReceipts iteratively downloads the scheduled block receipts, taking any 1236 // available peers, reserving a chunk of receipts for each, waiting for delivery 1237 // and also periodically checking for timeouts. 1238 func (d *Downloader) fetchReceipts(from uint64, beaconMode bool) error { 1239 log.Debug("Downloading receipts", "origin", from) 1240 err := d.concurrentFetch((*receiptQueue)(d), beaconMode) 1241 1242 log.Debug("Receipt download terminated", "err", err) 1243 return err 1244 } 1245 1246 // processHeaders takes batches of retrieved headers from an input channel and 1247 // keeps processing and scheduling them into the header chain and downloader's 1248 // queue until the stream ends or a failure occurs. 1249 func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode bool) error { 1250 // Keep a count of uncertain headers to roll back 1251 var ( 1252 rollback uint64 // Zero means no rollback (fine as you can't unroll the genesis) 1253 rollbackErr error 1254 mode = d.getMode() 1255 ) 1256 defer func() { 1257 if rollback > 0 { 1258 lastHeader, lastFastBlock, lastBlock := d.lightchain.CurrentHeader().Number, common.Big0, common.Big0 1259 if mode != LightSync { 1260 lastFastBlock = d.blockchain.CurrentFastBlock().Number() 1261 lastBlock = d.blockchain.CurrentBlock().Number() 1262 } 1263 if err := d.lightchain.SetHead(rollback - 1); err != nil { // -1 to target the parent of the first uncertain block 1264 // We're already unwinding the stack, only print the error to make it more visible 1265 log.Error("Failed to roll back chain segment", "head", rollback-1, "err", err) 1266 } 1267 curFastBlock, curBlock := common.Big0, common.Big0 1268 if mode != LightSync { 1269 curFastBlock = d.blockchain.CurrentFastBlock().Number() 1270 curBlock = d.blockchain.CurrentBlock().Number() 1271 } 1272 log.Warn("Rolled back chain segment", 1273 "header", fmt.Sprintf("%d->%d", lastHeader, d.lightchain.CurrentHeader().Number), 1274 "snap", fmt.Sprintf("%d->%d", lastFastBlock, curFastBlock), 1275 "block", fmt.Sprintf("%d->%d", lastBlock, curBlock), "reason", rollbackErr) 1276 } 1277 }() 1278 // Wait for batches of headers to process 1279 gotHeaders := false 1280 1281 for { 1282 select { 1283 case <-d.cancelCh: 1284 rollbackErr = errCanceled 1285 return errCanceled 1286 1287 case task := <-d.headerProcCh: 1288 // Terminate header processing if we synced up 1289 if task == nil || len(task.headers) == 0 { 1290 // Notify everyone that headers are fully processed 1291 for _, ch := range []chan bool{d.queue.blockWakeCh, d.queue.receiptWakeCh} { 1292 select { 1293 case ch <- false: 1294 case <-d.cancelCh: 1295 } 1296 } 1297 // If we're in legacy sync mode, we need to check total difficulty 1298 // violations from malicious peers. That is not needed in beacon 1299 // mode and we can skip to terminating sync. 1300 if !beaconMode { 1301 // If no headers were retrieved at all, the peer violated its TD promise that it had a 1302 // better chain compared to ours. The only exception is if its promised blocks were 1303 // already imported by other means (e.g. fetcher): 1304 // 1305 // R <remote peer>, L <local node>: Both at block 10 1306 // R: Mine block 11, and propagate it to L 1307 // L: Queue block 11 for import 1308 // L: Notice that R's head and TD increased compared to ours, start sync 1309 // L: Import of block 11 finishes 1310 // L: Sync begins, and finds common ancestor at 11 1311 // L: Request new headers up from 11 (R's TD was higher, it must have something) 1312 // R: Nothing to give 1313 if mode != LightSync { 1314 head := d.blockchain.CurrentBlock() 1315 if !gotHeaders && td.Cmp(d.blockchain.GetTd(head.Hash(), head.NumberU64())) > 0 { 1316 return errStallingPeer 1317 } 1318 } 1319 // If snap or light syncing, ensure promised headers are indeed delivered. This is 1320 // needed to detect scenarios where an attacker feeds a bad pivot and then bails out 1321 // of delivering the post-pivot blocks that would flag the invalid content. 1322 // 1323 // This check cannot be executed "as is" for full imports, since blocks may still be 1324 // queued for processing when the header download completes. However, as long as the 1325 // peer gave us something useful, we're already happy/progressed (above check). 1326 if mode == SnapSync || mode == LightSync { 1327 head := d.lightchain.CurrentHeader() 1328 if td.Cmp(d.lightchain.GetTd(head.Hash(), head.Number.Uint64())) > 0 { 1329 return errStallingPeer 1330 } 1331 } 1332 } 1333 // Disable any rollback and return 1334 rollback = 0 1335 return nil 1336 } 1337 // Otherwise split the chunk of headers into batches and process them 1338 headers, hashes := task.headers, task.hashes 1339 1340 gotHeaders = true 1341 for len(headers) > 0 { 1342 // Terminate if something failed in between processing chunks 1343 select { 1344 case <-d.cancelCh: 1345 rollbackErr = errCanceled 1346 return errCanceled 1347 default: 1348 } 1349 // Select the next chunk of headers to import 1350 limit := maxHeadersProcess 1351 if limit > len(headers) { 1352 limit = len(headers) 1353 } 1354 chunkHeaders := headers[:limit] 1355 chunkHashes := hashes[:limit] 1356 1357 // In case of header only syncing, validate the chunk immediately 1358 if mode == SnapSync || mode == LightSync { 1359 // If we're importing pure headers, verify based on their recentness 1360 var pivot uint64 1361 1362 d.pivotLock.RLock() 1363 if d.pivotHeader != nil { 1364 pivot = d.pivotHeader.Number.Uint64() 1365 } 1366 d.pivotLock.RUnlock() 1367 1368 frequency := fsHeaderCheckFrequency 1369 if chunkHeaders[len(chunkHeaders)-1].Number.Uint64()+uint64(fsHeaderForceVerify) > pivot { 1370 frequency = 1 1371 } 1372 // Although the received headers might be all valid, a legacy 1373 // PoW/PoA sync must not accept post-merge headers. Make sure 1374 // that any transition is rejected at this point. 1375 var ( 1376 rejected []*types.Header 1377 td *big.Int 1378 ) 1379 if !beaconMode && ttd != nil { 1380 td = d.blockchain.GetTd(chunkHeaders[0].ParentHash, chunkHeaders[0].Number.Uint64()-1) 1381 if td == nil { 1382 // This should never really happen, but handle gracefully for now 1383 log.Error("Failed to retrieve parent header TD", "number", chunkHeaders[0].Number.Uint64()-1, "hash", chunkHeaders[0].ParentHash) 1384 return fmt.Errorf("%w: parent TD missing", errInvalidChain) 1385 } 1386 for i, header := range chunkHeaders { 1387 td = new(big.Int).Add(td, header.Difficulty) 1388 if td.Cmp(ttd) >= 0 { 1389 // Terminal total difficulty reached, allow the last header in 1390 if new(big.Int).Sub(td, header.Difficulty).Cmp(ttd) < 0 { 1391 chunkHeaders, rejected = chunkHeaders[:i+1], chunkHeaders[i+1:] 1392 if len(rejected) > 0 { 1393 // Make a nicer user log as to the first TD truly rejected 1394 td = new(big.Int).Add(td, rejected[0].Difficulty) 1395 } 1396 } else { 1397 chunkHeaders, rejected = chunkHeaders[:i], chunkHeaders[i:] 1398 } 1399 break 1400 } 1401 } 1402 } 1403 if len(chunkHeaders) > 0 { 1404 if n, err := d.lightchain.InsertHeaderChain(chunkHeaders, frequency); err != nil { 1405 rollbackErr = err 1406 1407 // If some headers were inserted, track them as uncertain 1408 if (mode == SnapSync || frequency > 1) && n > 0 && rollback == 0 { 1409 rollback = chunkHeaders[0].Number.Uint64() 1410 } 1411 log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err) 1412 return fmt.Errorf("%w: %v", errInvalidChain, err) 1413 } 1414 // All verifications passed, track all headers within the allowed limits 1415 if mode == SnapSync { 1416 head := chunkHeaders[len(chunkHeaders)-1].Number.Uint64() 1417 if head-rollback > uint64(fsHeaderSafetyNet) { 1418 rollback = head - uint64(fsHeaderSafetyNet) 1419 } else { 1420 rollback = 1 1421 } 1422 } 1423 } 1424 if len(rejected) != 0 { 1425 // Merge threshold reached, stop importing, but don't roll back 1426 rollback = 0 1427 1428 log.Info("Legacy sync reached merge threshold", "number", rejected[0].Number, "hash", rejected[0].Hash(), "td", td, "ttd", ttd) 1429 return ErrMergeTransition 1430 } 1431 } 1432 // Unless we're doing light chains, schedule the headers for associated content retrieval 1433 if mode == FullSync || mode == SnapSync { 1434 // If we've reached the allowed number of pending headers, stall a bit 1435 for d.queue.PendingBodies() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders { 1436 select { 1437 case <-d.cancelCh: 1438 rollbackErr = errCanceled 1439 return errCanceled 1440 case <-time.After(time.Second): 1441 } 1442 } 1443 // Otherwise insert the headers for content retrieval 1444 inserts := d.queue.Schedule(chunkHeaders, chunkHashes, origin) 1445 if len(inserts) != len(chunkHeaders) { 1446 rollbackErr = fmt.Errorf("stale headers: len inserts %v len(chunk) %v", len(inserts), len(chunkHeaders)) 1447 return fmt.Errorf("%w: stale headers", errBadPeer) 1448 } 1449 } 1450 headers = headers[limit:] 1451 hashes = hashes[limit:] 1452 origin += uint64(limit) 1453 } 1454 // Update the highest block number we know if a higher one is found. 1455 d.syncStatsLock.Lock() 1456 if d.syncStatsChainHeight < origin { 1457 d.syncStatsChainHeight = origin - 1 1458 } 1459 d.syncStatsLock.Unlock() 1460 1461 // Signal the content downloaders of the availablility of new tasks 1462 for _, ch := range []chan bool{d.queue.blockWakeCh, d.queue.receiptWakeCh} { 1463 select { 1464 case ch <- true: 1465 default: 1466 } 1467 } 1468 } 1469 } 1470 } 1471 1472 // processFullSyncContent takes fetch results from the queue and imports them into the chain. 1473 func (d *Downloader) processFullSyncContent(ttd *big.Int, beaconMode bool) error { 1474 for { 1475 results := d.queue.Results(true) 1476 if len(results) == 0 { 1477 return nil 1478 } 1479 if d.chainInsertHook != nil { 1480 d.chainInsertHook(results) 1481 } 1482 // Although the received blocks might be all valid, a legacy PoW/PoA sync 1483 // must not accept post-merge blocks. Make sure that pre-merge blocks are 1484 // imported, but post-merge ones are rejected. 1485 var ( 1486 rejected []*fetchResult 1487 td *big.Int 1488 ) 1489 if !beaconMode && ttd != nil { 1490 td = d.blockchain.GetTd(results[0].Header.ParentHash, results[0].Header.Number.Uint64()-1) 1491 if td == nil { 1492 // This should never really happen, but handle gracefully for now 1493 log.Error("Failed to retrieve parent block TD", "number", results[0].Header.Number.Uint64()-1, "hash", results[0].Header.ParentHash) 1494 return fmt.Errorf("%w: parent TD missing", errInvalidChain) 1495 } 1496 for i, result := range results { 1497 td = new(big.Int).Add(td, result.Header.Difficulty) 1498 if td.Cmp(ttd) >= 0 { 1499 // Terminal total difficulty reached, allow the last block in 1500 if new(big.Int).Sub(td, result.Header.Difficulty).Cmp(ttd) < 0 { 1501 results, rejected = results[:i+1], results[i+1:] 1502 if len(rejected) > 0 { 1503 // Make a nicer user log as to the first TD truly rejected 1504 td = new(big.Int).Add(td, rejected[0].Header.Difficulty) 1505 } 1506 } else { 1507 results, rejected = results[:i], results[i:] 1508 } 1509 break 1510 } 1511 } 1512 } 1513 if err := d.importBlockResults(results); err != nil { 1514 return err 1515 } 1516 if len(rejected) != 0 { 1517 log.Info("Legacy sync reached merge threshold", "number", rejected[0].Header.Number, "hash", rejected[0].Header.Hash(), "td", td, "ttd", ttd) 1518 return ErrMergeTransition 1519 } 1520 } 1521 } 1522 1523 func (d *Downloader) importBlockResults(results []*fetchResult) error { 1524 // Check for any early termination requests 1525 if len(results) == 0 { 1526 return nil 1527 } 1528 select { 1529 case <-d.quitCh: 1530 return errCancelContentProcessing 1531 default: 1532 } 1533 // Retrieve the a batch of results to import 1534 first, last := results[0].Header, results[len(results)-1].Header 1535 log.Debug("Inserting downloaded chain", "items", len(results), 1536 "firstnum", first.Number, "firsthash", first.Hash(), 1537 "lastnum", last.Number, "lasthash", last.Hash(), 1538 ) 1539 blocks := make([]*types.Block, len(results)) 1540 for i, result := range results { 1541 blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) 1542 } 1543 // Downloaded blocks are always regarded as trusted after the 1544 // transition. Because the downloaded chain is guided by the 1545 // consensus-layer. 1546 if index, err := d.blockchain.InsertChain(blocks); err != nil { 1547 if index < len(results) { 1548 log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) 1549 } else { 1550 // The InsertChain method in blockchain.go will sometimes return an out-of-bounds index, 1551 // when it needs to preprocess blocks to import a sidechain. 1552 // The importer will put together a new list of blocks to import, which is a superset 1553 // of the blocks delivered from the downloader, and the indexing will be off. 1554 log.Debug("Downloaded item processing failed on sidechain import", "index", index, "err", err) 1555 } 1556 return fmt.Errorf("%w: %v", errInvalidChain, err) 1557 } 1558 return nil 1559 } 1560 1561 // processSnapSyncContent takes fetch results from the queue and writes them to the 1562 // database. It also controls the synchronisation of state nodes of the pivot block. 1563 func (d *Downloader) processSnapSyncContent() error { 1564 // Start syncing state of the reported head block. This should get us most of 1565 // the state of the pivot block. 1566 d.pivotLock.RLock() 1567 sync := d.syncState(d.pivotHeader.Root) 1568 d.pivotLock.RUnlock() 1569 1570 defer func() { 1571 // The `sync` object is replaced every time the pivot moves. We need to 1572 // defer close the very last active one, hence the lazy evaluation vs. 1573 // calling defer sync.Cancel() !!! 1574 sync.Cancel() 1575 }() 1576 1577 closeOnErr := func(s *stateSync) { 1578 if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled && err != snap.ErrCancelled { 1579 d.queue.Close() // wake up Results 1580 } 1581 } 1582 go closeOnErr(sync) 1583 1584 // To cater for moving pivot points, track the pivot block and subsequently 1585 // accumulated download results separately. 1586 var ( 1587 oldPivot *fetchResult // Locked in pivot block, might change eventually 1588 oldTail []*fetchResult // Downloaded content after the pivot 1589 ) 1590 for { 1591 // Wait for the next batch of downloaded data to be available, and if the pivot 1592 // block became stale, move the goalpost 1593 results := d.queue.Results(oldPivot == nil) // Block if we're not monitoring pivot staleness 1594 if len(results) == 0 { 1595 // If pivot sync is done, stop 1596 if oldPivot == nil { 1597 return sync.Cancel() 1598 } 1599 // If sync failed, stop 1600 select { 1601 case <-d.cancelCh: 1602 sync.Cancel() 1603 return errCanceled 1604 default: 1605 } 1606 } 1607 if d.chainInsertHook != nil { 1608 d.chainInsertHook(results) 1609 } 1610 // If we haven't downloaded the pivot block yet, check pivot staleness 1611 // notifications from the header downloader 1612 d.pivotLock.RLock() 1613 pivot := d.pivotHeader 1614 d.pivotLock.RUnlock() 1615 1616 if oldPivot == nil { 1617 if pivot.Root != sync.root { 1618 sync.Cancel() 1619 sync = d.syncState(pivot.Root) 1620 1621 go closeOnErr(sync) 1622 } 1623 } else { 1624 results = append(append([]*fetchResult{oldPivot}, oldTail...), results...) 1625 } 1626 // Split around the pivot block and process the two sides via snap/full sync 1627 if atomic.LoadInt32(&d.committed) == 0 { 1628 latest := results[len(results)-1].Header 1629 // If the height is above the pivot block by 2 sets, it means the pivot 1630 // become stale in the network and it was garbage collected, move to a 1631 // new pivot. 1632 // 1633 // Note, we have `reorgProtHeaderDelay` number of blocks withheld, Those 1634 // need to be taken into account, otherwise we're detecting the pivot move 1635 // late and will drop peers due to unavailable state!!! 1636 if height := latest.Number.Uint64(); height >= pivot.Number.Uint64()+2*uint64(fsMinFullBlocks)-uint64(reorgProtHeaderDelay) { 1637 log.Warn("Pivot became stale, moving", "old", pivot.Number.Uint64(), "new", height-uint64(fsMinFullBlocks)+uint64(reorgProtHeaderDelay)) 1638 pivot = results[len(results)-1-fsMinFullBlocks+reorgProtHeaderDelay].Header // must exist as lower old pivot is uncommitted 1639 1640 d.pivotLock.Lock() 1641 d.pivotHeader = pivot 1642 d.pivotLock.Unlock() 1643 1644 // Write out the pivot into the database so a rollback beyond it will 1645 // reenable snap sync 1646 rawdb.WriteLastPivotNumber(d.stateDB, pivot.Number.Uint64()) 1647 } 1648 } 1649 P, beforeP, afterP := splitAroundPivot(pivot.Number.Uint64(), results) 1650 if err := d.commitSnapSyncData(beforeP, sync); err != nil { 1651 return err 1652 } 1653 if P != nil { 1654 // If new pivot block found, cancel old state retrieval and restart 1655 if oldPivot != P { 1656 sync.Cancel() 1657 sync = d.syncState(P.Header.Root) 1658 1659 go closeOnErr(sync) 1660 oldPivot = P 1661 } 1662 // Wait for completion, occasionally checking for pivot staleness 1663 select { 1664 case <-sync.done: 1665 if sync.err != nil { 1666 return sync.err 1667 } 1668 if err := d.commitPivotBlock(P); err != nil { 1669 return err 1670 } 1671 oldPivot = nil 1672 1673 case <-time.After(time.Second): 1674 oldTail = afterP 1675 continue 1676 } 1677 } 1678 // Fast sync done, pivot commit done, full import 1679 if err := d.importBlockResults(afterP); err != nil { 1680 return err 1681 } 1682 } 1683 } 1684 1685 func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, before, after []*fetchResult) { 1686 if len(results) == 0 { 1687 return nil, nil, nil 1688 } 1689 if lastNum := results[len(results)-1].Header.Number.Uint64(); lastNum < pivot { 1690 // the pivot is somewhere in the future 1691 return nil, results, nil 1692 } 1693 // This can also be optimized, but only happens very seldom 1694 for _, result := range results { 1695 num := result.Header.Number.Uint64() 1696 switch { 1697 case num < pivot: 1698 before = append(before, result) 1699 case num == pivot: 1700 p = result 1701 default: 1702 after = append(after, result) 1703 } 1704 } 1705 return p, before, after 1706 } 1707 1708 func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *stateSync) error { 1709 // Check for any early termination requests 1710 if len(results) == 0 { 1711 return nil 1712 } 1713 select { 1714 case <-d.quitCh: 1715 return errCancelContentProcessing 1716 case <-stateSync.done: 1717 if err := stateSync.Wait(); err != nil { 1718 return err 1719 } 1720 default: 1721 } 1722 // Retrieve the a batch of results to import 1723 first, last := results[0].Header, results[len(results)-1].Header 1724 log.Debug("Inserting snap-sync blocks", "items", len(results), 1725 "firstnum", first.Number, "firsthash", first.Hash(), 1726 "lastnumn", last.Number, "lasthash", last.Hash(), 1727 ) 1728 blocks := make([]*types.Block, len(results)) 1729 receipts := make([]types.Receipts, len(results)) 1730 for i, result := range results { 1731 blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) 1732 receipts[i] = result.Receipts 1733 } 1734 if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil { 1735 log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) 1736 return fmt.Errorf("%w: %v", errInvalidChain, err) 1737 } 1738 return nil 1739 } 1740 1741 func (d *Downloader) commitPivotBlock(result *fetchResult) error { 1742 block := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) 1743 log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash()) 1744 1745 // Commit the pivot block as the new head, will require full sync from here on 1746 if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{result.Receipts}, d.ancientLimit); err != nil { 1747 return err 1748 } 1749 if err := d.blockchain.SnapSyncCommitHead(block.Hash()); err != nil { 1750 return err 1751 } 1752 atomic.StoreInt32(&d.committed, 1) 1753 return nil 1754 } 1755 1756 // DeliverSnapPacket is invoked from a peer's message handler when it transmits a 1757 // data packet for the local node to consume. 1758 func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) error { 1759 switch packet := packet.(type) { 1760 case *snap.AccountRangePacket: 1761 hashes, accounts, err := packet.Unpack() 1762 if err != nil { 1763 return err 1764 } 1765 return d.SnapSyncer.OnAccounts(peer, packet.ID, hashes, accounts, packet.Proof) 1766 1767 case *snap.StorageRangesPacket: 1768 hashset, slotset := packet.Unpack() 1769 return d.SnapSyncer.OnStorage(peer, packet.ID, hashset, slotset, packet.Proof) 1770 1771 case *snap.ByteCodesPacket: 1772 return d.SnapSyncer.OnByteCodes(peer, packet.ID, packet.Codes) 1773 1774 case *snap.TrieNodesPacket: 1775 return d.SnapSyncer.OnTrieNodes(peer, packet.ID, packet.Nodes) 1776 1777 default: 1778 return fmt.Errorf("unexpected snap packet type: %T", packet) 1779 } 1780 } 1781 1782 // readHeaderRange returns a list of headers, using the given last header as the base, 1783 // and going backwards towards genesis. This method assumes that the caller already has 1784 // placed a reasonable cap on count. 1785 func (d *Downloader) readHeaderRange(last *types.Header, count int) []*types.Header { 1786 var ( 1787 current = last 1788 headers []*types.Header 1789 ) 1790 for { 1791 parent := d.lightchain.GetHeaderByHash(current.ParentHash) 1792 if parent == nil { 1793 break // The chain is not continuous, or the chain is exhausted 1794 } 1795 headers = append(headers, parent) 1796 if len(headers) >= count { 1797 break 1798 } 1799 current = parent 1800 } 1801 return headers 1802 }