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 }