Skip to content
This repository has been archived by the owner on Aug 13, 2019. It is now read-only.

Testing framework #12

Merged
merged 18 commits into from
May 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8c4a269
Removed coverage file accidentally commited
austinabell Apr 19, 2019
f8abf14
Set up testing framework for eth tests
austinabell Apr 19, 2019
ef8429c
go: initialized modules (#10)
noot Apr 20, 2019
abca3a9
Updated struct formatting for unmarshalling
austinabell Apr 22, 2019
827a04e
Updated format of eth test struct and updated test files (were replac…
austinabell Apr 22, 2019
6663319
Using ethereum tests submodule and updated framework for testing
austinabell Apr 25, 2019
b4bd778
Updated hashing of logs comparison and logging of tests
austinabell Apr 25, 2019
1bc3652
Removed error checking on state execution because expected error in s…
austinabell Apr 25, 2019
e6d3e78
Updated testing structure into subtests for better reporting and so t…
austinabell Apr 26, 2019
9d0239d
Changed subtest name from full filepath to just the json file
austinabell Apr 26, 2019
d68d0df
Restructured and added light documentation
austinabell Apr 29, 2019
9a7b6d6
Added homestead specific state test
austinabell Apr 30, 2019
34f8064
Updated framework to be able to delete empty objects in trie for test…
austinabell May 1, 2019
d32514e
Changed conditional for when state objects are deleted and removed lo…
austinabell May 1, 2019
9b1ef1e
Changed folder back to intended from testing
austinabell May 1, 2019
d4f9780
Changed conditional to not skip EIP158 fork tests
austinabell May 1, 2019
f622487
Added functionality to skip tests
austinabell May 2, 2019
10f171a
Removed commented out code
austinabell May 2, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "tests/testData"]
path = tests/testData
url = https://github.com/ethereum/tests.git
30 changes: 30 additions & 0 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"math/rand"
"reflect"
"strings"

"github.com/eth-classic/go-ethereum/common/hexutil"
)

const (
Expand Down Expand Up @@ -115,6 +117,21 @@ func (h Hash) IsEmpty() bool {
return EmptyHash(h)
}

// UnprefixedHash allows marshaling a Hash without 0x prefix.
type UnprefixedHash Hash

// UnmarshalText decodes the hash from hex. The 0x prefix is optional.
func (h *UnprefixedHash) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedUnprefixedText("UnprefixedHash", input, h[:])
}

// MarshalText encodes the hash as hex.
func (h UnprefixedHash) MarshalText() ([]byte, error) {
return []byte(hex.EncodeToString(h[:])), nil
}

func (h UnprefixedHash) Bytes() []byte { return h[:] }

/////////// Address
func BytesToAddress(b []byte) Address {
var a Address
Expand Down Expand Up @@ -202,6 +219,19 @@ func (a *Address) UnmarshalJSON(data []byte) error {
return nil
}

// UnprefixedAddress allows marshaling an Address without 0x prefix.
type UnprefixedAddress Address

// UnmarshalText decodes the address from hex. The 0x prefix is optional.
func (a *UnprefixedAddress) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedUnprefixedText("UnprefixedAddress", input, a[:])
}

// MarshalText encodes the address as hex.
func (a UnprefixedAddress) MarshalText() ([]byte, error) {
return []byte(hex.EncodeToString(a[:])), nil
}

