github.com/tirogen/go-ethereum@v1.10.12-0.20221226051715-250cfede41b6/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  	"sync/atomic"
    23  	"time"
    24  
    25  	"github.com/tirogen/go-ethereum/common"
    26  	"github.com/tirogen/go-ethereum/core/types"
    27  	"github.com/tirogen/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  // SetBadBlockCallback sets the callback to run when a bad block is hit by the
   141  // block processor. This method is not thread safe and should be set only once
   142  // on startup before system events are fired.
   143  func (d *Downloader) SetBadBlockCallback(onBadBlock badBlockFn) {
   144  	d.badBlock = onBadBlock
   145  }
   146  
   147  // BeaconSync is the post-merge version of the chain synchronization, where the
   148  // chain is not downloaded from genesis onward, rather from trusted head announces
   149  // backwards.
   150  //
   151  // Internally backfilling and state sync is done the same way, but the header
   152  // retrieval and scheduling is replaced.
   153  func (d *Downloader) BeaconSync(mode SyncMode, head *types.Header) error {
   154  	return d.beaconSync(mode, head, true)
   155  }
   156  
   157  // BeaconExtend is an optimistic version of BeaconSync, where an attempt is made
   158  // to extend the current beacon chain with a new header, but in case of a mismatch,
   159  // the old sync will not be terminated and reorged, rather the new head is dropped.
   160  //
   161  // This is useful if a beacon client is feeding us large chunks of payloads to run,
   162  // but is not setting the head after each.
   163  func (d *Downloader) BeaconExtend(mode SyncMode, head *types.Header) error {
   164  	return d.beaconSync(mode, head, false)
   165  }
   166  
   167  // beaconSync is the post-merge version of the chain synchronization, where the
   168  // chain is not downloaded from genesis onward, rather from trusted head announces
   169  // backwards.
   170  //
   171  // Internally backfilling and state sync is done the same way, but the header
   172  // retrieval and scheduling is replaced.
   173  func (d *Downloader) beaconSync(mode SyncMode, head *types.Header, force bool) error {
   174  	// When the downloader starts a sync cycle, it needs to be aware of the sync
   175  	// mode to use (full, snap). To keep the skeleton chain oblivious, inject the
   176  	// mode into the backfiller directly.
   177  	//
   178  	// Super crazy dangerous type cast. Should be fine (TM), we're only using a
   179  	// different backfiller implementation for skeleton tests.
   180  	d.skeleton.filler.(*beaconBackfiller).setMode(mode)
   181  
   182  	// Signal the skeleton sync to switch to a new head, however it wants
   183  	if err := d.skeleton.Sync(head, force); err != nil {
   184  		return err
   185  	}
   186  	return nil
   187  }
   188  
   189  // findBeaconAncestor tries to locate the common ancestor link of the local chain
   190  // and the beacon chain just requested. In the general case when our node was in
   191  // sync and on the correct chain, checking the top N links should already get us
   192  // a match. In the rare scenario when we ended up on a long reorganisation (i.e.
   193  // none of the head links match), we do a binary search to find the ancestor.
   194  func (d *Downloader) findBeaconAncestor() (uint64, error) {
   195  	// Figure out the current local head position
   196  	var chainHead *types.Header
   197  
   198  	switch d.getMode() {
   199  	case FullSync:
   200  		chainHead = d.blockchain.CurrentBlock().Header()
   201  	case SnapSync:
   202  		chainHead = d.blockchain.CurrentFastBlock().Header()
   203  	default:
   204  		chainHead = d.lightchain.CurrentHeader()
   205  	}
   206  	number := chainHead.Number.Uint64()
   207  
   208  	// Retrieve the skeleton bounds and ensure they are linked to the local chain
   209  	beaconHead, beaconTail, err := d.skeleton.Bounds()
   210  	if err != nil {
   211  		// This is a programming error. The chain backfiller was called with an
   212  		// invalid beacon sync state. Ideally we would panic here, but erroring
   213  		// gives us at least a remote chance to recover. It's still a big fault!
   214  		log.Error("Failed to retrieve beacon bounds", "err", err)
   215  		return 0, err
   216  	}
   217  	var linked bool
   218  	switch d.getMode() {
   219  	case FullSync:
   220  		linked = d.blockchain.HasBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
   221  	case SnapSync:
   222  		linked = d.blockchain.HasFastBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
   223  	default:
   224  		linked = d.blockchain.HasHeader(beaconTail.ParentHash, beaconTail.Number.Uint64()-1)
   225  	}
   226  	if !linked {
   227  		// This is a programming error. The chain backfiller was called with a
   228  		// tail that's not linked to the local chain. Whilst this should never
   229  		// happen, there might be some weirdnesses if beacon sync backfilling
   230  		// races with the user (or beacon client) calling setHead. Whilst panic
   231  		// would be the ideal thing to do, it is safer long term to attempt a
   232  		// recovery and fix any noticed issue after the fact.
   233  		log.Error("Beacon sync linkup unavailable", "number", beaconTail.Number.Uint64()-1, "hash", beaconTail.ParentHash)
   234  		return 0, fmt.Errorf("beacon linkup unavailable locally: %d [%x]", beaconTail.Number.Uint64()-1, beaconTail.ParentHash)
   235  	}
   236  	// Binary search to find the ancestor
   237  	start, end := beaconTail.Number.Uint64()-1, number
   238  	if number := beaconHead.Number.Uint64(); end > number {
   239  		// This shouldn't really happen in a healthy network, but if the consensus
   240  		// clients feeds us a shorter chain as the canonical, we should not attempt
   241  		// to access non-existent skeleton items.
   242  		log.Warn("Beacon head lower than local chain", "beacon", number, "local", end)
   243  		end = number
   244  	}
   245  	for start+1 < end {
   246  		// Split our chain interval in two, and request the hash to cross check
   247  		check := (start + end) / 2
   248  
   249  		h := d.skeleton.Header(check)
   250  		n := h.Number.Uint64()
   251  
   252  		var known bool
   253  		switch d.getMode() {
   254  		case FullSync:
   255  			known = d.blockchain.HasBlock(h.Hash(), n)
   256  		case SnapSync:
   257  			known = d.blockchain.HasFastBlock(h.Hash(), n)
   258  		default:
   259  			known = d.lightchain.HasHeader(h.Hash(), n)
   260  		}
   261  		if !known {
   262  			end = check
   263  			continue
   264  		}
   265  		start = check
   266  	}
   267  	return start, nil
   268  }
   269  
   270  // fetchBeaconHeaders feeds skeleton headers to the downloader queue for scheduling
   271  // until sync errors or is finished.
   272  func (d *Downloader) fetchBeaconHeaders(from uint64) error {
   273  	head, tail, err := d.skeleton.Bounds()
   274  	if err != nil {
   275  		return err
   276  	}
   277  	// A part of headers are not in the skeleton space, try to resolve
   278  	// them from the local chain. Note the range should be very short
   279  	// and it should only happen when there are less than 64 post-merge
   280  	// blocks in the network.
   281  	var localHeaders []*types.Header
   282  	if from < tail.Number.Uint64() {
   283  		count := tail.Number.Uint64() - from
   284  		if count > uint64(fsMinFullBlocks) {
   285  			return fmt.Errorf("invalid origin (%d) of beacon sync (%d)", from, tail.Number)
   286  		}
   287  		localHeaders = d.readHeaderRange(tail, int(count))
   288  		log.Warn("Retrieved beacon headers from local", "from", from, "count", count)
   289  	}
   290  	for {
   291  		// Retrieve a batch of headers and feed it to the header processor
   292  		var (
   293  			headers = make([]*types.Header, 0, maxHeadersProcess)
   294  			hashes  = make([]common.Hash, 0, maxHeadersProcess)
   295  		)
   296  		for i := 0; i < maxHeadersProcess && from <= head.Number.Uint64(); i++ {
   297  			header := d.skeleton.Header(from)
   298  
   299  			// The header is not found in skeleton space, try to find it in local chain.
   300  			if header == nil && from < tail.Number.Uint64() {
   301  				dist := tail.Number.Uint64() - from
   302  				if len(localHeaders) >= int(dist) {
   303  					header = localHeaders[dist-1]
   304  				}
   305  			}
   306  			// The header is still missing, the beacon sync is corrupted and bail out
   307  			// the error here.
   308  			if header == nil {
   309  				return fmt.Errorf("missing beacon header %d", from)
   310  			}
   311  			headers = append(headers, header)
   312  			hashes = append(hashes, headers[i].Hash())
   313  			from++
   314  		}
   315  		if len(headers) > 0 {
   316  			log.Trace("Scheduling new beacon headers", "count", len(headers), "from", from-uint64(len(headers)))
   317  			select {
   318  			case d.headerProcCh <- &headerTask{
   319  				headers: headers,
   320  				hashes:  hashes,
   321  			}:
   322  			case <-d.cancelCh:
   323  				return errCanceled
   324  			}
   325  		}
   326  		// If we still have headers to import, loop and keep pushing them
   327  		if from <= head.Number.Uint64() {
   328  			continue
   329  		}
   330  		// If the pivot block is committed, signal header sync termination
   331  		if atomic.LoadInt32(&d.committed) == 1 {
   332  			select {
   333  			case d.headerProcCh <- nil:
   334  				return nil
   335  			case <-d.cancelCh:
   336  				return errCanceled
   337  			}
   338  		}
   339  		// State sync still going, wait a bit for new headers and retry
   340  		log.Trace("Pivot not yet committed, waiting...")
   341  		select {
   342  		case <-time.After(fsHeaderContCheck):
   343  		case <-d.cancelCh:
   344  			return errCanceled
   345  		}
   346  		head, _, err = d.skeleton.Bounds()
   347  		if err != nil {
   348  			return err
   349  		}
   350  	}
   351  }