github.com/dim4egster/coreth@v0.10.2/plugin/evm/vm_extra_state_root_test.go (about)

     1  // (c) 2019-2022, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package evm
     5  
     6  import (
     7  	"encoding/json"
     8  	"math/big"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/dim4egster/qmallgo/ids"
    13  	"github.com/dim4egster/qmallgo/snow/choices"
    14  	"github.com/dim4egster/qmallgo/utils/crypto"
    15  	"github.com/dim4egster/qmallgo/vms/components/chain"
    16  	"github.com/dim4egster/coreth/core"
    17  	"github.com/dim4egster/coreth/core/types"
    18  	"github.com/dim4egster/coreth/trie"
    19  	"github.com/ethereum/go-ethereum/common"
    20  	"github.com/stretchr/testify/assert"
    21  )
    22  
    23  var (
    24  	// testClementineTime is an arbitrary time used to test the VM's behavior when
    25  	// Clementine activates.
    26  	testClementineTime = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
    27  	// testClementineJSON is a modified genesisJSONClementine to include the Clementine
    28  	// upgrade at testClementineTime.
    29  	testClementineJSON string
    30  )
    31  
    32  func init() {
    33  	var genesis core.Genesis
    34  	if err := json.Unmarshal([]byte(genesisJSONClementine), &genesis); err != nil {
    35  		panic(err)
    36  	}
    37  	genesis.Config.ClementineBlockTimestamp = big.NewInt(testClementineTime.Unix())
    38  	json, err := json.Marshal(genesis)
    39  	if err != nil {
    40  		panic(err)
    41  	}
    42  	testClementineJSON = string(json)
    43  }
    44  
    45  type verifyExtraStateRootConfig struct {
    46  	genesis                string
    47  	blockTime1             time.Time
    48  	blockTime2             time.Time
    49  	expectedExtraStateRoot func(atomicRoot1, atomicRoot2 common.Hash) (common.Hash, common.Hash)
    50  }
    51  
    52  // testVerifyExtraState root builds 2 blocks using a vm with [test.genesis].
    53  // First block is built at [blockTime1] and includes an import tx.
    54  // Second block is build at [blockTime2] and includes an export tx.
    55  // After blocks build, [test.expectedExtraStateRoot] is called with the roots
    56  // of the atomic trie at block1 and block2 and the ExtraStateRoot field of
    57  // the blocks are checked against the return value of that function.
    58  func testVerifyExtraStateRoot(t *testing.T, test verifyExtraStateRootConfig) {
    59  	importAmount := uint64(50000000)
    60  	issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, test.genesis, "", "", map[ids.ShortID]uint64{
    61  		testShortIDAddrs[0]: importAmount,
    62  	})
    63  	defer func() {
    64  		if err := vm.Shutdown(); err != nil {
    65  			t.Fatal(err)
    66  		}
    67  	}()
    68  
    69  	// issue tx for block1
    70  	vm.clock.Set(test.blockTime1)
    71  	importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*crypto.PrivateKeySECP256K1R{testKeys[0]})
    72  	if err != nil {
    73  		t.Fatal(err)
    74  	}
    75  	if err := vm.issueTx(importTx, true /*=local*/); err != nil {
    76  		t.Fatal(err)
    77  	}
    78  
    79  	// build block1
    80  	<-issuer
    81  	blk, err := vm.BuildBlock()
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  	if err := blk.Verify(); err != nil {
    86  		t.Fatal(err)
    87  	}
    88  	if status := blk.Status(); status != choices.Processing {
    89  		t.Fatalf("Expected status of built block to be %s, but found %s", choices.Processing, status)
    90  	}
    91  	if err := vm.SetPreference(blk.ID()); err != nil {
    92  		t.Fatal(err)
    93  	}
    94  	if err := blk.Accept(); err != nil {
    95  		t.Fatal(err)
    96  	}
    97  	if status := blk.Status(); status != choices.Accepted {
    98  		t.Fatalf("Expected status of accepted block to be %s, but found %s", choices.Accepted, status)
    99  	}
   100  	if lastAcceptedID, err := vm.LastAccepted(); err != nil {
   101  		t.Fatal(err)
   102  	} else if lastAcceptedID != blk.ID() {
   103  		t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk.ID(), lastAcceptedID)
   104  	}
   105  
   106  	// issue tx for block2
   107  	vm.clock.Set(test.blockTime2)
   108  	exportAmount := importAmount / 2
   109  	exportTx, err := vm.newExportTx(vm.ctx.AVAXAssetID, exportAmount, vm.ctx.XChainID, testShortIDAddrs[0], initialBaseFee, []*crypto.PrivateKeySECP256K1R{testKeys[0]})
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	if err := vm.issueTx(exportTx, true /*=local*/); err != nil {
   114  		t.Fatal(err)
   115  	}
   116  
   117  	// build block2
   118  	<-issuer
   119  	blk2, err := vm.BuildBlock()
   120  	if err != nil {
   121  		t.Fatal(err)
   122  	}
   123  	if err := blk2.Verify(); err != nil {
   124  		t.Fatal(err)
   125  	}
   126  	if status := blk2.Status(); status != choices.Processing {
   127  		t.Fatalf("Expected status of built block to be %s, but found %s", choices.Processing, status)
   128  	}
   129  	if err := blk2.Accept(); err != nil {
   130  		t.Fatal(err)
   131  	}
   132  	if status := blk2.Status(); status != choices.Accepted {
   133  		t.Fatalf("Expected status of accepted block to be %s, but found %s", choices.Accepted, status)
   134  	}
   135  	if lastAcceptedID, err := vm.LastAccepted(); err != nil {
   136  		t.Fatal(err)
   137  	} else if lastAcceptedID != blk2.ID() {
   138  		t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk2.ID(), lastAcceptedID)
   139  	}
   140  
   141  	// Check that both atomic transactions were indexed as expected.
   142  	indexedImportTx, status, height, err := vm.getAtomicTx(importTx.ID())
   143  	assert.NoError(t, err)
   144  	assert.Equal(t, Accepted, status)
   145  	assert.Equal(t, uint64(1), height, "expected height of indexed import tx to be 1")
   146  	assert.Equal(t, indexedImportTx.ID(), importTx.ID(), "expected ID of indexed import tx to match original txID")
   147  
   148  	indexedExportTx, status, height, err := vm.getAtomicTx(exportTx.ID())
   149  	assert.NoError(t, err)
   150  	assert.Equal(t, Accepted, status)
   151  	assert.Equal(t, uint64(2), height, "expected height of indexed export tx to be 2")
   152  	assert.Equal(t, indexedExportTx.ID(), exportTx.ID(), "expected ID of indexed import tx to match original txID")
   153  
   154  	// Open an empty trie to re-create the expected atomic trie roots
   155  	trie, err := vm.atomicTrie.OpenTrie(common.Hash{})
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  	assert.NoError(t, vm.atomicTrie.UpdateTrie(trie, blk.Height(), importTx.mustAtomicOps()))
   160  	atomicRootBlock1 := trie.Hash()
   161  	assert.NoError(t, vm.atomicTrie.UpdateTrie(trie, blk2.Height(), exportTx.mustAtomicOps()))
   162  	atomicRootBlock2 := trie.Hash()
   163  	assert.NotZero(t, atomicRootBlock1)
   164  	assert.NotZero(t, atomicRootBlock2)
   165  	assert.NotEqual(t, atomicRootBlock1, atomicRootBlock2)
   166  
   167  	// verify atomic trie roots included in block header.
   168  	extraStateRoot := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock.Header().ExtraStateRoot
   169  	extraStateRoot2 := blk2.(*chain.BlockWrapper).Block.(*Block).ethBlock.Header().ExtraStateRoot
   170  	expectedRoot1, expectedRoot2 := test.expectedExtraStateRoot(atomicRootBlock1, atomicRootBlock2)
   171  	assert.Equal(t, expectedRoot1, extraStateRoot)
   172  	assert.Equal(t, expectedRoot2, extraStateRoot2)
   173  }
   174  
   175  // Verifies the root of the atomic trie is inclued in Clementine blocks.
   176  func TestIssueAtomicTxsClementine(t *testing.T) {
   177  	testVerifyExtraStateRoot(t, verifyExtraStateRootConfig{
   178  		genesis:    genesisJSONClementine,
   179  		blockTime1: time.Unix(0, 0), // genesis
   180  		blockTime2: time.Unix(2, 0), // a bit after, for fee purposes.
   181  		expectedExtraStateRoot: func(atomicRoot1, atomicRoot2 common.Hash) (common.Hash, common.Hash) {
   182  			return atomicRoot1, atomicRoot2 // we expect both blocks to contain the atomic trie roots respectively.
   183  		},
   184  	})
   185  }
   186  
   187  // Verifies the root of the atomic trie is inclued in the first Clementine block.
   188  func TestIssueAtomicTxsClementineTransition(t *testing.T) {
   189  	testVerifyExtraStateRoot(t, verifyExtraStateRootConfig{
   190  		genesis:    testClementineJSON,
   191  		blockTime1: testClementineTime.Add(-2 * time.Second), // a little before Clementine, so we can test next block at the upgrade timestamp
   192  		blockTime2: testClementineTime,                       // at the upgrade timestamp
   193  		expectedExtraStateRoot: func(atomicRoot1, atomicRoot2 common.Hash) (common.Hash, common.Hash) {
   194  			return common.Hash{}, atomicRoot2 // we only expect the Clementine block to include the atomic trie root.
   195  		},
   196  	})
   197  }
   198  
   199  // Calling Verify should not succeed if the proper ExtraStateRoot is not included in a Clementine block.
   200  // Calling Verify should not succeed if ExtraStateRoot is not empty pre-Clementine
   201  func TestClementineInvalidExtraStateRootWillNotVerify(t *testing.T) {
   202  	importAmount := uint64(50000000)
   203  	issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, testClementineJSON, "", "", map[ids.ShortID]uint64{
   204  		testShortIDAddrs[0]: importAmount,
   205  	})
   206  	defer func() {
   207  		if err := vm.Shutdown(); err != nil {
   208  			t.Fatal(err)
   209  		}
   210  	}()
   211  
   212  	// issue a tx and build a Clementine block
   213  	vm.clock.Set(testClementineTime)
   214  	importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*crypto.PrivateKeySECP256K1R{testKeys[0]})
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	if err := vm.issueTx(importTx, true /*=local*/); err != nil {
   219  		t.Fatal(err)
   220  	}
   221  
   222  	<-issuer
   223  
   224  	// calling Verify on blk will succeed, we use it as
   225  	// a starting point to make an invalid block.
   226  	blk, err := vm.BuildBlock()
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	validEthBlk := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock
   231  
   232  	// make a bad block by setting ExtraStateRoot to common.Hash{}
   233  	badHeader := validEthBlk.Header()
   234  	badHeader.ExtraStateRoot = common.Hash{}
   235  	ethBlkBad := types.NewBlock(badHeader, validEthBlk.Transactions(), validEthBlk.Uncles(), nil, trie.NewStackTrie(nil), validEthBlk.ExtData(), true)
   236  
   237  	badBlk, err := vm.newBlock(ethBlkBad)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	err = badBlk.Verify()
   242  	assert.ErrorIs(t, err, errInvalidExtraStateRoot)
   243  
   244  	// make a bad block by setting ExtraStateRoot to an incorrect hash
   245  	badHeader = validEthBlk.Header()
   246  	badHeader.ExtraStateRoot = common.BytesToHash([]byte("incorrect"))
   247  	ethBlkBad = types.NewBlock(badHeader, validEthBlk.Transactions(), validEthBlk.Uncles(), nil, trie.NewStackTrie(nil), validEthBlk.ExtData(), true)
   248  
   249  	badBlk, err = vm.newBlock(ethBlkBad)
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  	err = badBlk.Verify()
   254  	assert.ErrorIs(t, err, errInvalidExtraStateRoot)
   255  
   256  	// make a bad block by setting the timestamp before Clementine.
   257  	badHeader = validEthBlk.Header()
   258  	badHeader.Time = uint64(testClementineTime.Add(-2 * time.Second).Unix())
   259  	ethBlkBad = types.NewBlock(badHeader, validEthBlk.Transactions(), validEthBlk.Uncles(), nil, trie.NewStackTrie(nil), validEthBlk.ExtData(), true)
   260  
   261  	badBlk, err = vm.newBlock(ethBlkBad)
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	err = badBlk.Verify()
   266  	assert.ErrorIs(t, err, errInvalidExtraStateRoot)
   267  }