github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/eth/downloader/beaconsync.go (about)

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