github.com/ethereum/go-ethereum@v1.16.1/eth/downloader/downloader_test.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
    18  
    19  import (
    20  	"fmt"
    21  	"math/big"
    22  	"sync"
    23  	"sync/atomic"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/ethereum/go-ethereum"
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/consensus/ethash"
    30  	"github.com/ethereum/go-ethereum/core"
    31  	"github.com/ethereum/go-ethereum/core/rawdb"
    32  	"github.com/ethereum/go-ethereum/core/types"
    33  	"github.com/ethereum/go-ethereum/eth/protocols/eth"
    34  	"github.com/ethereum/go-ethereum/eth/protocols/snap"
    35  	"github.com/ethereum/go-ethereum/event"
    36  	"github.com/ethereum/go-ethereum/log"
    37  	"github.com/ethereum/go-ethereum/params"
    38  	"github.com/ethereum/go-ethereum/rlp"
    39  	"github.com/ethereum/go-ethereum/trie"
    40  )
    41  
    42  // downloadTester is a test simulator for mocking out local block chain.
    43  type downloadTester struct {
    44  	chain      *core.BlockChain
    45  	downloader *Downloader
    46  
    47  	peers map[string]*downloadTesterPeer
    48  	lock  sync.RWMutex
    49  }
    50  
    51  // newTester creates a new downloader test mocker.
    52  func newTester(t *testing.T) *downloadTester {
    53  	return newTesterWithNotification(t, nil)
    54  }
    55  
    56  // newTesterWithNotification creates a new downloader test mocker.
    57  func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
    58  	db, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
    59  	if err != nil {
    60  		panic(err)
    61  	}
    62  	t.Cleanup(func() {
    63  		db.Close()
    64  	})
    65  	gspec := &core.Genesis{
    66  		Config:  params.TestChainConfig,
    67  		Alloc:   types.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}},
    68  		BaseFee: big.NewInt(params.InitialBaseFee),
    69  	}
    70  	chain, err := core.NewBlockChain(db, gspec, ethash.NewFaker(), nil)
    71  	if err != nil {
    72  		panic(err)
    73  	}
    74  	tester := &downloadTester{
    75  		chain: chain,
    76  		peers: make(map[string]*downloadTesterPeer),
    77  	}
    78  	tester.downloader = New(db, new(event.TypeMux), tester.chain, tester.dropPeer, success)
    79  	return tester
    80  }
    81  
    82  // terminate aborts any operations on the embedded downloader and releases all
    83  // held resources.
    84  func (dl *downloadTester) terminate() {
    85  	dl.downloader.Terminate()
    86  	dl.chain.Stop()
    87  }
    88  
    89  // newPeer registers a new block download source into the downloader.
    90  func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block) *downloadTesterPeer {
    91  	dl.lock.Lock()
    92  	defer dl.lock.Unlock()
    93  
    94  	peer := &downloadTesterPeer{
    95  		dl:             dl,
    96  		id:             id,
    97  		chain:          newTestBlockchain(blocks),
    98  		withholdBodies: make(map[common.Hash]struct{}),
    99  	}
   100  	dl.peers[id] = peer
   101  
   102  	if err := dl.downloader.RegisterPeer(id, version, peer); err != nil {
   103  		panic(err)
   104  	}
   105  	if err := dl.downloader.SnapSyncer.Register(peer); err != nil {
   106  		panic(err)
   107  	}
   108  	return peer
   109  }
   110  
   111  // dropPeer simulates a hard peer removal from the connection pool.
   112  func (dl *downloadTester) dropPeer(id string) {
   113  	dl.lock.Lock()
   114  	defer dl.lock.Unlock()
   115  
   116  	delete(dl.peers, id)
   117  	dl.downloader.SnapSyncer.Unregister(id)
   118  	dl.downloader.UnregisterPeer(id)
   119  }
   120  
   121  type downloadTesterPeer struct {
   122  	dl             *downloadTester
   123  	withholdBodies map[common.Hash]struct{}
   124  	id             string
   125  	chain          *core.BlockChain
   126  }
   127  
   128  func unmarshalRlpHeaders(rlpdata []rlp.RawValue) []*types.Header {
   129  	var headers = make([]*types.Header, len(rlpdata))
   130  	for i, data := range rlpdata {
   131  		var h types.Header
   132  		if err := rlp.DecodeBytes(data, &h); err != nil {
   133  			panic(err)
   134  		}
   135  		headers[i] = &h
   136  	}
   137  	return headers
   138  }
   139  
   140  // RequestHeadersByHash constructs a GetBlockHeaders function based on a hashed
   141  // origin; associated with a particular peer in the download tester. The returned
   142  // function can be used to retrieve batches of headers from the particular peer.
   143  func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) {
   144  	// Service the header query via the live handler code
   145  	rlpHeaders := eth.ServiceGetBlockHeadersQuery(dlp.chain, &eth.GetBlockHeadersRequest{
   146  		Origin: eth.HashOrNumber{
   147  			Hash: origin,
   148  		},
   149  		Amount:  uint64(amount),
   150  		Skip:    uint64(skip),
   151  		Reverse: reverse,
   152  	}, nil)
   153  	headers := unmarshalRlpHeaders(rlpHeaders)
   154  	hashes := make([]common.Hash, len(headers))
   155  	for i, header := range headers {
   156  		hashes[i] = header.Hash()
   157  	}
   158  	// Deliver the headers to the downloader
   159  	req := &eth.Request{
   160  		Peer: dlp.id,
   161  	}
   162  	res := &eth.Response{
   163  		Req:  req,
   164  		Res:  (*eth.BlockHeadersRequest)(&headers),
   165  		Meta: hashes,
   166  		Time: 1,
   167  		Done: make(chan error, 1), // Ignore the returned status
   168  	}
   169  	go func() {
   170  		sink <- res
   171  	}()
   172  	return req, nil
   173  }
   174  
   175  // RequestHeadersByNumber constructs a GetBlockHeaders function based on a numbered
   176  // origin; associated with a particular peer in the download tester. The returned
   177  // function can be used to retrieve batches of headers from the particular peer.
   178  func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) {
   179  	// Service the header query via the live handler code
   180  	rlpHeaders := eth.ServiceGetBlockHeadersQuery(dlp.chain, &eth.GetBlockHeadersRequest{
   181  		Origin: eth.HashOrNumber{
   182  			Number: origin,
   183  		},
   184  		Amount:  uint64(amount),
   185  		Skip:    uint64(skip),
   186  		Reverse: reverse,
   187  	}, nil)
   188  	headers := unmarshalRlpHeaders(rlpHeaders)
   189  	hashes := make([]common.Hash, len(headers))
   190  	for i, header := range headers {
   191  		hashes[i] = header.Hash()
   192  	}
   193  	// Deliver the headers to the downloader
   194  	req := &eth.Request{
   195  		Peer: dlp.id,
   196  	}
   197  	res := &eth.Response{
   198  		Req:  req,
   199  		Res:  (*eth.BlockHeadersRequest)(&headers),
   200  		Meta: hashes,
   201  		Time: 1,
   202  		Done: make(chan error, 1), // Ignore the returned status
   203  	}
   204  	go func() {
   205  		sink <- res
   206  	}()
   207  	return req, nil
   208  }
   209  
   210  // RequestBodies constructs a getBlockBodies method associated with a particular
   211  // peer in the download tester. The returned function can be used to retrieve
   212  // batches of block bodies from the particularly requested peer.
   213  func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) {
   214  	blobs := eth.ServiceGetBlockBodiesQuery(dlp.chain, hashes)
   215  
   216  	bodies := make([]*eth.BlockBody, len(blobs))
   217  	for i, blob := range blobs {
   218  		bodies[i] = new(eth.BlockBody)
   219  		rlp.DecodeBytes(blob, bodies[i])
   220  	}
   221  	var (
   222  		txsHashes        = make([]common.Hash, len(bodies))
   223  		uncleHashes      = make([]common.Hash, len(bodies))
   224  		withdrawalHashes = make([]common.Hash, len(bodies))
   225  	)
   226  	hasher := trie.NewStackTrie(nil)
   227  	for i, body := range bodies {
   228  		hash := types.DeriveSha(types.Transactions(body.Transactions), hasher)
   229  		if _, ok := dlp.withholdBodies[hash]; ok {
   230  			txsHashes = append(txsHashes[:i], txsHashes[i+1:]...)
   231  			uncleHashes = append(uncleHashes[:i], uncleHashes[i+1:]...)
   232  			continue
   233  		}
   234  		txsHashes[i] = hash
   235  		uncleHashes[i] = types.CalcUncleHash(body.Uncles)
   236  	}
   237  	req := &eth.Request{
   238  		Peer: dlp.id,
   239  	}
   240  	res := &eth.Response{
   241  		Req:  req,
   242  		Res:  (*eth.BlockBodiesResponse)(&bodies),
   243  		Meta: [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes},
   244  		Time: 1,
   245  		Done: make(chan error, 1), // Ignore the returned status
   246  	}
   247  	go func() {
   248  		sink <- res
   249  	}()
   250  	return req, nil
   251  }
   252  
   253  // RequestReceipts constructs a getReceipts method associated with a particular
   254  // peer in the download tester. The returned function can be used to retrieve
   255  // batches of block receipts from the particularly requested peer.
   256  func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) {
   257  	blobs := eth.ServiceGetReceiptsQuery68(dlp.chain, hashes)
   258  
   259  	receipts := make([]types.Receipts, len(blobs))
   260  	for i, blob := range blobs {
   261  		rlp.DecodeBytes(blob, &receipts[i])
   262  	}
   263  	hasher := trie.NewStackTrie(nil)
   264  	hashes = make([]common.Hash, len(receipts))
   265  	for i, receipt := range receipts {
   266  		hashes[i] = types.DeriveSha(receipt, hasher)
   267  	}
   268  	req := &eth.Request{
   269  		Peer: dlp.id,
   270  	}
   271  	resp := eth.ReceiptsRLPResponse(types.EncodeBlockReceiptLists(receipts))
   272  	res := &eth.Response{
   273  		Req:  req,
   274  		Res:  &resp,
   275  		Meta: hashes,
   276  		Time: 1,
   277  		Done: make(chan error, 1), // Ignore the returned status
   278  	}
   279  	go func() {
   280  		sink <- res
   281  	}()
   282  	return req, nil
   283  }
   284  
   285  // ID retrieves the peer's unique identifier.
   286  func (dlp *downloadTesterPeer) ID() string {
   287  	return dlp.id
   288  }
   289  
   290  // RequestAccountRange fetches a batch of accounts rooted in a specific account
   291  // trie, starting with the origin.
   292  func (dlp *downloadTesterPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error {
   293  	// Create the request and service it
   294  	req := &snap.GetAccountRangePacket{
   295  		ID:     id,
   296  		Root:   root,
   297  		Origin: origin,
   298  		Limit:  limit,
   299  		Bytes:  bytes,
   300  	}
   301  	slimaccs, proofs := snap.ServiceGetAccountRangeQuery(dlp.chain, req)
   302  
   303  	// We need to convert to non-slim format, delegate to the packet code
   304  	res := &snap.AccountRangePacket{
   305  		ID:       id,
   306  		Accounts: slimaccs,
   307  		Proof:    proofs,
   308  	}
   309  	hashes, accounts, _ := res.Unpack()
   310  
   311  	go dlp.dl.downloader.SnapSyncer.OnAccounts(dlp, id, hashes, accounts, proofs)
   312  	return nil
   313  }
   314  
   315  // RequestStorageRanges fetches a batch of storage slots belonging to one or
   316  // more accounts. If slots from only one account is requested, an origin marker
   317  // may also be used to retrieve from there.
   318  func (dlp *downloadTesterPeer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error {
   319  	// Create the request and service it
   320  	req := &snap.GetStorageRangesPacket{
   321  		ID:       id,
   322  		Accounts: accounts,
   323  		Root:     root,
   324  		Origin:   origin,
   325  		Limit:    limit,
   326  		Bytes:    bytes,
   327  	}
   328  	storage, proofs := snap.ServiceGetStorageRangesQuery(dlp.chain, req)
   329  
   330  	// We need to convert to demultiplex, delegate to the packet code
   331  	res := &snap.StorageRangesPacket{
   332  		ID:    id,
   333  		Slots: storage,
   334  		Proof: proofs,
   335  	}
   336  	hashes, slots := res.Unpack()
   337  
   338  	go dlp.dl.downloader.SnapSyncer.OnStorage(dlp, id, hashes, slots, proofs)
   339  	return nil
   340  }
   341  
   342  // RequestByteCodes fetches a batch of bytecodes by hash.
   343  func (dlp *downloadTesterPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error {
   344  	req := &snap.GetByteCodesPacket{
   345  		ID:     id,
   346  		Hashes: hashes,
   347  		Bytes:  bytes,
   348  	}
   349  	codes := snap.ServiceGetByteCodesQuery(dlp.chain, req)
   350  	go dlp.dl.downloader.SnapSyncer.OnByteCodes(dlp, id, codes)
   351  	return nil
   352  }
   353  
   354  // RequestTrieNodes fetches a batch of account or storage trie nodes rooted in
   355  // a specific state trie.
   356  func (dlp *downloadTesterPeer) RequestTrieNodes(id uint64, root common.Hash, paths []snap.TrieNodePathSet, bytes uint64) error {
   357  	req := &snap.GetTrieNodesPacket{
   358  		ID:    id,
   359  		Root:  root,
   360  		Paths: paths,
   361  		Bytes: bytes,
   362  	}
   363  	nodes, _ := snap.ServiceGetTrieNodesQuery(dlp.chain, req, time.Now())
   364  	go dlp.dl.downloader.SnapSyncer.OnTrieNodes(dlp, id, nodes)
   365  	return nil
   366  }
   367  
   368  // Log retrieves the peer's own contextual logger.
   369  func (dlp *downloadTesterPeer) Log() log.Logger {
   370  	return log.New("peer", dlp.id)
   371  }
   372  
   373  // assertOwnChain checks if the local chain contains the correct number of items
   374  // of the various chain components.
   375  func assertOwnChain(t *testing.T, tester *downloadTester, length int) {
   376  	// Mark this method as a helper to report errors at callsite, not in here
   377  	t.Helper()
   378  
   379  	headers, blocks, receipts := length, length, length
   380  	if hs := int(tester.chain.CurrentHeader().Number.Uint64()) + 1; hs != headers {
   381  		t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers)
   382  	}
   383  	if bs := int(tester.chain.CurrentBlock().Number.Uint64()) + 1; bs != blocks {
   384  		t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, blocks)
   385  	}
   386  	if rs := int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1; rs != receipts {
   387  		t.Fatalf("synchronised receipts mismatch: have %v, want %v", rs, receipts)
   388  	}
   389  }
   390  
   391  func TestCanonicalSynchronisation68Full(t *testing.T) { testCanonSync(t, eth.ETH68, FullSync) }
   392  func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ETH68, SnapSync) }
   393  
   394  func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
   395  	success := make(chan struct{})
   396  	tester := newTesterWithNotification(t, func() {
   397  		close(success)
   398  	})
   399  	defer tester.terminate()
   400  
   401  	// Create a small enough block chain to download
   402  	chain := testChainBase.shorten(blockCacheMaxItems - 15)
   403  	tester.newPeer("peer", protocol, chain.blocks[1:])
   404  
   405  	// Synchronise with the peer and make sure all relevant data was retrieved
   406  	if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
   407  		t.Fatalf("failed to beacon-sync chain: %v", err)
   408  	}
   409  	select {
   410  	case <-success:
   411  		assertOwnChain(t, tester, len(chain.blocks))
   412  	case <-time.NewTimer(time.Second * 3).C:
   413  		t.Fatalf("Failed to sync chain in three seconds")
   414  	}
   415  }
   416  
   417  // Tests that if a large batch of blocks are being downloaded, it is throttled
   418  // until the cached blocks are retrieved.
   419  func TestThrottling68Full(t *testing.T) { testThrottling(t, eth.ETH68, FullSync) }
   420  func TestThrottling68Snap(t *testing.T) { testThrottling(t, eth.ETH68, SnapSync) }
   421  
   422  func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
   423  	tester := newTester(t)
   424  	defer tester.terminate()
   425  
   426  	// Create a long block chain to download and the tester
   427  	targetBlocks := len(testChainBase.blocks) - 1
   428  	tester.newPeer("peer", protocol, testChainBase.blocks[1:])
   429  
   430  	// Wrap the importer to allow stepping
   431  	var blocked atomic.Uint32
   432  	proceed := make(chan struct{})
   433  	tester.downloader.chainInsertHook = func(results []*fetchResult) {
   434  		blocked.Store(uint32(len(results)))
   435  		<-proceed
   436  	}
   437  	// Start a synchronisation concurrently
   438  	errc := make(chan error, 1)
   439  	go func() {
   440  		errc <- tester.downloader.BeaconSync(mode, testChainBase.blocks[len(testChainBase.blocks)-1].Header(), nil)
   441  	}()
   442  	// Iteratively take some blocks, always checking the retrieval count
   443  	for {
   444  		// Check the retrieval count synchronously (! reason for this ugly block)
   445  		tester.lock.RLock()
   446  		retrieved := int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1
   447  		tester.lock.RUnlock()
   448  		if retrieved >= targetBlocks+1 {
   449  			break
   450  		}
   451  		// Wait a bit for sync to throttle itself
   452  		var cached, frozen int
   453  		for start := time.Now(); time.Since(start) < 3*time.Second; {
   454  			time.Sleep(25 * time.Millisecond)
   455  
   456  			tester.lock.Lock()
   457  			tester.downloader.queue.lock.Lock()
   458  			tester.downloader.queue.resultCache.lock.Lock()
   459  			{
   460  				cached = tester.downloader.queue.resultCache.countCompleted()
   461  				frozen = int(blocked.Load())
   462  				retrieved = int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1
   463  			}
   464  			tester.downloader.queue.resultCache.lock.Unlock()
   465  			tester.downloader.queue.lock.Unlock()
   466  			tester.lock.Unlock()
   467  
   468  			if cached == blockCacheMaxItems ||
   469  				cached == blockCacheMaxItems-reorgProtHeaderDelay ||
   470  				retrieved+cached+frozen == targetBlocks+1 ||
   471  				retrieved+cached+frozen == targetBlocks+1-reorgProtHeaderDelay {
   472  				break
   473  			}
   474  		}
   475  		// Make sure we filled up the cache, then exhaust it
   476  		time.Sleep(25 * time.Millisecond) // give it a chance to screw up
   477  		tester.lock.RLock()
   478  		retrieved = int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1
   479  		tester.lock.RUnlock()
   480  		if cached != blockCacheMaxItems && cached != blockCacheMaxItems-reorgProtHeaderDelay && retrieved+cached+frozen != targetBlocks+1 && retrieved+cached+frozen != targetBlocks+1-reorgProtHeaderDelay {
   481  			t.Fatalf("block count mismatch: have %v, want %v (owned %v, blocked %v, target %v)", cached, blockCacheMaxItems, retrieved, frozen, targetBlocks+1)
   482  		}
   483  		// Permit the blocked blocks to import
   484  		if blocked.Load() > 0 {
   485  			blocked.Store(uint32(0))
   486  			proceed <- struct{}{}
   487  		}
   488  	}
   489  	// Check that we haven't pulled more blocks than available
   490  	assertOwnChain(t, tester, targetBlocks+1)
   491  	if err := <-errc; err != nil {
   492  		t.Fatalf("block synchronization failed: %v", err)
   493  	}
   494  }
   495  
   496  // Tests that a canceled download wipes all previously accumulated state.
   497  func TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) }
   498  func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) }
   499  
   500  func testCancel(t *testing.T, protocol uint, mode SyncMode) {
   501  	complete := make(chan struct{})
   502  	success := func() {
   503  		close(complete)
   504  	}
   505  	tester := newTesterWithNotification(t, success)
   506  	defer tester.terminate()
   507  
   508  	chain := testChainBase.shorten(MaxHeaderFetch)
   509  	tester.newPeer("peer", protocol, chain.blocks[1:])
   510  
   511  	// Make sure canceling works with a pristine downloader
   512  	tester.downloader.Cancel()
   513  	if !tester.downloader.queue.Idle() {
   514  		t.Errorf("download queue not idle")
   515  	}
   516  	// Synchronise with the peer, but cancel afterwards
   517  	if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
   518  		t.Fatalf("failed to synchronise blocks: %v", err)
   519  	}
   520  	<-complete
   521  	tester.downloader.Cancel()
   522  	if !tester.downloader.queue.Idle() {
   523  		t.Errorf("download queue not idle")
   524  	}
   525  }
   526  
   527  // Tests that synchronisations behave well in multi-version protocol environments
   528  // and not wreak havoc on other nodes in the network.
   529  func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) }
   530  func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH68, SnapSync) }
   531  
   532  func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
   533  	complete := make(chan struct{})
   534  	success := func() {
   535  		close(complete)
   536  	}
   537  	tester := newTesterWithNotification(t, success)
   538  	defer tester.terminate()
   539  
   540  	// Create a small enough block chain to download
   541  	chain := testChainBase.shorten(blockCacheMaxItems - 15)
   542  
   543  	// Create peers of every type
   544  	tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:])
   545  
   546  	if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
   547  		t.Fatalf("failed to start beacon sync: #{err}")
   548  	}
   549  	select {
   550  	case <-complete:
   551  		break
   552  	case <-time.NewTimer(time.Second * 3).C:
   553  		t.Fatalf("Failed to sync chain in three seconds")
   554  	}
   555  	assertOwnChain(t, tester, len(chain.blocks))
   556  
   557  	// Check that no peers have been dropped off
   558  	for _, version := range []int{68} {
   559  		peer := fmt.Sprintf("peer %d", version)
   560  		if _, ok := tester.peers[peer]; !ok {
   561  			t.Errorf("%s dropped", peer)
   562  		}
   563  	}
   564  }
   565  
   566  // Tests that if a block is empty (e.g. header only), no body request should be
   567  // made, and instead the header should be assembled into a whole block in itself.
   568  func TestEmptyShortCircuit68Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, FullSync) }
   569  func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, SnapSync) }
   570  
   571  func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
   572  	success := make(chan struct{})
   573  	tester := newTesterWithNotification(t, func() {
   574  		close(success)
   575  	})
   576  	defer tester.terminate()
   577  
   578  	// Create a block chain to download
   579  	chain := testChainBase
   580  	tester.newPeer("peer", protocol, chain.blocks[1:])
   581  
   582  	// Instrument the downloader to signal body requests
   583  	var bodiesHave, receiptsHave atomic.Int32
   584  	tester.downloader.bodyFetchHook = func(headers []*types.Header) {
   585  		bodiesHave.Add(int32(len(headers)))
   586  	}
   587  	tester.downloader.receiptFetchHook = func(headers []*types.Header) {
   588  		receiptsHave.Add(int32(len(headers)))
   589  	}
   590  
   591  	if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
   592  		t.Fatalf("failed to synchronise blocks: %v", err)
   593  	}
   594  	select {
   595  	case <-success:
   596  		checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
   597  			HighestBlock: uint64(len(chain.blocks) - 1),
   598  			CurrentBlock: uint64(len(chain.blocks) - 1),
   599  		})
   600  	case <-time.NewTimer(time.Second * 3).C:
   601  		t.Fatalf("Failed to sync chain in three seconds")
   602  	}
   603  	assertOwnChain(t, tester, len(chain.blocks))
   604  
   605  	// Validate the number of block bodies that should have been requested
   606  	bodiesNeeded, receiptsNeeded := 0, 0
   607  	for _, block := range chain.blocks[1:] {
   608  		if len(block.Transactions()) > 0 || len(block.Uncles()) > 0 {
   609  			bodiesNeeded++
   610  		}
   611  	}
   612  	for _, block := range chain.blocks[1:] {
   613  		if mode == SnapSync && len(block.Transactions()) > 0 {
   614  			receiptsNeeded++
   615  		}
   616  	}
   617  	if int(bodiesHave.Load()) != bodiesNeeded {
   618  		t.Errorf("body retrieval count mismatch: have %v, want %v", bodiesHave.Load(), bodiesNeeded)
   619  	}
   620  	if int(receiptsHave.Load()) != receiptsNeeded {
   621  		t.Errorf("receipt retrieval count mismatch: have %v, want %v", receiptsHave.Load(), receiptsNeeded)
   622  	}
   623  }
   624  
   625  func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.SyncProgress) {
   626  	// Mark this method as a helper to report errors at callsite, not in here
   627  	t.Helper()
   628  
   629  	p := d.Progress()
   630  	if p.StartingBlock != want.StartingBlock || p.CurrentBlock != want.CurrentBlock || p.HighestBlock != want.HighestBlock {
   631  		t.Fatalf("%s progress mismatch:\nhave %+v\nwant %+v", stage, p, want)
   632  	}
   633  }
   634  
   635  // Tests that peers below a pre-configured checkpoint block are prevented from
   636  // being fast-synced from, avoiding potential cheap eclipse attacks.
   637  func TestBeaconSync68Full(t *testing.T) { testBeaconSync(t, eth.ETH68, FullSync) }
   638  func TestBeaconSync68Snap(t *testing.T) { testBeaconSync(t, eth.ETH68, SnapSync) }
   639  
   640  func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
   641  	var cases = []struct {
   642  		name  string // The name of testing scenario
   643  		local int    // The length of local chain(canonical chain assumed), 0 means genesis is the head
   644  	}{
   645  		{name: "Beacon sync since genesis", local: 0},
   646  		{name: "Beacon sync with short local chain", local: 1},
   647  		{name: "Beacon sync with long local chain", local: blockCacheMaxItems - 15 - fsMinFullBlocks/2},
   648  		{name: "Beacon sync with full local chain", local: blockCacheMaxItems - 15 - 1},
   649  	}
   650  	for _, c := range cases {
   651  		t.Run(c.name, func(t *testing.T) {
   652  			success := make(chan struct{})
   653  			tester := newTesterWithNotification(t, func() {
   654  				close(success)
   655  			})
   656  			defer tester.terminate()
   657  
   658  			chain := testChainBase.shorten(blockCacheMaxItems - 15)
   659  			tester.newPeer("peer", protocol, chain.blocks[1:])
   660  
   661  			// Build the local chain segment if it's required
   662  			if c.local > 0 {
   663  				tester.chain.InsertChain(chain.blocks[1 : c.local+1])
   664  			}
   665  			if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
   666  				t.Fatalf("Failed to beacon sync chain %v %v", c.name, err)
   667  			}
   668  			select {
   669  			case <-success:
   670  				// Ok, downloader fully cancelled after sync cycle
   671  				if bs := int(tester.chain.CurrentBlock().Number.Uint64()) + 1; bs != len(chain.blocks) {
   672  					t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, len(chain.blocks))
   673  				}
   674  			case <-time.NewTimer(time.Second * 3).C:
   675  				t.Fatalf("Failed to sync chain in three seconds")
   676  			}
   677  		})
   678  	}
   679  }
   680  
   681  // Tests that synchronisation progress (origin block number, current block number
   682  // and highest block number) is tracked and updated correctly.
   683  func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) }
   684  func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) }
   685  
   686  func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
   687  	success := make(chan struct{})
   688  	tester := newTesterWithNotification(t, func() {
   689  		success <- struct{}{}
   690  	})
   691  	defer tester.terminate()
   692  	checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
   693  
   694  	chain := testChainBase.shorten(blockCacheMaxItems - 15)
   695  	shortChain := chain.shorten(len(chain.blocks) / 2).blocks[1:]
   696  
   697  	// Connect to peer that provides all headers and part of the bodies
   698  	faultyPeer := tester.newPeer("peer-half", protocol, shortChain)
   699  	for _, header := range shortChain {
   700  		faultyPeer.withholdBodies[header.Hash()] = struct{}{}
   701  	}
   702  
   703  	if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)/2-1].Header(), nil); err != nil {
   704  		t.Fatalf("failed to beacon-sync chain: %v", err)
   705  	}
   706  	select {
   707  	case <-success:
   708  		// Ok, downloader fully cancelled after sync cycle
   709  		checkProgress(t, tester.downloader, "peer-half", ethereum.SyncProgress{
   710  			CurrentBlock: uint64(len(chain.blocks)/2 - 1),
   711  			HighestBlock: uint64(len(chain.blocks)/2 - 1),
   712  		})
   713  	case <-time.NewTimer(time.Second * 3).C:
   714  		t.Fatalf("Failed to sync chain in three seconds")
   715  	}
   716  
   717  	// Synchronise all the blocks and check continuation progress
   718  	tester.newPeer("peer-full", protocol, chain.blocks[1:])
   719  	if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil {
   720  		t.Fatalf("failed to beacon-sync chain: %v", err)
   721  	}
   722  	startingBlock := uint64(len(chain.blocks)/2 - 1)
   723  
   724  	select {
   725  	case <-success:
   726  		// Ok, downloader fully cancelled after sync cycle
   727  		checkProgress(t, tester.downloader, "peer-full", ethereum.SyncProgress{
   728  			StartingBlock: startingBlock,
   729  			CurrentBlock:  uint64(len(chain.blocks) - 1),
   730  			HighestBlock:  uint64(len(chain.blocks) - 1),
   731  		})
   732  	case <-time.NewTimer(time.Second * 3).C:
   733  		t.Fatalf("Failed to sync chain in three seconds")
   734  	}
   735  }