diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 52c1eca71..6f9e47cf5 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -1,127 +1,127 @@ // Copyright 2017 The go-ethereum Authors // This file is part of go-ethereum. // // go-ethereum is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // go-ethereum is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with go-ethereum. If not, see . package main import ( "encoding/json" "errors" "fmt" "io/ioutil" "os" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/tests" cli "gopkg.in/urfave/cli.v1" ) var stateTestCommand = cli.Command{ Action: stateTestCmd, Name: "statetest", Usage: "executes the given state tests", ArgsUsage: "", } // StatetestResult contains the execution status after running a state test, any // error that might have occurred and a dump of the final state if requested. type StatetestResult struct { Name string `json:"name"` Pass bool `json:"pass"` Fork string `json:"fork"` Error string `json:"error,omitempty"` State *state.Dump `json:"state,omitempty"` } func stateTestCmd(ctx *cli.Context) error { if len(ctx.Args().First()) == 0 { return errors.New("path-to-test argument required") } // Configure the go-ethereum logger glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) log.Root().SetHandler(glogger) // Configure the EVM logger config := &vm.LogConfig{ DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), DisableStack: ctx.GlobalBool(DisableStackFlag.Name), } var ( tracer vm.Tracer debugger *vm.StructLogger ) switch { case ctx.GlobalBool(MachineFlag.Name): tracer = vm.NewJSONLogger(config, os.Stderr) case ctx.GlobalBool(DebugFlag.Name): debugger = vm.NewStructLogger(config) tracer = debugger default: debugger = vm.NewStructLogger(config) } // Load the test content from the input file src, err := ioutil.ReadFile(ctx.Args().First()) if err != nil { return err } var tests map[string]tests.StateTest if err = json.Unmarshal(src, &tests); err != nil { return err } // Iterate over all the tests, run them and aggregate the results cfg := vm.Config{ Tracer: tracer, Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name), } results := make([]StatetestResult, 0, len(tests)) for key, test := range tests { for _, st := range test.Subtests() { // Run the test and aggregate the result result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} - state, err := test.Run(st, cfg, false) + _, state, err := test.Run(st, cfg, false) // print state root for evmlab tracing if ctx.GlobalBool(MachineFlag.Name) && state != nil { fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false)) } if err != nil { // Test failed, mark as so and dump any state to aid debugging result.Pass, result.Error = false, err.Error() if ctx.GlobalBool(DumpFlag.Name) && state != nil { dump := state.RawDump(false, false, true) result.State = &dump } } results = append(results, *result) // Print any structured logs collected if ctx.GlobalBool(DebugFlag.Name) { if debugger != nil { fmt.Fprintln(os.Stderr, "#### TRACE ####") vm.WriteTrace(os.Stderr, debugger.StructLogs()) } } } } out, _ := json.MarshalIndent(results, "", " ") fmt.Println(string(out)) return nil } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 7227e20eb..cd625be0f 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -1,277 +1,277 @@ // Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package tracers import ( "crypto/ecdsa" "crypto/rand" "encoding/json" "io/ioutil" "math/big" "path/filepath" "reflect" "strings" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" ) // To generate a new callTracer test, copy paste the makeTest method below into // a Geth console and call it with a transaction hash you which to export. /* // makeTest generates a callTracer test by running a prestate reassembled and a // call trace run, assembling all the gathered information into a test case. var makeTest = function(tx, rewind) { // Generate the genesis block from the block, transaction and prestate data var block = eth.getBlock(eth.getTransaction(tx).blockHash); var genesis = eth.getBlock(block.parentHash); delete genesis.gasUsed; delete genesis.logsBloom; delete genesis.parentHash; delete genesis.receiptsRoot; delete genesis.sha3Uncles; delete genesis.size; delete genesis.transactions; delete genesis.transactionsRoot; delete genesis.uncles; genesis.gasLimit = genesis.gasLimit.toString(); genesis.number = genesis.number.toString(); genesis.timestamp = genesis.timestamp.toString(); genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind}); for (var key in genesis.alloc) { genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString(); } genesis.config = admin.nodeInfo.protocols.eth.config; // Generate the call trace and produce the test input var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind}); delete result.time; console.log(JSON.stringify({ genesis: genesis, context: { number: block.number.toString(), difficulty: block.difficulty, timestamp: block.timestamp.toString(), gasLimit: block.gasLimit.toString(), miner: block.miner, }, input: eth.getRawTransaction(tx), result: result, }, null, 2)); } */ // callTrace is the result of a callTracer run. type callTrace struct { Type string `json:"type"` From common.Address `json:"from"` To common.Address `json:"to"` Input hexutil.Bytes `json:"input"` Output hexutil.Bytes `json:"output"` Gas *hexutil.Uint64 `json:"gas,omitempty"` GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` Value *hexutil.Big `json:"value,omitempty"` Error string `json:"error,omitempty"` Calls []callTrace `json:"calls,omitempty"` } type callContext struct { Number math.HexOrDecimal64 `json:"number"` Difficulty *math.HexOrDecimal256 `json:"difficulty"` Time math.HexOrDecimal64 `json:"timestamp"` GasLimit math.HexOrDecimal64 `json:"gasLimit"` Miner common.Address `json:"miner"` } // callTracerTest defines a single test to check the call tracer against. type callTracerTest struct { Genesis *core.Genesis `json:"genesis"` Context *callContext `json:"context"` Input string `json:"input"` Result *callTrace `json:"result"` } func TestPrestateTracerCreate2(t *testing.T) { unsignedTx := types.NewTransaction(1, common.HexToAddress("0x00000000000000000000000000000000deadbeef"), new(big.Int), 5000000, big.NewInt(1), []byte{}) privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) if err != nil { t.Fatalf("err %v", err) } signer := types.NewEIP155Signer(big.NewInt(1)) tx, err := types.SignTx(unsignedTx, signer, privateKeyECDSA) if err != nil { t.Fatalf("err %v", err) } /** This comes from one of the test-vectors on the Skinny Create2 - EIP address 0x00000000000000000000000000000000deadbeef salt 0x00000000000000000000000000000000000000000000000000000000cafebabe init_code 0xdeadbeef gas (assuming no mem expansion): 32006 result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7 */ origin, _ := signer.Sender(tx) context := vm.Context{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, Origin: origin, Coinbase: common.Address{}, BlockNumber: new(big.Int).SetUint64(8000000), Time: new(big.Int).SetUint64(5), Difficulty: big.NewInt(0x30000), GasLimit: uint64(6000000), GasPrice: big.NewInt(1), } alloc := core.GenesisAlloc{} // The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns // the address alloc[common.HexToAddress("0x00000000000000000000000000000000deadbeef")] = core.GenesisAccount{ Nonce: 1, Code: hexutil.MustDecode("0x63deadbeef60005263cafebabe6004601c6000F560005260206000F3"), Balance: big.NewInt(1), } alloc[origin] = core.GenesisAccount{ Nonce: 1, Code: []byte{}, Balance: big.NewInt(500000000000000), } - statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) + _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) // Create the tracer, the EVM environment and run it tracer, err := New("prestateTracer") if err != nil { t.Fatalf("failed to create call tracer: %v", err) } evm := vm.NewEVM(context, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) msg, err := tx.AsMessage(signer) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) if _, err = st.TransitionDb(); err != nil { t.Fatalf("failed to execute transaction: %v", err) } // Retrieve the trace result and compare against the etalon res, err := tracer.GetResult() if err != nil { t.Fatalf("failed to retrieve trace result: %v", err) } ret := make(map[string]interface{}) if err := json.Unmarshal(res, &ret); err != nil { t.Fatalf("failed to unmarshal trace result: %v", err) } if _, has := ret["0x60f3f640a8508fc6a86d45df051962668e1e8ac7"]; !has { t.Fatalf("Expected 0x60f3f640a8508fc6a86d45df051962668e1e8ac7 in result") } } // Iterates over all the input-output datasets in the tracer test harness and // runs the JavaScript tracers against them. func TestCallTracer(t *testing.T) { files, err := ioutil.ReadDir("testdata") if err != nil { t.Fatalf("failed to retrieve tracer test suite: %v", err) } for _, file := range files { if !strings.HasPrefix(file.Name(), "call_tracer_") { continue } file := file // capture range variable t.Run(camel(strings.TrimSuffix(strings.TrimPrefix(file.Name(), "call_tracer_"), ".json")), func(t *testing.T) { t.Parallel() // Call tracer test found, read if from disk blob, err := ioutil.ReadFile(filepath.Join("testdata", file.Name())) if err != nil { t.Fatalf("failed to read testcase: %v", err) } test := new(callTracerTest) if err := json.Unmarshal(blob, test); err != nil { t.Fatalf("failed to parse testcase: %v", err) } // Configure a blockchain with the given prestate tx := new(types.Transaction) if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { t.Fatalf("failed to parse testcase input: %v", err) } signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) origin, _ := signer.Sender(tx) context := vm.Context{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, Origin: origin, Coinbase: test.Context.Miner, BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), Time: new(big.Int).SetUint64(uint64(test.Context.Time)), Difficulty: (*big.Int)(test.Context.Difficulty), GasLimit: uint64(test.Context.GasLimit), GasPrice: tx.GasPrice(), } - statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) + _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) // Create the tracer, the EVM environment and run it tracer, err := New("callTracer") if err != nil { t.Fatalf("failed to create call tracer: %v", err) } evm := vm.NewEVM(context, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) msg, err := tx.AsMessage(signer) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) if _, err = st.TransitionDb(); err != nil { t.Fatalf("failed to execute transaction: %v", err) } // Retrieve the trace result and compare against the etalon res, err := tracer.GetResult() if err != nil { t.Fatalf("failed to retrieve trace result: %v", err) } ret := new(callTrace) if err := json.Unmarshal(res, ret); err != nil { t.Fatalf("failed to unmarshal trace result: %v", err) } if !reflect.DeepEqual(ret, test.Result) { t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result) } }) } } diff --git a/tests/state_test.go b/tests/state_test.go index c0a90b3a4..413990147 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -1,117 +1,120 @@ // Copyright 2015 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package tests import ( "bufio" "bytes" "fmt" "reflect" "testing" "github.com/ethereum/go-ethereum/core/vm" ) func TestState(t *testing.T) { t.Parallel() st := new(testMatcher) // Long tests: st.slow(`^stAttackTest/ContractCreationSpam`) st.slow(`^stBadOpcode/badOpcodes`) st.slow(`^stPreCompiledContracts/modexp`) st.slow(`^stQuadraticComplexityTest/`) st.slow(`^stStaticCall/static_Call50000`) st.slow(`^stStaticCall/static_Return50000`) st.slow(`^stStaticCall/static_Call1MB`) st.slow(`^stSystemOperationsTest/CallRecursiveBomb`) st.slow(`^stTransactionTest/Opcodes_TransactionInit`) // Very time consuming st.skipLoad(`^stTimeConsuming/`) // Broken tests: // Expected failures: //st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/Byzantium/0`, "bug in test") //st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/Byzantium/3`, "bug in test") //st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/Constantinople/0`, "bug in test") //st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/Constantinople/3`, "bug in test") //st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/ConstantinopleFix/0`, "bug in test") //st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/ConstantinopleFix/3`, "bug in test") // For Istanbul, older tests were moved into LegacyTests for _, dir := range []string{ stateTestDir, legacyStateTestDir, } { st.walk(t, dir, func(t *testing.T, name string, test *StateTest) { for _, subtest := range test.Subtests() { subtest := subtest key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) name := name + "/" + key t.Run(key+"/trie", func(t *testing.T) { withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { - _, err := test.Run(subtest, vmconfig, false) + _, _, err := test.Run(subtest, vmconfig, false) return st.checkFailure(t, name+"/trie", err) }) }) t.Run(key+"/snap", func(t *testing.T) { withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { - _, err := test.Run(subtest, vmconfig, true) + snaps, statedb, err := test.Run(subtest, vmconfig, true) + if _, err := snaps.Journal(statedb.IntermediateRoot(false)); err != nil { + return err + } return st.checkFailure(t, name+"/snap", err) }) }) } }) } } // Transactions with gasLimit above this value will not get a VM trace on failure. const traceErrorLimit = 400000 func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) { // Use config from command line arguments. config := vm.Config{EVMInterpreter: *testEVM, EWASMInterpreter: *testEWASM} err := test(config) if err == nil { return } // Test failed, re-run with tracing enabled. t.Error(err) if gasLimit > traceErrorLimit { t.Log("gas limit too high for EVM trace") return } buf := new(bytes.Buffer) w := bufio.NewWriter(buf) tracer := vm.NewJSONLogger(&vm.LogConfig{DisableMemory: true}, w) config.Debug, config.Tracer = true, tracer err2 := test(config) if !reflect.DeepEqual(err, err2) { t.Errorf("different error for second run: %v", err2) } w.Flush() if buf.Len() == 0 { t.Log("no EVM operation logs generated") } else { t.Log("EVM operation log:\n" + buf.String()) } //t.Logf("EVM output: 0x%x", tracer.Output()) //t.Logf("EVM error: %v", tracer.Error()) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 4cea4d39e..a8d6fac51 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -1,298 +1,298 @@ // Copyright 2015 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package tests import ( "encoding/hex" "encoding/json" "fmt" "math/big" "strconv" "strings" "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" ) // StateTest checks transaction processing without block context. // See https://github.com/ethereum/EIPs/issues/176 for the test format specification. type StateTest struct { json stJSON } // StateSubtest selects a specific configuration of a General State Test. type StateSubtest struct { Fork string Index int } func (t *StateTest) UnmarshalJSON(in []byte) error { return json.Unmarshal(in, &t.json) } type stJSON struct { Env stEnv `json:"env"` Pre core.GenesisAlloc `json:"pre"` Tx stTransaction `json:"transaction"` Out hexutil.Bytes `json:"out"` Post map[string][]stPostState `json:"post"` } type stPostState struct { Root common.UnprefixedHash `json:"hash"` Logs common.UnprefixedHash `json:"logs"` Indexes struct { Data int `json:"data"` Gas int `json:"gas"` Value int `json:"value"` } } //go:generate gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go type stEnv struct { Coinbase common.Address `json:"currentCoinbase" gencodec:"required"` Difficulty *big.Int `json:"currentDifficulty" gencodec:"required"` GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` Number uint64 `json:"currentNumber" gencodec:"required"` Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` } type stEnvMarshaling struct { Coinbase common.UnprefixedAddress Difficulty *math.HexOrDecimal256 GasLimit math.HexOrDecimal64 Number math.HexOrDecimal64 Timestamp math.HexOrDecimal64 } //go:generate gencodec -type stTransaction -field-override stTransactionMarshaling -out gen_sttransaction.go type stTransaction struct { GasPrice *big.Int `json:"gasPrice"` Nonce uint64 `json:"nonce"` To string `json:"to"` Data []string `json:"data"` GasLimit []uint64 `json:"gasLimit"` Value []string `json:"value"` PrivateKey []byte `json:"secretKey"` } type stTransactionMarshaling struct { GasPrice *math.HexOrDecimal256 Nonce math.HexOrDecimal64 GasLimit []math.HexOrDecimal64 PrivateKey hexutil.Bytes } // getVMConfig takes a fork definition and returns a chain config. // The fork definition can be // - a plain forkname, e.g. `Byzantium`, // - a fork basename, and a list of EIPs to enable; e.g. `Byzantium+1884+1283`. func getVMConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) { var ( splitForks = strings.Split(forkString, "+") ok bool baseName, eipsStrings = splitForks[0], splitForks[1:] ) if baseConfig, ok = Forks[baseName]; !ok { return nil, nil, UnsupportedForkError{baseName} } for _, eip := range eipsStrings { if eipNum, err := strconv.Atoi(eip); err != nil { return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) } else { eips = append(eips, eipNum) } } return baseConfig, eips, nil } // Subtests returns all valid subtests of the test. func (t *StateTest) Subtests() []StateSubtest { var sub []StateSubtest for fork, pss := range t.json.Post { for i := range pss { sub = append(sub, StateSubtest{fork, i}) } } return sub } // Run executes a specific subtest and verifies the post-state and logs -func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*state.StateDB, error) { - statedb, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter) +func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*snapshot.Tree, *state.StateDB, error) { + snaps, statedb, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter) if err != nil { - return statedb, err + return snaps, statedb, err } post := t.json.Post[subtest.Fork][subtest.Index] // N.B: We need to do this in a two-step process, because the first Commit takes care // of suicides, and we need to touch the coinbase _after_ it has potentially suicided. if root != common.Hash(post.Root) { - return statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) + return snaps, statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) } if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) { - return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) + return snaps, statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) } - return statedb, nil + return snaps, statedb, nil } // RunNoVerify runs a specific subtest and returns the statedb and post-state root -func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*state.StateDB, common.Hash, error) { +func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*snapshot.Tree, *state.StateDB, common.Hash, error) { config, eips, err := getVMConfig(subtest.Fork) if err != nil { - return nil, common.Hash{}, UnsupportedForkError{subtest.Fork} + return nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork} } vmconfig.ExtraEips = eips block := t.genesis(config).ToBlock(nil) - statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter) + snaps, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter) post := t.json.Post[subtest.Fork][subtest.Index] msg, err := t.json.Tx.toMessage(post) if err != nil { - return nil, common.Hash{}, err + return nil, nil, common.Hash{}, err } context := core.NewEVMContext(msg, block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash evm := vm.NewEVM(context, statedb, config, vmconfig) gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) snapshot := statedb.Snapshot() if _, err := core.ApplyMessage(evm, msg, gaspool); err != nil { statedb.RevertToSnapshot(snapshot) } // Commit block statedb.Commit(config.IsEIP158(block.Number())) // Add 0-value mining reward. This only makes a difference in the cases // where // - the coinbase suicided, or // - there are only 'bad' transactions, which aren't executed. In those cases, // the coinbase gets no txfee, so isn't created, and thus needs to be touched statedb.AddBalance(block.Coinbase(), new(big.Int)) // And _now_ get the state root root := statedb.IntermediateRoot(config.IsEIP158(block.Number())) - return statedb, root, nil + return snaps, statedb, root, nil } func (t *StateTest) gasLimit(subtest StateSubtest) uint64 { return t.json.Tx.GasLimit[t.json.Post[subtest.Fork][subtest.Index].Indexes.Gas] } -func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool) *state.StateDB { +func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool) (*snapshot.Tree, *state.StateDB) { sdb := state.NewDatabase(db) statedb, _ := state.New(common.Hash{}, sdb, nil) for addr, a := range accounts { statedb.SetCode(addr, a.Code) statedb.SetNonce(addr, a.Nonce) statedb.SetBalance(addr, a.Balance) for k, v := range a.Storage { statedb.SetState(addr, k, v) } } // Commit and re-open to start with a clean state. root, _ := statedb.Commit(false) var snaps *snapshot.Tree if snapshotter { snaps = snapshot.New(db, sdb.TrieDB(), 1, root, false) } statedb, _ = state.New(root, sdb, snaps) - return statedb + return snaps, statedb } func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis { return &core.Genesis{ Config: config, Coinbase: t.json.Env.Coinbase, Difficulty: t.json.Env.Difficulty, GasLimit: t.json.Env.GasLimit, Number: t.json.Env.Number, Timestamp: t.json.Env.Timestamp, Alloc: t.json.Pre, } } func (tx *stTransaction) toMessage(ps stPostState) (core.Message, error) { // Derive sender from private key if present. var from common.Address if len(tx.PrivateKey) > 0 { key, err := crypto.ToECDSA(tx.PrivateKey) if err != nil { return nil, fmt.Errorf("invalid private key: %v", err) } from = crypto.PubkeyToAddress(key.PublicKey) } // Parse recipient if present. var to *common.Address if tx.To != "" { to = new(common.Address) if err := to.UnmarshalText([]byte(tx.To)); err != nil { return nil, fmt.Errorf("invalid to address: %v", err) } } // Get values specific to this post state. if ps.Indexes.Data > len(tx.Data) { return nil, fmt.Errorf("tx data index %d out of bounds", ps.Indexes.Data) } if ps.Indexes.Value > len(tx.Value) { return nil, fmt.Errorf("tx value index %d out of bounds", ps.Indexes.Value) } if ps.Indexes.Gas > len(tx.GasLimit) { return nil, fmt.Errorf("tx gas limit index %d out of bounds", ps.Indexes.Gas) } dataHex := tx.Data[ps.Indexes.Data] valueHex := tx.Value[ps.Indexes.Value] gasLimit := tx.GasLimit[ps.Indexes.Gas] // Value, Data hex encoding is messy: https://github.com/ethereum/tests/issues/203 value := new(big.Int) if valueHex != "0x" { v, ok := math.ParseBig256(valueHex) if !ok { return nil, fmt.Errorf("invalid tx value %q", valueHex) } value = v } data, err := hex.DecodeString(strings.TrimPrefix(dataHex, "0x")) if err != nil { return nil, fmt.Errorf("invalid tx data %q", dataHex) } msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, true) return msg, nil } func rlpHash(x interface{}) (h common.Hash) { hw := sha3.NewLegacyKeccak256() rlp.Encode(hw, x) hw.Sum(h[:0]) return h } diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index 9acbe59f4..ad124b7b2 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -1,151 +1,159 @@ // Copyright 2015 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package tests import ( "bytes" "encoding/json" "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" ) // VMTest checks EVM execution without block or transaction context. // See https://github.com/ethereum/tests/wiki/VM-Tests for the test format specification. type VMTest struct { json vmJSON } func (t *VMTest) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &t.json) } type vmJSON struct { Env stEnv `json:"env"` Exec vmExec `json:"exec"` Logs common.UnprefixedHash `json:"logs"` GasRemaining *math.HexOrDecimal64 `json:"gas"` Out hexutil.Bytes `json:"out"` Pre core.GenesisAlloc `json:"pre"` Post core.GenesisAlloc `json:"post"` PostStateRoot common.Hash `json:"postStateRoot"` } //go:generate gencodec -type vmExec -field-override vmExecMarshaling -out gen_vmexec.go type vmExec struct { Address common.Address `json:"address" gencodec:"required"` Caller common.Address `json:"caller" gencodec:"required"` Origin common.Address `json:"origin" gencodec:"required"` Code []byte `json:"code" gencodec:"required"` Data []byte `json:"data" gencodec:"required"` Value *big.Int `json:"value" gencodec:"required"` GasLimit uint64 `json:"gas" gencodec:"required"` GasPrice *big.Int `json:"gasPrice" gencodec:"required"` } type vmExecMarshaling struct { Address common.UnprefixedAddress Caller common.UnprefixedAddress Origin common.UnprefixedAddress Code hexutil.Bytes Data hexutil.Bytes Value *math.HexOrDecimal256 GasLimit math.HexOrDecimal64 GasPrice *math.HexOrDecimal256 } func (t *VMTest) Run(vmconfig vm.Config, snapshotter bool) error { - statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter) + snaps, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter) + if snapshotter { + preRoot := statedb.IntermediateRoot(false) + defer func() { + if _, err := snaps.Journal(preRoot); err != nil { + panic(err) + } + }() + } ret, gasRemaining, err := t.exec(statedb, vmconfig) if t.json.GasRemaining == nil { if err == nil { return fmt.Errorf("gas unspecified (indicating an error), but VM returned no error") } if gasRemaining > 0 { return fmt.Errorf("gas unspecified (indicating an error), but VM returned gas remaining > 0") } return nil } // Test declares gas, expecting outputs to match. if !bytes.Equal(ret, t.json.Out) { return fmt.Errorf("return data mismatch: got %x, want %x", ret, t.json.Out) } if gasRemaining != uint64(*t.json.GasRemaining) { return fmt.Errorf("remaining gas %v, want %v", gasRemaining, *t.json.GasRemaining) } for addr, account := range t.json.Post { for k, wantV := range account.Storage { if haveV := statedb.GetState(addr, k); haveV != wantV { return fmt.Errorf("wrong storage value at %x:\n got %x\n want %x", k, haveV, wantV) } } } // if root := statedb.IntermediateRoot(false); root != t.json.PostStateRoot { // return fmt.Errorf("post state root mismatch, got %x, want %x", root, t.json.PostStateRoot) // } if logs := rlpHash(statedb.Logs()); logs != common.Hash(t.json.Logs) { return fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, t.json.Logs) } return nil } func (t *VMTest) exec(statedb *state.StateDB, vmconfig vm.Config) ([]byte, uint64, error) { evm := t.newEVM(statedb, vmconfig) e := t.json.Exec return evm.Call(vm.AccountRef(e.Caller), e.Address, e.Data, e.GasLimit, e.Value) } func (t *VMTest) newEVM(statedb *state.StateDB, vmconfig vm.Config) *vm.EVM { initialCall := true canTransfer := func(db vm.StateDB, address common.Address, amount *big.Int) bool { if initialCall { initialCall = false return true } return core.CanTransfer(db, address, amount) } transfer := func(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {} context := vm.Context{ CanTransfer: canTransfer, Transfer: transfer, GetHash: vmTestBlockHash, Origin: t.json.Exec.Origin, Coinbase: t.json.Env.Coinbase, BlockNumber: new(big.Int).SetUint64(t.json.Env.Number), Time: new(big.Int).SetUint64(t.json.Env.Timestamp), GasLimit: t.json.Env.GasLimit, Difficulty: t.json.Env.Difficulty, GasPrice: t.json.Exec.GasPrice, } vmconfig.NoRecursion = true return vm.NewEVM(context, statedb, params.MainnetChainConfig, vmconfig) } func vmTestBlockHash(n uint64) common.Hash { return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String()))) }