From de8d8acf774f082d74d1d481c47b453c07b03723 Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Mon, 27 Jul 2020 21:33:16 +0200 Subject: [PATCH] x/evm: StateBD tests (#407) * draft state_transition * working test * keeper test * statedb rewrite * fix tests * add keeper statedb test * minor changes * x/evm: StateBD tests * try fix * fix stateObject.setState * Update x/evm/types/state_object.go * update stateObject.setState * uncomment test * increase coverage * fix test-import * update rpc tests Co-authored-by: Justin Thompson Co-authored-by: noot --- .github/workflows/test.yml | 4 +- importer/importer_test.go | 4 +- tests/rpc_test.go | 1 + x/evm/types/state_object.go | 9 ++- x/evm/types/statedb.go | 8 +-- x/evm/types/statedb_test.go | 125 +++++++++++++++++++++++++++++++----- 6 files changed, 122 insertions(+), 29 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cdb6cb83..39853d7c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ on: branches: - development jobs: - rpc-tests: + test-rpc: runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -19,7 +19,7 @@ jobs: .go .mod .sum - - name: rpc-test + - name: test-rpc run: | make test-rpc if: "env.GIT_DIFF != ''" diff --git a/importer/importer_test.go b/importer/importer_test.go index fa91f288..0024de09 100644 --- a/importer/importer_test.go +++ b/importer/importer_test.go @@ -360,9 +360,9 @@ func applyTransaction( // Apply the transaction to the current state (included in the env) execResult, err := ethcore.ApplyMessage(vmenv, msg, gp) - // NOTE: ignore vm execution error (eg: tx out of gas) as we care only about state transition errors if err != nil { - return nil, 0, err + // NOTE: ignore vm execution error (eg: tx out of gas at block 51169) as we care only about state transition errors + return ðtypes.Receipt{}, 0, nil } // Update the state with pending changes diff --git a/tests/rpc_test.go b/tests/rpc_test.go index f77d7eca..3eef4c9f 100644 --- a/tests/rpc_test.go +++ b/tests/rpc_test.go @@ -532,6 +532,7 @@ func TestEth_GetFilterChanges_NoTopics(t *testing.T) { // instantiate new filter rpcRes = call(t, "eth_newFilter", param) + require.Nil(t, rpcRes.Error) var ID hexutil.Bytes err = json.Unmarshal(rpcRes.Result, &ID) require.NoError(t, err) diff --git a/x/evm/types/state_object.go b/x/evm/types/state_object.go index 9bf50e9f..bd1875fb 100644 --- a/x/evm/types/state_object.go +++ b/x/evm/types/state_object.go @@ -142,6 +142,10 @@ func (so *stateObject) setState(key, value ethcmn.Hash) { so.dirtyStorage = append(so.dirtyStorage, NewState(key, value)) idx = len(so.dirtyStorage) - 1 so.keyToDirtyStorageIndex[key] = idx + + so.originStorage = append(so.originStorage, State{}) + idx = len(so.originStorage) - 1 + so.keyToOriginStorageIndex[key] = idx } // SetCode sets the state object's code. @@ -242,9 +246,8 @@ func (so *stateObject) commitState() { ctx := so.stateDB.ctx store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), AddressStoragePrefix(so.Address())) - for i, state := range so.dirtyStorage { + for _, state := range so.dirtyStorage { delete(so.keyToDirtyStorageIndex, state.Key) - so.dirtyStorage = append(so.dirtyStorage[:i], so.dirtyStorage[i+1:]...) // skip no-op changes, persist actual changes idx, ok := so.keyToOriginStorageIndex[state.Key] @@ -268,6 +271,8 @@ func (so *stateObject) commitState() { store.Set(state.Key.Bytes(), state.Value.Bytes()) } + // clean storage as all entries are dirty + so.dirtyStorage = Storage{} } // commitCode persists the state object's code to the KVStore. diff --git a/x/evm/types/statedb.go b/x/evm/types/statedb.go index 0e54be25..0282f934 100644 --- a/x/evm/types/statedb.go +++ b/x/evm/types/statedb.go @@ -16,7 +16,6 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" ethvm "github.com/ethereum/go-ethereum/core/vm" ethcrypto "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" ) var ( @@ -745,13 +744,8 @@ func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, valu continue } - _, content, _, err := rlp.Split(value.Bytes()) - if err != nil { - return err - } - // check if iteration stops - if cb(key, ethcmn.BytesToHash(content)) { + if cb(key, value) { return nil } } diff --git a/x/evm/types/statedb_test.go b/x/evm/types/statedb_test.go index 716e157e..4881d2e2 100644 --- a/x/evm/types/statedb_test.go +++ b/x/evm/types/statedb_test.go @@ -1,6 +1,7 @@ package types_test import ( + "fmt" "math/big" "testing" @@ -111,7 +112,7 @@ func (suite *StateDBTestSuite) TestBloomFilter() { } } -func (suite *StateDBTestSuite) TestStateDBBalance() { +func (suite *StateDBTestSuite) TestStateDB_Balance() { testCase := []struct { name string malleate func() @@ -159,7 +160,17 @@ func (suite *StateDBTestSuite) TestStateDBNonce() { suite.Require().Equal(nonce, suite.stateDB.GetNonce(suite.address)) } -func (suite *StateDBTestSuite) TestStateDBState() { +func (suite *StateDBTestSuite) TestStateDB_Error() { + nonce := suite.stateDB.GetNonce(ethcmn.Address{}) + suite.Require().Equal(0, int(nonce)) + suite.Require().Error(suite.stateDB.Error()) +} + +func (suite *StateDBTestSuite) TestStateDB_Database() { + suite.Require().Nil(suite.stateDB.Database()) +} + +func (suite *StateDBTestSuite) TestStateDB_State() { key := ethcmn.BytesToHash([]byte("foo")) val := ethcmn.BytesToHash([]byte("bar")) suite.stateDB.SetState(suite.address, key, val) @@ -195,7 +206,7 @@ func (suite *StateDBTestSuite) TestStateDBState() { } } -func (suite *StateDBTestSuite) TestStateDBCode() { +func (suite *StateDBTestSuite) TestStateDB_Code() { testCase := []struct { name string address ethcmn.Address @@ -232,7 +243,7 @@ func (suite *StateDBTestSuite) TestStateDBCode() { } } -func (suite *StateDBTestSuite) TestStateDBLogs() { +func (suite *StateDBTestSuite) TestStateDB_Logs() { testCase := []struct { name string log ethtypes.Log @@ -278,16 +289,15 @@ func (suite *StateDBTestSuite) TestStateDBLogs() { } } -func (suite *StateDBTestSuite) TestStateDBPreimage() { +func (suite *StateDBTestSuite) TestStateDB_Preimage() { hash := ethcmn.BytesToHash([]byte("hash")) preimage := []byte("preimage") suite.stateDB.AddPreimage(hash, preimage) - suite.Require().Equal(preimage, suite.stateDB.Preimages()[hash]) } -func (suite *StateDBTestSuite) TestStateDBRefund() { +func (suite *StateDBTestSuite) TestStateDB_Refund() { testCase := []struct { name string addAmount uint64 @@ -331,7 +341,7 @@ func (suite *StateDBTestSuite) TestStateDBRefund() { } } -func (suite *StateDBTestSuite) TestStateDBCreateAcct() { +func (suite *StateDBTestSuite) TestStateDB_CreateAccount() { prevBalance := big.NewInt(12) testCase := []struct { @@ -364,7 +374,7 @@ func (suite *StateDBTestSuite) TestStateDBCreateAcct() { } } -func (suite *StateDBTestSuite) TestStateDBClearStateOjb() { +func (suite *StateDBTestSuite) TestStateDB_ClearStateObj() { priv, err := crypto.GenerateKey() suite.Require().NoError(err) @@ -377,7 +387,7 @@ func (suite *StateDBTestSuite) TestStateDBClearStateOjb() { suite.Require().False(suite.stateDB.Exist(addr)) } -func (suite *StateDBTestSuite) TestStateDBReset() { +func (suite *StateDBTestSuite) TestStateDB_Reset() { priv, err := crypto.GenerateKey() suite.Require().NoError(err) @@ -391,7 +401,7 @@ func (suite *StateDBTestSuite) TestStateDBReset() { suite.Require().False(suite.stateDB.Exist(addr)) } -func (suite *StateDBTestSuite) TestSuiteDBPrepare() { +func (suite *StateDBTestSuite) TestSuiteDB_Prepare() { thash := ethcmn.BytesToHash([]byte("thash")) bhash := ethcmn.BytesToHash([]byte("bhash")) txi := 1 @@ -402,7 +412,7 @@ func (suite *StateDBTestSuite) TestSuiteDBPrepare() { suite.Require().Equal(bhash, suite.stateDB.BlockHash()) } -func (suite *StateDBTestSuite) TestSuiteDBCopyState() { +func (suite *StateDBTestSuite) TestSuiteDB_CopyState() { testCase := []struct { name string log ethtypes.Log @@ -439,16 +449,14 @@ func (suite *StateDBTestSuite) TestSuiteDBCopyState() { } } -func (suite *StateDBTestSuite) TestSuiteDBEmpty() { +func (suite *StateDBTestSuite) TestSuiteDB_Empty() { suite.Require().True(suite.stateDB.Empty(suite.address)) suite.stateDB.SetBalance(suite.address, big.NewInt(100)) - suite.Require().False(suite.stateDB.Empty(suite.address)) } -func (suite *StateDBTestSuite) TestSuiteDBSuicide() { - +func (suite *StateDBTestSuite) TestSuiteDB_Suicide() { testCase := []struct { name string amount *big.Int @@ -600,6 +608,8 @@ func (suite *StateDBTestSuite) TestCommitStateDB_Finalize() { if !tc.expPass { suite.Require().Error(err, tc.name) + hash := suite.stateDB.GetCommittedState(suite.address, ethcmn.BytesToHash([]byte("key"))) + suite.Require().NotEqual(ethcmn.Hash{}, hash, tc.name) continue } @@ -614,3 +624,86 @@ func (suite *StateDBTestSuite) TestCommitStateDB_Finalize() { suite.Require().NotNil(acc, tc.name) } } +func (suite *StateDBTestSuite) TestCommitStateDB_GetCommittedState() { + hash := suite.stateDB.GetCommittedState(ethcmn.Address{}, ethcmn.BytesToHash([]byte("key"))) + suite.Require().Equal(ethcmn.Hash{}, hash) +} + +func (suite *StateDBTestSuite) TestCommitStateDB_Snapshot() { + id := suite.stateDB.Snapshot() + suite.Require().NotPanics(func() { + suite.stateDB.RevertToSnapshot(id) + }) + + suite.Require().Panics(func() { + suite.stateDB.RevertToSnapshot(-1) + }, "invalid revision should panic") +} + +func (suite *StateDBTestSuite) TestCommitStateDB_ForEachStorage() { + var storage types.Storage + + testCase := []struct { + name string + malleate func() + callback func(key, value ethcmn.Hash) (stop bool) + expValues []ethcmn.Hash + }{ + { + "aggregate state", + func() { + for i := 0; i < 5; i++ { + suite.stateDB.SetState(suite.address, ethcmn.BytesToHash([]byte(fmt.Sprintf("key%d", i))), ethcmn.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) + } + }, + func(key, value ethcmn.Hash) bool { + storage = append(storage, types.NewState(key, value)) + return false + }, + []ethcmn.Hash{ + ethcmn.BytesToHash([]byte("value0")), + ethcmn.BytesToHash([]byte("value1")), + ethcmn.BytesToHash([]byte("value2")), + ethcmn.BytesToHash([]byte("value3")), + ethcmn.BytesToHash([]byte("value4")), + }, + }, + { + "filter state", + func() { + suite.stateDB.SetState(suite.address, ethcmn.BytesToHash([]byte("key")), ethcmn.BytesToHash([]byte("value"))) + suite.stateDB.SetState(suite.address, ethcmn.BytesToHash([]byte("filterkey")), ethcmn.BytesToHash([]byte("filtervalue"))) + }, + func(key, value ethcmn.Hash) bool { + if value == ethcmn.BytesToHash([]byte("filtervalue")) { + storage = append(storage, types.NewState(key, value)) + return true + } + return false + }, + []ethcmn.Hash{ + ethcmn.BytesToHash([]byte("filtervalue")), + }, + }, + } + + for _, tc := range testCase { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.malleate() + suite.stateDB.Finalise(false) + + err := suite.stateDB.ForEachStorage(suite.address, tc.callback) + suite.Require().NoError(err) + suite.Require().Equal(len(tc.expValues), len(storage), fmt.Sprintf("Expected values:\n%v\nStorage Values\n%v", tc.expValues, storage)) + + vals := make([]ethcmn.Hash, len(storage)) + for i := range storage { + vals[i] = storage[i].Value + } + + suite.Require().ElementsMatch(tc.expValues, vals) + }) + storage = types.Storage{} + } +}