github.com/theQRL/go-zond@v0.1.1/zond/catalyst/api_test.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 catalyst
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	crand "crypto/rand"
    23  	"fmt"
    24  	"math/big"
    25  	"math/rand"
    26  	"reflect"
    27  	"sync"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/mattn/go-colorable"
    32  	"github.com/theQRL/go-zond/beacon/engine"
    33  	"github.com/theQRL/go-zond/common"
    34  	"github.com/theQRL/go-zond/common/hexutil"
    35  	"github.com/theQRL/go-zond/consensus"
    36  	beaconConsensus "github.com/theQRL/go-zond/consensus/beacon"
    37  	"github.com/theQRL/go-zond/consensus/ethash"
    38  	"github.com/theQRL/go-zond/core"
    39  	"github.com/theQRL/go-zond/core/types"
    40  	"github.com/theQRL/go-zond/crypto/kzg4844"
    41  	"github.com/theQRL/go-zond/log"
    42  	"github.com/theQRL/go-zond/miner"
    43  	"github.com/theQRL/go-zond/node"
    44  	"github.com/theQRL/go-zond/p2p"
    45  	"github.com/theQRL/go-zond/params"
    46  	"github.com/theQRL/go-zond/pqcrypto"
    47  	"github.com/theQRL/go-zond/rpc"
    48  	"github.com/theQRL/go-zond/trie"
    49  	"github.com/theQRL/go-zond/zond"
    50  	"github.com/theQRL/go-zond/zond/downloader"
    51  	"github.com/theQRL/go-zond/zond/ethconfig"
    52  )
    53  
    54  var (
    55  	// testKey is a private key to use for funding a tester account.
    56  	testKey, _ = pqcrypto.HexToDilithium("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
    57  
    58  	// testAddr is the Ethereum address of the tester account.
    59  	testAddr = common.Address(testKey.GetAddress())
    60  
    61  	testBalance = big.NewInt(2e18)
    62  )
    63  
    64  func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) {
    65  	config := *params.AllEthashProtocolChanges
    66  	engine := consensus.Engine(beaconConsensus.New(ethash.NewFaker()))
    67  	if merged {
    68  		config.TerminalTotalDifficulty = common.Big0
    69  		config.TerminalTotalDifficultyPassed = true
    70  		engine = beaconConsensus.NewFaker()
    71  	}
    72  	genesis := &core.Genesis{
    73  		Config: &config,
    74  		Alloc: core.GenesisAlloc{
    75  			testAddr:                         {Balance: testBalance},
    76  			params.BeaconRootsStorageAddress: {Balance: common.Big0, Code: common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500")},
    77  		},
    78  		ExtraData:  []byte("test genesis"),
    79  		Timestamp:  9000,
    80  		BaseFee:    big.NewInt(params.InitialBaseFee),
    81  		Difficulty: big.NewInt(0),
    82  	}
    83  	testNonce := uint64(0)
    84  	generate := func(i int, g *core.BlockGen) {
    85  		g.OffsetTime(5)
    86  		g.SetExtra([]byte("test"))
    87  		tx, _ := types.SignTx(types.NewTransaction(testNonce, common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"), big.NewInt(1), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), types.LatestSigner(&config), testKey)
    88  		g.AddTx(tx)
    89  		testNonce++
    90  	}
    91  	_, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, n, generate)
    92  
    93  	if !merged {
    94  		totalDifficulty := big.NewInt(0)
    95  		for _, b := range blocks {
    96  			totalDifficulty.Add(totalDifficulty, b.Difficulty())
    97  		}
    98  		config.TerminalTotalDifficulty = totalDifficulty
    99  	}
   100  
   101  	return genesis, blocks
   102  }
   103  
   104  func TestEth2AssembleBlock(t *testing.T) {
   105  	genesis, blocks := generateMergeChain(10, false)
   106  	n, ethservice := startEthService(t, genesis, blocks)
   107  	defer n.Close()
   108  
   109  	api := NewConsensusAPI(ethservice)
   110  	signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID)
   111  	tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
   112  	if err != nil {
   113  		t.Fatalf("error signing transaction, err=%v", err)
   114  	}
   115  	ethservice.TxPool().Add([]*types.Transaction{tx}, true, false)
   116  	blockParams := engine.PayloadAttributes{
   117  		Timestamp: blocks[9].Time() + 5,
   118  	}
   119  	// The miner needs to pick up on the txs in the pool, so a few retries might be
   120  	// needed.
   121  	if _, testErr := assembleWithTransactions(api, blocks[9].Hash(), &blockParams, 1); testErr != nil {
   122  		t.Fatal(testErr)
   123  	}
   124  }
   125  
   126  // assembleWithTransactions tries to assemble a block, retrying until it has 'want',
   127  // number of transactions in it, or it has retried three times.
   128  func assembleWithTransactions(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes, want int) (execData *engine.ExecutableData, err error) {
   129  	for retries := 3; retries > 0; retries-- {
   130  		execData, err = assembleBlock(api, parentHash, params)
   131  		if err != nil {
   132  			return nil, err
   133  		}
   134  		if have, want := len(execData.Transactions), want; have != want {
   135  			err = fmt.Errorf("invalid number of transactions, have %d want %d", have, want)
   136  			continue
   137  		}
   138  		return execData, nil
   139  	}
   140  	return nil, err
   141  }
   142  
   143  func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
   144  	genesis, blocks := generateMergeChain(10, false)
   145  	n, ethservice := startEthService(t, genesis, blocks[:9])
   146  	defer n.Close()
   147  
   148  	api := NewConsensusAPI(ethservice)
   149  
   150  	// Put the 10th block's tx in the pool and produce a new block
   151  	txs := blocks[9].Transactions()
   152  	api.eth.TxPool().Add(txs, false, true)
   153  	blockParams := engine.PayloadAttributes{
   154  		Timestamp: blocks[8].Time() + 5,
   155  	}
   156  	// The miner needs to pick up on the txs in the pool, so a few retries might be
   157  	// needed.
   158  	if _, err := assembleWithTransactions(api, blocks[8].Hash(), &blockParams, blocks[9].Transactions().Len()); err != nil {
   159  		t.Fatal(err)
   160  	}
   161  }
   162  
   163  func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
   164  	genesis, blocks := generateMergeChain(10, false)
   165  	n, ethservice := startEthService(t, genesis, blocks)
   166  	defer n.Close()
   167  
   168  	api := NewConsensusAPI(ethservice)
   169  	fcState := engine.ForkchoiceStateV1{
   170  		HeadBlockHash:      blocks[5].Hash(),
   171  		SafeBlockHash:      common.Hash{},
   172  		FinalizedBlockHash: common.Hash{},
   173  	}
   174  	if resp, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   175  		t.Errorf("fork choice updated should not error: %v", err)
   176  	} else if resp.PayloadStatus.Status != engine.INVALID_TERMINAL_BLOCK.Status {
   177  		t.Errorf("fork choice updated before total terminal difficulty should be INVALID")
   178  	}
   179  }
   180  
   181  func TestEth2PrepareAndGetPayload(t *testing.T) {
   182  	genesis, blocks := generateMergeChain(10, false)
   183  	// We need to properly set the terminal total difficulty
   184  	genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[9].Difficulty())
   185  	n, ethservice := startEthService(t, genesis, blocks[:9])
   186  	defer n.Close()
   187  
   188  	api := NewConsensusAPI(ethservice)
   189  
   190  	// Put the 10th block's tx in the pool and produce a new block
   191  	txs := blocks[9].Transactions()
   192  	ethservice.TxPool().Add(txs, true, false)
   193  	blockParams := engine.PayloadAttributes{
   194  		Timestamp: blocks[8].Time() + 5,
   195  	}
   196  	fcState := engine.ForkchoiceStateV1{
   197  		HeadBlockHash:      blocks[8].Hash(),
   198  		SafeBlockHash:      common.Hash{},
   199  		FinalizedBlockHash: common.Hash{},
   200  	}
   201  	_, err := api.ForkchoiceUpdatedV1(fcState, &blockParams)
   202  	if err != nil {
   203  		t.Fatalf("error preparing payload, err=%v", err)
   204  	}
   205  	// give the payload some time to be built
   206  	time.Sleep(100 * time.Millisecond)
   207  	payloadID := (&miner.BuildPayloadArgs{
   208  		Parent:       fcState.HeadBlockHash,
   209  		Timestamp:    blockParams.Timestamp,
   210  		FeeRecipient: blockParams.SuggestedFeeRecipient,
   211  		Random:       blockParams.Random,
   212  		BeaconRoot:   blockParams.BeaconRoot,
   213  	}).Id()
   214  	execData, err := api.GetPayloadV1(payloadID)
   215  	if err != nil {
   216  		t.Fatalf("error getting payload, err=%v", err)
   217  	}
   218  	if len(execData.Transactions) != blocks[9].Transactions().Len() {
   219  		t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
   220  	}
   221  	// Test invalid payloadID
   222  	var invPayload engine.PayloadID
   223  	copy(invPayload[:], payloadID[:])
   224  	invPayload[0] = ^invPayload[0]
   225  	_, err = api.GetPayloadV1(invPayload)
   226  	if err == nil {
   227  		t.Fatal("expected error retrieving invalid payload")
   228  	}
   229  }
   230  
   231  func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan core.RemovedLogsEvent, wantNew, wantRemoved int) {
   232  	t.Helper()
   233  
   234  	if len(logsCh) != wantNew {
   235  		t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew)
   236  	}
   237  	if len(rmLogsCh) != wantRemoved {
   238  		t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved)
   239  	}
   240  	// Drain events.
   241  	for i := 0; i < len(logsCh); i++ {
   242  		<-logsCh
   243  	}
   244  	for i := 0; i < len(rmLogsCh); i++ {
   245  		<-rmLogsCh
   246  	}
   247  }
   248  
   249  func TestInvalidPayloadTimestamp(t *testing.T) {
   250  	genesis, preMergeBlocks := generateMergeChain(10, false)
   251  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   252  	defer n.Close()
   253  	var (
   254  		api    = NewConsensusAPI(ethservice)
   255  		parent = ethservice.BlockChain().CurrentBlock()
   256  	)
   257  	tests := []struct {
   258  		time      uint64
   259  		shouldErr bool
   260  	}{
   261  		{0, true},
   262  		{parent.Time, true},
   263  		{parent.Time - 1, true},
   264  
   265  		// TODO (MariusVanDerWijden) following tests are currently broken,
   266  		// fixed in upcoming merge-kiln-v2 pr
   267  		//{parent.Time() + 1, false},
   268  		//{uint64(time.Now().Unix()) + uint64(time.Minute), false},
   269  	}
   270  
   271  	for i, test := range tests {
   272  		t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) {
   273  			params := engine.PayloadAttributes{
   274  				Timestamp:             test.time,
   275  				Random:                crypto.Keccak256Hash([]byte{byte(123)}),
   276  				SuggestedFeeRecipient: parent.Coinbase,
   277  			}
   278  			fcState := engine.ForkchoiceStateV1{
   279  				HeadBlockHash:      parent.Hash(),
   280  				SafeBlockHash:      common.Hash{},
   281  				FinalizedBlockHash: common.Hash{},
   282  			}
   283  			_, err := api.ForkchoiceUpdatedV1(fcState, &params)
   284  			if test.shouldErr && err == nil {
   285  				t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err)
   286  			} else if !test.shouldErr && err != nil {
   287  				t.Fatalf("error preparing payload with valid timestamp, err=%v", err)
   288  			}
   289  		})
   290  	}
   291  }
   292  
   293  func TestEth2NewBlock(t *testing.T) {
   294  	genesis, preMergeBlocks := generateMergeChain(10, false)
   295  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   296  	defer n.Close()
   297  
   298  	var (
   299  		api    = NewConsensusAPI(ethservice)
   300  		parent = preMergeBlocks[len(preMergeBlocks)-1]
   301  
   302  		// This EVM code generates a log when the contract is created.
   303  		logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
   304  	)
   305  	// The event channels.
   306  	newLogCh := make(chan []*types.Log, 10)
   307  	rmLogsCh := make(chan core.RemovedLogsEvent, 10)
   308  	ethservice.BlockChain().SubscribeLogsEvent(newLogCh)
   309  	ethservice.BlockChain().SubscribeRemovedLogsEvent(rmLogsCh)
   310  
   311  	for i := 0; i < 10; i++ {
   312  		statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
   313  		nonce := statedb.GetNonce(testAddr)
   314  		tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
   315  		ethservice.TxPool().Add([]*types.Transaction{tx}, true, false)
   316  
   317  		execData, err := assembleWithTransactions(api, parent.Hash(), &engine.PayloadAttributes{
   318  			Timestamp: parent.Time() + 5,
   319  		}, 1)
   320  		if err != nil {
   321  			t.Fatalf("Failed to create the executable data %v", err)
   322  		}
   323  		block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
   324  		if err != nil {
   325  			t.Fatalf("Failed to convert executable data to block %v", err)
   326  		}
   327  		newResp, err := api.NewPayloadV1(*execData)
   328  		switch {
   329  		case err != nil:
   330  			t.Fatalf("Failed to insert block: %v", err)
   331  		case newResp.Status != "VALID":
   332  			t.Fatalf("Failed to insert block: %v", newResp.Status)
   333  		case ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1:
   334  			t.Fatalf("Chain head shouldn't be updated")
   335  		}
   336  		checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
   337  		fcState := engine.ForkchoiceStateV1{
   338  			HeadBlockHash:      block.Hash(),
   339  			SafeBlockHash:      block.Hash(),
   340  			FinalizedBlockHash: block.Hash(),
   341  		}
   342  		if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   343  			t.Fatalf("Failed to insert block: %v", err)
   344  		}
   345  		if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want {
   346  			t.Fatalf("Chain head should be updated, have %d want %d", have, want)
   347  		}
   348  		checkLogEvents(t, newLogCh, rmLogsCh, 1, 0)
   349  
   350  		parent = block
   351  	}
   352  
   353  	// Introduce fork chain
   354  	var (
   355  		head = ethservice.BlockChain().CurrentBlock().Number.Uint64()
   356  	)
   357  	parent = preMergeBlocks[len(preMergeBlocks)-1]
   358  	for i := 0; i < 10; i++ {
   359  		execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{
   360  			Timestamp: parent.Time() + 6,
   361  		})
   362  		if err != nil {
   363  			t.Fatalf("Failed to create the executable data %v", err)
   364  		}
   365  		block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
   366  		if err != nil {
   367  			t.Fatalf("Failed to convert executable data to block %v", err)
   368  		}
   369  		newResp, err := api.NewPayloadV1(*execData)
   370  		if err != nil || newResp.Status != "VALID" {
   371  			t.Fatalf("Failed to insert block: %v", err)
   372  		}
   373  		if ethservice.BlockChain().CurrentBlock().Number.Uint64() != head {
   374  			t.Fatalf("Chain head shouldn't be updated")
   375  		}
   376  
   377  		fcState := engine.ForkchoiceStateV1{
   378  			HeadBlockHash:      block.Hash(),
   379  			SafeBlockHash:      block.Hash(),
   380  			FinalizedBlockHash: block.Hash(),
   381  		}
   382  		if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   383  			t.Fatalf("Failed to insert block: %v", err)
   384  		}
   385  		if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() {
   386  			t.Fatalf("Chain head should be updated")
   387  		}
   388  		parent, head = block, block.NumberU64()
   389  	}
   390  }
   391  
   392  func TestEth2DeepReorg(t *testing.T) {
   393  	// TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg
   394  	// before the totalTerminalDifficulty threshold
   395  	/*
   396  		genesis, preMergeBlocks := generateMergeChain(core.TriesInMemory * 2, false)
   397  		n, ethservice := startEthService(t, genesis, preMergeBlocks)
   398  		defer n.Close()
   399  
   400  		var (
   401  			api    = NewConsensusAPI(ethservice, nil)
   402  			parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1]
   403  			head   = ethservice.BlockChain().CurrentBlock().Number.Uint64()()
   404  		)
   405  		if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) {
   406  			t.Errorf("Block %d not pruned", parent.NumberU64())
   407  		}
   408  		for i := 0; i < 10; i++ {
   409  			execData, err := api.assembleBlock(AssembleBlockParams{
   410  				ParentHash: parent.Hash(),
   411  				Timestamp:  parent.Time() + 5,
   412  			})
   413  			if err != nil {
   414  				t.Fatalf("Failed to create the executable data %v", err)
   415  			}
   416  			block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData)
   417  			if err != nil {
   418  				t.Fatalf("Failed to convert executable data to block %v", err)
   419  			}
   420  			newResp, err := api.ExecutePayload(*execData)
   421  			if err != nil || newResp.Status != "VALID" {
   422  				t.Fatalf("Failed to insert block: %v", err)
   423  			}
   424  			if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != head {
   425  				t.Fatalf("Chain head shouldn't be updated")
   426  			}
   427  			if err := api.setHead(block.Hash()); err != nil {
   428  				t.Fatalf("Failed to set head: %v", err)
   429  			}
   430  			if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != block.NumberU64() {
   431  				t.Fatalf("Chain head should be updated")
   432  			}
   433  			parent, head = block, block.NumberU64()
   434  		}
   435  	*/
   436  }
   437  
   438  // startEthService creates a full node instance for testing.
   439  func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *zond.Ethereum) {
   440  	t.Helper()
   441  
   442  	n, err := node.New(&node.Config{
   443  		P2P: p2p.Config{
   444  			ListenAddr:  "0.0.0.0:0",
   445  			NoDiscovery: true,
   446  			MaxPeers:    25,
   447  		}})
   448  	if err != nil {
   449  		t.Fatal("can't create node:", err)
   450  	}
   451  
   452  	ethcfg := &ethconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
   453  	ethservice, err := zond.New(n, ethcfg)
   454  	if err != nil {
   455  		t.Fatal("can't create zond service:", err)
   456  	}
   457  	if err := n.Start(); err != nil {
   458  		t.Fatal("can't start node:", err)
   459  	}
   460  	if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil {
   461  		n.Close()
   462  		t.Fatal("can't import test blocks:", err)
   463  	}
   464  
   465  	ethservice.SetEtherbase(testAddr)
   466  	ethservice.SetSynced()
   467  	return n, ethservice
   468  }
   469  
   470  func TestFullAPI(t *testing.T) {
   471  	genesis, preMergeBlocks := generateMergeChain(10, false)
   472  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   473  	defer n.Close()
   474  	var (
   475  		parent = ethservice.BlockChain().CurrentBlock()
   476  		// This EVM code generates a log when the contract is created.
   477  		logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
   478  	)
   479  
   480  	callback := func(parent *types.Header) {
   481  		statedb, _ := ethservice.BlockChain().StateAt(parent.Root)
   482  		nonce := statedb.GetNonce(testAddr)
   483  		tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
   484  		ethservice.TxPool().Add([]*types.Transaction{tx}, true, false)
   485  	}
   486  
   487  	setupBlocks(t, ethservice, 10, parent, callback, nil)
   488  }
   489  
   490  func setupBlocks(t *testing.T, ethservice *zond.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal) []*types.Header {
   491  	api := NewConsensusAPI(ethservice)
   492  	var blocks []*types.Header
   493  	for i := 0; i < n; i++ {
   494  		callback(parent)
   495  		var w []*types.Withdrawal
   496  		if withdrawals != nil {
   497  			w = withdrawals[i]
   498  		}
   499  
   500  		payload := getNewPayload(t, api, parent, w)
   501  		execResp, err := api.NewPayloadV2(*payload)
   502  		if err != nil {
   503  			t.Fatalf("can't execute payload: %v", err)
   504  		}
   505  		if execResp.Status != engine.VALID {
   506  			t.Fatalf("invalid status: %v", execResp.Status)
   507  		}
   508  		fcState := engine.ForkchoiceStateV1{
   509  			HeadBlockHash:      payload.BlockHash,
   510  			SafeBlockHash:      payload.ParentHash,
   511  			FinalizedBlockHash: payload.ParentHash,
   512  		}
   513  		if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   514  			t.Fatalf("Failed to insert block: %v", err)
   515  		}
   516  		if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number {
   517  			t.Fatal("Chain head should be updated")
   518  		}
   519  		if ethservice.BlockChain().CurrentFinalBlock().Number.Uint64() != payload.Number-1 {
   520  			t.Fatal("Finalized block should be updated")
   521  		}
   522  		parent = ethservice.BlockChain().CurrentBlock()
   523  		blocks = append(blocks, parent)
   524  	}
   525  	return blocks
   526  }
   527  
   528  func TestExchangeTransitionConfig(t *testing.T) {
   529  	genesis, preMergeBlocks := generateMergeChain(10, false)
   530  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   531  	defer n.Close()
   532  
   533  	// invalid ttd
   534  	api := NewConsensusAPI(ethservice)
   535  	config := engine.TransitionConfigurationV1{
   536  		TerminalTotalDifficulty: (*hexutil.Big)(big.NewInt(0)),
   537  		TerminalBlockHash:       common.Hash{},
   538  		TerminalBlockNumber:     0,
   539  	}
   540  	if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil {
   541  		t.Fatal("expected error on invalid config, invalid ttd")
   542  	}
   543  	// invalid terminal block hash
   544  	config = engine.TransitionConfigurationV1{
   545  		TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty),
   546  		TerminalBlockHash:       common.Hash{1},
   547  		TerminalBlockNumber:     0,
   548  	}
   549  	if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil {
   550  		t.Fatal("expected error on invalid config, invalid hash")
   551  	}
   552  	// valid config
   553  	config = engine.TransitionConfigurationV1{
   554  		TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty),
   555  		TerminalBlockHash:       common.Hash{},
   556  		TerminalBlockNumber:     0,
   557  	}
   558  	if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil {
   559  		t.Fatalf("expected no error on valid config, got %v", err)
   560  	}
   561  	// valid config
   562  	config = engine.TransitionConfigurationV1{
   563  		TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty),
   564  		TerminalBlockHash:       preMergeBlocks[5].Hash(),
   565  		TerminalBlockNumber:     6,
   566  	}
   567  	if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil {
   568  		t.Fatalf("expected no error on valid config, got %v", err)
   569  	}
   570  }
   571  
   572  /*
   573  TestNewPayloadOnInvalidChain sets up a valid chain and tries to feed blocks
   574  from an invalid chain to test if latestValidHash (LVH) works correctly.
   575  
   576  We set up the following chain where P1 ... Pn and P1” are valid while
   577  P1' is invalid.
   578  We expect
   579  (1) The LVH to point to the current inserted payload if it was valid.
   580  (2) The LVH to point to the valid parent on an invalid payload (if the parent is available).
   581  (3) If the parent is unavailable, the LVH should not be set.
   582  
   583  	CommonAncestor◄─▲── P1 ◄── P2  ◄─ P3  ◄─ ... ◄─ Pn
   584  	                │
   585  	                └── P1' ◄─ P2' ◄─ P3' ◄─ ... ◄─ Pn'
   586  	                │
   587  	                └── P1''
   588  */
   589  func TestNewPayloadOnInvalidChain(t *testing.T) {
   590  	genesis, preMergeBlocks := generateMergeChain(10, false)
   591  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   592  	defer n.Close()
   593  
   594  	var (
   595  		api    = NewConsensusAPI(ethservice)
   596  		parent = ethservice.BlockChain().CurrentBlock()
   597  		signer = types.LatestSigner(ethservice.BlockChain().Config())
   598  		// This EVM code generates a log when the contract is created.
   599  		logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
   600  	)
   601  	for i := 0; i < 10; i++ {
   602  		statedb, _ := ethservice.BlockChain().StateAt(parent.Root)
   603  		tx := types.MustSignNewTx(testKey, signer, &types.LegacyTx{
   604  			Nonce:    statedb.GetNonce(testAddr),
   605  			Value:    new(big.Int),
   606  			Gas:      1000000,
   607  			GasPrice: big.NewInt(2 * params.InitialBaseFee),
   608  			Data:     logCode,
   609  		})
   610  		ethservice.TxPool().Add([]*types.Transaction{tx}, false, true)
   611  		var (
   612  			params = engine.PayloadAttributes{
   613  				Timestamp:             parent.Time + 1,
   614  				Random:                crypto.Keccak256Hash([]byte{byte(i)}),
   615  				SuggestedFeeRecipient: parent.Coinbase,
   616  			}
   617  			fcState = engine.ForkchoiceStateV1{
   618  				HeadBlockHash:      parent.Hash(),
   619  				SafeBlockHash:      common.Hash{},
   620  				FinalizedBlockHash: common.Hash{},
   621  			}
   622  			payload *engine.ExecutableData
   623  			resp    engine.ForkChoiceResponse
   624  			err     error
   625  		)
   626  		for i := 0; ; i++ {
   627  			if resp, err = api.ForkchoiceUpdatedV1(fcState, &params); err != nil {
   628  				t.Fatalf("error preparing payload, err=%v", err)
   629  			}
   630  			if resp.PayloadStatus.Status != engine.VALID {
   631  				t.Fatalf("error preparing payload, invalid status: %v", resp.PayloadStatus.Status)
   632  			}
   633  			// give the payload some time to be built
   634  			time.Sleep(50 * time.Millisecond)
   635  			if payload, err = api.GetPayloadV1(*resp.PayloadID); err != nil {
   636  				t.Fatalf("can't get payload: %v", err)
   637  			}
   638  			if len(payload.Transactions) > 0 {
   639  				break
   640  			}
   641  			// No luck this time we need to update the params and try again.
   642  			params.Timestamp = params.Timestamp + 1
   643  			if i > 10 {
   644  				t.Fatalf("payload should not be empty")
   645  			}
   646  		}
   647  		execResp, err := api.NewPayloadV1(*payload)
   648  		if err != nil {
   649  			t.Fatalf("can't execute payload: %v", err)
   650  		}
   651  		if execResp.Status != engine.VALID {
   652  			t.Fatalf("invalid status: %v", execResp.Status)
   653  		}
   654  		fcState = engine.ForkchoiceStateV1{
   655  			HeadBlockHash:      payload.BlockHash,
   656  			SafeBlockHash:      payload.ParentHash,
   657  			FinalizedBlockHash: payload.ParentHash,
   658  		}
   659  		if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   660  			t.Fatalf("Failed to insert block: %v", err)
   661  		}
   662  		if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number {
   663  			t.Fatalf("Chain head should be updated")
   664  		}
   665  		parent = ethservice.BlockChain().CurrentBlock()
   666  	}
   667  }
   668  
   669  func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes) (*engine.ExecutableData, error) {
   670  	args := &miner.BuildPayloadArgs{
   671  		Parent:       parentHash,
   672  		Timestamp:    params.Timestamp,
   673  		FeeRecipient: params.SuggestedFeeRecipient,
   674  		Random:       params.Random,
   675  		Withdrawals:  params.Withdrawals,
   676  		BeaconRoot:   params.BeaconRoot,
   677  	}
   678  	payload, err := api.eth.Miner().BuildPayload(args)
   679  	if err != nil {
   680  		return nil, err
   681  	}
   682  	return payload.ResolveFull().ExecutionPayload, nil
   683  }
   684  
   685  func TestEmptyBlocks(t *testing.T) {
   686  	genesis, preMergeBlocks := generateMergeChain(10, false)
   687  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   688  	defer n.Close()
   689  
   690  	commonAncestor := ethservice.BlockChain().CurrentBlock()
   691  	api := NewConsensusAPI(ethservice)
   692  
   693  	// Setup 10 blocks on the canonical chain
   694  	setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil)
   695  
   696  	// (1) check LatestValidHash by sending a normal payload (P1'')
   697  	payload := getNewPayload(t, api, commonAncestor, nil)
   698  
   699  	status, err := api.NewPayloadV1(*payload)
   700  	if err != nil {
   701  		t.Fatal(err)
   702  	}
   703  	if status.Status != engine.VALID {
   704  		t.Errorf("invalid status: expected VALID got: %v", status.Status)
   705  	}
   706  	if !bytes.Equal(status.LatestValidHash[:], payload.BlockHash[:]) {
   707  		t.Fatalf("invalid LVH: got %v want %v", status.LatestValidHash, payload.BlockHash)
   708  	}
   709  
   710  	// (2) Now send P1' which is invalid
   711  	payload = getNewPayload(t, api, commonAncestor, nil)
   712  	payload.GasUsed += 1
   713  	payload = setBlockhash(payload)
   714  	// Now latestValidHash should be the common ancestor
   715  	status, err = api.NewPayloadV1(*payload)
   716  	if err != nil {
   717  		t.Fatal(err)
   718  	}
   719  	if status.Status != engine.INVALID {
   720  		t.Errorf("invalid status: expected INVALID got: %v", status.Status)
   721  	}
   722  	// Expect 0x0 on INVALID block on top of PoW block
   723  	expected := common.Hash{}
   724  	if !bytes.Equal(status.LatestValidHash[:], expected[:]) {
   725  		t.Fatalf("invalid LVH: got %v want %v", status.LatestValidHash, expected)
   726  	}
   727  
   728  	// (3) Now send a payload with unknown parent
   729  	payload = getNewPayload(t, api, commonAncestor, nil)
   730  	payload.ParentHash = common.Hash{1}
   731  	payload = setBlockhash(payload)
   732  	// Now latestValidHash should be the common ancestor
   733  	status, err = api.NewPayloadV1(*payload)
   734  	if err != nil {
   735  		t.Fatal(err)
   736  	}
   737  	if status.Status != engine.SYNCING {
   738  		t.Errorf("invalid status: expected SYNCING got: %v", status.Status)
   739  	}
   740  	if status.LatestValidHash != nil {
   741  		t.Fatalf("invalid LVH: got %v wanted nil", status.LatestValidHash)
   742  	}
   743  }
   744  
   745  func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal) *engine.ExecutableData {
   746  	params := engine.PayloadAttributes{
   747  		Timestamp:             parent.Time + 1,
   748  		Random:                crypto.Keccak256Hash([]byte{byte(1)}),
   749  		SuggestedFeeRecipient: parent.Coinbase,
   750  		Withdrawals:           withdrawals,
   751  	}
   752  
   753  	payload, err := assembleBlock(api, parent.Hash(), &params)
   754  	if err != nil {
   755  		t.Fatal(err)
   756  	}
   757  	return payload
   758  }
   759  
   760  // setBlockhash sets the blockhash of a modified ExecutableData.
   761  // Can be used to make modified payloads look valid.
   762  func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData {
   763  	txs, _ := decodeTransactions(data.Transactions)
   764  	number := big.NewInt(0)
   765  	number.SetUint64(data.Number)
   766  	header := &types.Header{
   767  		ParentHash:  data.ParentHash,
   768  		UncleHash:   types.EmptyUncleHash,
   769  		Coinbase:    data.FeeRecipient,
   770  		Root:        data.StateRoot,
   771  		TxHash:      types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
   772  		ReceiptHash: data.ReceiptsRoot,
   773  		Bloom:       types.BytesToBloom(data.LogsBloom),
   774  		Difficulty:  common.Big0,
   775  		Number:      number,
   776  		GasLimit:    data.GasLimit,
   777  		GasUsed:     data.GasUsed,
   778  		Time:        data.Timestamp,
   779  		BaseFee:     data.BaseFeePerGas,
   780  		Extra:       data.ExtraData,
   781  		MixDigest:   data.Random,
   782  	}
   783  	block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
   784  	data.BlockHash = block.Hash()
   785  	return data
   786  }
   787  
   788  func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
   789  	var txs = make([]*types.Transaction, len(enc))
   790  	for i, encTx := range enc {
   791  		var tx types.Transaction
   792  		if err := tx.UnmarshalBinary(encTx); err != nil {
   793  			return nil, fmt.Errorf("invalid transaction %d: %v", i, err)
   794  		}
   795  		txs[i] = &tx
   796  	}
   797  	return txs, nil
   798  }
   799  
   800  func TestTrickRemoteBlockCache(t *testing.T) {
   801  	// Setup two nodes
   802  	genesis, preMergeBlocks := generateMergeChain(10, false)
   803  	nodeA, ethserviceA := startEthService(t, genesis, preMergeBlocks)
   804  	nodeB, ethserviceB := startEthService(t, genesis, preMergeBlocks)
   805  	defer nodeA.Close()
   806  	defer nodeB.Close()
   807  	for nodeB.Server().NodeInfo().Ports.Listener == 0 {
   808  		time.Sleep(250 * time.Millisecond)
   809  	}
   810  	nodeA.Server().AddPeer(nodeB.Server().Self())
   811  	nodeB.Server().AddPeer(nodeA.Server().Self())
   812  	apiA := NewConsensusAPI(ethserviceA)
   813  	apiB := NewConsensusAPI(ethserviceB)
   814  
   815  	commonAncestor := ethserviceA.BlockChain().CurrentBlock()
   816  
   817  	// Setup 10 blocks on the canonical chain
   818  	setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil)
   819  	commonAncestor = ethserviceA.BlockChain().CurrentBlock()
   820  
   821  	var invalidChain []*engine.ExecutableData
   822  	// create a valid payload (P1)
   823  	//payload1 := getNewPayload(t, apiA, commonAncestor)
   824  	//invalidChain = append(invalidChain, payload1)
   825  
   826  	// create an invalid payload2 (P2)
   827  	payload2 := getNewPayload(t, apiA, commonAncestor, nil)
   828  	//payload2.ParentHash = payload1.BlockHash
   829  	payload2.GasUsed += 1
   830  	payload2 = setBlockhash(payload2)
   831  	invalidChain = append(invalidChain, payload2)
   832  
   833  	head := payload2
   834  	// create some valid payloads on top
   835  	for i := 0; i < 10; i++ {
   836  		payload := getNewPayload(t, apiA, commonAncestor, nil)
   837  		payload.ParentHash = head.BlockHash
   838  		payload = setBlockhash(payload)
   839  		invalidChain = append(invalidChain, payload)
   840  		head = payload
   841  	}
   842  
   843  	// feed the payloads to node B
   844  	for _, payload := range invalidChain {
   845  		status, err := apiB.NewPayloadV1(*payload)
   846  		if err != nil {
   847  			panic(err)
   848  		}
   849  		if status.Status == engine.VALID {
   850  			t.Error("invalid status: VALID on an invalid chain")
   851  		}
   852  		// Now reorg to the head of the invalid chain
   853  		resp, err := apiB.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil)
   854  		if err != nil {
   855  			t.Fatal(err)
   856  		}
   857  		if resp.PayloadStatus.Status == engine.VALID {
   858  			t.Error("invalid status: VALID on an invalid chain")
   859  		}
   860  		time.Sleep(100 * time.Millisecond)
   861  	}
   862  }
   863  
   864  func TestInvalidBloom(t *testing.T) {
   865  	genesis, preMergeBlocks := generateMergeChain(10, false)
   866  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   867  	ethservice.Merger().ReachTTD()
   868  	defer n.Close()
   869  
   870  	commonAncestor := ethservice.BlockChain().CurrentBlock()
   871  	api := NewConsensusAPI(ethservice)
   872  
   873  	// Setup 10 blocks on the canonical chain
   874  	setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil)
   875  
   876  	// (1) check LatestValidHash by sending a normal payload (P1'')
   877  	payload := getNewPayload(t, api, commonAncestor, nil)
   878  	payload.LogsBloom = append(payload.LogsBloom, byte(1))
   879  	status, err := api.NewPayloadV1(*payload)
   880  	if err != nil {
   881  		t.Fatal(err)
   882  	}
   883  	if status.Status != engine.INVALID {
   884  		t.Errorf("invalid status: expected INVALID got: %v", status.Status)
   885  	}
   886  }
   887  
   888  func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) {
   889  	genesis, preMergeBlocks := generateMergeChain(100, false)
   890  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   891  	defer n.Close()
   892  	api := NewConsensusAPI(ethservice)
   893  
   894  	// Test parent already post TTD in FCU
   895  	parent := preMergeBlocks[len(preMergeBlocks)-2]
   896  	fcState := engine.ForkchoiceStateV1{
   897  		HeadBlockHash:      parent.Hash(),
   898  		SafeBlockHash:      common.Hash{},
   899  		FinalizedBlockHash: common.Hash{},
   900  	}
   901  	resp, err := api.ForkchoiceUpdatedV1(fcState, nil)
   902  	if err != nil {
   903  		t.Fatalf("error sending forkchoice, err=%v", err)
   904  	}
   905  	if resp.PayloadStatus != engine.INVALID_TERMINAL_BLOCK {
   906  		t.Fatalf("error sending invalid forkchoice, invalid status: %v", resp.PayloadStatus.Status)
   907  	}
   908  
   909  	// Test parent already post TTD in NewPayload
   910  	args := &miner.BuildPayloadArgs{
   911  		Parent:       parent.Hash(),
   912  		Timestamp:    parent.Time() + 1,
   913  		Random:       crypto.Keccak256Hash([]byte{byte(1)}),
   914  		FeeRecipient: parent.Coinbase(),
   915  	}
   916  	payload, err := api.eth.Miner().BuildPayload(args)
   917  	if err != nil {
   918  		t.Fatalf("error preparing payload, err=%v", err)
   919  	}
   920  	data := *payload.Resolve().ExecutionPayload
   921  	// We need to recompute the blockhash, since the miner computes a wrong (correct) blockhash
   922  	txs, _ := decodeTransactions(data.Transactions)
   923  	header := &types.Header{
   924  		ParentHash:  data.ParentHash,
   925  		UncleHash:   types.EmptyUncleHash,
   926  		Coinbase:    data.FeeRecipient,
   927  		Root:        data.StateRoot,
   928  		TxHash:      types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
   929  		ReceiptHash: data.ReceiptsRoot,
   930  		Bloom:       types.BytesToBloom(data.LogsBloom),
   931  		Difficulty:  common.Big0,
   932  		Number:      new(big.Int).SetUint64(data.Number),
   933  		GasLimit:    data.GasLimit,
   934  		GasUsed:     data.GasUsed,
   935  		Time:        data.Timestamp,
   936  		BaseFee:     data.BaseFeePerGas,
   937  		Extra:       data.ExtraData,
   938  		MixDigest:   data.Random,
   939  	}
   940  	block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
   941  	data.BlockHash = block.Hash()
   942  	// Send the new payload
   943  	resp2, err := api.NewPayloadV1(data)
   944  	if err != nil {
   945  		t.Fatalf("error sending NewPayload, err=%v", err)
   946  	}
   947  	if resp2 != engine.INVALID_TERMINAL_BLOCK {
   948  		t.Fatalf("error sending invalid forkchoice, invalid status: %v", resp.PayloadStatus.Status)
   949  	}
   950  }
   951  
   952  // TestSimultaneousNewBlock does several parallel inserts, both as
   953  // newPayLoad and forkchoiceUpdate. This is to test that the api behaves
   954  // well even of the caller is not being 'serial'.
   955  func TestSimultaneousNewBlock(t *testing.T) {
   956  	genesis, preMergeBlocks := generateMergeChain(10, false)
   957  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   958  	defer n.Close()
   959  
   960  	var (
   961  		api    = NewConsensusAPI(ethservice)
   962  		parent = preMergeBlocks[len(preMergeBlocks)-1]
   963  	)
   964  	for i := 0; i < 10; i++ {
   965  		execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{
   966  			Timestamp: parent.Time() + 5,
   967  		})
   968  		if err != nil {
   969  			t.Fatalf("Failed to create the executable data %v", err)
   970  		}
   971  		// Insert it 10 times in parallel. Should be ignored.
   972  		{
   973  			var (
   974  				wg      sync.WaitGroup
   975  				testErr error
   976  				errMu   sync.Mutex
   977  			)
   978  			wg.Add(10)
   979  			for ii := 0; ii < 10; ii++ {
   980  				go func() {
   981  					defer wg.Done()
   982  					if newResp, err := api.NewPayloadV1(*execData); err != nil {
   983  						errMu.Lock()
   984  						testErr = fmt.Errorf("Failed to insert block: %w", err)
   985  						errMu.Unlock()
   986  					} else if newResp.Status != "VALID" {
   987  						errMu.Lock()
   988  						testErr = fmt.Errorf("Failed to insert block: %v", newResp.Status)
   989  						errMu.Unlock()
   990  					}
   991  				}()
   992  			}
   993  			wg.Wait()
   994  			if testErr != nil {
   995  				t.Fatal(testErr)
   996  			}
   997  		}
   998  		block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
   999  		if err != nil {
  1000  			t.Fatalf("Failed to convert executable data to block %v", err)
  1001  		}
  1002  		if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1 {
  1003  			t.Fatalf("Chain head shouldn't be updated")
  1004  		}
  1005  		fcState := engine.ForkchoiceStateV1{
  1006  			HeadBlockHash:      block.Hash(),
  1007  			SafeBlockHash:      block.Hash(),
  1008  			FinalizedBlockHash: block.Hash(),
  1009  		}
  1010  		{
  1011  			var (
  1012  				wg      sync.WaitGroup
  1013  				testErr error
  1014  				errMu   sync.Mutex
  1015  			)
  1016  			wg.Add(10)
  1017  			// Do each FCU 10 times
  1018  			for ii := 0; ii < 10; ii++ {
  1019  				go func() {
  1020  					defer wg.Done()
  1021  					if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
  1022  						errMu.Lock()
  1023  						testErr = fmt.Errorf("Failed to insert block: %w", err)
  1024  						errMu.Unlock()
  1025  					}
  1026  				}()
  1027  			}
  1028  			wg.Wait()
  1029  			if testErr != nil {
  1030  				t.Fatal(testErr)
  1031  			}
  1032  		}
  1033  		if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want {
  1034  			t.Fatalf("Chain head should be updated, have %d want %d", have, want)
  1035  		}
  1036  		parent = block
  1037  	}
  1038  }
  1039  
  1040  // TestWithdrawals creates and verifies two post-Shanghai blocks. The first
  1041  // includes zero withdrawals and the second includes two.
  1042  func TestWithdrawals(t *testing.T) {
  1043  	genesis, blocks := generateMergeChain(10, true)
  1044  	// Set shanghai time to last block + 5 seconds (first post-merge block)
  1045  	time := blocks[len(blocks)-1].Time() + 5
  1046  	genesis.Config.ShanghaiTime = &time
  1047  
  1048  	n, ethservice := startEthService(t, genesis, blocks)
  1049  	ethservice.Merger().ReachTTD()
  1050  	defer n.Close()
  1051  
  1052  	api := NewConsensusAPI(ethservice)
  1053  
  1054  	// 10: Build Shanghai block with no withdrawals.
  1055  	parent := ethservice.BlockChain().CurrentHeader()
  1056  	blockParams := engine.PayloadAttributes{
  1057  		Timestamp:   parent.Time + 5,
  1058  		Withdrawals: make([]*types.Withdrawal, 0),
  1059  	}
  1060  	fcState := engine.ForkchoiceStateV1{
  1061  		HeadBlockHash: parent.Hash(),
  1062  	}
  1063  	resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
  1064  	if err != nil {
  1065  		t.Fatalf("error preparing payload, err=%v", err)
  1066  	}
  1067  	if resp.PayloadStatus.Status != engine.VALID {
  1068  		t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID)
  1069  	}
  1070  
  1071  	// 10: verify state root is the same as parent
  1072  	payloadID := (&miner.BuildPayloadArgs{
  1073  		Parent:       fcState.HeadBlockHash,
  1074  		Timestamp:    blockParams.Timestamp,
  1075  		FeeRecipient: blockParams.SuggestedFeeRecipient,
  1076  		Random:       blockParams.Random,
  1077  		Withdrawals:  blockParams.Withdrawals,
  1078  		BeaconRoot:   blockParams.BeaconRoot,
  1079  	}).Id()
  1080  	execData, err := api.GetPayloadV2(payloadID)
  1081  	if err != nil {
  1082  		t.Fatalf("error getting payload, err=%v", err)
  1083  	}
  1084  	if execData.ExecutionPayload.StateRoot != parent.Root {
  1085  		t.Fatalf("mismatch state roots (got: %s, want: %s)", execData.ExecutionPayload.StateRoot, blocks[8].Root())
  1086  	}
  1087  
  1088  	// 10: verify locally built block
  1089  	if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
  1090  		t.Fatalf("error validating payload: %v", err)
  1091  	} else if status.Status != engine.VALID {
  1092  		t.Fatalf("invalid payload")
  1093  	}
  1094  
  1095  	// 11: build shanghai block with withdrawal
  1096  	aa := common.Address{0xaa}
  1097  	bb := common.Address{0xbb}
  1098  	blockParams = engine.PayloadAttributes{
  1099  		Timestamp: execData.ExecutionPayload.Timestamp + 5,
  1100  		Withdrawals: []*types.Withdrawal{
  1101  			{
  1102  				Index:   0,
  1103  				Address: aa,
  1104  				Amount:  32,
  1105  			},
  1106  			{
  1107  				Index:   1,
  1108  				Address: bb,
  1109  				Amount:  33,
  1110  			},
  1111  		},
  1112  	}
  1113  	fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
  1114  	_, err = api.ForkchoiceUpdatedV2(fcState, &blockParams)
  1115  	if err != nil {
  1116  		t.Fatalf("error preparing payload, err=%v", err)
  1117  	}
  1118  
  1119  	// 11: verify locally build block.
  1120  	payloadID = (&miner.BuildPayloadArgs{
  1121  		Parent:       fcState.HeadBlockHash,
  1122  		Timestamp:    blockParams.Timestamp,
  1123  		FeeRecipient: blockParams.SuggestedFeeRecipient,
  1124  		Random:       blockParams.Random,
  1125  		Withdrawals:  blockParams.Withdrawals,
  1126  		BeaconRoot:   blockParams.BeaconRoot,
  1127  	}).Id()
  1128  	execData, err = api.GetPayloadV2(payloadID)
  1129  	if err != nil {
  1130  		t.Fatalf("error getting payload, err=%v", err)
  1131  	}
  1132  	if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
  1133  		t.Fatalf("error validating payload: %v", err)
  1134  	} else if status.Status != engine.VALID {
  1135  		t.Fatalf("invalid payload")
  1136  	}
  1137  
  1138  	// 11: set block as head.
  1139  	fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
  1140  	_, err = api.ForkchoiceUpdatedV2(fcState, nil)
  1141  	if err != nil {
  1142  		t.Fatalf("error preparing payload, err=%v", err)
  1143  	}
  1144  
  1145  	// 11: verify withdrawals were processed.
  1146  	db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number))
  1147  	if err != nil {
  1148  		t.Fatalf("unable to load db: %v", err)
  1149  	}
  1150  	for i, w := range blockParams.Withdrawals {
  1151  		// w.Amount is in gwei, balance in wei
  1152  		if db.GetBalance(w.Address).Uint64() != w.Amount*params.GWei {
  1153  			t.Fatalf("failed to process withdrawal %d", i)
  1154  		}
  1155  	}
  1156  }
  1157  
  1158  func TestNilWithdrawals(t *testing.T) {
  1159  	genesis, blocks := generateMergeChain(10, true)
  1160  	// Set shanghai time to last block + 4 seconds (first post-merge block)
  1161  	time := blocks[len(blocks)-1].Time() + 4
  1162  	genesis.Config.ShanghaiTime = &time
  1163  
  1164  	n, ethservice := startEthService(t, genesis, blocks)
  1165  	ethservice.Merger().ReachTTD()
  1166  	defer n.Close()
  1167  
  1168  	api := NewConsensusAPI(ethservice)
  1169  	parent := ethservice.BlockChain().CurrentHeader()
  1170  	aa := common.Address{0xaa}
  1171  
  1172  	type test struct {
  1173  		blockParams engine.PayloadAttributes
  1174  		wantErr     bool
  1175  	}
  1176  	tests := []test{
  1177  		// Before Shanghai
  1178  		{
  1179  			blockParams: engine.PayloadAttributes{
  1180  				Timestamp:   parent.Time + 2,
  1181  				Withdrawals: nil,
  1182  			},
  1183  			wantErr: false,
  1184  		},
  1185  		{
  1186  			blockParams: engine.PayloadAttributes{
  1187  				Timestamp:   parent.Time + 2,
  1188  				Withdrawals: make([]*types.Withdrawal, 0),
  1189  			},
  1190  			wantErr: true,
  1191  		},
  1192  		{
  1193  			blockParams: engine.PayloadAttributes{
  1194  				Timestamp: parent.Time + 2,
  1195  				Withdrawals: []*types.Withdrawal{
  1196  					{
  1197  						Index:   0,
  1198  						Address: aa,
  1199  						Amount:  32,
  1200  					},
  1201  				},
  1202  			},
  1203  			wantErr: true,
  1204  		},
  1205  		// After Shanghai
  1206  		{
  1207  			blockParams: engine.PayloadAttributes{
  1208  				Timestamp:   parent.Time + 5,
  1209  				Withdrawals: nil,
  1210  			},
  1211  			wantErr: true,
  1212  		},
  1213  		{
  1214  			blockParams: engine.PayloadAttributes{
  1215  				Timestamp:   parent.Time + 5,
  1216  				Withdrawals: make([]*types.Withdrawal, 0),
  1217  			},
  1218  			wantErr: false,
  1219  		},
  1220  		{
  1221  			blockParams: engine.PayloadAttributes{
  1222  				Timestamp: parent.Time + 5,
  1223  				Withdrawals: []*types.Withdrawal{
  1224  					{
  1225  						Index:   0,
  1226  						Address: aa,
  1227  						Amount:  32,
  1228  					},
  1229  				},
  1230  			},
  1231  			wantErr: false,
  1232  		},
  1233  	}
  1234  
  1235  	fcState := engine.ForkchoiceStateV1{
  1236  		HeadBlockHash: parent.Hash(),
  1237  	}
  1238  
  1239  	for _, test := range tests {
  1240  		_, err := api.ForkchoiceUpdatedV2(fcState, &test.blockParams)
  1241  		if test.wantErr {
  1242  			if err == nil {
  1243  				t.Fatal("wanted error on fcuv2 with invalid withdrawals")
  1244  			}
  1245  			continue
  1246  		}
  1247  		if err != nil {
  1248  			t.Fatalf("error preparing payload, err=%v", err)
  1249  		}
  1250  
  1251  		// 11: verify locally build block.
  1252  		payloadID := (&miner.BuildPayloadArgs{
  1253  			Parent:       fcState.HeadBlockHash,
  1254  			Timestamp:    test.blockParams.Timestamp,
  1255  			FeeRecipient: test.blockParams.SuggestedFeeRecipient,
  1256  			Random:       test.blockParams.Random,
  1257  			BeaconRoot:   test.blockParams.BeaconRoot,
  1258  		}).Id()
  1259  		execData, err := api.GetPayloadV2(payloadID)
  1260  		if err != nil {
  1261  			t.Fatalf("error getting payload, err=%v", err)
  1262  		}
  1263  		if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
  1264  			t.Fatalf("error validating payload: %v", err)
  1265  		} else if status.Status != engine.VALID {
  1266  			t.Fatalf("invalid payload")
  1267  		}
  1268  	}
  1269  }
  1270  
  1271  func setupBodies(t *testing.T) (*node.Node, *zond.Ethereum, []*types.Block) {
  1272  	genesis, blocks := generateMergeChain(10, true)
  1273  	// enable shanghai on the last block
  1274  	time := blocks[len(blocks)-1].Header().Time + 1
  1275  	genesis.Config.ShanghaiTime = &time
  1276  	n, ethservice := startEthService(t, genesis, blocks)
  1277  
  1278  	var (
  1279  		parent = ethservice.BlockChain().CurrentBlock()
  1280  		// This EVM code generates a log when the contract is created.
  1281  		logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
  1282  	)
  1283  
  1284  	callback := func(parent *types.Header) {
  1285  		statedb, _ := ethservice.BlockChain().StateAt(parent.Root)
  1286  		nonce := statedb.GetNonce(testAddr)
  1287  		tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
  1288  		ethservice.TxPool().Add([]*types.Transaction{tx}, false, false)
  1289  	}
  1290  
  1291  	withdrawals := make([][]*types.Withdrawal, 10)
  1292  	withdrawals[0] = nil // should be filtered out by miner
  1293  	withdrawals[1] = make([]*types.Withdrawal, 0)
  1294  	for i := 2; i < len(withdrawals); i++ {
  1295  		addr := make([]byte, 20)
  1296  		crand.Read(addr)
  1297  		withdrawals[i] = []*types.Withdrawal{
  1298  			{Index: rand.Uint64(), Validator: rand.Uint64(), Amount: rand.Uint64(), Address: common.BytesToAddress(addr)},
  1299  		}
  1300  	}
  1301  
  1302  	postShanghaiHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals)
  1303  	postShanghaiBlocks := make([]*types.Block, len(postShanghaiHeaders))
  1304  	for i, header := range postShanghaiHeaders {
  1305  		postShanghaiBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64())
  1306  	}
  1307  	return n, ethservice, append(blocks, postShanghaiBlocks...)
  1308  }
  1309  
  1310  func allHashes(blocks []*types.Block) []common.Hash {
  1311  	var hashes []common.Hash
  1312  	for _, b := range blocks {
  1313  		hashes = append(hashes, b.Hash())
  1314  	}
  1315  	return hashes
  1316  }
  1317  func allBodies(blocks []*types.Block) []*types.Body {
  1318  	var bodies []*types.Body
  1319  	for _, b := range blocks {
  1320  		bodies = append(bodies, b.Body())
  1321  	}
  1322  	return bodies
  1323  }
  1324  
  1325  func TestGetBlockBodiesByHash(t *testing.T) {
  1326  	node, eth, blocks := setupBodies(t)
  1327  	api := NewConsensusAPI(eth)
  1328  	defer node.Close()
  1329  
  1330  	tests := []struct {
  1331  		results []*types.Body
  1332  		hashes  []common.Hash
  1333  	}{
  1334  		// First pow block
  1335  		{
  1336  			results: []*types.Body{eth.BlockChain().GetBlockByNumber(0).Body()},
  1337  			hashes:  []common.Hash{eth.BlockChain().GetBlockByNumber(0).Hash()},
  1338  		},
  1339  		// Last pow block
  1340  		{
  1341  			results: []*types.Body{blocks[9].Body()},
  1342  			hashes:  []common.Hash{blocks[9].Hash()},
  1343  		},
  1344  		// First post-merge block
  1345  		{
  1346  			results: []*types.Body{blocks[10].Body()},
  1347  			hashes:  []common.Hash{blocks[10].Hash()},
  1348  		},
  1349  		// Pre & post merge blocks
  1350  		{
  1351  			results: []*types.Body{blocks[0].Body(), blocks[9].Body(), blocks[14].Body()},
  1352  			hashes:  []common.Hash{blocks[0].Hash(), blocks[9].Hash(), blocks[14].Hash()},
  1353  		},
  1354  		// unavailable block
  1355  		{
  1356  			results: []*types.Body{blocks[0].Body(), nil, blocks[14].Body()},
  1357  			hashes:  []common.Hash{blocks[0].Hash(), {1, 2}, blocks[14].Hash()},
  1358  		},
  1359  		// same block multiple times
  1360  		{
  1361  			results: []*types.Body{blocks[0].Body(), nil, blocks[0].Body(), blocks[0].Body()},
  1362  			hashes:  []common.Hash{blocks[0].Hash(), {1, 2}, blocks[0].Hash(), blocks[0].Hash()},
  1363  		},
  1364  		// all blocks
  1365  		{
  1366  			results: allBodies(blocks),
  1367  			hashes:  allHashes(blocks),
  1368  		},
  1369  	}
  1370  
  1371  	for k, test := range tests {
  1372  		result := api.GetPayloadBodiesByHashV1(test.hashes)
  1373  		for i, r := range result {
  1374  			if !equalBody(test.results[i], r) {
  1375  				t.Fatalf("test %v: invalid response: expected %+v got %+v", k, test.results[i], r)
  1376  			}
  1377  		}
  1378  	}
  1379  }
  1380  
  1381  func TestGetBlockBodiesByRange(t *testing.T) {
  1382  	node, eth, blocks := setupBodies(t)
  1383  	api := NewConsensusAPI(eth)
  1384  	defer node.Close()
  1385  
  1386  	tests := []struct {
  1387  		results []*types.Body
  1388  		start   hexutil.Uint64
  1389  		count   hexutil.Uint64
  1390  	}{
  1391  		{
  1392  			results: []*types.Body{blocks[9].Body()},
  1393  			start:   10,
  1394  			count:   1,
  1395  		},
  1396  		// Genesis
  1397  		{
  1398  			results: []*types.Body{blocks[0].Body()},
  1399  			start:   1,
  1400  			count:   1,
  1401  		},
  1402  		// First post-merge block
  1403  		{
  1404  			results: []*types.Body{blocks[9].Body()},
  1405  			start:   10,
  1406  			count:   1,
  1407  		},
  1408  		// Pre & post merge blocks
  1409  		{
  1410  			results: []*types.Body{blocks[7].Body(), blocks[8].Body(), blocks[9].Body(), blocks[10].Body()},
  1411  			start:   8,
  1412  			count:   4,
  1413  		},
  1414  		// unavailable block
  1415  		{
  1416  			results: []*types.Body{blocks[18].Body(), blocks[19].Body()},
  1417  			start:   19,
  1418  			count:   3,
  1419  		},
  1420  		// unavailable block
  1421  		{
  1422  			results: []*types.Body{blocks[19].Body()},
  1423  			start:   20,
  1424  			count:   2,
  1425  		},
  1426  		{
  1427  			results: []*types.Body{blocks[19].Body()},
  1428  			start:   20,
  1429  			count:   1,
  1430  		},
  1431  		// whole range unavailable
  1432  		{
  1433  			results: make([]*types.Body, 0),
  1434  			start:   22,
  1435  			count:   2,
  1436  		},
  1437  		// allBlocks
  1438  		{
  1439  			results: allBodies(blocks),
  1440  			start:   1,
  1441  			count:   hexutil.Uint64(len(blocks)),
  1442  		},
  1443  	}
  1444  
  1445  	for k, test := range tests {
  1446  		result, err := api.GetPayloadBodiesByRangeV1(test.start, test.count)
  1447  		if err != nil {
  1448  			t.Fatal(err)
  1449  		}
  1450  		if len(result) == len(test.results) {
  1451  			for i, r := range result {
  1452  				if !equalBody(test.results[i], r) {
  1453  					t.Fatalf("test %d: invalid response: expected \n%+v\ngot\n%+v", k, test.results[i], r)
  1454  				}
  1455  			}
  1456  		} else {
  1457  			t.Fatalf("test %d: invalid length want %v got %v", k, len(test.results), len(result))
  1458  		}
  1459  	}
  1460  }
  1461  
  1462  func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) {
  1463  	node, eth, _ := setupBodies(t)
  1464  	api := NewConsensusAPI(eth)
  1465  	defer node.Close()
  1466  	tests := []struct {
  1467  		start hexutil.Uint64
  1468  		count hexutil.Uint64
  1469  		want  *engine.EngineAPIError
  1470  	}{
  1471  		// Genesis
  1472  		{
  1473  			start: 0,
  1474  			count: 1,
  1475  			want:  engine.InvalidParams,
  1476  		},
  1477  		// No block requested
  1478  		{
  1479  			start: 1,
  1480  			count: 0,
  1481  			want:  engine.InvalidParams,
  1482  		},
  1483  		// Genesis & no block
  1484  		{
  1485  			start: 0,
  1486  			count: 0,
  1487  			want:  engine.InvalidParams,
  1488  		},
  1489  		// More than 1024 blocks
  1490  		{
  1491  			start: 1,
  1492  			count: 1025,
  1493  			want:  engine.TooLargeRequest,
  1494  		},
  1495  	}
  1496  	for i, tc := range tests {
  1497  		result, err := api.GetPayloadBodiesByRangeV1(tc.start, tc.count)
  1498  		if err == nil {
  1499  			t.Fatalf("test %d: expected error, got %v", i, result)
  1500  		}
  1501  		if have, want := err.Error(), tc.want.Error(); have != want {
  1502  			t.Fatalf("test %d: have %s, want %s", i, have, want)
  1503  		}
  1504  	}
  1505  }
  1506  
  1507  func equalBody(a *types.Body, b *engine.ExecutionPayloadBodyV1) bool {
  1508  	if a == nil && b == nil {
  1509  		return true
  1510  	} else if a == nil || b == nil {
  1511  		return false
  1512  	}
  1513  	if len(a.Transactions) != len(b.TransactionData) {
  1514  		return false
  1515  	}
  1516  	for i, tx := range a.Transactions {
  1517  		data, _ := tx.MarshalBinary()
  1518  		if !bytes.Equal(data, b.TransactionData[i]) {
  1519  			return false
  1520  		}
  1521  	}
  1522  	return reflect.DeepEqual(a.Withdrawals, b.Withdrawals)
  1523  }
  1524  
  1525  func TestBlockToPayloadWithBlobs(t *testing.T) {
  1526  	header := types.Header{}
  1527  	var txs []*types.Transaction
  1528  
  1529  	inner := types.BlobTx{
  1530  		BlobHashes: make([]common.Hash, 1),
  1531  	}
  1532  
  1533  	txs = append(txs, types.NewTx(&inner))
  1534  	sidecars := []*types.BlobTxSidecar{
  1535  		{
  1536  			Blobs:       make([]kzg4844.Blob, 1),
  1537  			Commitments: make([]kzg4844.Commitment, 1),
  1538  			Proofs:      make([]kzg4844.Proof, 1),
  1539  		},
  1540  	}
  1541  
  1542  	block := types.NewBlock(&header, txs, nil, nil, trie.NewStackTrie(nil))
  1543  	envelope := engine.BlockToExecutableData(block, nil, sidecars)
  1544  	var want int
  1545  	for _, tx := range txs {
  1546  		want += len(tx.BlobHashes())
  1547  	}
  1548  	if got := len(envelope.BlobsBundle.Commitments); got != want {
  1549  		t.Fatalf("invalid number of commitments: got %v, want %v", got, want)
  1550  	}
  1551  	if got := len(envelope.BlobsBundle.Proofs); got != want {
  1552  		t.Fatalf("invalid number of proofs: got %v, want %v", got, want)
  1553  	}
  1554  	if got := len(envelope.BlobsBundle.Blobs); got != want {
  1555  		t.Fatalf("invalid number of blobs: got %v, want %v", got, want)
  1556  	}
  1557  	_, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil)
  1558  	if err != nil {
  1559  		t.Error(err)
  1560  	}
  1561  }
  1562  
  1563  // This checks that beaconRoot is applied to the state from the engine API.
  1564  func TestParentBeaconBlockRoot(t *testing.T) {
  1565  	log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
  1566  
  1567  	genesis, blocks := generateMergeChain(10, true)
  1568  
  1569  	// Set cancun time to last block + 5 seconds
  1570  	time := blocks[len(blocks)-1].Time() + 5
  1571  	genesis.Config.ShanghaiTime = &time
  1572  	genesis.Config.CancunTime = &time
  1573  
  1574  	n, ethservice := startEthService(t, genesis, blocks)
  1575  	ethservice.Merger().ReachTTD()
  1576  	defer n.Close()
  1577  
  1578  	api := NewConsensusAPI(ethservice)
  1579  
  1580  	// 11: Build Shanghai block with no withdrawals.
  1581  	parent := ethservice.BlockChain().CurrentHeader()
  1582  	blockParams := engine.PayloadAttributes{
  1583  		Timestamp:   parent.Time + 5,
  1584  		Withdrawals: make([]*types.Withdrawal, 0),
  1585  		BeaconRoot:  &common.Hash{42},
  1586  	}
  1587  	fcState := engine.ForkchoiceStateV1{
  1588  		HeadBlockHash: parent.Hash(),
  1589  	}
  1590  	resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
  1591  	if err != nil {
  1592  		t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
  1593  	}
  1594  	if resp.PayloadStatus.Status != engine.VALID {
  1595  		t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID)
  1596  	}
  1597  
  1598  	// 11: verify state root is the same as parent
  1599  	payloadID := (&miner.BuildPayloadArgs{
  1600  		Parent:       fcState.HeadBlockHash,
  1601  		Timestamp:    blockParams.Timestamp,
  1602  		FeeRecipient: blockParams.SuggestedFeeRecipient,
  1603  		Random:       blockParams.Random,
  1604  		Withdrawals:  blockParams.Withdrawals,
  1605  		BeaconRoot:   blockParams.BeaconRoot,
  1606  	}).Id()
  1607  	execData, err := api.GetPayloadV3(payloadID)
  1608  	if err != nil {
  1609  		t.Fatalf("error getting payload, err=%v", err)
  1610  	}
  1611  
  1612  	// 11: verify locally built block
  1613  	if status, err := api.NewPayloadV3(*execData.ExecutionPayload, []common.Hash{}, &common.Hash{42}); err != nil {
  1614  		t.Fatalf("error validating payload: %v", err)
  1615  	} else if status.Status != engine.VALID {
  1616  		t.Fatalf("invalid payload")
  1617  	}
  1618  
  1619  	fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
  1620  	resp, err = api.ForkchoiceUpdatedV3(fcState, nil)
  1621  	if err != nil {
  1622  		t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
  1623  	}
  1624  	if resp.PayloadStatus.Status != engine.VALID {
  1625  		t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID)
  1626  	}
  1627  
  1628  	// 11: verify beacon root was processed.
  1629  	db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number))
  1630  	if err != nil {
  1631  		t.Fatalf("unable to load db: %v", err)
  1632  	}
  1633  	var (
  1634  		timeIdx = common.BigToHash(big.NewInt(int64(execData.ExecutionPayload.Timestamp % 98304)))
  1635  		rootIdx = common.BigToHash(big.NewInt(int64((execData.ExecutionPayload.Timestamp % 98304) + 98304)))
  1636  	)
  1637  
  1638  	if num := db.GetState(params.BeaconRootsStorageAddress, timeIdx); num != timeIdx {
  1639  		t.Fatalf("incorrect number stored: want %s, got %s", timeIdx, num)
  1640  	}
  1641  	if root := db.GetState(params.BeaconRootsStorageAddress, rootIdx); root != *blockParams.BeaconRoot {
  1642  		t.Fatalf("incorrect root stored: want %s, got %s", *blockParams.BeaconRoot, root)
  1643  	}
  1644  }