github.com/ethereum/go-ethereum@v1.16.1/eth/downloader/beaconsync.go (about)

     1  // Copyright 2022 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
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/core/rawdb"
    26  	"github.com/ethereum/go-ethereum/core/types"
    27  	"github.com/ethereum/go-ethereum/eth/ethconfig"
    28  	"github.com/ethereum/go-ethereum/log"
    29  )
    30  
    31  // beaconBackfiller is the chain and state backfilling that can be commenced once
    32  // the skeleton syncer has successfully reverse downloaded all the headers up to
    33  // the genesis block or an existing header in the database. Its operation is fully
    34  // directed by the skeleton sync's head/tail events.
    35  type beaconBackfiller struct {
    36  	downloader *Downloader   // Downloader to direct via this callback implementation
    37  	syncMode   SyncMode      // Sync mode to use for backfilling the skeleton chains
    38  	success    func()        // Callback to run on successful sync cycle completion
    39  	filling    bool          // Flag whether the downloader is backfilling or not
    40  	filled     *types.Header // Last header filled by the last terminated sync loop
    41  	started    chan struct{} // Notification channel whether the downloader inited
    42  	lock       sync.Mutex    // Mutex protecting the sync lock
    43  }
    44  
    45  // newBeaconBackfiller is a helper method to create the backfiller.
    46  func newBeaconBackfiller(dl *Downloader, success func()) backfiller {
    47  	return &beaconBackfiller{
    48  		downloader: dl,
    49  		success:    success,
    50  	}
    51  }
    52  
    53  // suspend cancels any background downloader threads and returns the last header
    54  // that has been successfully backfilled (potentially in a previous run), or the
    55  // genesis.
    56  func (b *beaconBackfiller) suspend() *types.Header {
    57  	// If no filling is running, don't waste cycles
    58  	b.lock.Lock()
    59  	filling := b.filling
    60  	filled := b.filled
    61  	started := b.started
    62  	b.lock.Unlock()
    63  
    64  	if !filling {
    65  		return filled // Return the filled header on the previous sync completion
    66  	}
    67  	// A previous filling should be running, though it may happen that it hasn't
    68  	// yet started (being done on a new goroutine). Many concurrent beacon head
    69  	// announcements can lead to sync start/stop thrashing. In that case we need
    70  	// to wait for initialization before we can safely cancel it. It is safe to
    71  	// read this channel multiple times, it gets closed on startup.
    72  	<-started
    73  
    74  	// Now that we're sure the downloader successfully started up, we can cancel
    75  	// it safely without running the risk of data races.
    76  	b.downloader.Cancel()
    77  
    78  	// Sync cycle was just terminated, retrieve and return the last filled header.
    79  	// Can't use `filled` as that contains a stale value from before cancellation.
    80  	return b.downloader.blockchain.CurrentSnapBlock()
    81  }
    82  
    83  // resume starts the downloader threads for backfilling state and chain data.
    84  func (b *beaconBackfiller) resume() {
    85  	b.lock.Lock()
    86  	if b.filling {
    87  		// If a previous filling cycle is still running, just ignore this start
    88  		// request. // TODO(karalabe): We should make this channel driven
    89  		b.lock.Unlock()
    90  		return
    91  	}
    92  	b.filling = true
    93  	b.filled = nil
    94  	b.started = make(chan struct{})
    95  	mode := b.syncMode
    96  	b.lock.Unlock()
    97  
    98  	// Start the backfilling on its own thread since the downloader does not have
    99  	// its own lifecycle runloop.
   100  	go func() {
   101  		// Set the backfiller to non-filling when download completes
   102  		defer func() {
   103  			b.lock.Lock()
   104  			b.filling = false
   105  			b.filled = b.downloader.blockchain.CurrentSnapBlock()
   106  			b.lock.Unlock()
   107  		}()
   108  		// If the downloader fails, report an error as in beacon chain mode there
   109  		// should be no errors as long as the chain we're syncing to is valid.
   110  		if err := b.downloader.synchronise(mode, b.started); err != nil {
   111  			log.Error("Beacon backfilling failed", "err", err)
   112  			return
   113  		}
   114  		// Synchronization succeeded. Since this happens async, notify the outer
   115  		// context to disable snap syncing and enable transaction propagation.
   116  		if b.success != nil {
   117  			b.success()
   118  		}
   119  	}()
   120  }
   121  
   122  // setMode updates the sync mode from the current one to the requested one. If
   123  // there's an active sync in progress, it will be cancelled and restarted.
   124  func (b *beaconBackfiller) setMode(mode SyncMode) {
   125  	// Update the old sync mode and track if it was changed
   126  	b.lock.Lock()
   127  	oldMode := b.syncMode
   128  	updated := oldMode != mode
   129  	filling := b.filling
   130  	b.syncMode = mode
   131  	b.lock.Unlock()
   132  
   133  	// If the sync mode was changed mid-sync, restart. This should never ever
   134  	// really happen, we just handle it to detect programming errors.
   135  	if !updated || !filling {
   136  		return
   137  	}
   138  	log.Error("Downloader sync mode changed mid-run", "old", oldMode.String(), "new", mode.String())
   139  	b.suspend()
   140  	b.resume()
   141  }
   142  
   143  // SetBadBlockCallback sets the callback to run when a bad block is hit by the
   144  // block processor. This method is not thread safe and should be set only once
   145  // on startup before system events are fired.
   146  func (d *Downloader) SetBadBlockCallback(onBadBlock badBlockFn) {
   147  	d.badBlock = onBadBlock
   148  }
   149  
   150  // BeaconSync is the post-merge version of the chain synchronization, where the
   151  // chain is not downloaded from genesis onward, rather from trusted head announces
   152  // backwards.
   153  //
   154  // Internally backfilling and state sync is done the same way, but the header
   155  // retrieval and scheduling is replaced.
   156  func (d *Downloader) BeaconSync(mode SyncMode, head *types.Header, final *types.Header) error {
   157  	return d.beaconSync(mode, head, final, true)
   158  }
   159  
   160  // BeaconExtend is an optimistic version of BeaconSync, where an attempt is made
   161  // to extend the current beacon chain with a new header, but in case of a mismatch,
   162  // the old sync will not be terminated and reorged, rather the new head is dropped.
   163  //
   164  // This is useful if a beacon client is feeding us large chunks of payloads to run,
   165  // but is not setting the head after each.
   166  func (d *Downloader) BeaconExtend(mode SyncMode, head *types.Header) error {
   167  	return d.beaconSync(mode, head, nil, false)
   168  }
   169  
   170  // beaconSync is the post-merge version of the chain synchronization, where the
   171  // chain is not downloaded from genesis onward, rather from trusted head announces
   172  // backwards.
   173  //
   174  // Internally backfilling and state sync is done the same way, but the header
   175  // retrieval and scheduling is replaced.
   176  func (d *Downloader) beaconSync(mode SyncMode, head *types.Header, final *types.Header, force bool) error {
   177  	// When the downloader starts a sync cycle, it needs to be aware of the sync
   178  	// mode to use (full, snap). To keep the skeleton chain oblivious, inject the
   179  	// mode into the backfiller directly.
   180  	//
   181  	// Super crazy dangerous type cast. Should be fine (TM), we're only using a
   182  	// different backfiller implementation for skeleton tests.
   183  	d.skeleton.filler.(*beaconBackfiller).setMode(mode)
   184  
   185  	// Signal the skeleton sync to switch to a new head, however it wants
   186  	if err := d.skeleton.Sync(head, final, force); err != nil {
   187  		return err
   188  	}
   189  	return nil
   190  }
   191  
   192  // findBeaconAncestor tries to locate the common ancestor link of the local chain
   193  // and the beacon chain just requested. In the general case when our node was in
   194  // sync and on the correct chain, checking the top N links should already get us
   195  // a match. In the rare scenario when we ended up on a long reorganisation (i.e.
   196  // none of the head links match), we do a binary search to find the ancestor.
   197  func (d *Downloader) findBeaconAncestor() (uint64, error) {
   198  	// Figure out the current local head position
   199  	var chainHead *types.Header
   200  
   201  	switch d.getMode() {
   202  	case ethconfig.FullSync:
   203  		chainHead = d.blockchain.CurrentBlock()
   204  	case ethconfig.SnapSync:
   205  		chainHead = d.blockchain.CurrentSnapBlock()
   206  	default:
   207  		panic("unknown sync mode")
   208  	}
   209  	number := chainHead.Number.Uint64()
   210  
   211  	// Retrieve the skeleton bounds and ensure they are linked to the local chain
   212  	beaconHead, beaconTail, _, err := d.skeleton.Bounds()
   213  	if err != nil {
   214  		// This is a programming error. The chain backfiller was called with an
   215  		// invalid beacon sync state. Ideally we would panic here, but erroring
   216  		// gives us at least a remote chance to recover. It's still a big fault!
   217  		log.Error("Failed to retrieve beacon bounds", "err", err)
   218  		return 0, err
   219  	}
   220  	var linked bool
   221  	switch d.getMode() {
   222  	case ethconfig.FullSync:
   223  		linked = d.blockchain.HasBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
   224  	case ethconfig.SnapSync:
   225  		linked = d.blockchain.HasFastBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
   226  	default:
   227  		panic("unknown sync mode")
   228  	}
   229  	if !linked {
   230  		// This is a programming error. The chain backfiller was called with a
   231  		// tail that's not linked to the local chain. Whilst this should never
   232  		// happen, there might be some weirdnesses if beacon sync backfilling
   233  		// races with the user (or beacon client) calling setHead. Whilst panic
   234  		// would be the ideal thing to do, it is safer long term to attempt a
   235  		// recovery and fix any noticed issue after the fact.
   236  		log.Error("Beacon sync linkup unavailable", "number", beaconTail.Number.Uint64()-1, "hash", beaconTail.ParentHash)
   237  		return 0, fmt.Errorf("beacon linkup unavailable locally: %d [%x]", beaconTail.Number.Uint64()-1, beaconTail.ParentHash)
   238  	}
   239  	// Binary search to find the ancestor
   240  	start, end := beaconTail.Number.Uint64()-1, number
   241  	if number := beaconHead.Number.Uint64(); end > number {
   242  		// This shouldn't really happen in a healthy network, but if the consensus
   243  		// clients feeds us a shorter chain as the canonical, we should not attempt
   244  		// to access non-existent skeleton items.
   245  		log.Warn("Beacon head lower than local chain", "beacon", number, "local", end)
   246  		end = number
   247  	}
   248  	for start+1 < end {
   249  		// Split our chain interval in two, and request the hash to cross check
   250  		check := (start + end) / 2
   251  
   252  		h := d.skeleton.Header(check)
   253  		n := h.Number.Uint64()
   254  
   255  		var known bool
   256  		switch d.getMode() {
   257  		case ethconfig.FullSync:
   258  			known = d.blockchain.HasBlock(h.Hash(), n)
   259  		case ethconfig.SnapSync:
   260  			known = d.blockchain.HasFastBlock(h.Hash(), n)
   261  		default:
   262  			panic("unknown sync mode")
   263  		}
   264  		if !known {
   265  			end = check
   266  			continue
   267  		}
   268  		start = check
   269  	}
   270  	return start, nil
   271  }
   272  
   273  // fetchHeaders feeds skeleton headers to the downloader queue for scheduling
   274  // until sync errors or is finished.
   275  func (d *Downloader) fetchHeaders(from uint64) error {
   276  	head, tail, _, err := d.skeleton.Bounds()
   277  	if err != nil {
   278  		return err
   279  	}
   280  	// A part of headers are not in the skeleton space, try to resolve
   281  	// them from the local chain. Note the range should be very short
   282  	// and it should only happen when there are less than 64 post-merge
   283  	// blocks in the network.
   284  	var localHeaders []*types.Header
   285  	if from < tail.Number.Uint64() {
   286  		count := tail.Number.Uint64() - from
   287  		if count > uint64(fsMinFullBlocks) {
   288  			return fmt.Errorf("invalid origin (%d) of beacon sync (%d)", from, tail.Number)
   289  		}
   290  		localHeaders = d.readHeaderRange(tail, int(count))
   291  		log.Warn("Retrieved beacon headers from local", "from", from, "count", count)
   292  	}
   293  	fsHeaderContCheckTimer := time.NewTimer(fsHeaderContCheck)
   294  	defer fsHeaderContCheckTimer.Stop()
   295  
   296  	// Verify the header at configured chain cutoff, ensuring it's matched with
   297  	// the configured hash. Skip the check if the configured cutoff is even higher
   298  	// than the sync target, which is definitely not a common case.
   299  	if d.chainCutoffNumber != 0 && d.chainCutoffNumber >= from && d.chainCutoffNumber <= head.Number.Uint64() {
   300  		h := d.skeleton.Header(d.chainCutoffNumber)
   301  		if h == nil {
   302  			if d.chainCutoffNumber < tail.Number.Uint64() {
   303  				dist := tail.Number.Uint64() - d.chainCutoffNumber
   304  				if len(localHeaders) >= int(dist) {
   305  					h = localHeaders[dist-1]
   306  				}
   307  			}
   308  		}
   309  		if h == nil {
   310  			return fmt.Errorf("header at chain cutoff is not available, cutoff: %d", d.chainCutoffNumber)
   311  		}
   312  		if h.Hash() != d.chainCutoffHash {
   313  			return fmt.Errorf("header at chain cutoff mismatched, want: %v, got: %v", d.chainCutoffHash, h.Hash())
   314  		}
   315  	}
   316  
   317  	for {
   318  		// Some beacon headers might have appeared since the last cycle, make
   319  		// sure we're always syncing to all available ones
   320  		head, _, _, err = d.skeleton.Bounds()
   321  		if err != nil {
   322  			return err
   323  		}
   324  		// If the pivot became stale (older than 2*64-8 (bit of wiggle room)),
   325  		// move it ahead to HEAD-64
   326  		d.pivotLock.Lock()
   327  		if d.pivotHeader != nil {
   328  			if head.Number.Uint64() > d.pivotHeader.Number.Uint64()+2*uint64(fsMinFullBlocks)-8 {
   329  				// Retrieve the next pivot header, either from skeleton chain
   330  				// or the filled chain
   331  				number := head.Number.Uint64() - uint64(fsMinFullBlocks)
   332  
   333  				log.Warn("Pivot seemingly stale, moving", "old", d.pivotHeader.Number, "new", number)
   334  				if d.pivotHeader = d.skeleton.Header(number); d.pivotHeader == nil {
   335  					if number < tail.Number.Uint64() {
   336  						dist := tail.Number.Uint64() - number
   337  						if len(localHeaders) >= int(dist) {
   338  							d.pivotHeader = localHeaders[dist-1]
   339  							log.Warn("Retrieved pivot header from local", "number", d.pivotHeader.Number, "hash", d.pivotHeader.Hash(), "latest", head.Number, "oldest", tail.Number)
   340  						}
   341  					}
   342  				}
   343  				// Print an error log and return directly in case the pivot header
   344  				// is still not found. It means the skeleton chain is not linked
   345  				// correctly with local chain.
   346  				if d.pivotHeader == nil {
   347  					log.Error("Pivot header is not found", "number", number)
   348  					d.pivotLock.Unlock()
   349  					return errNoPivotHeader
   350  				}
   351  				// Write out the pivot into the database so a rollback beyond
   352  				// it will reenable snap sync and update the state root that
   353  				// the state syncer will be downloading
   354  				rawdb.WriteLastPivotNumber(d.stateDB, d.pivotHeader.Number.Uint64())
   355  			}
   356  		}
   357  		d.pivotLock.Unlock()
   358  
   359  		// Retrieve a batch of headers and feed it to the header processor
   360  		var (
   361  			headers = make([]*types.Header, 0, maxHeadersProcess)
   362  			hashes  = make([]common.Hash, 0, maxHeadersProcess)
   363  		)
   364  		for i := 0; i < maxHeadersProcess && from <= head.Number.Uint64(); i++ {
   365  			header := d.skeleton.Header(from)
   366  
   367  			// The header is not found in skeleton space, try to find it in local chain.
   368  			if header == nil && from < tail.Number.Uint64() {
   369  				dist := tail.Number.Uint64() - from
   370  				if len(localHeaders) >= int(dist) {
   371  					header = localHeaders[dist-1]
   372  				}
   373  			}
   374  			// The header is still missing, the beacon sync is corrupted and bail out
   375  			// the error here.
   376  			if header == nil {
   377  				return fmt.Errorf("missing beacon header %d", from)
   378  			}
   379  			headers = append(headers, header)
   380  			hashes = append(hashes, headers[i].Hash())
   381  			from++
   382  		}
   383  		if len(headers) > 0 {
   384  			log.Trace("Scheduling new beacon headers", "count", len(headers), "from", from-uint64(len(headers)))
   385  			select {
   386  			case d.headerProcCh <- &headerTask{
   387  				headers: headers,
   388  				hashes:  hashes,
   389  			}:
   390  			case <-d.cancelCh:
   391  				return errCanceled
   392  			}
   393  		}
   394  		// If we still have headers to import, loop and keep pushing them
   395  		if from <= head.Number.Uint64() {
   396  			continue
   397  		}
   398  		// If the pivot block is committed, signal header sync termination
   399  		if d.committed.Load() {
   400  			select {
   401  			case d.headerProcCh <- nil:
   402  				return nil
   403  			case <-d.cancelCh:
   404  				return errCanceled
   405  			}
   406  		}
   407  		// State sync still going, wait a bit for new headers and retry
   408  		log.Trace("Pivot not yet committed, waiting...")
   409  		fsHeaderContCheckTimer.Reset(fsHeaderContCheck)
   410  		select {
   411  		case <-fsHeaderContCheckTimer.C:
   412  		case <-d.cancelCh:
   413  			return errCanceled
   414  		}
   415  	}
   416  }