diff options
author | Jeffrey Wilcke <jeffrey@ethereum.org> | 2015-06-20 20:32:33 +0800 |
---|---|---|
committer | Jeffrey Wilcke <jeffrey@ethereum.org> | 2015-06-20 20:32:33 +0800 |
commit | 9c69c051ba010e2323d665d2ef273773f9dd7aa3 (patch) | |
tree | e0d705729778ad60b70c6b3de9d0c3d22f9a0fa7 | |
parent | 53e042f0c47242afa3e13cfebb35becdacdc69c0 (diff) | |
parent | d1e589289c56140144241a245e1756dbdc7280a0 (diff) | |
download | go-tangerine-9c69c051ba010e2323d665d2ef273773f9dd7aa3.tar.gz go-tangerine-9c69c051ba010e2323d665d2ef273773f9dd7aa3.tar.zst go-tangerine-9c69c051ba010e2323d665d2ef273773f9dd7aa3.zip |
Merge pull request #1236 from tgerring/ethtest
ethtest improvements
-rwxr-xr-x | build/test-global-coverage.sh | 2 | ||||
-rw-r--r-- | cmd/ethtest/main.go | 310 | ||||
-rw-r--r-- | cmd/geth/blocktestcmd.go | 1 | ||||
-rw-r--r-- | tests/block_test.go | 111 | ||||
-rw-r--r-- | tests/block_test_util.go | 161 | ||||
-rw-r--r-- | tests/helper/common.go | 11 | ||||
-rw-r--r-- | tests/helper/init.go | 16 | ||||
-rw-r--r-- | tests/helper/readers.go | 42 | ||||
-rw-r--r-- | tests/helper/trie.go | 31 | ||||
-rw-r--r-- | tests/init.go | 82 | ||||
-rw-r--r-- | tests/state_test.go | 134 | ||||
-rw-r--r-- | tests/state_test_util.go | 181 | ||||
-rw-r--r-- | tests/transaction_test.go | 26 | ||||
-rw-r--r-- | tests/transaction_test_util.go | 68 | ||||
-rw-r--r-- | tests/util.go (renamed from tests/helper/vm.go) | 160 | ||||
-rw-r--r-- | tests/vm/.ethtest | 0 | ||||
-rw-r--r-- | tests/vm/gh_test.go | 388 | ||||
-rw-r--r-- | tests/vm/nowarn.go | 3 | ||||
-rw-r--r-- | tests/vm_test.go | 107 | ||||
-rw-r--r-- | tests/vm_test_util.go | 170 |
20 files changed, 1138 insertions, 866 deletions
diff --git a/build/test-global-coverage.sh b/build/test-global-coverage.sh index 417c829f4..5bb233a31 100755 --- a/build/test-global-coverage.sh +++ b/build/test-global-coverage.sh @@ -16,7 +16,7 @@ for pkg in $(go list ./...); do # drop the namespace prefix. dir=${pkg##github.com/ethereum/go-ethereum/} - if [[ $dir != "tests/vm" ]]; then + if [[ $dir != "tests" ]]; then go test -covermode=count -coverprofile=$dir/profile.tmp $pkg fi if [[ -f $dir/profile.tmp ]]; then diff --git a/cmd/ethtest/main.go b/cmd/ethtest/main.go index c2c94d6c4..278d2133f 100644 --- a/cmd/ethtest/main.go +++ b/cmd/ethtest/main.go @@ -17,217 +17,201 @@ /** * @authors: * Jeffrey Wilcke <i@jev.io> + * Taylor Gerring <taylor.gerring@gmail.com> */ package main import ( - "bytes" - "encoding/json" + "fmt" "io" "io/ioutil" - "log" - "math/big" "os" - "strconv" + "path/filepath" "strings" - "github.com/ethereum/go-ethereum/common" - "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/ethdb" - "github.com/ethereum/go-ethereum/logger" + "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/tests/helper" + "github.com/ethereum/go-ethereum/tests" ) -type Log struct { - AddressF string `json:"address"` - DataF string `json:"data"` - TopicsF []string `json:"topics"` - BloomF string `json:"bloom"` -} +var ( + continueOnError = false + testExtension = ".json" + defaultTest = "all" + defaultDir = "." + allTests = []string{"BlockTests", "StateTests", "TransactionTests", "VMTests"} + skipTests = []string{} + + TestFlag = cli.StringFlag{ + Name: "test", + Usage: "Test type (string): VMTests, TransactionTests, StateTests, BlockTests", + Value: defaultTest, + } + FileFlag = cli.StringFlag{ + Name: "file", + Usage: "Test file or directory. Directories are searched for .json files 1 level deep", + Value: defaultDir, + EnvVar: "ETHEREUM_TEST_PATH", + } + ContinueOnErrorFlag = cli.BoolFlag{ + Name: "continue", + Usage: "Continue running tests on error (true) or [default] exit immediately (false)", + } + ReadStdInFlag = cli.BoolFlag{ + Name: "stdin", + Usage: "Accept input from stdin instead of reading from file", + } + SkipTestsFlag = cli.StringFlag{ + Name: "skip", + Usage: "Tests names to skip", + } +) -func (self Log) Address() []byte { return common.Hex2Bytes(self.AddressF) } -func (self Log) Data() []byte { return common.Hex2Bytes(self.DataF) } -func (self Log) RlpData() interface{} { return nil } -func (self Log) Topics() [][]byte { - t := make([][]byte, len(self.TopicsF)) - for i, topic := range self.TopicsF { - t[i] = common.Hex2Bytes(topic) +func runTestWithReader(test string, r io.Reader) error { + glog.Infoln("runTest", test) + var err error + switch strings.ToLower(test) { + case "bk", "block", "blocktest", "blockchaintest", "blocktests", "blockchaintests": + err = tests.RunBlockTestWithReader(r, skipTests) + case "st", "state", "statetest", "statetests": + err = tests.RunStateTestWithReader(r, skipTests) + case "tx", "transactiontest", "transactiontests": + err = tests.RunTransactionTestsWithReader(r, skipTests) + case "vm", "vmtest", "vmtests": + err = tests.RunVmTestWithReader(r, skipTests) + default: + err = fmt.Errorf("Invalid test type specified: %v", test) } - return t -} -type Account struct { - Balance string - Code string - Nonce string - Storage map[string]string -} + if err != nil { + return err + } -func StateObjectFromAccount(db common.Database, addr string, account Account) *state.StateObject { - obj := state.NewStateObject(common.HexToAddress(addr), db) - obj.SetBalance(common.Big(account.Balance)) + return nil +} - if common.IsHex(account.Code) { - account.Code = account.Code[2:] +func getFiles(path string) ([]string, error) { + glog.Infoln("getFiles", path) + var files []string + f, err := os.Open(path) + if err != nil { + return nil, err } - obj.SetCode(common.Hex2Bytes(account.Code)) - obj.SetNonce(common.Big(account.Nonce).Uint64()) + defer f.Close() - return obj -} + fi, err := f.Stat() + if err != nil { + return nil, err + } -type VmTest struct { - Callcreates interface{} - Env Env - Exec map[string]string - Transaction map[string]string - Logs []Log - Gas string - Out string - Post map[string]Account - Pre map[string]Account - PostStateRoot string -} + switch mode := fi.Mode(); { + case mode.IsDir(): + fi, _ := ioutil.ReadDir(path) + files = make([]string, len(fi)) + for i, v := range fi { + // only go 1 depth and leave directory entires blank + if !v.IsDir() && v.Name()[len(v.Name())-len(testExtension):len(v.Name())] == testExtension { + files[i] = filepath.Join(path, v.Name()) + glog.Infoln("Found file", files[i]) + } + } + case mode.IsRegular(): + files = make([]string, 1) + files[0] = path + } -type Env struct { - CurrentCoinbase string - CurrentDifficulty string - CurrentGasLimit string - CurrentNumber string - CurrentTimestamp interface{} - PreviousHash string + return files, nil } -func RunVmTest(r io.Reader) (failed int) { - tests := make(map[string]VmTest) +func runSuite(test, file string) { + var tests []string - data, _ := ioutil.ReadAll(r) - err := json.Unmarshal(data, &tests) - if err != nil { - log.Fatalln(err) + if test == defaultTest { + tests = allTests + } else { + tests = []string{test} } - vm.Debug = true - glog.SetV(4) - glog.SetToStderr(true) - for name, test := range tests { - db, _ := ethdb.NewMemDatabase() - statedb := state.New(common.Hash{}, db) - for addr, account := range test.Pre { - obj := StateObjectFromAccount(db, addr, account) - statedb.SetStateObject(obj) - } + for _, curTest := range tests { + glog.Infoln("runSuite", curTest, file) + var err error + var files []string + if test == defaultTest { + files, err = getFiles(filepath.Join(file, curTest)) - env := make(map[string]string) - env["currentCoinbase"] = test.Env.CurrentCoinbase - env["currentDifficulty"] = test.Env.CurrentDifficulty - env["currentGasLimit"] = test.Env.CurrentGasLimit - env["currentNumber"] = test.Env.CurrentNumber - env["previousHash"] = test.Env.PreviousHash - if n, ok := test.Env.CurrentTimestamp.(float64); ok { - env["currentTimestamp"] = strconv.Itoa(int(n)) } else { - env["currentTimestamp"] = test.Env.CurrentTimestamp.(string) + files, err = getFiles(file) } - - ret, logs, _, _ := helper.RunState(statedb, env, test.Transaction) - statedb.Sync() - - rexp := helper.FromHex(test.Out) - if bytes.Compare(rexp, ret) != 0 { - glog.V(logger.Info).Infof("%s's return failed. Expected %x, got %x\n", name, rexp, ret) - failed = 1 + if err != nil { + glog.Fatalln(err) } - for addr, account := range test.Post { - obj := statedb.GetStateObject(common.HexToAddress(addr)) - if obj == nil { + if len(files) == 0 { + glog.Warningln("No files matched path") + } + for _, curFile := range files { + // Skip blank entries + if len(curFile) == 0 { continue } - if len(test.Exec) == 0 { - if obj.Balance().Cmp(common.Big(account.Balance)) != 0 { - glog.V(logger.Info).Infof("%s's : (%x) balance failed. Expected %v, got %v => %v\n", name, obj.Address().Bytes()[:4], account.Balance, obj.Balance(), new(big.Int).Sub(common.Big(account.Balance), obj.Balance())) - failed = 1 - } + r, err := os.Open(curFile) + if err != nil { + glog.Fatalln(err) } - - for addr, value := range account.Storage { - v := obj.GetState(common.HexToHash(addr)).Bytes() - vexp := helper.FromHex(value) - - if bytes.Compare(v, vexp) != 0 { - glog.V(logger.Info).Infof("%s's : (%x: %s) storage failed. Expected %x, got %x (%v %v)\n", name, obj.Address().Bytes()[0:4], addr, vexp, v, common.BigD(vexp), common.BigD(v)) - failed = 1 + defer r.Close() + + err = runTestWithReader(curTest, r) + if err != nil { + if continueOnError { + glog.Errorln(err) + } else { + glog.Fatalln(err) } } - } - statedb.Sync() - //if !bytes.Equal(common.Hex2Bytes(test.PostStateRoot), statedb.Root()) { - if common.HexToHash(test.PostStateRoot) != statedb.Root() { - glog.V(logger.Info).Infof("%s's : Post state root failed. Expected %s, got %x", name, test.PostStateRoot, statedb.Root()) - failed = 1 } + } +} - if len(test.Logs) > 0 { - if len(test.Logs) != len(logs) { - glog.V(logger.Info).Infof("log length failed. Expected %d, got %d", len(test.Logs), len(logs)) - failed = 1 - } else { - for i, log := range test.Logs { - if common.HexToAddress(log.AddressF) != logs[i].Address { - glog.V(logger.Info).Infof("'%s' log address failed. Expected %v got %x", name, log.AddressF, logs[i].Address) - failed = 1 - } - - if !bytes.Equal(logs[i].Data, helper.FromHex(log.DataF)) { - glog.V(logger.Info).Infof("'%s' log data failed. Expected %v got %x", name, log.DataF, logs[i].Data) - failed = 1 - } - - if len(log.TopicsF) != len(logs[i].Topics) { - glog.V(logger.Info).Infof("'%s' log topics length failed. Expected %d got %d", name, len(log.TopicsF), logs[i].Topics) - failed = 1 - } else { - for j, topic := range log.TopicsF { - if common.HexToHash(topic) != logs[i].Topics[j] { - glog.V(logger.Info).Infof("'%s' log topic[%d] failed. Expected %v got %x", name, j, topic, logs[i].Topics[j]) - failed = 1 - } - } - } - genBloom := common.LeftPadBytes(types.LogsBloom(state.Logs{logs[i]}).Bytes(), 256) - - if !bytes.Equal(genBloom, common.Hex2Bytes(log.BloomF)) { - glog.V(logger.Info).Infof("'%s' bloom failed.", name) - failed = 1 - } - } - } - } +func setupApp(c *cli.Context) { + flagTest := c.GlobalString(TestFlag.Name) + flagFile := c.GlobalString(FileFlag.Name) + continueOnError = c.GlobalBool(ContinueOnErrorFlag.Name) + useStdIn := c.GlobalBool(ReadStdInFlag.Name) + skipTests = strings.Split(c.GlobalString(SkipTestsFlag.Name), " ") - if failed == 1 { - glog.V(logger.Info).Infoln(string(statedb.Dump())) + if !useStdIn { + runSuite(flagTest, flagFile) + } else { + if err := runTestWithReader(flagTest, os.Stdin); err != nil { + glog.Fatalln(err) } - logger.Flush() } - - return } func main() { - helper.Logger.SetLogLevel(5) - vm.Debug = true + glog.SetToStderr(true) - if len(os.Args) > 1 { - os.Exit(RunVmTest(strings.NewReader(os.Args[1]))) - } else { - os.Exit(RunVmTest(os.Stdin)) + app := cli.NewApp() + app.Name = "ethtest" + app.Usage = "go-ethereum test interface" + app.Action = setupApp + app.Version = "0.2.0" + app.Author = "go-ethereum team" + + app.Flags = []cli.Flag{ + TestFlag, + FileFlag, + ContinueOnErrorFlag, + ReadStdInFlag, + SkipTestsFlag, } + + if err := app.Run(os.Args); err != nil { + glog.Fatalln(err) + } + } diff --git a/cmd/geth/blocktestcmd.go b/cmd/geth/blocktestcmd.go index ffea4400e..116eec2b3 100644 --- a/cmd/geth/blocktestcmd.go +++ b/cmd/geth/blocktestcmd.go @@ -86,6 +86,7 @@ func runBlockTest(ctx *cli.Context) { } func runOneBlockTest(ctx *cli.Context, test *tests.BlockTest) (*eth.Ethereum, error) { + // TODO remove in favor of logic contained in tests package cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx) cfg.NewDB = func(path string) (common.Database, error) { return ethdb.NewMemDatabase() } cfg.MaxPeers = 0 // disable network diff --git a/tests/block_test.go b/tests/block_test.go index d5136efce..bdf983786 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -3,112 +3,71 @@ package tests import ( "path/filepath" "testing" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/ethdb" ) -// TODO: refactor test setup & execution to better align with vm and tx tests func TestBcValidBlockTests(t *testing.T) { - // SimpleTx3 genesis block does not validate against calculated state root - // as of 2015-06-09. unskip once working /Gustav - runBlockTestsInFile("files/BlockTests/bcValidBlockTest.json", []string{"SimpleTx3"}, t) + err := RunBlockTest(filepath.Join(blockTestDir, "bcValidBlockTest.json"), BlockSkipTests) + if err != nil { + t.Fatal(err) + } } func TestBcUncleTests(t *testing.T) { - runBlockTestsInFile("files/BlockTests/bcUncleTest.json", []string{}, t) - runBlockTestsInFile("files/BlockTests/bcBruncleTest.json", []string{}, t) + err := RunBlockTest(filepath.Join(blockTestDir, "bcUncleTest.json"), BlockSkipTests) + if err != nil { + t.Fatal(err) + } + err = RunBlockTest(filepath.Join(blockTestDir, "bcBruncleTest.json"), BlockSkipTests) + if err != nil { + t.Fatal(err) + } } func TestBcUncleHeaderValidityTests(t *testing.T) { - runBlockTestsInFile("files/BlockTests/bcUncleHeaderValiditiy.json", []string{}, t) + err := RunBlockTest(filepath.Join(blockTestDir, "bcUncleHeaderValiditiy.json"), BlockSkipTests) + if err != nil { + t.Fatal(err) + } } func TestBcInvalidHeaderTests(t *testing.T) { - runBlockTestsInFile("files/BlockTests/bcInvalidHeaderTest.json", []string{}, t) -} - -func TestBcInvalidRLPTests(t *testing.T) { - runBlockTestsInFile("files/BlockTests/bcInvalidRLPTest.json", []string{}, t) -} - -func TestBcRPCAPITests(t *testing.T) { - runBlockTestsInFile("files/BlockTests/bcRPC_API_Test.json", []string{}, t) -} - -func TestBcForkBlockTests(t *testing.T) { - runBlockTestsInFile("files/BlockTests/bcForkBlockTest.json", []string{}, t) -} - -func TestBcTotalDifficulty(t *testing.T) { - runBlockTestsInFile("files/BlockTests/bcTotalDifficultyTest.json", []string{}, t) -} - -func TestBcWallet(t *testing.T) { - runBlockTestsInFile("files/BlockTests/bcWalletTest.json", []string{}, t) -} - -func runBlockTestsInFile(filepath string, snafus []string, t *testing.T) { - bt, err := LoadBlockTests(filepath) + err := RunBlockTest(filepath.Join(blockTestDir, "bcInvalidHeaderTest.json"), BlockSkipTests) if err != nil { t.Fatal(err) } - - notWorking := make(map[string]bool, 100) - for _, name := range snafus { - notWorking[name] = true - } - - for name, test := range bt { - if !notWorking[name] { - runBlockTest(name, test, t) - } - } } -func runBlockTest(name string, test *BlockTest, t *testing.T) { - cfg := testEthConfig() - ethereum, err := eth.New(cfg) +func TestBcInvalidRLPTests(t *testing.T) { + err := RunBlockTest(filepath.Join(blockTestDir, "bcInvalidRLPTest.json"), BlockSkipTests) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } +} - err = ethereum.Start() +func TestBcRPCAPITests(t *testing.T) { + err := RunBlockTest(filepath.Join(blockTestDir, "bcRPC_API_Test.json"), BlockSkipTests) if err != nil { - t.Fatalf("%v", err) + t.Fatal(err) } +} - // import the genesis block - ethereum.ResetWithGenesisBlock(test.Genesis) - - // import pre accounts - statedb, err := test.InsertPreState(ethereum) +func TestBcForkBlockTests(t *testing.T) { + err := RunBlockTest(filepath.Join(blockTestDir, "bcForkBlockTest.json"), BlockSkipTests) if err != nil { - t.Fatalf("InsertPreState: %v", err) + t.Fatal(err) } +} - err = test.TryBlocksInsert(ethereum.ChainManager()) +func TestBcTotalDifficulty(t *testing.T) { + err := RunBlockTest(filepath.Join(blockTestDir, "bcTotalDifficultyTest.json"), BlockSkipTests) if err != nil { t.Fatal(err) } - - if err = test.ValidatePostState(statedb); err != nil { - t.Fatal("post state validation failed: %v", err) - } - t.Log("Test passed: ", name) } -func testEthConfig() *eth.Config { - ks := crypto.NewKeyStorePassphrase(filepath.Join(common.DefaultDataDir(), "keystore")) - - return ð.Config{ - DataDir: common.DefaultDataDir(), - Verbosity: 5, - Etherbase: "primary", - AccountManager: accounts.NewManager(ks), - NewDB: func(path string) (common.Database, error) { return ethdb.NewMemDatabase() }, +func TestBcWallet(t *testing.T) { + err := RunBlockTest(filepath.Join(blockTestDir, "bcWalletTest.json"), BlockSkipTests) + if err != nil { + t.Fatal(err) } } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 200fcbd59..5fdc6402e 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -3,21 +3,24 @@ package tests import ( "bytes" "encoding/hex" - "encoding/json" "fmt" - "io/ioutil" + "io" "math/big" + "path/filepath" "runtime" "strconv" "strings" "time" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" ) @@ -83,20 +86,104 @@ type btTransaction struct { Value string } -// LoadBlockTests loads a block test JSON file. -func LoadBlockTests(file string) (map[string]*BlockTest, error) { - bt := make(map[string]*btJSON) - if err := LoadJSON(file, &bt); err != nil { - return nil, err +func RunBlockTestWithReader(r io.Reader, skipTests []string) error { + btjs := make(map[string]*btJSON) + if err := readJson(r, &btjs); err != nil { + return err } - out := make(map[string]*BlockTest) - for name, in := range bt { - var err error - if out[name], err = convertTest(in); err != nil { - return out, fmt.Errorf("bad test %q: %v", name, err) + + bt, err := convertBlockTests(btjs) + if err != nil { + return err + } + + if err := runBlockTests(bt, skipTests); err != nil { + return err + } + return nil +} + +func RunBlockTest(file string, skipTests []string) error { + btjs := make(map[string]*btJSON) + if err := readJsonFile(file, &btjs); err != nil { + return err + } + + bt, err := convertBlockTests(btjs) + if err != nil { + return err + } + if err := runBlockTests(bt, skipTests); err != nil { + return err + } + return nil +} + +func runBlockTests(bt map[string]*BlockTest, skipTests []string) error { + skipTest := make(map[string]bool, len(skipTests)) + for _, name := range skipTests { + skipTest[name] = true + } + + for name, test := range bt { + // if the test should be skipped, return + if skipTest[name] { + glog.Infoln("Skipping block test", name) + return nil } + + // test the block + if err := runBlockTest(test); err != nil { + return err + } + glog.Infoln("Block test passed: ", name) + + } + return nil + +} +func runBlockTest(test *BlockTest) error { + cfg := test.makeEthConfig() + ethereum, err := eth.New(cfg) + if err != nil { + return err + } + + err = ethereum.Start() + if err != nil { + return err + } + + // import the genesis block + ethereum.ResetWithGenesisBlock(test.Genesis) + + // import pre accounts + statedb, err := test.InsertPreState(ethereum) + if err != nil { + return fmt.Errorf("InsertPreState: %v", err) + } + + err = test.TryBlocksInsert(ethereum.ChainManager()) + if err != nil { + return err + } + + if err = test.ValidatePostState(statedb); err != nil { + return fmt.Errorf("post state validation failed: %v", err) + } + return nil +} + +func (test *BlockTest) makeEthConfig() *eth.Config { + ks := crypto.NewKeyStorePassphrase(filepath.Join(common.DefaultDataDir(), "keystore")) + + return ð.Config{ + DataDir: common.DefaultDataDir(), + Verbosity: 5, + Etherbase: "primary", + AccountManager: accounts.NewManager(ks), + NewDB: func(path string) (common.Database, error) { return ethdb.NewMemDatabase() }, } - return out, nil } // InsertPreState populates the given database with the genesis @@ -173,7 +260,7 @@ func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error { if b.BlockHeader == nil { return fmt.Errorf("Block insertion should have failed") } - err = validateBlockHeader(b.BlockHeader, cb.Header()) + err = t.validateBlockHeader(b.BlockHeader, cb.Header()) if err != nil { return fmt.Errorf("Block header validation failed: ", err) } @@ -181,7 +268,7 @@ func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error { return nil } -func validateBlockHeader(h *btHeader, h2 *types.Header) error { +func (s *BlockTest) validateBlockHeader(h *btHeader, h2 *types.Header) error { expectedBloom := mustConvertBytes(h.Bloom) if !bytes.Equal(expectedBloom, h2.Bloom.Bytes()) { return fmt.Errorf("Bloom: expected: %v, decoded: %v", expectedBloom, h2.Bloom.Bytes()) @@ -284,7 +371,18 @@ func (t *BlockTest) ValidatePostState(statedb *state.StateDB) error { return nil } -func convertTest(in *btJSON) (out *BlockTest, err error) { +func convertBlockTests(in map[string]*btJSON) (map[string]*BlockTest, error) { + out := make(map[string]*BlockTest) + for name, test := range in { + var err error + if out[name], err = convertBlockTest(test); err != nil { + return out, fmt.Errorf("bad test %q: %v", name, err) + } + } + return out, nil +} + +func convertBlockTest(in *btJSON) (out *BlockTest, err error) { // the conversion handles errors by catching panics. // you might consider this ugly, but the alternative (passing errors) // would be much harder to read. @@ -392,34 +490,13 @@ func mustConvertUint(in string, base int) uint64 { return out } -// LoadJSON reads the given file and unmarshals its content. -func LoadJSON(file string, val interface{}) error { - content, err := ioutil.ReadFile(file) - if err != nil { - return err - } - if err := json.Unmarshal(content, val); err != nil { - if syntaxerr, ok := err.(*json.SyntaxError); ok { - line := findLine(content, syntaxerr.Offset) - return fmt.Errorf("JSON syntax error at %v:%v: %v", file, line, err) - } - return fmt.Errorf("JSON unmarshal error in %v: %v", file, err) +func LoadBlockTests(file string) (map[string]*BlockTest, error) { + btjs := make(map[string]*btJSON) + if err := readJsonFile(file, &btjs); err != nil { + return nil, err } - return nil -} -// findLine returns the line number for the given offset into data. -func findLine(data []byte, offset int64) (line int) { - line = 1 - for i, r := range string(data) { - if int64(i) >= offset { - return - } - if r == '\n' { - line++ - } - } - return + return convertBlockTests(btjs) } // Nothing to see here, please move along... diff --git a/tests/helper/common.go b/tests/helper/common.go deleted file mode 100644 index 21ea2261f..000000000 --- a/tests/helper/common.go +++ /dev/null @@ -1,11 +0,0 @@ -package helper - -import "github.com/ethereum/go-ethereum/common" - -func FromHex(h string) []byte { - if common.IsHex(h) { - h = h[2:] - } - - return common.Hex2Bytes(h) -} diff --git a/tests/helper/init.go b/tests/helper/init.go deleted file mode 100644 index 73d563e02..000000000 --- a/tests/helper/init.go +++ /dev/null @@ -1,16 +0,0 @@ -package helper - -import ( - "log" - "os" - - logpkg "github.com/ethereum/go-ethereum/logger" -) - -var Logger *logpkg.StdLogSystem -var Log = logpkg.NewLogger("TEST") - -func init() { - Logger = logpkg.NewStdLogSystem(os.Stdout, log.LstdFlags, logpkg.InfoLevel) - logpkg.AddLogSystem(Logger) -} diff --git a/tests/helper/readers.go b/tests/helper/readers.go deleted file mode 100644 index 03313aeda..000000000 --- a/tests/helper/readers.go +++ /dev/null @@ -1,42 +0,0 @@ -package helper - -import ( - "encoding/json" - "io" - "io/ioutil" - "net/http" - "os" - "testing" -) - -func readJSON(t *testing.T, reader io.Reader, value interface{}) { - data, err := ioutil.ReadAll(reader) - err = json.Unmarshal(data, &value) - if err != nil { - t.Error(err) - } -} - -func CreateHttpTests(t *testing.T, uri string, value interface{}) { - resp, err := http.Get(uri) - if err != nil { - t.Error(err) - - return - } - defer resp.Body.Close() - - readJSON(t, resp.Body, value) -} - -func CreateFileTests(t *testing.T, fn string, value interface{}) { - file, err := os.Open(fn) - if err != nil { - t.Error(err) - - return - } - defer file.Close() - - readJSON(t, file, value) -} diff --git a/tests/helper/trie.go b/tests/helper/trie.go deleted file mode 100644 index 9e666d333..000000000 --- a/tests/helper/trie.go +++ /dev/null @@ -1,31 +0,0 @@ -package helper - -import "github.com/ethereum/go-ethereum/trie" - -type MemDatabase struct { - db map[string][]byte -} - -func NewMemDatabase() (*MemDatabase, error) { - db := &MemDatabase{db: make(map[string][]byte)} - return db, nil -} -func (db *MemDatabase) Put(key []byte, value []byte) { - db.db[string(key)] = value -} -func (db *MemDatabase) Get(key []byte) ([]byte, error) { - return db.db[string(key)], nil -} -func (db *MemDatabase) Delete(key []byte) error { - delete(db.db, string(key)) - return nil -} -func (db *MemDatabase) Print() {} -func (db *MemDatabase) Close() {} -func (db *MemDatabase) LastKnownTD() []byte { return nil } - -func NewTrie() *trie.Trie { - db, _ := NewMemDatabase() - - return trie.New(nil, db) -} diff --git a/tests/init.go b/tests/init.go new file mode 100644 index 000000000..9fe98a0d1 --- /dev/null +++ b/tests/init.go @@ -0,0 +1,82 @@ +package tests + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" +) + +var ( + baseDir = filepath.Join(".", "files") + blockTestDir = filepath.Join(baseDir, "BlockTests") + stateTestDir = filepath.Join(baseDir, "StateTests") + transactionTestDir = filepath.Join(baseDir, "TransactionTests") + vmTestDir = filepath.Join(baseDir, "VMTests") + + BlockSkipTests = []string{"SimpleTx3"} + TransSkipTests = []string{"TransactionWithHihghNonce256"} + StateSkipTests = []string{"mload32bitBound_return", "mload32bitBound_return2"} + VmSkipTests = []string{} +) + +func readJson(reader io.Reader, value interface{}) error { + data, err := ioutil.ReadAll(reader) + if err != nil { + return fmt.Errorf("Error reading JSON file", err.Error()) + } + + if err = json.Unmarshal(data, &value); err != nil { + if syntaxerr, ok := err.(*json.SyntaxError); ok { + line := findLine(data, syntaxerr.Offset) + return fmt.Errorf("JSON syntax error at line %v: %v", line, err) + } + return fmt.Errorf("JSON unmarshal error: %v", err) + } + return nil +} + +func readJsonHttp(uri string, value interface{}) error { + resp, err := http.Get(uri) + if err != nil { + return err + } + defer resp.Body.Close() + + err = readJson(resp.Body, value) + if err != nil { + return err + } + return nil +} + +func readJsonFile(fn string, value interface{}) error { + file, err := os.Open(fn) + if err != nil { + return err + } + defer file.Close() + + err = readJson(file, value) + if err != nil { + return fmt.Errorf("%s in file %s", err.Error(), fn) + } + return nil +} + +// findLine returns the line number for the given offset into data. +func findLine(data []byte, offset int64) (line int) { + line = 1 + for i, r := range string(data) { + if int64(i) >= offset { + return + } + if r == '\n' { + line++ + } + } + return +} diff --git a/tests/state_test.go b/tests/state_test.go new file mode 100644 index 000000000..e58f588f4 --- /dev/null +++ b/tests/state_test.go @@ -0,0 +1,134 @@ +package tests + +import ( + "os" + "path/filepath" + "testing" +) + +func TestStateSystemOperations(t *testing.T) { + fn := filepath.Join(stateTestDir, "stSystemOperationsTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStateExample(t *testing.T) { + fn := filepath.Join(stateTestDir, "stExample.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStatePreCompiledContracts(t *testing.T) { + fn := filepath.Join(stateTestDir, "stPreCompiledContracts.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStateRecursiveCreate(t *testing.T) { + fn := filepath.Join(stateTestDir, "stRecursiveCreate.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStateSpecial(t *testing.T) { + fn := filepath.Join(stateTestDir, "stSpecialTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStateRefund(t *testing.T) { + fn := filepath.Join(stateTestDir, "stRefundTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStateBlockHash(t *testing.T) { + fn := filepath.Join(stateTestDir, "stBlockHashTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStateInitCode(t *testing.T) { + fn := filepath.Join(stateTestDir, "stInitCodeTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStateLog(t *testing.T) { + fn := filepath.Join(stateTestDir, "stLogTests.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStateTransaction(t *testing.T) { + fn := filepath.Join(stateTestDir, "stTransactionTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestCallCreateCallCode(t *testing.T) { + fn := filepath.Join(stateTestDir, "stCallCreateCallCodeTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestMemory(t *testing.T) { + fn := filepath.Join(stateTestDir, "stMemoryTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestMemoryStress(t *testing.T) { + if os.Getenv("TEST_VM_COMPLEX") == "" { + t.Skip() + } + fn := filepath.Join(stateTestDir, "stMemoryStressTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestQuadraticComplexity(t *testing.T) { + if os.Getenv("TEST_VM_COMPLEX") == "" { + t.Skip() + } + fn := filepath.Join(stateTestDir, "stQuadraticComplexityTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestSolidity(t *testing.T) { + fn := filepath.Join(stateTestDir, "stSolidityTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestWallet(t *testing.T) { + fn := filepath.Join(stateTestDir, "stWalletTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + +func TestStateTestsRandom(t *testing.T) { + fns, _ := filepath.Glob("./files/StateTests/RandomTests/*") + for _, fn := range fns { + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } + } +} diff --git a/tests/state_test_util.go b/tests/state_test_util.go new file mode 100644 index 000000000..e9abad788 --- /dev/null +++ b/tests/state_test_util.go @@ -0,0 +1,181 @@ +package tests + +import ( + "bytes" + "fmt" + "io" + "math/big" + "strconv" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "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/ethdb" + "github.com/ethereum/go-ethereum/logger/glog" +) + +func RunStateTestWithReader(r io.Reader, skipTests []string) error { + tests := make(map[string]VmTest) + if err := readJson(r, &tests); err != nil { + return err + } + + if err := runStateTests(tests, skipTests); err != nil { + return err + } + + return nil +} + +func RunStateTest(p string, skipTests []string) error { + tests := make(map[string]VmTest) + if err := readJsonFile(p, &tests); err != nil { + return err + } + + if err := runStateTests(tests, skipTests); err != nil { + return err + } + + return nil + +} + +func runStateTests(tests map[string]VmTest, skipTests []string) error { + skipTest := make(map[string]bool, len(skipTests)) + for _, name := range skipTests { + skipTest[name] = true + } + + for name, test := range tests { + if skipTest[name] { + glog.Infoln("Skipping state test", name) + return nil + } + + if err := runStateTest(test); err != nil { + return fmt.Errorf("%s: %s\n", name, err.Error()) + } + + glog.Infoln("State test passed: ", name) + //fmt.Println(string(statedb.Dump())) + } + return nil + +} + +func runStateTest(test VmTest) error { + db, _ := ethdb.NewMemDatabase() + statedb := state.New(common.Hash{}, db) + for addr, account := range test.Pre { + obj := StateObjectFromAccount(db, addr, account) + statedb.SetStateObject(obj) + for a, v := range account.Storage { + obj.SetState(common.HexToHash(a), common.HexToHash(v)) + } + } + + // XXX Yeah, yeah... + env := make(map[string]string) + env["currentCoinbase"] = test.Env.CurrentCoinbase + env["currentDifficulty"] = test.Env.CurrentDifficulty + env["currentGasLimit"] = test.Env.CurrentGasLimit + env["currentNumber"] = test.Env.CurrentNumber + env["previousHash"] = test.Env.PreviousHash + if n, ok := test.Env.CurrentTimestamp.(float64); ok { + env["currentTimestamp"] = strconv.Itoa(int(n)) + } else { + env["currentTimestamp"] = test.Env.CurrentTimestamp.(string) + } + + var ( + ret []byte + // gas *big.Int + // err error + logs state.Logs + ) + + ret, logs, _, _ = RunState(statedb, env, test.Transaction) + + // // Compare expected and actual return + rexp := common.FromHex(test.Out) + if bytes.Compare(rexp, ret) != 0 { + return fmt.Errorf("return failed. Expected %x, got %x\n", rexp, ret) + } + + // check post state + for addr, account := range test.Post { + obj := statedb.GetStateObject(common.HexToAddress(addr)) + if obj == nil { + continue + } + + if obj.Balance().Cmp(common.Big(account.Balance)) != 0 { + return fmt.Errorf("(%x) balance failed. Expected %v, got %v => %v\n", obj.Address().Bytes()[:4], account.Balance, obj.Balance(), new(big.Int).Sub(common.Big(account.Balance), obj.Balance())) + } + + if obj.Nonce() != common.String2Big(account.Nonce).Uint64() { + return fmt.Errorf("(%x) nonce failed. Expected %v, got %v\n", obj.Address().Bytes()[:4], account.Nonce, obj.Nonce()) + } + + for addr, value := range account.Storage { + v := obj.GetState(common.HexToHash(addr)) + vexp := common.HexToHash(value) + + if v != vexp { + return fmt.Errorf("(%x: %s) storage failed. Expected %x, got %x (%v %v)\n", obj.Address().Bytes()[0:4], addr, vexp, v, vexp.Big(), v.Big()) + } + } + } + + statedb.Sync() + if common.HexToHash(test.PostStateRoot) != statedb.Root() { + return fmt.Errorf("Post state root error. Expected %s, got %x", test.PostStateRoot, statedb.Root()) + } + + // check logs + if len(test.Logs) > 0 { + if err := checkLogs(test.Logs, logs); err != nil { + return err + } + } + + return nil +} + +func RunState(statedb *state.StateDB, env, tx map[string]string) ([]byte, state.Logs, *big.Int, error) { + var ( + keyPair, _ = crypto.NewKeyPairFromSec([]byte(common.Hex2Bytes(tx["secretKey"]))) + data = common.FromHex(tx["data"]) + gas = common.Big(tx["gasLimit"]) + price = common.Big(tx["gasPrice"]) + value = common.Big(tx["value"]) + nonce = common.Big(tx["nonce"]).Uint64() + caddr = common.HexToAddress(env["currentCoinbase"]) + ) + + var to *common.Address + if len(tx["to"]) > 2 { + t := common.HexToAddress(tx["to"]) + to = &t + } + // Set pre compiled contracts + vm.Precompiled = vm.PrecompiledContracts() + + snapshot := statedb.Copy() + coinbase := statedb.GetOrNewStateObject(caddr) + coinbase.SetGasPool(common.Big(env["currentGasLimit"])) + + message := NewMessage(common.BytesToAddress(keyPair.Address()), to, data, value, gas, price, nonce) + vmenv := NewEnvFromMap(statedb, env, tx) + vmenv.origin = common.BytesToAddress(keyPair.Address()) + ret, _, err := core.ApplyMessage(vmenv, message, coinbase) + if core.IsNonceErr(err) || core.IsInvalidTxErr(err) || state.IsGasLimitErr(err) { + statedb.Set(snapshot) + } + statedb.Update() + + return ret, vmenv.state.Logs(), vmenv.Gas, err +} diff --git a/tests/transaction_test.go b/tests/transaction_test.go index 7ae1c8788..70aa65cdd 100644 --- a/tests/transaction_test.go +++ b/tests/transaction_test.go @@ -1,44 +1,26 @@ package tests import ( + "path/filepath" "testing" ) func TestTransactions(t *testing.T) { - notWorking := make(map[string]bool, 100) - - // TODO: all these tests should work! remove them from the array when they work - snafus := []string{ - "TransactionWithHihghNonce256", // fails due to testing upper bound of 256 bit nonce - } - - for _, name := range snafus { - notWorking[name] = true - } - - var err error - err = RunTransactionTests("./files/TransactionTests/ttTransactionTest.json", - notWorking) + err := RunTransactionTests(filepath.Join(transactionTestDir, "ttTransactionTest.json"), TransSkipTests) if err != nil { t.Fatal(err) } } func TestWrongRLPTransactions(t *testing.T) { - notWorking := make(map[string]bool, 100) - var err error - err = RunTransactionTests("./files/TransactionTests/ttWrongRLPTransaction.json", - notWorking) + err := RunTransactionTests(filepath.Join(transactionTestDir, "ttWrongRLPTransaction.json"), TransSkipTests) if err != nil { t.Fatal(err) } } func Test10MBtx(t *testing.T) { - notWorking := make(map[string]bool, 100) - var err error - err = RunTransactionTests("./files/TransactionTests/tt10mbDataField.json", - notWorking) + err := RunTransactionTests(filepath.Join(transactionTestDir, "tt10mbDataField.json"), TransSkipTests) if err != nil { t.Fatal(err) } diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 82038c3e8..45caf26fd 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -4,10 +4,12 @@ import ( "bytes" "errors" "fmt" + "io" "runtime" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" ) @@ -30,25 +32,69 @@ type TransactionTest struct { Transaction TtTransaction } -func RunTransactionTests(file string, notWorking map[string]bool) error { +func RunTransactionTestsWithReader(r io.Reader, skipTests []string) error { + skipTest := make(map[string]bool, len(skipTests)) + for _, name := range skipTests { + skipTest[name] = true + } + bt := make(map[string]TransactionTest) - if err := LoadJSON(file, &bt); err != nil { + if err := readJson(r, &bt); err != nil { return err } - for name, in := range bt { - var err error - // TODO: remove this, we currently ignore some tests which are broken - if !notWorking[name] { - if err = runTest(in); err != nil { - return fmt.Errorf("bad test %s: %v", name, err) - } - fmt.Println("Test passed:", name) + + for name, test := range bt { + // if the test should be skipped, return + if skipTest[name] { + glog.Infoln("Skipping transaction test", name) + return nil } + // test the block + if err := runTransactionTest(test); err != nil { + return err + } + glog.Infoln("Transaction test passed: ", name) + + } + return nil +} + +func RunTransactionTests(file string, skipTests []string) error { + tests := make(map[string]TransactionTest) + if err := readJsonFile(file, &tests); err != nil { + return err + } + + if err := runTransactionTests(tests, skipTests); err != nil { + return err + } + return nil +} + +func runTransactionTests(tests map[string]TransactionTest, skipTests []string) error { + skipTest := make(map[string]bool, len(skipTests)) + for _, name := range skipTests { + skipTest[name] = true + } + + for name, test := range tests { + // if the test should be skipped, return + if skipTest[name] { + glog.Infoln("Skipping transaction test", name) + return nil + } + + // test the block + if err := runTransactionTest(test); err != nil { + return err + } + glog.Infoln("Transaction test passed: ", name) + } return nil } -func runTest(txTest TransactionTest) (err error) { +func runTransactionTest(txTest TransactionTest) (err error) { tx := new(types.Transaction) err = rlp.DecodeBytes(mustConvertBytes(txTest.Rlp), tx) diff --git a/tests/helper/vm.go b/tests/util.go index e29a2d8ee..67650c188 100644 --- a/tests/helper/vm.go +++ b/tests/util.go @@ -1,16 +1,113 @@ -package helper +package tests import ( + "bytes" "errors" + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "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" ) +func checkLogs(tlog []Log, logs state.Logs) error { + + if len(tlog) != len(logs) { + return fmt.Errorf("log length mismatch. Expected %d, got %d", len(tlog), len(logs)) + } else { + for i, log := range tlog { + if common.HexToAddress(log.AddressF) != logs[i].Address { + return fmt.Errorf("log address expected %v got %x", log.AddressF, logs[i].Address) + } + + if !bytes.Equal(logs[i].Data, common.FromHex(log.DataF)) { + return fmt.Errorf("log data expected %v got %x", log.DataF, logs[i].Data) + } + + if len(log.TopicsF) != len(logs[i].Topics) { + return fmt.Errorf("log topics length expected %d got %d", len(log.TopicsF), logs[i].Topics) + } else { + for j, topic := range log.TopicsF { + if common.HexToHash(topic) != logs[i].Topics[j] { + return fmt.Errorf("log topic[%d] expected %v got %x", j, topic, logs[i].Topics[j]) + } + } + } + genBloom := common.LeftPadBytes(types.LogsBloom(state.Logs{logs[i]}).Bytes(), 256) + + if !bytes.Equal(genBloom, common.Hex2Bytes(log.BloomF)) { + return fmt.Errorf("bloom mismatch") + } + } + } + return nil +} + +type Account struct { + Balance string + Code string + Nonce string + Storage map[string]string +} + +type Log struct { + AddressF string `json:"address"` + DataF string `json:"data"` + TopicsF []string `json:"topics"` + BloomF string `json:"bloom"` +} + +func (self Log) Address() []byte { return common.Hex2Bytes(self.AddressF) } +func (self Log) Data() []byte { return common.Hex2Bytes(self.DataF) } +func (self Log) RlpData() interface{} { return nil } +func (self Log) Topics() [][]byte { + t := make([][]byte, len(self.TopicsF)) + for i, topic := range self.TopicsF { + t[i] = common.Hex2Bytes(topic) + } + return t +} + +func StateObjectFromAccount(db common.Database, addr string, account Account) *state.StateObject { + obj := state.NewStateObject(common.HexToAddress(addr), db) + obj.SetBalance(common.Big(account.Balance)) + + if common.IsHex(account.Code) { + account.Code = account.Code[2:] + } + obj.SetCode(common.Hex2Bytes(account.Code)) + obj.SetNonce(common.Big(account.Nonce).Uint64()) + + return obj +} + +type VmEnv struct { + CurrentCoinbase string + CurrentDifficulty string + CurrentGasLimit string + CurrentNumber string + CurrentTimestamp interface{} + PreviousHash string +} + +type VmTest struct { + Callcreates interface{} + //Env map[string]string + Env VmEnv + Exec map[string]string + Transaction map[string]string + Logs []Log + Gas string + Out string + Post map[string]Account + Pre map[string]Account + PostStateRoot string +} + type Env struct { depth int state *state.StateDB @@ -27,8 +124,9 @@ type Env struct { difficulty *big.Int gasLimit *big.Int + logs []vm.StructLog + vmTest bool - logs []vm.StructLog } func NewEnv(state *state.StateDB) *Env { @@ -140,64 +238,6 @@ func (self *Env) Create(caller vm.ContextRef, data []byte, gas, price, value *bi } } -func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, state.Logs, *big.Int, error) { - var ( - to = common.HexToAddress(exec["address"]) - from = common.HexToAddress(exec["caller"]) - data = FromHex(exec["data"]) - gas = common.Big(exec["gas"]) - price = common.Big(exec["gasPrice"]) - value = common.Big(exec["value"]) - ) - // Reset the pre-compiled contracts for VM tests. - vm.Precompiled = make(map[string]*vm.PrecompiledAccount) - - caller := state.GetOrNewStateObject(from) - - vmenv := NewEnvFromMap(state, env, exec) - vmenv.vmTest = true - vmenv.skipTransfer = true - vmenv.initial = true - ret, err := vmenv.Call(caller, to, data, gas, price, value) - - return ret, vmenv.state.Logs(), vmenv.Gas, err -} - -func RunState(statedb *state.StateDB, env, tx map[string]string) ([]byte, state.Logs, *big.Int, error) { - var ( - keyPair, _ = crypto.NewKeyPairFromSec([]byte(common.Hex2Bytes(tx["secretKey"]))) - data = FromHex(tx["data"]) - gas = common.Big(tx["gasLimit"]) - price = common.Big(tx["gasPrice"]) - value = common.Big(tx["value"]) - nonce = common.Big(tx["nonce"]).Uint64() - caddr = common.HexToAddress(env["currentCoinbase"]) - ) - - var to *common.Address - if len(tx["to"]) > 2 { - t := common.HexToAddress(tx["to"]) - to = &t - } - // Set pre compiled contracts - vm.Precompiled = vm.PrecompiledContracts() - - snapshot := statedb.Copy() - coinbase := statedb.GetOrNewStateObject(caddr) - coinbase.SetGasPool(common.Big(env["currentGasLimit"])) - - message := NewMessage(common.BytesToAddress(keyPair.Address()), to, data, value, gas, price, nonce) - vmenv := NewEnvFromMap(statedb, env, tx) - vmenv.origin = common.BytesToAddress(keyPair.Address()) - ret, _, err := core.ApplyMessage(vmenv, message, coinbase) - if core.IsNonceErr(err) || core.IsInvalidTxErr(err) || state.IsGasLimitErr(err) { - statedb.Set(snapshot) - } - statedb.Update() - - return ret, vmenv.state.Logs(), vmenv.Gas, err -} - type Message struct { from common.Address to *common.Address diff --git a/tests/vm/.ethtest b/tests/vm/.ethtest deleted file mode 100644 index e69de29bb..000000000 --- a/tests/vm/.ethtest +++ /dev/null diff --git a/tests/vm/gh_test.go b/tests/vm/gh_test.go deleted file mode 100644 index be9e89d9c..000000000 --- a/tests/vm/gh_test.go +++ /dev/null @@ -1,388 +0,0 @@ -package vm - -import ( - "bytes" - "math/big" - "os" - "path/filepath" - "strconv" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/tests/helper" -) - -type Account struct { - Balance string - Code string - Nonce string - Storage map[string]string -} - -type Log struct { - AddressF string `json:"address"` - DataF string `json:"data"` - TopicsF []string `json:"topics"` - BloomF string `json:"bloom"` -} - -func (self Log) Address() []byte { return common.Hex2Bytes(self.AddressF) } -func (self Log) Data() []byte { return common.Hex2Bytes(self.DataF) } -func (self Log) RlpData() interface{} { return nil } -func (self Log) Topics() [][]byte { - t := make([][]byte, len(self.TopicsF)) - for i, topic := range self.TopicsF { - t[i] = common.Hex2Bytes(topic) - } - return t -} - -func StateObjectFromAccount(db common.Database, addr string, account Account) *state.StateObject { - obj := state.NewStateObject(common.HexToAddress(addr), db) - obj.SetBalance(common.Big(account.Balance)) - - if common.IsHex(account.Code) { - account.Code = account.Code[2:] - } - obj.SetCode(common.Hex2Bytes(account.Code)) - obj.SetNonce(common.Big(account.Nonce).Uint64()) - - return obj -} - -type Env struct { - CurrentCoinbase string - CurrentDifficulty string - CurrentGasLimit string - CurrentNumber string - CurrentTimestamp interface{} - PreviousHash string -} - -type VmTest struct { - Callcreates interface{} - //Env map[string]string - Env Env - Exec map[string]string - Transaction map[string]string - Logs []Log - Gas string - Out string - Post map[string]Account - Pre map[string]Account - PostStateRoot string -} - -func RunVmTest(p string, t *testing.T) { - - tests := make(map[string]VmTest) - helper.CreateFileTests(t, p, &tests) - - for name, test := range tests { - /* - vm.Debug = true - glog.SetV(4) - glog.SetToStderr(true) - if name != "Call50000_sha256" { - continue - } - */ - db, _ := ethdb.NewMemDatabase() - statedb := state.New(common.Hash{}, db) - for addr, account := range test.Pre { - obj := StateObjectFromAccount(db, addr, account) - statedb.SetStateObject(obj) - for a, v := range account.Storage { - obj.SetState(common.HexToHash(a), common.HexToHash(v)) - } - } - - // XXX Yeah, yeah... - env := make(map[string]string) - env["currentCoinbase"] = test.Env.CurrentCoinbase - env["currentDifficulty"] = test.Env.CurrentDifficulty - env["currentGasLimit"] = test.Env.CurrentGasLimit - env["currentNumber"] = test.Env.CurrentNumber - env["previousHash"] = test.Env.PreviousHash - if n, ok := test.Env.CurrentTimestamp.(float64); ok { - env["currentTimestamp"] = strconv.Itoa(int(n)) - } else { - env["currentTimestamp"] = test.Env.CurrentTimestamp.(string) - } - - var ( - ret []byte - gas *big.Int - err error - logs state.Logs - ) - - isVmTest := len(test.Exec) > 0 - if isVmTest { - ret, logs, gas, err = helper.RunVm(statedb, env, test.Exec) - } else { - ret, logs, gas, err = helper.RunState(statedb, env, test.Transaction) - } - - switch name { - // the memory required for these tests (4294967297 bytes) would take too much time. - // on 19 May 2015 decided to skip these tests their output. - case "mload32bitBound_return", "mload32bitBound_return2": - default: - rexp := helper.FromHex(test.Out) - if bytes.Compare(rexp, ret) != 0 { - t.Errorf("%s's return failed. Expected %x, got %x\n", name, rexp, ret) - } - } - - if isVmTest { - if len(test.Gas) == 0 && err == nil { - t.Errorf("%s's gas unspecified, indicating an error. VM returned (incorrectly) successfull", name) - } else { - gexp := common.Big(test.Gas) - if gexp.Cmp(gas) != 0 { - t.Errorf("%s's gas failed. Expected %v, got %v\n", name, gexp, gas) - } - } - } - - for addr, account := range test.Post { - obj := statedb.GetStateObject(common.HexToAddress(addr)) - if obj == nil { - continue - } - - if len(test.Exec) == 0 { - if obj.Balance().Cmp(common.Big(account.Balance)) != 0 { - t.Errorf("%s's : (%x) balance failed. Expected %v, got %v => %v\n", name, obj.Address().Bytes()[:4], account.Balance, obj.Balance(), new(big.Int).Sub(common.Big(account.Balance), obj.Balance())) - } - - if obj.Nonce() != common.String2Big(account.Nonce).Uint64() { - t.Errorf("%s's : (%x) nonce failed. Expected %v, got %v\n", name, obj.Address().Bytes()[:4], account.Nonce, obj.Nonce()) - } - - } - - for addr, value := range account.Storage { - v := obj.GetState(common.HexToHash(addr)) - vexp := common.HexToHash(value) - - if v != vexp { - t.Errorf("%s's : (%x: %s) storage failed. Expected %x, got %x (%v %v)\n", name, obj.Address().Bytes()[0:4], addr, vexp, v, vexp.Big(), v.Big()) - } - } - } - - if !isVmTest { - statedb.Sync() - //if !bytes.Equal(common.Hex2Bytes(test.PostStateRoot), statedb.Root()) { - if common.HexToHash(test.PostStateRoot) != statedb.Root() { - t.Errorf("%s's : Post state root error. Expected %s, got %x", name, test.PostStateRoot, statedb.Root()) - } - } - - if len(test.Logs) > 0 { - if len(test.Logs) != len(logs) { - t.Errorf("log length mismatch. Expected %d, got %d", len(test.Logs), len(logs)) - } else { - for i, log := range test.Logs { - if common.HexToAddress(log.AddressF) != logs[i].Address { - t.Errorf("'%s' log address expected %v got %x", name, log.AddressF, logs[i].Address) - } - - if !bytes.Equal(logs[i].Data, helper.FromHex(log.DataF)) { - t.Errorf("'%s' log data expected %v got %x", name, log.DataF, logs[i].Data) - } - - if len(log.TopicsF) != len(logs[i].Topics) { - t.Errorf("'%s' log topics length expected %d got %d", name, len(log.TopicsF), logs[i].Topics) - } else { - for j, topic := range log.TopicsF { - if common.HexToHash(topic) != logs[i].Topics[j] { - t.Errorf("'%s' log topic[%d] expected %v got %x", name, j, topic, logs[i].Topics[j]) - } - } - } - genBloom := common.LeftPadBytes(types.LogsBloom(state.Logs{logs[i]}).Bytes(), 256) - - if !bytes.Equal(genBloom, common.Hex2Bytes(log.BloomF)) { - t.Errorf("'%s' bloom mismatch", name) - } - } - } - } - //fmt.Println(string(statedb.Dump())) - } - logger.Flush() -} - -// I've created a new function for each tests so it's easier to identify where the problem lies if any of them fail. -func TestVMArithmetic(t *testing.T) { - const fn = "../files/VMTests/vmArithmeticTest.json" - RunVmTest(fn, t) -} - -func TestBitwiseLogicOperation(t *testing.T) { - const fn = "../files/VMTests/vmBitwiseLogicOperationTest.json" - RunVmTest(fn, t) -} - -func TestBlockInfo(t *testing.T) { - const fn = "../files/VMTests/vmBlockInfoTest.json" - RunVmTest(fn, t) -} - -func TestEnvironmentalInfo(t *testing.T) { - const fn = "../files/VMTests/vmEnvironmentalInfoTest.json" - RunVmTest(fn, t) -} - -func TestFlowOperation(t *testing.T) { - const fn = "../files/VMTests/vmIOandFlowOperationsTest.json" - RunVmTest(fn, t) -} - -func TestLogTest(t *testing.T) { - const fn = "../files/VMTests/vmLogTest.json" - RunVmTest(fn, t) -} - -func TestPerformance(t *testing.T) { - const fn = "../files/VMTests/vmPerformanceTest.json" - RunVmTest(fn, t) -} - -func TestPushDupSwap(t *testing.T) { - const fn = "../files/VMTests/vmPushDupSwapTest.json" - RunVmTest(fn, t) -} - -func TestVMSha3(t *testing.T) { - const fn = "../files/VMTests/vmSha3Test.json" - RunVmTest(fn, t) -} - -func TestVm(t *testing.T) { - const fn = "../files/VMTests/vmtests.json" - RunVmTest(fn, t) -} - -func TestVmLog(t *testing.T) { - const fn = "../files/VMTests/vmLogTest.json" - RunVmTest(fn, t) -} - -func TestInputLimits(t *testing.T) { - const fn = "../files/VMTests/vmInputLimits.json" - RunVmTest(fn, t) -} - -func TestInputLimitsLight(t *testing.T) { - const fn = "../files/VMTests/vmInputLimitsLight.json" - RunVmTest(fn, t) -} - -func TestStateSystemOperations(t *testing.T) { - const fn = "../files/StateTests/stSystemOperationsTest.json" - RunVmTest(fn, t) -} - -func TestStateExample(t *testing.T) { - const fn = "../files/StateTests/stExample.json" - RunVmTest(fn, t) -} - -func TestStatePreCompiledContracts(t *testing.T) { - const fn = "../files/StateTests/stPreCompiledContracts.json" - RunVmTest(fn, t) -} - -func TestStateRecursiveCreate(t *testing.T) { - const fn = "../files/StateTests/stRecursiveCreate.json" - RunVmTest(fn, t) -} - -func TestStateSpecial(t *testing.T) { - const fn = "../files/StateTests/stSpecialTest.json" - RunVmTest(fn, t) -} - -func TestStateRefund(t *testing.T) { - const fn = "../files/StateTests/stRefundTest.json" - RunVmTest(fn, t) -} - -func TestStateBlockHash(t *testing.T) { - const fn = "../files/StateTests/stBlockHashTest.json" - RunVmTest(fn, t) -} - -func TestStateInitCode(t *testing.T) { - const fn = "../files/StateTests/stInitCodeTest.json" - RunVmTest(fn, t) -} - -func TestStateLog(t *testing.T) { - const fn = "../files/StateTests/stLogTests.json" - RunVmTest(fn, t) -} - -func TestStateTransaction(t *testing.T) { - const fn = "../files/StateTests/stTransactionTest.json" - RunVmTest(fn, t) -} - -func TestCallCreateCallCode(t *testing.T) { - const fn = "../files/StateTests/stCallCreateCallCodeTest.json" - RunVmTest(fn, t) -} - -func TestMemory(t *testing.T) { - const fn = "../files/StateTests/stMemoryTest.json" - RunVmTest(fn, t) -} - -func TestMemoryStress(t *testing.T) { - if os.Getenv("TEST_VM_COMPLEX") == "" { - t.Skip() - } - const fn = "../files/StateTests/stMemoryStressTest.json" - RunVmTest(fn, t) -} - -func TestQuadraticComplexity(t *testing.T) { - if os.Getenv("TEST_VM_COMPLEX") == "" { - t.Skip() - } - const fn = "../files/StateTests/stQuadraticComplexityTest.json" - RunVmTest(fn, t) -} - -func TestSolidity(t *testing.T) { - const fn = "../files/StateTests/stSolidityTest.json" - RunVmTest(fn, t) -} - -func TestWallet(t *testing.T) { - const fn = "../files/StateTests/stWalletTest.json" - RunVmTest(fn, t) -} - -func TestStateTestsRandom(t *testing.T) { - fns, _ := filepath.Glob("../files/StateTests/RandomTests/*") - for _, fn := range fns { - RunVmTest(fn, t) - } -} - -func TestVMRandom(t *testing.T) { - t.Skip() // fucked as of 2015-06-09. unskip once unfucked /Gustav - fns, _ := filepath.Glob("../files/VMTests/RandomTests/*") - for _, fn := range fns { - RunVmTest(fn, t) - } -} diff --git a/tests/vm/nowarn.go b/tests/vm/nowarn.go deleted file mode 100644 index 2a45a6cc6..000000000 --- a/tests/vm/nowarn.go +++ /dev/null @@ -1,3 +0,0 @@ -// This silences the warning given by 'go install ./...'. - -package vm diff --git a/tests/vm_test.go b/tests/vm_test.go new file mode 100644 index 000000000..4e417da5a --- /dev/null +++ b/tests/vm_test.go @@ -0,0 +1,107 @@ +package tests + +import ( + "path/filepath" + "testing" +) + +// I've created a new function for each tests so it's easier to identify where the problem lies if any of them fail. +func TestVMArithmetic(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmArithmeticTest.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestBitwiseLogicOperation(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmBitwiseLogicOperationTest.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestBlockInfo(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmBlockInfoTest.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestEnvironmentalInfo(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmEnvironmentalInfoTest.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestFlowOperation(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmIOandFlowOperationsTest.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestLogTest(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmLogTest.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestPerformance(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmPerformanceTest.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestPushDupSwap(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmPushDupSwapTest.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestVMSha3(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmSha3Test.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestVm(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmtests.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestVmLog(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmLogTest.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestInputLimits(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmInputLimits.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestInputLimitsLight(t *testing.T) { + fn := filepath.Join(vmTestDir, "vmInputLimitsLight.json") + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } +} + +func TestVMRandom(t *testing.T) { + fns, _ := filepath.Glob(filepath.Join(baseDir, "RandomTests", "*")) + for _, fn := range fns { + if err := RunVmTest(fn, VmSkipTests); err != nil { + t.Error(err) + } + } +} diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go new file mode 100644 index 000000000..286991764 --- /dev/null +++ b/tests/vm_test_util.go @@ -0,0 +1,170 @@ +package tests + +import ( + "bytes" + "fmt" + "io" + "math/big" + "strconv" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/logger/glog" +) + +func RunVmTestWithReader(r io.Reader, skipTests []string) error { + tests := make(map[string]VmTest) + err := readJson(r, &tests) + if err != nil { + return err + } + + if err != nil { + return err + } + + if err := runVmTests(tests, skipTests); err != nil { + return err + } + + return nil +} + +func RunVmTest(p string, skipTests []string) error { + + tests := make(map[string]VmTest) + err := readJsonFile(p, &tests) + if err != nil { + return err + } + + if err := runVmTests(tests, skipTests); err != nil { + return err + } + + return nil +} + +func runVmTests(tests map[string]VmTest, skipTests []string) error { + skipTest := make(map[string]bool, len(skipTests)) + for _, name := range skipTests { + skipTest[name] = true + } + + for name, test := range tests { + if skipTest[name] { + glog.Infoln("Skipping VM test", name) + return nil + } + + if err := runVmTest(test); err != nil { + return fmt.Errorf("%s %s", name, err.Error()) + } + + glog.Infoln("VM test passed: ", name) + //fmt.Println(string(statedb.Dump())) + } + return nil +} + +func runVmTest(test VmTest) error { + db, _ := ethdb.NewMemDatabase() + statedb := state.New(common.Hash{}, db) + for addr, account := range test.Pre { + obj := StateObjectFromAccount(db, addr, account) + statedb.SetStateObject(obj) + for a, v := range account.Storage { + obj.SetState(common.HexToHash(a), common.HexToHash(v)) + } + } + + // XXX Yeah, yeah... + env := make(map[string]string) + env["currentCoinbase"] = test.Env.CurrentCoinbase + env["currentDifficulty"] = test.Env.CurrentDifficulty + env["currentGasLimit"] = test.Env.CurrentGasLimit + env["currentNumber"] = test.Env.CurrentNumber + env["previousHash"] = test.Env.PreviousHash + if n, ok := test.Env.CurrentTimestamp.(float64); ok { + env["currentTimestamp"] = strconv.Itoa(int(n)) + } else { + env["currentTimestamp"] = test.Env.CurrentTimestamp.(string) + } + + var ( + ret []byte + gas *big.Int + err error + logs state.Logs + ) + + ret, logs, gas, err = RunVm(statedb, env, test.Exec) + + // Compare expected and actual return + rexp := common.FromHex(test.Out) + if bytes.Compare(rexp, ret) != 0 { + return fmt.Errorf("return failed. Expected %x, got %x\n", rexp, ret) + } + + // Check gas usage + if len(test.Gas) == 0 && err == nil { + return fmt.Errorf("gas unspecified, indicating an error. VM returned (incorrectly) successfull") + } else { + gexp := common.Big(test.Gas) + if gexp.Cmp(gas) != 0 { + return fmt.Errorf("gas failed. Expected %v, got %v\n", gexp, gas) + } + } + + // check post state + for addr, account := range test.Post { + obj := statedb.GetStateObject(common.HexToAddress(addr)) + if obj == nil { + continue + } + + for addr, value := range account.Storage { + v := obj.GetState(common.HexToHash(addr)) + vexp := common.HexToHash(value) + + if v != vexp { + return fmt.Errorf("(%x: %s) storage failed. Expected %x, got %x (%v %v)\n", obj.Address().Bytes()[0:4], addr, vexp, v, vexp.Big(), v.Big()) + } + } + } + + // check logs + if len(test.Logs) > 0 { + lerr := checkLogs(test.Logs, logs) + if lerr != nil { + return lerr + } + } + + return nil +} + +func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, state.Logs, *big.Int, error) { + var ( + to = common.HexToAddress(exec["address"]) + from = common.HexToAddress(exec["caller"]) + data = common.FromHex(exec["data"]) + gas = common.Big(exec["gas"]) + price = common.Big(exec["gasPrice"]) + value = common.Big(exec["value"]) + ) + // Reset the pre-compiled contracts for VM tests. + vm.Precompiled = make(map[string]*vm.PrecompiledAccount) + + caller := state.GetOrNewStateObject(from) + + vmenv := NewEnvFromMap(state, env, exec) + vmenv.vmTest = true + vmenv.skipTransfer = true + vmenv.initial = true + ret, err := vmenv.Call(caller, to, data, gas, price, value) + + return ret, vmenv.state.Logs(), vmenv.Gas, err +} |