From fef16af3821b6b401c6c57c0908616f9cea29a4d Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Mon, 13 Jul 2020 18:30:24 +0200 Subject: [PATCH] x/evm: journal tests (#384) * x/evm: journal tests * comment tests * cleanup setup * fixes * add test for various logs * lint * minor fix --- x/evm/types/journal.go | 4 +- x/evm/types/journal_test.go | 253 ++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 x/evm/types/journal_test.go diff --git a/x/evm/types/journal.go b/x/evm/types/journal.go index 45a47033..366574d8 100644 --- a/x/evm/types/journal.go +++ b/x/evm/types/journal.go @@ -210,9 +210,11 @@ func (ch addLogChange) revert(s *CommitStateDB) { panic(err) } - if len(logs) == 1 { + // delete logs if entry is empty or has only one item + if len(logs) <= 1 { s.DeleteLogs(ch.txhash) } else if err := s.SetLogs(ch.txhash, logs[:len(logs)-1]); err != nil { + // panic on marshal error panic(err) } diff --git a/x/evm/types/journal_test.go b/x/evm/types/journal_test.go new file mode 100644 index 00000000..8b71ed4a --- /dev/null +++ b/x/evm/types/journal_test.go @@ -0,0 +1,253 @@ +package types + +import ( + "os" + "testing" + + "github.com/stretchr/testify/suite" + + abci "github.com/tendermint/tendermint/abci/types" + tmlog "github.com/tendermint/tendermint/libs/log" + tmdb "github.com/tendermint/tm-db" + + sdkcodec "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/params" + + ethcmn "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + + "github.com/cosmos/ethermint/codec" + "github.com/cosmos/ethermint/crypto" + ethermint "github.com/cosmos/ethermint/types" +) + +type JournalTestSuite struct { + suite.Suite + + address ethcmn.Address + journal *journal + ctx sdk.Context + stateDB *CommitStateDB +} + +func newTestCodec() *codec.Codec { + cdc := sdkcodec.New() + + RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + crypto.RegisterCodec(cdc) + sdkcodec.RegisterCrypto(cdc) + ethermint.RegisterCodec(cdc) + + appCodec := codec.NewAppCodec(cdc) + + return appCodec +} + +func (suite *JournalTestSuite) SetupTest() { + suite.setup() + + privkey, err := crypto.GenerateKey() + suite.Require().NoError(err) + + suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes()) + suite.journal = newJournal() + + acc := ðermint.EthAccount{ + BaseAccount: auth.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0), + CodeHash: ethcrypto.Keccak256(nil), + } + + suite.stateDB.accountKeeper.SetAccount(suite.ctx, acc) + suite.stateDB.bankKeeper.SetBalance(suite.ctx, sdk.AccAddress(suite.address.Bytes()), sdk.NewCoin(ethermint.DenomDefault, sdk.NewInt(100))) + suite.stateDB.SetLogs(ethcmn.BytesToHash([]byte("txhash")), []*ethtypes.Log{ + { + Address: suite.address, + Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic_0"))}, + Data: []byte("data_0"), + BlockNumber: 1, + TxHash: ethcmn.BytesToHash([]byte("tx_hash")), + TxIndex: 1, + BlockHash: ethcmn.BytesToHash([]byte("block_hash")), + Index: 1, + Removed: false, + }, + { + Address: suite.address, + Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic_1"))}, + Data: []byte("data_1"), + BlockNumber: 10, + TxHash: ethcmn.BytesToHash([]byte("tx_hash")), + TxIndex: 0, + BlockHash: ethcmn.BytesToHash([]byte("block_hash")), + Index: 0, + Removed: false, + }, + }) +} + +// setup performs a manual setup of the GoLevelDB and mounts the required IAVL stores. We use the manual +// setup here instead of the Ethermint app test setup because the journal methods are private and using +// the latter would result in a cycle dependency. We also want to avoid declaring the journal methods public +// to maintain consistency with the Geth implementation. +func (suite *JournalTestSuite) setup() { + authKey := sdk.NewKVStoreKey(auth.StoreKey) + bankKey := sdk.NewKVStoreKey(bank.StoreKey) + storeKey := sdk.NewKVStoreKey(StoreKey) + + db := tmdb.NewDB("state", tmdb.GoLevelDBBackend, "temp") + defer func() { + os.RemoveAll("temp") + }() + + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(authKey, sdk.StoreTypeIAVL, db) + cms.MountStoreWithDB(bankKey, sdk.StoreTypeIAVL, db) + cms.MountStoreWithDB(storeKey, sdk.StoreTypeIAVL, db) + + err := cms.LoadLatestVersion() + suite.Require().NoError(err) + + appCodec := newTestCodec() + + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + paramsKeeper := params.NewKeeper(appCodec, keyParams, tkeyParams) + + authSubspace := paramsKeeper.Subspace(auth.DefaultParamspace) + bankSubspace := paramsKeeper.Subspace(bank.DefaultParamspace) + + ak := auth.NewAccountKeeper(appCodec, authKey, authSubspace, ethermint.ProtoAccount) + bk := bank.NewBaseKeeper(appCodec, bankKey, ak, bankSubspace, nil) + + suite.ctx = sdk.NewContext(cms, abci.Header{ChainID: "8"}, false, tmlog.NewNopLogger()) + suite.stateDB = NewCommitStateDB(suite.ctx, storeKey, ak, bk).WithContext(suite.ctx) +} + +func TestJournalTestSuite(t *testing.T) { + suite.Run(t, new(JournalTestSuite)) +} + +func (suite *JournalTestSuite) TestJournal_append_revert() { + testCases := []struct { + name string + entry journalEntry + }{ + { + "createObjectChange", + createObjectChange{ + account: &suite.address, + }, + }, + { + "resetObjectChange", + resetObjectChange{ + prev: &stateObject{ + address: suite.address, + balance: sdk.OneInt(), + }, + }, + }, + { + "suicideChange", + suicideChange{ + account: &suite.address, + prev: false, + prevBalance: sdk.OneInt(), + }, + }, + { + "balanceChange", + balanceChange{ + account: &suite.address, + prev: sdk.OneInt(), + }, + }, + { + "nonceChange", + nonceChange{ + account: &suite.address, + prev: 1, + }, + }, + { + "storageChange", + storageChange{ + account: &suite.address, + key: ethcmn.BytesToHash([]byte("key")), + prevValue: ethcmn.BytesToHash([]byte("value")), + }, + }, + { + "codeChange", + codeChange{ + account: &suite.address, + prevCode: []byte("code"), + prevHash: []byte("hash"), + }, + }, + { + "touchChange", + touchChange{ + account: &suite.address, + }, + }, + { + "refundChange", + refundChange{ + prev: 1, + }, + }, + { + "addPreimageChange", + addPreimageChange{ + hash: ethcmn.BytesToHash([]byte("hash")), + }, + }, + { + "addLogChange", + addLogChange{ + txhash: ethcmn.BytesToHash([]byte("hash")), + }, + }, + { + "addLogChange - 2 logs", + addLogChange{ + txhash: ethcmn.BytesToHash([]byte("txhash")), + }, + }, + } + var dirtyCount int + for i, tc := range testCases { + suite.journal.append(tc.entry) + suite.Require().Equal(suite.journal.length(), i+1, tc.name) + if tc.entry.dirtied() != nil { + dirtyCount++ + suite.Require().Equal(dirtyCount, suite.journal.dirties[suite.address], tc.name) + } + } + + // revert to the initial journal state + suite.journal.revert(suite.stateDB, 0) + + // verify the dirty entry has been deleted + count, ok := suite.journal.dirties[suite.address] + suite.Require().False(ok) + suite.Require().Zero(count) +} + +func (suite *JournalTestSuite) TestJournal_dirty() { + // dirty entry hasn't been set + count, ok := suite.journal.dirties[suite.address] + suite.Require().False(ok) + suite.Require().Zero(count) + + // update dirty count + suite.journal.dirty(suite.address) + suite.Require().Equal(1, suite.journal.dirties[suite.address]) +}