// PP Pretty Prints a byte slice in the following format:
// hex(value[:4])...(hex[len(value)-4:])
func PP(value []byte) string {
Expand Down
7 changes: 6 additions & 1 deletion core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,13 +525,18 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// and clears the journal as well as the refunds.
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
for addr := range s.stateObjectsDirty {
stateObject := s.stateObjects[addr]
stateObject, exist := s.stateObjects[addr]
if !exist {
continue
}

if stateObject.suicided || (deleteEmptyObjects && stateObject.empty()) {
s.deleteStateObject(stateObject)
} else {
stateObject.updateRoot(s.db)
s.updateStateObject(stateObject)
}
s.stateObjectsDirty[addr] = struct{}{}
}
// Invalidate journal because reverting across transactions is not allowed.
s.clearJournalAndRefund()
Expand Down
2 changes: 2 additions & 0 deletions tests/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ var (
transactionTestDir = filepath.Join(baseDir, "TransactionTests")
vmTestDir = filepath.Join(baseDir, "VMTests")
rlpTestDir = filepath.Join(baseDir, "RLPTests")
ethDir = filepath.Join(".", "testData")
ethGeneralStateDir = filepath.Join(ethDir, "GeneralStateTests")

BlockSkipTests = initBlockSkipTests()

Expand Down
71 changes: 71 additions & 0 deletions tests/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package tests

import (
"fmt"
"math/big"
"os"
"path/filepath"
"strings"
"testing"
)

Expand Down Expand Up @@ -667,3 +669,72 @@ func TestEIP150HomesteadBounds(t *testing.T) {
t.Error(err)
}
}

// func TestETHRevert(t *testing.T) {
// skipTests := make(map[string]string)

// // Bugs in these tests
// skipTests["RevertPrecompiledTouch.json/Byzantium/0"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch.json/Byzantium/3"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch.json/Constantinople/0"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch.json/Constantinople/3"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch.json/ConstantinopleFix/0"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch.json/ConstantinopleFix/3"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch_storage.json/Byzantium/0"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch_storage.json/Byzantium/3"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch_storage.json/Constantinople/0"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch_storage.json/Constantinople/3"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch_storage.json/ConstantinopleFix/0"] = "Bug in Test"
// skipTests["RevertPrecompiledTouch_storage.json/ConstantinopleFix/3"] = "Bug in Test"

// fns, _ := filepath.Glob(filepath.Join(ethGeneralStateDir, "stRevertTest", "*"))
// runETHTests(t, fns, skipTests)
// }

func TestETHHomestead(t *testing.T) {
fns, _ := filepath.Glob(filepath.Join(ethGeneralStateDir, "stHomesteadSpecific", "*"))
runETHTests(t, fns, make(map[string]string))
}

func runETHTests(t *testing.T, fileNames []string, skipTests map[string]string) {
for _, fn := range fileNames {
// Fill StateTest mapping with tests from file
stateTests, err := CreateStateTests(fn)
if err != nil {
t.Error(err)
continue
}

// JSON file subtest
fileName := fn[strings.LastIndex(fn, "/")+1 : len(fn)]
t.Run(fileName, func(t *testing.T) {
// Check if file is skipped
if skipTests[fileName] != "" {
t.Skipf("Test file %s skipped: %s", fileName, skipTests[fileName])
}

for _, test := range stateTests {
for _, subtest := range test.Subtests() {
key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index)

// Not supported implementations to test
if subtest.Fork == "Constantinople" || subtest.Fork == "ConstantinopleFix" {
continue
}

// Subtest within the JSON file
t.Run(key, func(t *testing.T) {
// Check if subtest is skipped
if skipTests[fileName+"/"+key] != "" {
t.Skipf("subtest %s skipped: %s", key, skipTests[fileName+"/"+key])
}

if err := test.runETHSubtest(subtest); err != nil {
t.Error(err)
}
})
}
}
})
}
}
109 changes: 108 additions & 1 deletion tests/state_test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"io"
"math/big"
"strconv"
"strings"
"testing"

"github.com/eth-classic/go-ethereum/common"
Expand All @@ -32,8 +33,27 @@ import (
"github.com/eth-classic/go-ethereum/crypto"
"github.com/eth-classic/go-ethereum/ethdb"
"github.com/eth-classic/go-ethereum/logger/glog"
"github.com/eth-classic/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
)

// StateSubtest selects a specific configuration of a General State Test.
type StateSubtest struct {
Fork string
Index int
}

// Subtests returns all valid subtests of the test.
func (t *StateTest) Subtests() []StateSubtest {
var sub []StateSubtest
for fork, pss := range t.Post {
for i := range pss {
sub = append(sub, StateSubtest{fork, i})
}
}
return sub
}

