github.com/hardtosaygoodbye/go-ethereum@v1.10.16-0.20220122011429-97003b9e6c15/eth/catalyst/api_test.go (about)

     1  // Copyright 2020 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  	"fmt"
    21  	"math/big"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/hardtosaygoodbye/go-ethereum/common"
    26  	"github.com/hardtosaygoodbye/go-ethereum/consensus/ethash"
    27  	"github.com/hardtosaygoodbye/go-ethereum/core"
    28  	"github.com/hardtosaygoodbye/go-ethereum/core/rawdb"
    29  	"github.com/hardtosaygoodbye/go-ethereum/core/types"
    30  	"github.com/hardtosaygoodbye/go-ethereum/crypto"
    31  	"github.com/hardtosaygoodbye/go-ethereum/eth"
    32  	"github.com/hardtosaygoodbye/go-ethereum/eth/ethconfig"
    33  	"github.com/hardtosaygoodbye/go-ethereum/node"
    34  	"github.com/hardtosaygoodbye/go-ethereum/params"
    35  )
    36  
    37  var (
    38  	// testKey is a private key to use for funding a tester account.
    39  	testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
    40  
    41  	// testAddr is the Ethereum address of the tester account.
    42  	testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
    43  
    44  	testBalance = big.NewInt(2e18)
    45  )
    46  
    47  func generatePreMergeChain(n int) (*core.Genesis, []*types.Block) {
    48  	db := rawdb.NewMemoryDatabase()
    49  	config := params.AllEthashProtocolChanges
    50  	genesis := &core.Genesis{
    51  		Config:    config,
    52  		Alloc:     core.GenesisAlloc{testAddr: {Balance: testBalance}},
    53  		ExtraData: []byte("test genesis"),
    54  		Timestamp: 9000,
    55  		BaseFee:   big.NewInt(params.InitialBaseFee),
    56  	}
    57  	testNonce := uint64(0)
    58  	generate := func(i int, g *core.BlockGen) {
    59  		g.OffsetTime(5)
    60  		g.SetExtra([]byte("test"))
    61  		tx, _ := types.SignTx(types.NewTransaction(testNonce, common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"), big.NewInt(1), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), types.LatestSigner(config), testKey)
    62  		g.AddTx(tx)
    63  		testNonce++
    64  	}
    65  	gblock := genesis.ToBlock(db)
    66  	engine := ethash.NewFaker()
    67  	blocks, _ := core.GenerateChain(config, gblock, engine, db, n, generate)
    68  	totalDifficulty := big.NewInt(0)
    69  	for _, b := range blocks {
    70  		totalDifficulty.Add(totalDifficulty, b.Difficulty())
    71  	}
    72  	config.TerminalTotalDifficulty = totalDifficulty
    73  	return genesis, blocks
    74  }
    75  
    76  func TestEth2AssembleBlock(t *testing.T) {
    77  	genesis, blocks := generatePreMergeChain(10)
    78  	n, ethservice := startEthService(t, genesis, blocks)
    79  	defer n.Close()
    80  
    81  	api := NewConsensusAPI(ethservice, nil)
    82  	signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID)
    83  	tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
    84  	if err != nil {
    85  		t.Fatalf("error signing transaction, err=%v", err)
    86  	}
    87  	ethservice.TxPool().AddLocal(tx)
    88  	blockParams := PayloadAttributesV1{
    89  		Timestamp: blocks[9].Time() + 5,
    90  	}
    91  	execData, err := api.assembleBlock(blocks[9].Hash(), &blockParams)
    92  	if err != nil {
    93  		t.Fatalf("error producing block, err=%v", err)
    94  	}
    95  	if len(execData.Transactions) != 1 {
    96  		t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
    97  	}
    98  }
    99  
   100  func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
   101  	genesis, blocks := generatePreMergeChain(10)
   102  	n, ethservice := startEthService(t, genesis, blocks[:9])
   103  	defer n.Close()
   104  
   105  	api := NewConsensusAPI(ethservice, nil)
   106  
   107  	// Put the 10th block's tx in the pool and produce a new block
   108  	api.insertTransactions(blocks[9].Transactions())
   109  	blockParams := PayloadAttributesV1{
   110  		Timestamp: blocks[8].Time() + 5,
   111  	}
   112  	execData, err := api.assembleBlock(blocks[8].Hash(), &blockParams)
   113  	if err != nil {
   114  		t.Fatalf("error producing block, err=%v", err)
   115  	}
   116  	if len(execData.Transactions) != blocks[9].Transactions().Len() {
   117  		t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
   118  	}
   119  }
   120  
   121  func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
   122  	genesis, blocks := generatePreMergeChain(10)
   123  	n, ethservice := startEthService(t, genesis, blocks)
   124  	defer n.Close()
   125  
   126  	api := NewConsensusAPI(ethservice, nil)
   127  	fcState := ForkchoiceStateV1{
   128  		HeadBlockHash:      blocks[5].Hash(),
   129  		SafeBlockHash:      common.Hash{},
   130  		FinalizedBlockHash: common.Hash{},
   131  	}
   132  	if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err == nil {
   133  		t.Errorf("fork choice updated before total terminal difficulty should fail")
   134  	}
   135  }
   136  
   137  func TestEth2PrepareAndGetPayload(t *testing.T) {
   138  	genesis, blocks := generatePreMergeChain(10)
   139  	// We need to properly set the terminal total difficulty
   140  	genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[9].Difficulty())
   141  	n, ethservice := startEthService(t, genesis, blocks[:9])
   142  	defer n.Close()
   143  
   144  	api := NewConsensusAPI(ethservice, nil)
   145  
   146  	// Put the 10th block's tx in the pool and produce a new block
   147  	api.insertTransactions(blocks[9].Transactions())
   148  	blockParams := PayloadAttributesV1{
   149  		Timestamp: blocks[8].Time() + 5,
   150  	}
   151  	fcState := ForkchoiceStateV1{
   152  		HeadBlockHash:      blocks[8].Hash(),
   153  		SafeBlockHash:      common.Hash{},
   154  		FinalizedBlockHash: common.Hash{},
   155  	}
   156  	_, err := api.ForkchoiceUpdatedV1(fcState, &blockParams)
   157  	if err != nil {
   158  		t.Fatalf("error preparing payload, err=%v", err)
   159  	}
   160  	payloadID := computePayloadId(fcState.HeadBlockHash, &blockParams)
   161  	execData, err := api.GetPayloadV1(payloadID)
   162  	if err != nil {
   163  		t.Fatalf("error getting payload, err=%v", err)
   164  	}
   165  	if len(execData.Transactions) != blocks[9].Transactions().Len() {
   166  		t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
   167  	}
   168  	// Test invalid payloadID
   169  	var invPayload PayloadID
   170  	copy(invPayload[:], payloadID[:])
   171  	invPayload[0] = ^invPayload[0]
   172  	_, err = api.GetPayloadV1(invPayload)
   173  	if err == nil {
   174  		t.Fatal("expected error retrieving invalid payload")
   175  	}
   176  }
   177  
   178  func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan core.RemovedLogsEvent, wantNew, wantRemoved int) {
   179  	t.Helper()
   180  
   181  	if len(logsCh) != wantNew {
   182  		t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew)
   183  	}
   184  	if len(rmLogsCh) != wantRemoved {
   185  		t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved)
   186  	}
   187  	// Drain events.
   188  	for i := 0; i < len(logsCh); i++ {
   189  		<-logsCh
   190  	}
   191  	for i := 0; i < len(rmLogsCh); i++ {
   192  		<-rmLogsCh
   193  	}
   194  }
   195  
   196  func TestInvalidPayloadTimestamp(t *testing.T) {
   197  	genesis, preMergeBlocks := generatePreMergeChain(10)
   198  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   199  	ethservice.Merger().ReachTTD()
   200  	defer n.Close()
   201  	var (
   202  		api    = NewConsensusAPI(ethservice, nil)
   203  		parent = ethservice.BlockChain().CurrentBlock()
   204  	)
   205  	tests := []struct {
   206  		time      uint64
   207  		shouldErr bool
   208  	}{
   209  		{0, true},
   210  		{parent.Time(), true},
   211  		{parent.Time() - 1, true},
   212  		{parent.Time() + 1, false},
   213  		{uint64(time.Now().Unix()) + uint64(time.Minute), false},
   214  	}
   215  
   216  	for i, test := range tests {
   217  		t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) {
   218  			params := PayloadAttributesV1{
   219  				Timestamp:             test.time,
   220  				Random:                crypto.Keccak256Hash([]byte{byte(123)}),
   221  				SuggestedFeeRecipient: parent.Coinbase(),
   222  			}
   223  			fcState := ForkchoiceStateV1{
   224  				HeadBlockHash:      parent.Hash(),
   225  				SafeBlockHash:      common.Hash{},
   226  				FinalizedBlockHash: common.Hash{},
   227  			}
   228  			_, err := api.ForkchoiceUpdatedV1(fcState, &params)
   229  			if test.shouldErr && err == nil {
   230  				t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err)
   231  			} else if !test.shouldErr && err != nil {
   232  				t.Fatalf("error preparing payload with valid timestamp, err=%v", err)
   233  			}
   234  		})
   235  	}
   236  }
   237  
   238  func TestEth2NewBlock(t *testing.T) {
   239  	genesis, preMergeBlocks := generatePreMergeChain(10)
   240  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   241  	ethservice.Merger().ReachTTD()
   242  	defer n.Close()
   243  
   244  	var (
   245  		api    = NewConsensusAPI(ethservice, nil)
   246  		parent = preMergeBlocks[len(preMergeBlocks)-1]
   247  
   248  		// This EVM code generates a log when the contract is created.
   249  		logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
   250  	)
   251  	// The event channels.
   252  	newLogCh := make(chan []*types.Log, 10)
   253  	rmLogsCh := make(chan core.RemovedLogsEvent, 10)
   254  	ethservice.BlockChain().SubscribeLogsEvent(newLogCh)
   255  	ethservice.BlockChain().SubscribeRemovedLogsEvent(rmLogsCh)
   256  
   257  	for i := 0; i < 10; i++ {
   258  		statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
   259  		nonce := statedb.GetNonce(testAddr)
   260  		tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
   261  		ethservice.TxPool().AddLocal(tx)
   262  
   263  		execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{
   264  			Timestamp: parent.Time() + 5,
   265  		})
   266  		if err != nil {
   267  			t.Fatalf("Failed to create the executable data %v", err)
   268  		}
   269  		block, err := ExecutableDataToBlock(*execData)
   270  		if err != nil {
   271  			t.Fatalf("Failed to convert executable data to block %v", err)
   272  		}
   273  		newResp, err := api.ExecutePayloadV1(*execData)
   274  		if err != nil || newResp.Status != "VALID" {
   275  			t.Fatalf("Failed to insert block: %v", err)
   276  		}
   277  		if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64()-1 {
   278  			t.Fatalf("Chain head shouldn't be updated")
   279  		}
   280  		checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
   281  		fcState := ForkchoiceStateV1{
   282  			HeadBlockHash:      block.Hash(),
   283  			SafeBlockHash:      block.Hash(),
   284  			FinalizedBlockHash: block.Hash(),
   285  		}
   286  		if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   287  			t.Fatalf("Failed to insert block: %v", err)
   288  		}
   289  		if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() {
   290  			t.Fatalf("Chain head should be updated")
   291  		}
   292  		checkLogEvents(t, newLogCh, rmLogsCh, 1, 0)
   293  
   294  		parent = block
   295  	}
   296  
   297  	// Introduce fork chain
   298  	var (
   299  		head = ethservice.BlockChain().CurrentBlock().NumberU64()
   300  	)
   301  	parent = preMergeBlocks[len(preMergeBlocks)-1]
   302  	for i := 0; i < 10; i++ {
   303  		execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{
   304  			Timestamp: parent.Time() + 6,
   305  		})
   306  		if err != nil {
   307  			t.Fatalf("Failed to create the executable data %v", err)
   308  		}
   309  		block, err := ExecutableDataToBlock(*execData)
   310  		if err != nil {
   311  			t.Fatalf("Failed to convert executable data to block %v", err)
   312  		}
   313  		newResp, err := api.ExecutePayloadV1(*execData)
   314  		if err != nil || newResp.Status != "VALID" {
   315  			t.Fatalf("Failed to insert block: %v", err)
   316  		}
   317  		if ethservice.BlockChain().CurrentBlock().NumberU64() != head {
   318  			t.Fatalf("Chain head shouldn't be updated")
   319  		}
   320  
   321  		fcState := ForkchoiceStateV1{
   322  			HeadBlockHash:      block.Hash(),
   323  			SafeBlockHash:      block.Hash(),
   324  			FinalizedBlockHash: block.Hash(),
   325  		}
   326  		if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   327  			t.Fatalf("Failed to insert block: %v", err)
   328  		}
   329  		if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() {
   330  			t.Fatalf("Chain head should be updated")
   331  		}
   332  		parent, head = block, block.NumberU64()
   333  	}
   334  }
   335  
   336  func TestEth2DeepReorg(t *testing.T) {
   337  	// TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg
   338  	// before the totalTerminalDifficulty threshold
   339  	/*
   340  		genesis, preMergeBlocks := generatePreMergeChain(core.TriesInMemory * 2)
   341  		n, ethservice := startEthService(t, genesis, preMergeBlocks)
   342  		defer n.Close()
   343  
   344  		var (
   345  			api    = NewConsensusAPI(ethservice, nil)
   346  			parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1]
   347  			head   = ethservice.BlockChain().CurrentBlock().NumberU64()
   348  		)
   349  		if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) {
   350  			t.Errorf("Block %d not pruned", parent.NumberU64())
   351  		}
   352  		for i := 0; i < 10; i++ {
   353  			execData, err := api.assembleBlock(AssembleBlockParams{
   354  				ParentHash: parent.Hash(),
   355  				Timestamp:  parent.Time() + 5,
   356  			})
   357  			if err != nil {
   358  				t.Fatalf("Failed to create the executable data %v", err)
   359  			}
   360  			block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData)
   361  			if err != nil {
   362  				t.Fatalf("Failed to convert executable data to block %v", err)
   363  			}
   364  			newResp, err := api.ExecutePayload(*execData)
   365  			if err != nil || newResp.Status != "VALID" {
   366  				t.Fatalf("Failed to insert block: %v", err)
   367  			}
   368  			if ethservice.BlockChain().CurrentBlock().NumberU64() != head {
   369  				t.Fatalf("Chain head shouldn't be updated")
   370  			}
   371  			if err := api.setHead(block.Hash()); err != nil {
   372  				t.Fatalf("Failed to set head: %v", err)
   373  			}
   374  			if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() {
   375  				t.Fatalf("Chain head should be updated")
   376  			}
   377  			parent, head = block, block.NumberU64()
   378  		}
   379  	*/
   380  }
   381  
   382  // startEthService creates a full node instance for testing.
   383  func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
   384  	t.Helper()
   385  
   386  	n, err := node.New(&node.Config{})
   387  	if err != nil {
   388  		t.Fatal("can't create node:", err)
   389  	}
   390  
   391  	ethcfg := &ethconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
   392  	ethservice, err := eth.New(n, ethcfg)
   393  	if err != nil {
   394  		t.Fatal("can't create eth service:", err)
   395  	}
   396  	if err := n.Start(); err != nil {
   397  		t.Fatal("can't start node:", err)
   398  	}
   399  	if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil {
   400  		n.Close()
   401  		t.Fatal("can't import test blocks:", err)
   402  	}
   403  	ethservice.SetEtherbase(testAddr)
   404  	ethservice.SetSynced()
   405  
   406  	return n, ethservice
   407  }
   408  
   409  func TestFullAPI(t *testing.T) {
   410  	genesis, preMergeBlocks := generatePreMergeChain(10)
   411  	n, ethservice := startEthService(t, genesis, preMergeBlocks)
   412  	ethservice.Merger().ReachTTD()
   413  	defer n.Close()
   414  	var (
   415  		api    = NewConsensusAPI(ethservice, nil)
   416  		parent = ethservice.BlockChain().CurrentBlock()
   417  		// This EVM code generates a log when the contract is created.
   418  		logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
   419  	)
   420  	for i := 0; i < 10; i++ {
   421  		statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
   422  		nonce := statedb.GetNonce(testAddr)
   423  		tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
   424  		ethservice.TxPool().AddLocal(tx)
   425  
   426  		params := PayloadAttributesV1{
   427  			Timestamp:             parent.Time() + 1,
   428  			Random:                crypto.Keccak256Hash([]byte{byte(i)}),
   429  			SuggestedFeeRecipient: parent.Coinbase(),
   430  		}
   431  		fcState := ForkchoiceStateV1{
   432  			HeadBlockHash:      parent.Hash(),
   433  			SafeBlockHash:      common.Hash{},
   434  			FinalizedBlockHash: common.Hash{},
   435  		}
   436  		resp, err := api.ForkchoiceUpdatedV1(fcState, &params)
   437  		if err != nil {
   438  			t.Fatalf("error preparing payload, err=%v", err)
   439  		}
   440  		if resp.Status != SUCCESS.Status {
   441  			t.Fatalf("error preparing payload, invalid status: %v", resp.Status)
   442  		}
   443  		payloadID := computePayloadId(parent.Hash(), &params)
   444  		payload, err := api.GetPayloadV1(payloadID)
   445  		if err != nil {
   446  			t.Fatalf("can't get payload: %v", err)
   447  		}
   448  		execResp, err := api.ExecutePayloadV1(*payload)
   449  		if err != nil {
   450  			t.Fatalf("can't execute payload: %v", err)
   451  		}
   452  		if execResp.Status != VALID.Status {
   453  			t.Fatalf("invalid status: %v", execResp.Status)
   454  		}
   455  		fcState = ForkchoiceStateV1{
   456  			HeadBlockHash:      payload.BlockHash,
   457  			SafeBlockHash:      payload.ParentHash,
   458  			FinalizedBlockHash: payload.ParentHash,
   459  		}
   460  		if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
   461  			t.Fatalf("Failed to insert block: %v", err)
   462  		}
   463  		if ethservice.BlockChain().CurrentBlock().NumberU64() != payload.Number {
   464  			t.Fatalf("Chain head should be updated")
   465  		}
   466  		parent = ethservice.BlockChain().CurrentBlock()
   467  	}
   468  }