func RunStateTestWithReader(ruleSet RuleSet, r io.Reader, skipTests []string) error {
tests := make(map[string]VmTest)
if err := readJson(r, &tests); err != nil {
Expand Down Expand Up @@ -61,6 +81,14 @@ func RunStateTest(ruleSet RuleSet, p string, skipTests []string) error {

}

func CreateStateTests(f string) (map[string]StateTest, error) {
stateTest := make(map[string]StateTest)
if err := readJsonFile(f, &stateTest); err != nil {
return stateTest, err
}
return stateTest, nil
}

func BenchStateTest(ruleSet RuleSet, p string, conf bconf, b *testing.B) error {
tests := make(map[string]VmTest)
if err := readJsonFile(p, &tests); err != nil {
Expand Down Expand Up @@ -99,6 +127,8 @@ func benchStateTest(ruleSet RuleSet, test VmTest, env map[string]string, b *test
b.StartTimer()

RunState(ruleSet, db, statedb, env, test.Exec)

statedb.CommitTo(db, false)
}

func runStateTests(ruleSet RuleSet, tests map[string]VmTest, skipTests []string) error {
Expand Down Expand Up @@ -204,6 +234,77 @@ func runStateTest(ruleSet RuleSet, test VmTest) error {
return nil
}

func (t *StateTest) runETHSubtest(subtest StateSubtest) error {
db, _ := ethdb.NewMemDatabase()
statedb := makePreState(db, t.Pre)

// Putting environment variables into string mapping for running state
env := make(map[string]string)
env["currentCoinbase"] = t.Env.CurrentCoinbase
env["currentDifficulty"] = t.Env.CurrentDifficulty
env["currentGasLimit"] = t.Env.CurrentGasLimit
env["currentNumber"] = t.Env.CurrentNumber
env["previousHash"] = t.Env.PreviousHash
if n, ok := t.Env.CurrentTimestamp.(float64); ok {
env["currentTimestamp"] = strconv.Itoa(int(n))
} else {
env["currentTimestamp"] = t.Env.CurrentTimestamp.(string)
}

// Post state object from StateTest based on subtest
postState := t.Post[subtest.Fork][subtest.Index]

// Transaction data from StateTest object
vmTx := map[string]string{
"data": t.Tx.Data[postState.Indexes.Data],
"gasLimit": t.Tx.GasLimit[postState.Indexes.Gas],
"gasPrice": t.Tx.GasPrice,
"nonce": t.Tx.Nonce,
"secretKey": strings.TrimPrefix(t.Tx.PrivateKey, "0x"),
"to": strings.TrimPrefix(t.Tx.To, "0x"),
"value": t.Tx.Value[postState.Indexes.Value],
}

// Hard coded RuleSet based on previous tests (should change)
ruleSet := RuleSet{
HomesteadBlock: new(big.Int),
HomesteadGasRepriceBlock: big.NewInt(2457000),
}

var (
// ret []byte
// gas *big.Int
// err error
logs vm.Logs
)

_, logs, _, _ = RunState(ruleSet, db, statedb, env, vmTx)

// Only tests that are < EIP158 are Homestead
deleteEmptyObjects := subtest.Fork != "Homestead"

// Commit block
statedb.CommitTo(db, deleteEmptyObjects)

// 0-value mining reward
statedb.AddBalance(common.HexToAddress(t.Env.CurrentCoinbase), new(big.Int))

// Get state root
root := statedb.IntermediateRoot(deleteEmptyObjects)

// Compare Post state root
if root != common.BytesToHash(postState.Root.Bytes()) {
return fmt.Errorf("Post state root error. Expected: %x have: %x", postState.Root, root)
}

// Compare logs
if hashedLogs := rlpHash(logs); hashedLogs != common.Hash(postState.Logs) {
return fmt.Errorf("post state logs hash mismatch: Expected: %x have: %x", postState.Logs, logs)
}

return nil
}

func RunState(ruleSet RuleSet, db ethdb.Database, statedb *state.StateDB, env, tx map[string]string) ([]byte, vm.Logs, *big.Int, error) {
data := common.FromHex(tx["data"])
gas, _ := new(big.Int).SetString(tx["gasLimit"], 0)
Expand Down Expand Up @@ -243,7 +344,13 @@ func RunState(ruleSet RuleSet, db ethdb.Database, statedb *state.StateDB, env, t
if core.IsNonceErr(err) || core.IsInvalidTxErr(err) || core.IsGasLimitErr(err) {
statedb.RevertToSnapshot(snapshot)
}
statedb.CommitTo(db, false)

return ret, vmenv.state.Logs(), vmenv.Gas, err
}

func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewLegacyKeccak256()
rlp.Encode(hw, x)
hw.Sum(h[:0])
return h
}
1 change: 1 addition & 0 deletions tests/testData
Submodule testData added at 3d0724
43 changes: 42 additions & 1 deletion tests/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,13 @@ func (self Log) Topics() [][]byte {
}

func makePreState(db ethdb.Database, accounts map[string]Account) *state.StateDB {
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
sdb := state.NewDatabase(db)
statedb, _ := state.New(common.Hash{}, sdb)
for addr, account := range accounts {
insertAccount(statedb, addr, account)
}
root, _ := statedb.CommitTo(db, false)
statedb, _ = state.New(root, sdb)
return statedb
}

Expand Down Expand Up @@ -153,6 +156,44 @@ type RuleSet struct {
ExplosionBlock *big.Int
}

// StateTest object that matches the General State Test json file
type StateTest struct {
Env VmEnv `json:"env"`
Pre map[string]Account `json:"pre"`
Tx stTransaction `json:"transaction"`
Out string `json:"out"`
Post map[string][]stPostState `json:"post"`
}

// GenesisAccount is an account in the state of the genesis block.
type GenesisAccount struct {
Code string `json:"code,omitempty"`
Storage map[string]string `json:"storage,omitempty"`
Balance string `json:"balance" gencodec:"required"`
Nonce string `json:"nonce,omitempty"`
PrivateKey string `json:"secretKey,omitempty"` // for tests
}

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"`
}
}

type stTransaction struct {
GasPrice string `json:"gasPrice"`
Nonce string `json:"nonce"`
To string `json:"to"`
Data []string `json:"data"`
GasLimit []string `json:"gasLimit"`
Value []string `json:"value"`
PrivateKey string `json:"secretKey"`
}

func (r RuleSet) IsHomestead(n *big.Int) bool {
return n.Cmp(r.HomesteadBlock) >= 0
}
Expand Down