From 6c1e7fec01dbd4d711cc7269f91204e6d393790b Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Tue, 25 May 2021 08:56:36 -0400 Subject: [PATCH] app, ante, evm: `Keeper` `StateDB` refactor (#30) * evm: keeper statedb refactor * keeper: implement stateDB account, balance, nonce and suicide functions * keeper: implement stateDB code and iterator functions * keeper: implement stateDB log and preimage functions * update code to use CommitStateDB * tests updates * journal changes (wip) * cache fields * journal and logs * minor cleanup * evm: remove journal related changes * evm: delete empty account code and storage state * app, evm: transient store * ante, evm: refund gas transient * evm: remove transient keeper state fields * address comments from review * evm: undo revision change --- app/ante/ante.go | 2 +- app/ante/eth.go | 17 +- app/app.go | 5 +- tests/importer/importer_test.go | 16 +- x/evm/genesis.go | 20 +- x/evm/handler_test.go | 11 +- x/evm/keeper/abci.go | 20 +- x/evm/keeper/grpc_query.go | 20 +- x/evm/keeper/grpc_query_test.go | 10 +- x/evm/keeper/invariants.go | 8 +- x/evm/keeper/invariants_test.go | 10 +- x/evm/keeper/keeper.go | 202 ++++- x/evm/keeper/keeper_test.go | 32 +- x/evm/keeper/msg_server.go | 19 +- x/evm/keeper/params.go | 25 +- x/evm/keeper/statedb.go | 708 +++++++++++++----- x/evm/keeper/statedb_test.go | 643 ---------------- x/evm/types/access_list.go | 42 +- x/evm/types/access_list_test.go | 14 +- .../{expected_keepers.go => interfaces.go} | 3 + x/evm/types/key.go | 63 +- x/evm/types/state_object.go | 10 +- x/evm/types/state_transition.go | 3 +- x/evm/types/state_transition_test.go | 4 +- x/evm/types/statedb.go | 8 +- 25 files changed, 924 insertions(+), 991 deletions(-) rename x/evm/types/{expected_keepers.go => interfaces.go} (81%) diff --git a/app/ante/ante.go b/app/ante/ante.go index 13629a26..5d22260d 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -67,7 +67,7 @@ func NewAnteHandler( // handle as *evmtypes.MsgEthereumTx anteHandler = sdk.ChainAnteDecorators( - NewEthSetupContextDecorator(), // outermost AnteDecorator. EthSetUpContext must be called first + NewEthSetupContextDecorator(evmKeeper), // outermost AnteDecorator. EthSetUpContext must be called first NewEthMempoolFeeDecorator(evmKeeper), NewEthValidateBasicDecorator(), authante.TxTimeoutHeightDecorator{}, diff --git a/app/ante/eth.go b/app/ante/eth.go index e835bfda..01aa842c 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -22,6 +22,8 @@ import ( type EVMKeeper interface { GetParams(ctx sdk.Context) evmtypes.Params GetChainConfig(ctx sdk.Context) (evmtypes.ChainConfig, bool) + WithContext(ctx sdk.Context) + ResetRefundTransient(ctx sdk.Context) } // EthSetupContextDecorator sets the infinite GasMeter in the Context and wraps @@ -30,11 +32,15 @@ type EVMKeeper interface { // on gas provided and gas used. // CONTRACT: Must be first decorator in the chain // CONTRACT: Tx must implement GasTx interface -type EthSetupContextDecorator struct{} +type EthSetupContextDecorator struct { + evmKeeper EVMKeeper +} // NewEthSetupContextDecorator creates a new EthSetupContextDecorator -func NewEthSetupContextDecorator() EthSetupContextDecorator { - return EthSetupContextDecorator{} +func NewEthSetupContextDecorator(ek EVMKeeper) EthSetupContextDecorator { + return EthSetupContextDecorator{ + evmKeeper: ek, + } } // AnteHandle sets the infinite gas meter to done to ignore costs in AnteHandler checks. @@ -43,6 +49,9 @@ func NewEthSetupContextDecorator() EthSetupContextDecorator { func (escd EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + // reset the refund gas value for the current transaction + escd.evmKeeper.ResetRefundTransient(ctx) + // all transactions must implement GasTx gasTx, ok := tx.(authante.GasTx) if !ok { @@ -431,6 +440,8 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula // Set gas meter after ante handler to ignore gaskv costs newCtx = authante.SetGasMeter(simulate, ctx, gasLimit) + egcd.evmKeeper.WithContext(newCtx) + return next(newCtx, tx, simulate) } diff --git a/app/app.go b/app/app.go index 871a1c27..c2e2126e 100644 --- a/app/app.go +++ b/app/app.go @@ -252,7 +252,8 @@ func NewEthermintApp( evmtypes.StoreKey, ) - tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) + // Add the EVM transient store key + tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey, evmtypes.TransientKey) memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) app := &EthermintApp{ @@ -310,7 +311,7 @@ func NewEthermintApp( // Create Ethermint keepers app.EvmKeeper = evmkeeper.NewKeeper( - appCodec, keys[evmtypes.StoreKey], app.GetSubspace(evmtypes.ModuleName), app.AccountKeeper, app.BankKeeper, + appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], app.GetSubspace(evmtypes.ModuleName), app.AccountKeeper, app.BankKeeper, ) // Create IBC Keeper diff --git a/tests/importer/importer_test.go b/tests/importer/importer_test.go index a50692fc..4470c6a1 100644 --- a/tests/importer/importer_test.go +++ b/tests/importer/importer_test.go @@ -105,6 +105,7 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak authkeeper. genBlock := ethcore.DefaultGenesisBlock() ms := cms.CacheMultiStore() ctx := sdk.NewContext(ms, tmproto.Header{}, false, logger) + evmKeeper.CommitStateDB.WithContext(ctx) // Set the default Ethermint parameters to the parameter keeper store evmKeeper.SetParams(ctx, evmtypes.DefaultParams()) @@ -123,23 +124,23 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak authkeeper. addr := ethcmn.HexToAddress(addrStr) acc := genBlock.Alloc[addr] - evmKeeper.AddBalance(ctx, addr, acc.Balance) - evmKeeper.SetCode(ctx, addr, acc.Code) - evmKeeper.SetNonce(ctx, addr, acc.Nonce) + evmKeeper.CommitStateDB.AddBalance(addr, acc.Balance) + evmKeeper.CommitStateDB.SetCode(addr, acc.Code) + evmKeeper.CommitStateDB.SetNonce(addr, acc.Nonce) for key, value := range acc.Storage { - evmKeeper.SetState(ctx, addr, key, value) + evmKeeper.CommitStateDB.SetState(addr, key, value) } } // get balance of one of the genesis account having 400 ETH - b := evmKeeper.GetBalance(ctx, genInvestor) + b := evmKeeper.CommitStateDB.GetBalance(genInvestor) require.Equal(t, "200000000000000000000", b.String()) // commit the stateDB with 'false' to delete empty objects // // NOTE: Commit does not yet return the intra merkle root (version) - _, err := evmKeeper.Commit(ctx, false) + _, err := evmKeeper.CommitStateDB.Commit(false) require.NoError(t, err) // persist multi-store cache state @@ -257,13 +258,14 @@ func TestImportBlocks(t *testing.T) { ms := cms.CacheMultiStore() ctx := sdk.NewContext(ms, tmproto.Header{}, false, logger) ctx = ctx.WithBlockHeight(int64(block.NumberU64())) + evmKeeper.CommitStateDB.WithContext(ctx) if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 { applyDAOHardFork(evmKeeper) } for i, tx := range block.Transactions() { - evmKeeper.Prepare(ctx, tx.Hash(), block.Hash(), i) + evmKeeper.CommitStateDB.Prepare(tx.Hash(), block.Hash(), i) // evmKeeper.CommitStateDB.Set(block.Hash()) receipt, gas, err := applyTransaction( diff --git a/x/evm/genesis.go b/x/evm/genesis.go index 897b8d93..802b0e60 100644 --- a/x/evm/genesis.go +++ b/x/evm/genesis.go @@ -21,6 +21,8 @@ func InitGenesis( bankKeeper types.BankKeeper, data types.GenesisState, ) []abci.ValidatorUpdate { + k.CommitStateDB.WithContext(ctx) + k.SetParams(ctx, data.Params) evmDenom := data.Params.EvmDenom @@ -43,18 +45,18 @@ func InitGenesis( } evmBalance := bankKeeper.GetBalance(ctx, accAddress, evmDenom) - k.SetBalance(ctx, address, evmBalance.Amount.BigInt()) - k.SetNonce(ctx, address, acc.GetSequence()) - k.SetCode(ctx, address, ethcmn.Hex2Bytes(account.Code)) + k.CommitStateDB.SetBalance(address, evmBalance.Amount.BigInt()) + k.CommitStateDB.SetNonce(address, acc.GetSequence()) + k.CommitStateDB.SetCode(address, ethcmn.Hex2Bytes(account.Code)) for _, storage := range account.Storage { - k.SetState(ctx, address, ethcmn.HexToHash(storage.Key), ethcmn.HexToHash(storage.Value)) + k.SetState(address, ethcmn.HexToHash(storage.Key), ethcmn.HexToHash(storage.Value)) } } var err error for _, txLog := range data.TxsLogs { - err = k.SetLogs(ctx, ethcmn.HexToHash(txLog.Hash), txLog.EthLogs()) + err = k.CommitStateDB.SetLogs(ethcmn.HexToHash(txLog.Hash), txLog.EthLogs()) if err != nil { panic(err) } @@ -63,14 +65,14 @@ func InitGenesis( k.SetChainConfig(ctx, data.ChainConfig) // set state objects and code to store - _, err = k.Commit(ctx, false) + _, err = k.CommitStateDB.Commit(false) if err != nil { panic(err) } // set storage to store // NOTE: don't delete empty object to prevent import-export simulation failure - err = k.Finalise(ctx, false) + err = k.CommitStateDB.Finalise(false) if err != nil { panic(err) } @@ -80,6 +82,8 @@ func InitGenesis( // ExportGenesis exports genesis state of the EVM module func ExportGenesis(ctx sdk.Context, k keeper.Keeper, ak types.AccountKeeper) *types.GenesisState { + k.CommitStateDB.WithContext(ctx) + // nolint: prealloc var ethGenAccounts []types.GenesisAccount ak.IterateAccounts(ctx, func(account authtypes.AccountI) bool { @@ -98,7 +102,7 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper, ak types.AccountKeeper) *ty genAccount := types.GenesisAccount{ Address: addr.String(), - Code: ethcmn.Bytes2Hex(k.GetCode(ctx, addr)), + Code: ethcmn.Bytes2Hex(k.CommitStateDB.GetCode(addr)), Storage: storage, } diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index 1577b452..58db8af2 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -47,6 +47,7 @@ func (suite *EvmTestSuite) SetupTest() { suite.app = app.Setup(checkTx) suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "ethermint-888", Time: time.Now().UTC()}) + suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx) suite.handler = evm.NewHandler(suite.app.EvmKeeper) suite.codec = suite.app.AppCodec() suite.chainID = suite.chainID @@ -80,7 +81,7 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() { { "passed", func() { - suite.app.EvmKeeper.SetBalance(suite.ctx, suite.from, big.NewInt(100)) + suite.app.EvmKeeper.CommitStateDB.SetBalance(suite.from, big.NewInt(100)) to := ethcmn.BytesToAddress(suite.to) tx = types.NewMsgEthereumTx(suite.chainID, 0, &to, big.NewInt(100), 0, big.NewInt(10000), nil, nil) tx.From = suite.from.String() @@ -193,10 +194,10 @@ func (suite *EvmTestSuite) TestHandlerLogs() { suite.Require().Equal(len(txResponse.TxLogs.Logs[0].Topics), 2) hash := []byte{1} - err = suite.app.EvmKeeper.SetLogs(suite.ctx, ethcmn.BytesToHash(hash), txResponse.TxLogs.EthLogs()) + err = suite.app.EvmKeeper.CommitStateDB.SetLogs(ethcmn.BytesToHash(hash), txResponse.TxLogs.EthLogs()) suite.Require().NoError(err) - logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, ethcmn.BytesToHash(hash)) + logs, err := suite.app.EvmKeeper.CommitStateDB.GetLogs(ethcmn.BytesToHash(hash)) suite.Require().NoError(err, "failed to get logs") suite.Require().Equal(logs, txResponse.TxLogs.Logs) @@ -227,7 +228,7 @@ func (suite *EvmTestSuite) TestQueryTxLogs() { // get logs by tx hash hash := txResponse.TxLogs.Hash - logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, ethcmn.HexToHash(hash)) + logs, err := suite.app.EvmKeeper.CommitStateDB.GetLogs(ethcmn.HexToHash(hash)) suite.Require().NoError(err, "failed to get logs") suite.Require().Equal(logs, txResponse.TxLogs.EthLogs()) @@ -345,7 +346,7 @@ func (suite *EvmTestSuite) TestSendTransaction() { gasLimit := uint64(21000) gasPrice := big.NewInt(0x55ae82600) - suite.app.EvmKeeper.SetBalance(suite.ctx, suite.from, big.NewInt(100)) + suite.app.EvmKeeper.CommitStateDB.SetBalance(suite.from, big.NewInt(100)) // send simple value transfer with gasLimit=21000 tx := types.NewMsgEthereumTx(suite.chainID, 1, ðcmn.Address{0x1}, big.NewInt(1), gasLimit, gasPrice, nil, nil) diff --git a/x/evm/keeper/abci.go b/x/evm/keeper/abci.go index 373ed4ec..2443fb5b 100644 --- a/x/evm/keeper/abci.go +++ b/x/evm/keeper/abci.go @@ -33,10 +33,6 @@ func (k *Keeper) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { // special setter for csdb k.SetHeightHash(ctx, uint64(req.Header.Height), common.BytesToHash(req.Hash)) - - // reset counters that are used on CommitStateDB.Prepare - k.Bloom = big.NewInt(0) - k.TxCount = 0 } // EndBlock updates the accounts and commits state objects to the KV Store, while @@ -48,11 +44,12 @@ func (k Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.Valid // Gas costs are handled within msg handler so costs should be ignored ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + k.CommitStateDB.WithContext(ctx) // Update account balances before committing other parts of state - k.UpdateAccounts(ctx) + k.CommitStateDB.UpdateAccounts() - root, err := k.Commit(ctx, true) + root, err := k.CommitStateDB.Commit(true) // Commit state objects to KV store if err != nil { k.Logger(ctx).Error("failed to commit state objects", "error", err, "height", ctx.BlockHeight()) @@ -60,12 +57,17 @@ func (k Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.Valid } // reset all cache after account data has been committed, that make sure node state consistent - if err = k.Reset(ctx, root); err != nil { + if err = k.CommitStateDB.Reset(root); err != nil { panic(err) } - // set the block bloom filter bytes to store - bloom := ethtypes.BytesToBloom(k.Bloom.Bytes()) + // get the block bloom bytes from the transient store and set it to the persistent storage + bloomBig, found := k.GetBlockBloomTransient() + if !found { + bloomBig = big.NewInt(0) + } + + bloom := ethtypes.BytesToBloom(bloomBig.Bytes()) k.SetBlockBloom(ctx, req.Height, bloom) return []abci.ValidatorUpdate{} diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index 537f9b87..d4355dad 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -32,7 +32,9 @@ func (k Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*typ } ctx := sdk.UnwrapSDKContext(c) - so := k.GetOrNewStateObject(ctx, ethcmn.HexToAddress(req.Address)) + k.CommitStateDB.WithContext(ctx) + + so := k.CommitStateDB.GetOrNewStateObject(ethcmn.HexToAddress(req.Address)) balance, err := ethermint.MarshalBigInt(so.Balance()) if err != nil { return nil, err @@ -57,6 +59,7 @@ func (k Keeper) CosmosAccount(c context.Context, req *types.QueryCosmosAccountRe } ctx := sdk.UnwrapSDKContext(c) + k.CommitStateDB.WithContext(ctx) ethAddr := ethcmn.HexToAddress(req.Address) cosmosAddr := sdk.AccAddress(ethAddr.Bytes()) @@ -88,8 +91,9 @@ func (k Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*typ } ctx := sdk.UnwrapSDKContext(c) + k.CommitStateDB.WithContext(ctx) - balanceInt := k.GetBalance(ctx, ethcmn.HexToAddress(req.Address)) + balanceInt := k.CommitStateDB.GetBalance(ethcmn.HexToAddress(req.Address)) balance, err := ethermint.MarshalBigInt(balanceInt) if err != nil { return nil, status.Error( @@ -117,11 +121,12 @@ func (k Keeper) Storage(c context.Context, req *types.QueryStorageRequest) (*typ } ctx := sdk.UnwrapSDKContext(c) + k.CommitStateDB.WithContext(ctx) address := ethcmn.HexToAddress(req.Address) key := ethcmn.HexToHash(req.Key) - state := k.GetState(ctx, address, key) + state := k.CommitStateDB.GetState(address, key) return &types.QueryStorageResponse{ Value: state.String(), @@ -142,9 +147,10 @@ func (k Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.Que } ctx := sdk.UnwrapSDKContext(c) + k.CommitStateDB.WithContext(ctx) address := ethcmn.HexToAddress(req.Address) - code := k.GetCode(ctx, address) + code := k.CommitStateDB.GetCode(address) return &types.QueryCodeResponse{ Code: code, @@ -165,9 +171,10 @@ func (k Keeper) TxLogs(c context.Context, req *types.QueryTxLogsRequest) (*types } ctx := sdk.UnwrapSDKContext(c) + k.CommitStateDB.WithContext(ctx) hash := ethcmn.HexToHash(req.Hash) - logs, err := k.GetLogs(ctx, hash) + logs, err := k.CommitStateDB.GetLogs(hash) if err != nil { return nil, status.Error( codes.Internal, @@ -296,6 +303,7 @@ func (k Keeper) StaticCall(c context.Context, req *types.QueryStaticCallRequest) } ctx := sdk.UnwrapSDKContext(c) + k.CommitStateDB.WithContext(ctx) // parse the chainID from a string to a base-10 integer chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID()) @@ -312,7 +320,7 @@ func (k Keeper) StaticCall(c context.Context, req *types.QueryStaticCallRequest) recipient = &addr } - so := k.GetOrNewStateObject(ctx, *recipient) + so := k.CommitStateDB.GetOrNewStateObject(*recipient) sender := ethcmn.HexToAddress("0xaDd00275E3d9d213654Ce5223f0FADE8b106b707") msg := types.NewMsgEthereumTx( diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index 0f18d02d..9c9277da 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -247,7 +247,7 @@ func (suite *KeeperTestSuite) TestQueryStorage() { key := ethcmn.BytesToHash([]byte("key")) value := ethcmn.BytesToHash([]byte("value")) expValue = value.String() - suite.app.EvmKeeper.SetState(suite.ctx, suite.address, key, value) + suite.app.EvmKeeper.CommitStateDB.SetState(suite.address, key, value) req = &types.QueryStorageRequest{ Address: suite.address.String(), Key: key.String(), @@ -302,7 +302,7 @@ func (suite *KeeperTestSuite) TestQueryCode() { "success", func() { expCode = []byte("code") - suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, expCode) + suite.app.EvmKeeper.CommitStateDB.SetCode(suite.address, expCode) req = &types.QueryCodeRequest{ Address: suite.address.String(), @@ -379,7 +379,7 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() { }, } - suite.app.EvmKeeper.SetLogs(suite.ctx, hash, types.LogsToEthereum(expLogs)) + suite.app.EvmKeeper.CommitStateDB.SetLogs(hash, types.LogsToEthereum(expLogs)) req = &types.QueryTxLogsRequest{ Hash: hash.String(), @@ -488,8 +488,8 @@ func (suite *KeeperTestSuite) TestQueryBlockLogs() { }, } - suite.app.EvmKeeper.SetLogs(suite.ctx, ethcmn.BytesToHash([]byte("tx_hash_0")), types.LogsToEthereum(expLogs[0].Logs)) - suite.app.EvmKeeper.SetLogs(suite.ctx, ethcmn.BytesToHash([]byte("tx_hash_1")), types.LogsToEthereum(expLogs[1].Logs)) + suite.app.EvmKeeper.CommitStateDB.SetLogs(ethcmn.BytesToHash([]byte("tx_hash_0")), types.LogsToEthereum(expLogs[0].Logs)) + suite.app.EvmKeeper.CommitStateDB.SetLogs(ethcmn.BytesToHash([]byte("tx_hash_1")), types.LogsToEthereum(expLogs[1].Logs)) req = &types.QueryBlockLogsRequest{ Hash: hash.String(), diff --git a/x/evm/keeper/invariants.go b/x/evm/keeper/invariants.go index c2bbc3fb..17713e77 100644 --- a/x/evm/keeper/invariants.go +++ b/x/evm/keeper/invariants.go @@ -30,6 +30,8 @@ func (k Keeper) BalanceInvariant() sdk.Invariant { count int ) + k.CommitStateDB.WithContext(ctx) + k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) bool { ethAccount, ok := account.(*ethermint.EthAccount) if !ok { @@ -40,7 +42,7 @@ func (k Keeper) BalanceInvariant() sdk.Invariant { evmDenom := k.GetParams(ctx).EvmDenom accountBalance := k.bankKeeper.GetBalance(ctx, ethAccount.GetAddress(), evmDenom) - evmBalance := k.GetBalance(ctx, ethAccount.EthAddress()) + evmBalance := k.CommitStateDB.GetBalance(ethAccount.EthAddress()) if evmBalance.Cmp(accountBalance.Amount.BigInt()) != 0 { count++ @@ -71,6 +73,8 @@ func (k Keeper) NonceInvariant() sdk.Invariant { count int ) + k.CommitStateDB.WithContext(ctx) + k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) bool { ethAccount, ok := account.(*ethermint.EthAccount) if !ok { @@ -78,7 +82,7 @@ func (k Keeper) NonceInvariant() sdk.Invariant { return false } - evmNonce := k.GetNonce(ctx, ethAccount.EthAddress()) + evmNonce := k.CommitStateDB.GetNonce(ethAccount.EthAddress()) if evmNonce != ethAccount.Sequence { count++ diff --git a/x/evm/keeper/invariants_test.go b/x/evm/keeper/invariants_test.go index 5dac7fb7..b8bb167c 100644 --- a/x/evm/keeper/invariants_test.go +++ b/x/evm/keeper/invariants_test.go @@ -29,7 +29,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() { suite.Require().NoError(err) suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - suite.app.EvmKeeper.SetBalance(suite.ctx, address, big.NewInt(1000)) + suite.app.EvmKeeper.CommitStateDB.SetBalance(address, big.NewInt(1000)) }, true, }, @@ -42,7 +42,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() { suite.Require().NoError(err) suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - suite.app.EvmKeeper.SetBalance(suite.ctx, address, big.NewInt(1)) + suite.app.EvmKeeper.CommitStateDB.SetBalance(address, big.NewInt(1)) }, false, }, @@ -60,6 +60,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() { suite.Run(tc.name, func() { suite.SetupTest() // reset values + suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx) tc.malleate() _, broken := suite.app.EvmKeeper.BalanceInvariant()(suite.ctx) @@ -92,7 +93,7 @@ func (suite *KeeperTestSuite) TestNonceInvariant() { suite.Require().NoError(err) suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - suite.app.EvmKeeper.SetNonce(suite.ctx, address, 100) + suite.app.EvmKeeper.CommitStateDB.SetNonce(address, 100) }, true, }, @@ -105,7 +106,7 @@ func (suite *KeeperTestSuite) TestNonceInvariant() { suite.Require().NoError(err) suite.app.AccountKeeper.SetAccount(suite.ctx, acc) - suite.app.EvmKeeper.SetNonce(suite.ctx, address, 1) + suite.app.EvmKeeper.CommitStateDB.SetNonce(address, 1) }, false, }, @@ -123,6 +124,7 @@ func (suite *KeeperTestSuite) TestNonceInvariant() { suite.Run(tc.name, func() { suite.SetupTest() // reset values + suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx) tc.malleate() _, broken := suite.app.EvmKeeper.NonceInvariant()(suite.ctx) diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 87e5432a..3ceee7b9 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -1,9 +1,11 @@ package keeper import ( + "bytes" "math/big" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/ethereum/go-ethereum/common" @@ -26,25 +28,28 @@ type Keeper struct { // - storing block hash -> block height map. Needed for the Web3 API. TODO: remove storeKey sdk.StoreKey + // key to access the transient store, which is reset on every block during Commit + transientKey sdk.StoreKey + + paramSpace paramtypes.Subspace accountKeeper types.AccountKeeper bankKeeper types.BankKeeper + ctx sdk.Context + // Ethermint concrete implementation on the EVM StateDB interface CommitStateDB *types.CommitStateDB - // Transaction counter in a block. Used on StateSB's Prepare function. - // It is reset to 0 every block on BeginBlock so there's no point in storing the counter - // on the KVStore or adding it as a field on the EVM genesis state. - TxCount int - Bloom *big.Int - // LogsCache keeps mapping of contract address -> eth logs emitted - // during EVM execution in the current block. - LogsCache map[common.Address][]*ethtypes.Log + // Per-transaction access list + // See EIP-2930 for more info: https://eips.ethereum.org/EIPS/eip-2930 + // TODO: (@fedekunze) for how long should we persist the entries in the access list? + // same block (i.e Transient Store)? 2 or more (KVStore with module Parameter which resets the state after that window)? + accessList *types.AccessListMappings } // NewKeeper generates new evm module keeper func NewKeeper( - cdc codec.BinaryMarshaler, storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, + cdc codec.BinaryMarshaler, storeKey, transientKey sdk.StoreKey, paramSpace paramtypes.Subspace, ak types.AccountKeeper, bankKeeper types.BankKeeper, ) *Keeper { // set KeyTable if it has not already been set @@ -55,13 +60,13 @@ func NewKeeper( // NOTE: we pass in the parameter space to the CommitStateDB in order to use custom denominations for the EVM operations return &Keeper{ cdc: cdc, + paramSpace: paramSpace, accountKeeper: ak, bankKeeper: bankKeeper, storeKey: storeKey, + transientKey: transientKey, CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, paramSpace, ak, bankKeeper), - TxCount: 0, - Bloom: big.NewInt(0), - LogsCache: map[common.Address][]*ethtypes.Log{}, + accessList: types.NewAccessListMappings(), } } @@ -70,8 +75,13 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", types.ModuleName) } +// WithContext sets an updated SDK context to the keeper +func (k *Keeper) WithContext(ctx sdk.Context) { + k.ctx = ctx +} + // ---------------------------------------------------------------------------- -// Block bloom bits mapping functions +// Block Bloom // Required by Web3 API. // ---------------------------------------------------------------------------- @@ -94,6 +104,28 @@ func (k Keeper) SetBlockBloom(ctx sdk.Context, height int64, bloom ethtypes.Bloo store.Set(key, bloom.Bytes()) } +// GetBlockBloomTransient returns bloom bytes for the current block height +func (k Keeper) GetBlockBloomTransient() (*big.Int, bool) { + store := k.ctx.TransientStore(k.transientKey) + bz := store.Get(types.KeyPrefixTransientBloom) + if len(bz) == 0 { + return nil, false + } + + return new(big.Int).SetBytes(bz), true +} + +// SetBlockBloomTransient sets the given bloom bytes to the transient store. This value is reset on +// every block. +func (k Keeper) SetBlockBloomTransient(bloom *big.Int) { + store := k.ctx.TransientStore(k.transientKey) + store.Set(types.KeyPrefixTransientBloom, bloom.Bytes()) +} + +// ---------------------------------------------------------------------------- +// Block +// ---------------------------------------------------------------------------- + // GetBlockHash gets block height from block consensus hash func (k Keeper) GetBlockHashFromHeight(ctx sdk.Context, height int64) (common.Hash, bool) { store := ctx.KVStore(k.storeKey) @@ -150,11 +182,40 @@ func (k Keeper) SetHeightHash(ctx sdk.Context, height uint64, hash common.Hash) k.CommitStateDB.WithContext(ctx).SetHeightHash(height, hash) } +// ---------------------------------------------------------------------------- +// Tx +// ---------------------------------------------------------------------------- + +// GetTxIndexTransient returns EVM transaction index on the current block. +func (k Keeper) GetTxIndexTransient() uint64 { + store := k.ctx.TransientStore(k.transientKey) + bz := store.Get(types.KeyPrefixTransientBloom) + if len(bz) == 0 { + return 0 + } + + return sdk.BigEndianToUint64(bz) +} + +// IncreaseTxIndexTransient fetches the current EVM tx index from the transient store, increases its +// value by one and then sets the new index back to the transient store. +func (k Keeper) IncreaseTxIndexTransient() { + txIndex := k.GetTxIndexTransient() + store := k.ctx.TransientStore(k.transientKey) + store.Set(types.KeyPrefixTransientBloom, sdk.Uint64ToBigEndian(txIndex+1)) +} + +// ResetRefundTransient resets the refund gas value. +func (k Keeper) ResetRefundTransient(ctx sdk.Context) { + store := ctx.TransientStore(k.transientKey) + store.Delete(types.KeyPrefixTransientRefund) +} + // GetTxReceiptFromHash gets tx receipt by tx hash. func (k Keeper) GetTxReceiptFromHash(ctx sdk.Context, hash common.Hash) (*types.TxReceipt, bool) { store := ctx.KVStore(k.storeKey) data := store.Get(types.KeyHashTxReceipt(hash)) - if data == nil || len(data) == 0 { + if len(data) == 0 { return nil, false } @@ -216,7 +277,7 @@ func (k Keeper) GetTxReceiptsByBlockHeight(ctx sdk.Context, blockHeight int64) [ for idx, txHash := range txs { data := store.Get(types.KeyHashTxReceipt(txHash)) - if data == nil || len(data) == 0 { + if len(data) == 0 { continue } @@ -239,6 +300,10 @@ func (k Keeper) GetTxReceiptsByBlockHash(ctx sdk.Context, hash common.Hash) []*t return k.GetTxReceiptsByBlockHeight(ctx, blockHeight) } +// ---------------------------------------------------------------------------- +// Log +// ---------------------------------------------------------------------------- + // GetAllTxLogs return all the transaction logs from the store. func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs { store := ctx.KVStore(k.storeKey) @@ -256,14 +321,51 @@ func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs { return txsLogs } +// GetLogs returns the current logs for a given transaction hash from the KVStore. +// This function returns an empty, non-nil slice if no logs are found. +func (k Keeper) GetTxLogs(txHash common.Hash) []*ethtypes.Log { + store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixLogs) + + bz := store.Get(txHash.Bytes()) + if len(bz) == 0 { + return []*ethtypes.Log{} + } + + var logs types.TransactionLogs + k.cdc.MustUnmarshalBinaryBare(bz, &logs) + + return logs.EthLogs() +} + +// SetLogs sets the logs for a transaction in the KVStore. +func (k Keeper) SetLogs(txHash common.Hash, logs []*ethtypes.Log) { + store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixLogs) + + txLogs := types.NewTransactionLogsFromEth(txHash, logs) + bz := k.cdc.MustMarshalBinaryBare(&txLogs) + + store.Set(txHash.Bytes(), bz) +} + +// DeleteLogs removes the logs from the KVStore. It is used during journal.Revert. +func (k Keeper) DeleteTxLogs(ctx sdk.Context, txHash common.Hash) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixLogs) + store.Delete(txHash.Bytes()) +} + +// ---------------------------------------------------------------------------- +// Storage +// ---------------------------------------------------------------------------- + // GetAccountStorage return state storage associated with an account func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (types.Storage, error) { storage := types.Storage{} - err := k.ForEachStorage(ctx, address, func(key, value common.Hash) bool { + err := k.ForEachStorage(address, func(key, value common.Hash) bool { storage = append(storage, types.NewState(key, value)) return false }) + if err != nil { return types.Storage{}, err } @@ -271,22 +373,62 @@ func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (type return storage, nil } -// GetChainConfig gets block height from block consensus hash -func (k Keeper) GetChainConfig(ctx sdk.Context) (types.ChainConfig, bool) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.KeyPrefixChainConfig) - if len(bz) == 0 { - return types.ChainConfig{}, false +// ---------------------------------------------------------------------------- +// Account +// ---------------------------------------------------------------------------- + +func (k Keeper) DeleteState(addr common.Address, key common.Hash) { + store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) + key = types.KeyAddressStorage(addr, key) + store.Delete(key.Bytes()) +} + +// DeleteAccountStorage clears all the storage state associated with the given address. +func (k Keeper) DeleteAccountStorage(addr common.Address) { + _ = k.ForEachStorage(addr, func(key, _ common.Hash) bool { + k.DeleteState(addr, key) + return false + }) +} + +// DeleteCode removes the contract code byte array from the store associated with +// the given address. +func (k Keeper) DeleteCode(addr common.Address) { + hash := k.GetCodeHash(addr) + if bytes.Equal(hash.Bytes(), common.BytesToHash(types.EmptyCodeHash).Bytes()) { + return } - var config types.ChainConfig - k.cdc.MustUnmarshalBinaryBare(bz, &config) - return config, true + store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode) + store.Delete(hash.Bytes()) } -// SetChainConfig sets the mapping from block consensus hash to block height -func (k Keeper) SetChainConfig(ctx sdk.Context, config types.ChainConfig) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryBare(&config) - store.Set(types.KeyPrefixChainConfig, bz) +// ClearBalance subtracts the EVM all the balance denomination from the address +// balance while also updating the total supply. +func (k Keeper) ClearBalance(addr sdk.AccAddress) (prevBalance sdk.Coin, err error) { + params := k.GetParams(k.ctx) + + prevBalance = k.bankKeeper.GetBalance(k.ctx, addr, params.EvmDenom) + if prevBalance.IsPositive() { + err := k.bankKeeper.SubtractCoins(k.ctx, addr, sdk.Coins{prevBalance}) + if err != nil { + return sdk.Coin{}, err + } + } + + return prevBalance, nil +} + +// ResetAccount removes the code, storage state and evm denom balance coins stored +// with the given address. +func (k Keeper) ResetAccount(addr common.Address) { + k.DeleteCode(addr) + k.DeleteAccountStorage(addr) + _, err := k.ClearBalance(addr.Bytes()) + if err != nil { + k.Logger(k.ctx).Error( + "failed to clear balance during account reset", + "ethereum-address", addr.Hex(), + ) + } } diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 2b928930..ee8ffb25 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -43,6 +43,8 @@ func (suite *KeeperTestSuite) SetupTest() { suite.app = app.Setup(checkTx) suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "ethermint-3", Time: time.Now().UTC()}) + suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx) + suite.address = ethcmn.HexToAddress(addrHex) queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry()) @@ -77,18 +79,18 @@ func (suite *KeeperTestSuite) TestTransactionLogs() { } expLogs := []*ethtypes.Log{log} - err := suite.app.EvmKeeper.SetLogs(suite.ctx, ethHash, expLogs) + err := suite.app.EvmKeeper.CommitStateDB.SetLogs(ethHash, expLogs) suite.Require().NoError(err) - logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, ethHash) + logs, err := suite.app.EvmKeeper.CommitStateDB.GetLogs(ethHash) suite.Require().NoError(err) suite.Require().Equal(expLogs, logs) expLogs = []*ethtypes.Log{log2, log} // add another log under the zero hash - suite.app.EvmKeeper.AddLog(suite.ctx, log2) - logs = suite.app.EvmKeeper.AllLogs(suite.ctx) + suite.app.EvmKeeper.CommitStateDB.AddLog(log2) + logs = suite.app.EvmKeeper.CommitStateDB.AllLogs() suite.Require().Equal(expLogs, logs) // add another log under the zero hash @@ -97,7 +99,7 @@ func (suite *KeeperTestSuite) TestTransactionLogs() { Data: []byte("log3"), BlockNumber: 10, } - suite.app.EvmKeeper.AddLog(suite.ctx, log3) + suite.app.EvmKeeper.CommitStateDB.AddLog(log3) txLogs := suite.app.EvmKeeper.GetAllTxLogs(suite.ctx) suite.Require().Equal(2, len(txLogs)) @@ -111,28 +113,28 @@ func (suite *KeeperTestSuite) TestTransactionLogs() { func (suite *KeeperTestSuite) TestDBStorage() { // Perform state transitions - suite.app.EvmKeeper.CreateAccount(suite.ctx, suite.address) - suite.app.EvmKeeper.SetBalance(suite.ctx, suite.address, big.NewInt(5)) - suite.app.EvmKeeper.SetNonce(suite.ctx, suite.address, 4) - suite.app.EvmKeeper.SetState(suite.ctx, suite.address, ethcmn.HexToHash("0x2"), ethcmn.HexToHash("0x3")) - suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, []byte{0x1}) + suite.app.EvmKeeper.CommitStateDB.CreateAccount(suite.address) + suite.app.EvmKeeper.CommitStateDB.SetBalance(suite.address, big.NewInt(5)) + suite.app.EvmKeeper.CommitStateDB.SetNonce(suite.address, 4) + suite.app.EvmKeeper.CommitStateDB.SetState(suite.address, ethcmn.HexToHash("0x2"), ethcmn.HexToHash("0x3")) + suite.app.EvmKeeper.CommitStateDB.SetCode(suite.address, []byte{0x1}) // Test block height mapping functionality testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3}) suite.app.EvmKeeper.SetBlockBloom(suite.ctx, 4, testBloom) // Get those state transitions - suite.Require().Equal(suite.app.EvmKeeper.GetBalance(suite.ctx, suite.address).Cmp(big.NewInt(5)), 0) - suite.Require().Equal(suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address), uint64(4)) - suite.Require().Equal(suite.app.EvmKeeper.GetState(suite.ctx, suite.address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3")) - suite.Require().Equal(suite.app.EvmKeeper.GetCode(suite.ctx, suite.address), []byte{0x1}) + suite.Require().Equal(suite.app.EvmKeeper.CommitStateDB.GetBalance(suite.address).Cmp(big.NewInt(5)), 0) + suite.Require().Equal(suite.app.EvmKeeper.CommitStateDB.GetNonce(suite.address), uint64(4)) + suite.Require().Equal(suite.app.EvmKeeper.CommitStateDB.GetState(suite.address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3")) + suite.Require().Equal(suite.app.EvmKeeper.CommitStateDB.GetCode(suite.address), []byte{0x1}) bloom, found := suite.app.EvmKeeper.GetBlockBloom(suite.ctx, 4) suite.Require().True(found) suite.Require().Equal(bloom, testBloom) // commit stateDB - _, err := suite.app.EvmKeeper.Commit(suite.ctx, false) + _, err := suite.app.EvmKeeper.CommitStateDB.Commit(false) suite.Require().NoError(err, "failed to commit StateDB") // simulate BaseApp EndBlocker commitment diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 4e845ea4..a6208c1d 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -3,6 +3,7 @@ package keeper import ( "context" "errors" + "math/big" "time" "github.com/armon/go-metrics" @@ -24,6 +25,7 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.TypeMsgEthereumTx) ctx := sdk.UnwrapSDKContext(goCtx) + k.CommitStateDB.WithContext(ctx) ethMsg, err := msg.AsMessage() if err != nil { @@ -67,8 +69,8 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t // other nodes, causing a consensus error if !st.Simulate { // Prepare db for logs - k.Prepare(ctx, ethHash, blockHash, k.TxCount) - k.TxCount++ + k.CommitStateDB.Prepare(ethHash, blockHash, int(k.GetTxIndexTransient())) + k.IncreaseTxIndexTransient() } executionResult, err := st.TransitionDb(ctx, config) @@ -102,11 +104,16 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t } if !st.Simulate { + bloom, found := k.GetBlockBloomTransient() + if !found { + bloom = big.NewInt(0) + } // update block bloom filter - k.Bloom.Or(k.Bloom, executionResult.Bloom) + bloom = bloom.Or(bloom, executionResult.Bloom) + k.SetBlockBloomTransient(bloom) // update transaction logs in KVStore - err = k.SetLogs(ctx, ethHash, executionResult.Logs) + err = k.CommitStateDB.SetLogs(ethHash, executionResult.Logs) if err != nil { panic(err) } @@ -130,10 +137,6 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t }) k.AddTxHashToBlock(ctx, ctx.BlockHeight(), ethHash) - - for _, ethLog := range executionResult.Logs { - k.LogsCache[ethLog.Address] = append(k.LogsCache[ethLog.Address], ethLog) - } } defer func() { diff --git a/x/evm/keeper/params.go b/x/evm/keeper/params.go index c24ec8e4..25abb7b6 100644 --- a/x/evm/keeper/params.go +++ b/x/evm/keeper/params.go @@ -8,10 +8,31 @@ import ( // GetParams returns the total set of evm parameters. func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - return k.CommitStateDB.WithContext(ctx).GetParams() + k.paramSpace.GetParamSet(ctx, ¶ms) + return params } // SetParams sets the evm parameters to the param space. func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { - k.CommitStateDB.WithContext(ctx).SetParams(params) + k.paramSpace.SetParamSet(ctx, ¶ms) +} + +// GetChainConfig gets block height from block consensus hash +func (k Keeper) GetChainConfig(ctx sdk.Context) (types.ChainConfig, bool) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.KeyPrefixChainConfig) + if len(bz) == 0 { + return types.ChainConfig{}, false + } + + var config types.ChainConfig + k.cdc.MustUnmarshalBinaryBare(bz, &config) + return config, true +} + +// SetChainConfig sets the mapping from block consensus hash to block height +func (k Keeper) SetChainConfig(ctx sdk.Context, config types.ChainConfig) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryBare(&config) + store.Set(types.KeyPrefixChainConfig, bz) } diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index 0337b708..eb1a50ec 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -1,265 +1,589 @@ package keeper import ( + "bytes" + "fmt" "math/big" - "github.com/cosmos/ethermint/x/evm/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - - ethcmn "github.com/ethereum/go-ethereum/common" - ethstate "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" - ethvm "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + ethermint "github.com/cosmos/ethermint/types" + "github.com/cosmos/ethermint/x/evm/types" ) +var _ vm.StateDB = &Keeper{} + // ---------------------------------------------------------------------------- -// Setters +// Account // ---------------------------------------------------------------------------- -// SetBalance calls CommitStateDB.SetBalance using the passed in context -func (k *Keeper) SetBalance(ctx sdk.Context, addr ethcmn.Address, amount *big.Int) { - k.CommitStateDB.WithContext(ctx).SetBalance(addr, amount) +// CreateAccount creates a new EthAccount instance from the provided address and +// sets the value to store. +func (k *Keeper) CreateAccount(addr common.Address) { + cosmosAddr := sdk.AccAddress(addr.Bytes()) + + account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) + log := "" + if account == nil { + log = "account created" + } else { + log = "account overwritten" + k.ResetAccount(addr) + } + + _ = k.accountKeeper.NewAccountWithAddress(k.ctx, cosmosAddr) + + k.Logger(k.ctx).Debug( + log, + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + ) } +// ---------------------------------------------------------------------------- +// Balance +// ---------------------------------------------------------------------------- + // AddBalance calls CommitStateDB.AddBalance using the passed in context -func (k *Keeper) AddBalance(ctx sdk.Context, addr ethcmn.Address, amount *big.Int) { - k.CommitStateDB.WithContext(ctx).AddBalance(addr, amount) +func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) { + cosmosAddr := sdk.AccAddress(addr.Bytes()) + + params := k.GetParams(k.ctx) + coins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(amount))} + + if err := k.bankKeeper.AddCoins(k.ctx, cosmosAddr, coins); err != nil { + k.Logger(k.ctx).Error( + "failed to add balance", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + "error", err, + ) + return + } + + k.Logger(k.ctx).Debug( + "balance addition", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + ) } // SubBalance calls CommitStateDB.SubBalance using the passed in context -func (k *Keeper) SubBalance(ctx sdk.Context, addr ethcmn.Address, amount *big.Int) { - k.CommitStateDB.WithContext(ctx).SubBalance(addr, amount) +func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) { + cosmosAddr := sdk.AccAddress(addr.Bytes()) + + params := k.GetParams(k.ctx) + coins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(amount))} + + if err := k.bankKeeper.SubtractCoins(k.ctx, cosmosAddr, coins); err != nil { + k.Logger(k.ctx).Error( + "failed to subtract balance", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + "error", err, + ) + + return + } + + k.Logger(k.ctx).Debug( + "balance subtraction", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + ) +} + +// GetBalance calls CommitStateDB.GetBalance using the passed in context +func (k *Keeper) GetBalance(addr common.Address) *big.Int { + cosmosAddr := sdk.AccAddress(addr.Bytes()) + params := k.GetParams(k.ctx) + balance := k.bankKeeper.GetBalance(k.ctx, cosmosAddr, params.EvmDenom) + + return balance.Amount.BigInt() +} + +// ---------------------------------------------------------------------------- +// Nonce +// ---------------------------------------------------------------------------- + +// GetNonce calls CommitStateDB.GetNonce using the passed in context +func (k *Keeper) GetNonce(addr common.Address) uint64 { + cosmosAddr := sdk.AccAddress(addr.Bytes()) + nonce, err := k.accountKeeper.GetSequence(k.ctx, cosmosAddr) + if err != nil { + k.Logger(k.ctx).Error( + "account not found", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + "error", err, + ) + } + + return nonce } // SetNonce calls CommitStateDB.SetNonce using the passed in context -func (k *Keeper) SetNonce(ctx sdk.Context, addr ethcmn.Address, nonce uint64) { - k.CommitStateDB.WithContext(ctx).SetNonce(addr, nonce) -} +func (k *Keeper) SetNonce(addr common.Address, nonce uint64) { + cosmosAddr := sdk.AccAddress(addr.Bytes()) + account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) + if account == nil { + k.Logger(k.ctx).Debug( + "account not found", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + ) -// SetState calls CommitStateDB.SetState using the passed in context -func (k *Keeper) SetState(ctx sdk.Context, addr ethcmn.Address, key, value ethcmn.Hash) { - k.CommitStateDB.WithContext(ctx).SetState(addr, key, value) -} + // create address if it doesn't exist + account = k.accountKeeper.NewAccountWithAddress(k.ctx, cosmosAddr) + } -// SetCode calls CommitStateDB.SetCode using the passed in context -func (k *Keeper) SetCode(ctx sdk.Context, addr ethcmn.Address, code []byte) { - k.CommitStateDB.WithContext(ctx).SetCode(addr, code) -} + if err := account.SetSequence(nonce); err != nil { + k.Logger(k.ctx).Error( + "failed to set nonce", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + "nonce", nonce, + "error", err, + ) -// SetLogs calls CommitStateDB.SetLogs using the passed in context -func (k *Keeper) SetLogs(ctx sdk.Context, hash ethcmn.Hash, logs []*ethtypes.Log) error { - // TODO:@albert - // since SetLogs is only called for non-simulation mode, GasEstimation is quite not correct - // I suggest using ctx.ConsumeGas(XXX) where XXX is max of gas consume for set logs. - ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + return + } - return k.CommitStateDB.WithContext(ctx).SetLogs(hash, logs) -} + k.accountKeeper.SetAccount(k.ctx, account) -// DeleteLogs calls CommitStateDB.DeleteLogs using the passed in context -func (k *Keeper) DeleteLogs(ctx sdk.Context, hash ethcmn.Hash) { - k.CommitStateDB.WithContext(ctx).DeleteLogs(hash) -} - -// AddLog calls CommitStateDB.AddLog using the passed in context -func (k *Keeper) AddLog(ctx sdk.Context, log *ethtypes.Log) { - k.CommitStateDB.WithContext(ctx).AddLog(log) -} - -// AddPreimage calls CommitStateDB.AddPreimage using the passed in context -func (k *Keeper) AddPreimage(ctx sdk.Context, hash ethcmn.Hash, preimage []byte) { - k.CommitStateDB.WithContext(ctx).AddPreimage(hash, preimage) -} - -// AddRefund calls CommitStateDB.AddRefund using the passed in context -func (k *Keeper) AddRefund(ctx sdk.Context, gas uint64) { - k.CommitStateDB.WithContext(ctx).AddRefund(gas) -} - -// SubRefund calls CommitStateDB.SubRefund using the passed in context -func (k *Keeper) SubRefund(ctx sdk.Context, gas uint64) { - k.CommitStateDB.WithContext(ctx).SubRefund(gas) + k.Logger(k.ctx).Debug( + "nonce set", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + "nonce", nonce, + ) } // ---------------------------------------------------------------------------- -// Getters +// Code // ---------------------------------------------------------------------------- -// GetBalance calls CommitStateDB.GetBalance using the passed in context -func (k *Keeper) GetBalance(ctx sdk.Context, addr ethcmn.Address) *big.Int { - return k.CommitStateDB.WithContext(ctx).GetBalance(addr) -} +// GetCodeHash calls CommitStateDB.GetCodeHash using the passed in context +func (k *Keeper) GetCodeHash(addr common.Address) common.Hash { + cosmosAddr := sdk.AccAddress(addr.Bytes()) + account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) + if account == nil { + return common.BytesToHash(types.EmptyCodeHash) + } -// GetNonce calls CommitStateDB.GetNonce using the passed in context -func (k *Keeper) GetNonce(ctx sdk.Context, addr ethcmn.Address) uint64 { - return k.CommitStateDB.WithContext(ctx).GetNonce(addr) -} + ethAccount, isEthAccount := account.(*ethermint.EthAccount) + if !isEthAccount { + return common.BytesToHash(types.EmptyCodeHash) + } -// TxIndex calls CommitStateDB.TxIndex using the passed in context -func (k *Keeper) TxIndex(ctx sdk.Context) int { - return k.CommitStateDB.WithContext(ctx).TxIndex() -} - -// BlockHash calls CommitStateDB.BlockHash using the passed in context -func (k *Keeper) BlockHash(ctx sdk.Context) ethcmn.Hash { - return k.CommitStateDB.WithContext(ctx).BlockHash() + return common.BytesToHash(ethAccount.CodeHash) } // GetCode calls CommitStateDB.GetCode using the passed in context -func (k *Keeper) GetCode(ctx sdk.Context, addr ethcmn.Address) []byte { - return k.CommitStateDB.WithContext(ctx).GetCode(addr) +func (k *Keeper) GetCode(addr common.Address) []byte { + hash := k.GetCodeHash(addr) + + if bytes.Equal(hash.Bytes(), common.BytesToHash(types.EmptyCodeHash).Bytes()) { + return nil + } + + store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode) + code := store.Get(hash.Bytes()) + + if len(code) == 0 { + k.Logger(k.ctx).Debug( + "code not found", + "ethereum-address", addr.Hex(), + "code-hash", hash.Hex(), + ) + } + + return code } -// GetCodeSize calls CommitStateDB.GetCodeSize using the passed in context -func (k *Keeper) GetCodeSize(ctx sdk.Context, addr ethcmn.Address) int { - return k.CommitStateDB.WithContext(ctx).GetCodeSize(addr) +// SetCode calls CommitStateDB.SetCode using the passed in context +func (k *Keeper) SetCode(addr common.Address, code []byte) { + hash := crypto.Keccak256Hash(code) + + // update account code hash + account := k.accountKeeper.GetAccount(k.ctx, addr.Bytes()) + if account == nil { + account = k.accountKeeper.NewAccountWithAddress(k.ctx, addr.Bytes()) + } + + ethAccount, isEthAccount := account.(*ethermint.EthAccount) + if !isEthAccount { + k.Logger(k.ctx).Error( + "invalid account type", + "ethereum-address", addr.Hex(), + "code-hash", hash.Hex(), + ) + return + } + + ethAccount.CodeHash = hash.Bytes() + k.accountKeeper.SetAccount(k.ctx, ethAccount) + + store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode) + + action := "updated" + + // store or delete code + if len(code) == 0 { + store.Delete(hash.Bytes()) + action = "deleted" + } else { + store.Set(hash.Bytes(), code) + } + + k.Logger(k.ctx).Debug( + fmt.Sprintf("code %s", action), + "ethereum-address", addr.Hex(), + "code-hash", hash.Hex(), + ) } -// GetCodeHash calls CommitStateDB.GetCodeHash using the passed in context -func (k *Keeper) GetCodeHash(ctx sdk.Context, addr ethcmn.Address) ethcmn.Hash { - return k.CommitStateDB.WithContext(ctx).GetCodeHash(addr) +// GetCodeSize returns the code hash stored in the address account. +func (k *Keeper) GetCodeSize(addr common.Address) int { + return len(k.GetCode(addr)) +} + +// ---------------------------------------------------------------------------- +// Refund +// ---------------------------------------------------------------------------- + +// NOTE: gas refunded needs to be tracked and stored in a separate variable in +// order to add it subtract/add it from/to the gas used value after the EVM +// execution has finalised. The refund value is cleared on every transaction and +// at the end of every block. + +// AddRefund adds the given amount of gas to the refund cached value. +func (k *Keeper) AddRefund(gas uint64) { + refund := k.GetRefund() + + refund += gas + + store := k.ctx.TransientStore(k.transientKey) + store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund)) +} + +// SubRefund subtracts the given amount of gas from the refund value. This function +// will panic if gas amount is greater than the stored refund. +func (k *Keeper) SubRefund(gas uint64) { + refund := k.GetRefund() + + if gas > refund { + // TODO: (@fedekunze) set to 0?? Geth panics here + panic("refund counter below zero") + } + + refund -= gas + + store := k.ctx.TransientStore(k.transientKey) + store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund)) +} + +// GetRefund returns the amount of gas available for return after the tx execution +// finalises. This value is reset to 0 on every transaction. +func (k *Keeper) GetRefund() uint64 { + store := k.ctx.TransientStore(k.transientKey) + + bz := store.Get(types.KeyPrefixTransientRefund) + if len(bz) == 0 { + return 0 + } + + return sdk.BigEndianToUint64(bz) +} + +// ---------------------------------------------------------------------------- +// State +// ---------------------------------------------------------------------------- + +// GetCommittedState calls CommitStateDB.GetCommittedState using the passed in context +func (k *Keeper) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) + + key := types.KeyAddressStorage(addr, hash) + value := store.Get(key.Bytes()) + if len(value) == 0 { + return common.Hash{} + } + + return common.BytesToHash(value) } // GetState calls CommitStateDB.GetState using the passed in context -func (k *Keeper) GetState(ctx sdk.Context, addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash { - return k.CommitStateDB.WithContext(ctx).GetState(addr, hash) +func (k *Keeper) GetState(addr common.Address, hash common.Hash) common.Hash { + // All state is committed directly + return k.GetCommittedState(addr, hash) } -// GetCommittedState calls CommitStateDB.GetCommittedState using the passed in context -func (k *Keeper) GetCommittedState(ctx sdk.Context, addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash { - return k.CommitStateDB.WithContext(ctx).GetCommittedState(addr, hash) -} +// SetState calls CommitStateDB.SetState using the passed in context +func (k *Keeper) SetState(addr common.Address, key, value common.Hash) { + store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) + key = types.KeyAddressStorage(addr, key) -// GetLogs calls CommitStateDB.GetLogs using the passed in context -func (k *Keeper) GetLogs(ctx sdk.Context, hash ethcmn.Hash) ([]*ethtypes.Log, error) { - return k.CommitStateDB.WithContext(ctx).GetLogs(hash) -} + action := "updated" + if ethermint.IsEmptyHash(value.Hex()) { + store.Delete(key.Bytes()) + action = "deleted" + } else { + store.Set(key.Bytes(), value.Bytes()) + } -// AllLogs calls CommitStateDB.AllLogs using the passed in context -func (k *Keeper) AllLogs(ctx sdk.Context) []*ethtypes.Log { - return k.CommitStateDB.WithContext(ctx).AllLogs() -} - -// GetRefund calls CommitStateDB.GetRefund using the passed in context -func (k *Keeper) GetRefund(ctx sdk.Context) uint64 { - return k.CommitStateDB.WithContext(ctx).GetRefund() -} - -// Preimages calls CommitStateDB.Preimages using the passed in context -func (k *Keeper) Preimages(ctx sdk.Context) map[ethcmn.Hash][]byte { - return k.CommitStateDB.WithContext(ctx).Preimages() -} - -// HasSuicided calls CommitStateDB.HasSuicided using the passed in context -func (k *Keeper) HasSuicided(ctx sdk.Context, addr ethcmn.Address) bool { - return k.CommitStateDB.WithContext(ctx).HasSuicided(addr) -} - -// StorageTrie calls CommitStateDB.StorageTrie using the passed in context -func (k *Keeper) StorageTrie(ctx sdk.Context, addr ethcmn.Address) ethstate.Trie { - return k.CommitStateDB.WithContext(ctx).StorageTrie(addr) + k.Logger(k.ctx).Debug( + fmt.Sprintf("state %s", action), + "ethereum-address", addr.Hex(), + "key", key.Hex(), + ) } // ---------------------------------------------------------------------------- -// Persistence +// Suicide // ---------------------------------------------------------------------------- -// Commit calls CommitStateDB.Commit using the passed in context -func (k *Keeper) Commit(ctx sdk.Context, deleteEmptyObjects bool) (root ethcmn.Hash, err error) { - return k.CommitStateDB.WithContext(ctx).Commit(deleteEmptyObjects) +// Suicide marks the given account as suicided and clears the account balance of +// the EVM tokens. +func (k *Keeper) Suicide(addr common.Address) bool { + prev := k.HasSuicided(addr) + if prev { + return true + } + + cosmosAddr := sdk.AccAddress(addr.Bytes()) + + _, err := k.ClearBalance(cosmosAddr) + if err != nil { + k.Logger(k.ctx).Error( + "failed to subtract balance on suicide", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + "error", err, + ) + + return false + } + + // TODO: (@fedekunze) do we also need to delete the storage state and the code? + + // Set a single byte to the transient store + store := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided) + store.Set(addr.Bytes(), []byte{1}) + + k.Logger(k.ctx).Debug( + "account suicided", + "ethereum-address", addr.Hex(), + "cosmos-address", cosmosAddr.String(), + ) + + return true } -// Finalise calls CommitStateDB.Finalise using the passed in context -func (k *Keeper) Finalise(ctx sdk.Context, deleteEmptyObjects bool) error { - return k.CommitStateDB.WithContext(ctx).Finalise(deleteEmptyObjects) +// HasSuicided queries the transient store to check if the account has been marked as suicided in the +// current block. Accounts that are suicided will be returned as non-nil during queries and "cleared" +// after the block has been committed. +func (k *Keeper) HasSuicided(addr common.Address) bool { + store := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided) + return store.Has(addr.Bytes()) } -// IntermediateRoot calls CommitStateDB.IntermediateRoot using the passed in context -func (k *Keeper) IntermediateRoot(ctx sdk.Context, deleteEmptyObjects bool) error { - _, err := k.CommitStateDB.WithContext(ctx).IntermediateRoot(deleteEmptyObjects) - return err +// ---------------------------------------------------------------------------- +// Account Exist / Empty +// ---------------------------------------------------------------------------- + +// Exist returns true if the given account exists in store or if it has been +// marked as suicided in the transient store. +func (k *Keeper) Exist(addr common.Address) bool { + // return true if the account has suicided + if k.HasSuicided(addr) { + return true + } + + cosmosAddr := sdk.AccAddress(addr.Bytes()) + account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) + return account != nil +} + +// Empty returns true if the address meets the following conditions: +// - nonce is 0 +// - balance amount for evm denom is 0 +// - account code hash is empty +func (k *Keeper) Empty(addr common.Address) bool { + nonce := uint64(0) + codeHash := types.EmptyCodeHash + + cosmosAddr := sdk.AccAddress(addr.Bytes()) + account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) + + if account != nil { + nonce = account.GetSequence() + ethAccount, isEthAccount := account.(*ethermint.EthAccount) + if !isEthAccount { + // NOTE: non-ethereum accounts are considered not empty + return false + } + + codeHash = ethAccount.CodeHash + } + + balance := k.GetBalance(addr) + hasZeroBalance := balance.Sign() == 0 + hasEmptyCodeHash := bytes.Equal(codeHash, types.EmptyCodeHash) + + return hasZeroBalance && nonce == 0 && hasEmptyCodeHash +} + +// ---------------------------------------------------------------------------- +// Access List +// ---------------------------------------------------------------------------- + +// PrepareAccessList handles the preparatory steps for executing a state transition with +// regards to both EIP-2929 and EIP-2930: +// +// - Add sender to access list (2929) +// - Add destination to access list (2929) +// - Add precompiles to access list (2929) +// - Add the contents of the optional tx access list (2930) +// +// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number. +func (k *Keeper) PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses ethtypes.AccessList) { + // NOTE: only update the access list during DeliverTx + if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() { + return + } + + k.AddAddressToAccessList(sender) + if dest != nil { + k.AddAddressToAccessList(*dest) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range precompiles { + k.AddAddressToAccessList(addr) + } + for _, tuple := range txAccesses { + k.AddAddressToAccessList(tuple.Address) + for _, key := range tuple.StorageKeys { + k.AddSlotToAccessList(tuple.Address, key) + } + } +} + +// AddressInAccessList returns true if the address is registered on the access list map. +func (k *Keeper) AddressInAccessList(addr common.Address) bool { + return k.accessList.ContainsAddress(addr) +} + +func (k *Keeper) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { + return k.accessList.Contains(addr, slot) +} + +// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform +// even if the feature/fork is not active yet +func (k *Keeper) AddAddressToAccessList(addr common.Address) { + // NOTE: only update the access list during DeliverTx + if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() { + return + } + + // NOTE: ignore change return bool because we don't have to keep a journal for state changes + _ = k.accessList.AddAddress(addr) +} + +// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform +// even if the feature/fork is not active yet +func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) { + // NOTE: only update the access list during DeliverTx + if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() { + return + } + + // NOTE: ignore change return booleans because we don't have to keep a journal for state changes + _, _ = k.accessList.AddSlot(addr, slot) } // ---------------------------------------------------------------------------- // Snapshotting // ---------------------------------------------------------------------------- -// Snapshot calls CommitStateDB.Snapshot using the passed in context -func (k *Keeper) Snapshot(ctx sdk.Context) int { - return k.CommitStateDB.WithContext(ctx).Snapshot() +// Snapshot return zero as the state changes won't be committed if the state transition fails. So there +// is no need to snapshot before the VM execution. +// See Cosmos SDK docs for more info: https://docs.cosmos.network/master/core/baseapp.html#delivertx-state-updates +func (k *Keeper) Snapshot() int { + return 0 } -// RevertToSnapshot calls CommitStateDB.RevertToSnapshot using the passed in context -func (k *Keeper) RevertToSnapshot(ctx sdk.Context, revID int) { - k.CommitStateDB.WithContext(ctx).RevertToSnapshot(revID) +// RevertToSnapshot performs a no-op because when a transaction execution fails on the EVM, the state +// won't be persisted during ABCI DeliverTx. +func (k *Keeper) RevertToSnapshot(_ int) {} + +// ---------------------------------------------------------------------------- +// Log +// ---------------------------------------------------------------------------- + +// AddLog appends the given ethereum Log to the list of Logs associated with the transaction hash kept in the current +// context. This function also fills in the tx hash, block hash, tx index and log index fields before setting the log +// to store. +func (k *Keeper) AddLog(log *ethtypes.Log) { + txHash := common.BytesToHash(tmtypes.Tx(k.ctx.TxBytes()).Hash()) + blockHash, found := k.GetBlockHashFromHeight(k.ctx, k.ctx.BlockHeight()) + if found { + log.BlockHash = blockHash + } + + log.TxHash = txHash + log.TxIndex = uint(k.GetTxIndexTransient()) + + logs := k.GetTxLogs(txHash) + log.Index = uint(len(logs)) + logs = append(logs, log) + k.SetLogs(txHash, logs) + + k.Logger(k.ctx).Debug( + "log added", + "tx-hash", txHash.Hex(), + "log-index", int(log.Index), + ) } // ---------------------------------------------------------------------------- -// Auxiliary +// Trie // ---------------------------------------------------------------------------- -// Database calls CommitStateDB.Database using the passed in context -func (k *Keeper) Database(ctx sdk.Context) ethstate.Database { - return k.CommitStateDB.WithContext(ctx).Database() -} +// AddPreimage performs a no-op since the EnablePreimageRecording flag is disabled +// on the vm.Config during state transitions. No store trie preimages are written +// to the database. +func (k *Keeper) AddPreimage(_ common.Hash, _ []byte) {} -// Empty calls CommitStateDB.Empty using the passed in context -func (k *Keeper) Empty(ctx sdk.Context, addr ethcmn.Address) bool { - return k.CommitStateDB.WithContext(ctx).Empty(addr) -} - -// Exist calls CommitStateDB.Exist using the passed in context -func (k *Keeper) Exist(ctx sdk.Context, addr ethcmn.Address) bool { - return k.CommitStateDB.WithContext(ctx).Exist(addr) -} - -// Error calls CommitStateDB.Error using the passed in context -func (k *Keeper) Error(ctx sdk.Context) error { - return k.CommitStateDB.WithContext(ctx).Error() -} - -// Suicide calls CommitStateDB.Suicide using the passed in context -func (k *Keeper) Suicide(ctx sdk.Context, addr ethcmn.Address) bool { - return k.CommitStateDB.WithContext(ctx).Suicide(addr) -} - -// Reset calls CommitStateDB.Reset using the passed in context -func (k *Keeper) Reset(ctx sdk.Context, root ethcmn.Hash) error { - return k.CommitStateDB.WithContext(ctx).Reset(root) -} - -// Prepare calls CommitStateDB.Prepare using the passed in context -func (k *Keeper) Prepare(ctx sdk.Context, thash, bhash ethcmn.Hash, txi int) { - k.CommitStateDB.WithContext(ctx).Prepare(thash, bhash, txi) -} - -// CreateAccount calls CommitStateDB.CreateAccount using the passed in context -func (k *Keeper) CreateAccount(ctx sdk.Context, addr ethcmn.Address) { - k.CommitStateDB.WithContext(ctx).CreateAccount(addr) -} - -// UpdateAccounts calls CommitStateDB.UpdateAccounts using the passed in context -func (k *Keeper) UpdateAccounts(ctx sdk.Context) { - k.CommitStateDB.WithContext(ctx).UpdateAccounts() -} - -// ClearStateObjects calls CommitStateDB.ClearStateObjects using the passed in context -func (k *Keeper) ClearStateObjects(ctx sdk.Context) { - k.CommitStateDB.WithContext(ctx).ClearStateObjects() -} - -// Copy calls CommitStateDB.Copy using the passed in context -func (k *Keeper) Copy(ctx sdk.Context) ethvm.StateDB { - return k.CommitStateDB.WithContext(ctx).Copy() -} +// ---------------------------------------------------------------------------- +// Iterator +// ---------------------------------------------------------------------------- // ForEachStorage calls CommitStateDB.ForEachStorage using passed in context -func (k *Keeper) ForEachStorage(ctx sdk.Context, addr ethcmn.Address, cb func(key, value ethcmn.Hash) bool) error { - return k.CommitStateDB.WithContext(ctx).ForEachStorage(addr, cb) -} +func (k *Keeper) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { + store := k.ctx.KVStore(k.storeKey) + prefix := types.AddressStoragePrefix(addr) -// GetOrNewStateObject calls CommitStateDB.GetOrNetStateObject using the passed in context -func (k *Keeper) GetOrNewStateObject(ctx sdk.Context, addr ethcmn.Address) types.StateObject { - return k.CommitStateDB.WithContext(ctx).GetOrNewStateObject(addr) + iterator := sdk.KVStorePrefixIterator(store, prefix) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + + // TODO: check if the key prefix needs to be trimmed + key := common.BytesToHash(iterator.Key()) + value := common.BytesToHash(iterator.Value()) + + // check if iteration stops + if cb(key, value) { + return nil + } + } + + return nil } diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index 76af93d8..94292649 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -1,644 +1 @@ package keeper_test - -import ( - "fmt" - "math/big" - - sdk "github.com/cosmos/cosmos-sdk/types" - - 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/crypto/ethsecp256k1" - ethermint "github.com/cosmos/ethermint/types" - "github.com/cosmos/ethermint/x/evm/types" -) - -func (suite *KeeperTestSuite) TestBloomFilter() { - // Prepare db for logs - tHash := ethcmn.BytesToHash([]byte{0x1}) - suite.app.EvmKeeper.Prepare(suite.ctx, tHash, ethcmn.Hash{}, 0) - contractAddress := ethcmn.BigToAddress(big.NewInt(1)) - log := ethtypes.Log{Address: contractAddress, Topics: []ethcmn.Hash{}} - - testCase := []struct { - name string - malleate func() - numLogs int - isBloom bool - }{ - { - "no logs", - func() {}, - 0, - false, - }, - { - "add log", - func() { - suite.app.EvmKeeper.AddLog(suite.ctx, &log) - }, - 1, - false, - }, - { - "bloom", - func() {}, - 0, - true, - }, - } - - for _, tc := range testCase { - tc.malleate() - logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, tHash) - if !tc.isBloom { - suite.Require().NoError(err, tc.name) - suite.Require().Len(logs, tc.numLogs, tc.name) - if len(logs) != 0 { - suite.Require().Equal(log, *logs[0], tc.name) - } - } else { - // get logs bloom from the log - bloomInt := ethtypes.LogsBloom(logs) - bloomFilter := ethtypes.BytesToBloom(bloomInt) - suite.Require().True(ethtypes.BloomLookup(bloomFilter, contractAddress), tc.name) - suite.Require().False(ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2))), tc.name) - } - } -} - -func (suite *KeeperTestSuite) TestStateDB_Balance() { - testCase := []struct { - name string - malleate func() - balance *big.Int - }{ - { - "set balance", - func() { - suite.app.EvmKeeper.SetBalance(suite.ctx, suite.address, big.NewInt(100)) - }, - big.NewInt(100), - }, - { - "sub balance", - func() { - suite.app.EvmKeeper.SubBalance(suite.ctx, suite.address, big.NewInt(100)) - }, - big.NewInt(0), - }, - { - "add balance", - func() { - suite.app.EvmKeeper.AddBalance(suite.ctx, suite.address, big.NewInt(200)) - }, - big.NewInt(200), - }, - } - - for _, tc := range testCase { - tc.malleate() - suite.Require().Equal(tc.balance, suite.app.EvmKeeper.GetBalance(suite.ctx, suite.address), tc.name) - } -} - -func (suite *KeeperTestSuite) TestStateDBNonce() { - nonce := uint64(123) - suite.app.EvmKeeper.SetNonce(suite.ctx, suite.address, nonce) - suite.Require().Equal(nonce, suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)) -} - -func (suite *KeeperTestSuite) TestStateDB_Error() { - nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, ethcmn.Address{}) - suite.Require().Equal(0, int(nonce)) - suite.Require().Error(suite.app.EvmKeeper.Error(suite.ctx)) -} - -func (suite *KeeperTestSuite) TestStateDB_Database() { - suite.Require().Nil(suite.app.EvmKeeper.Database(suite.ctx)) -} - -func (suite *KeeperTestSuite) TestStateDB_State() { - key := ethcmn.BytesToHash([]byte("foo")) - val := ethcmn.BytesToHash([]byte("bar")) - suite.app.EvmKeeper.SetState(suite.ctx, suite.address, key, val) - - testCase := []struct { - name string - address ethcmn.Address - key ethcmn.Hash - value ethcmn.Hash - }{ - { - "found state", - suite.address, - ethcmn.BytesToHash([]byte("foo")), - ethcmn.BytesToHash([]byte("bar")), - }, - { - "state not found", - suite.address, - ethcmn.BytesToHash([]byte("key")), - ethcmn.Hash{}, - }, - { - "object not found", - ethcmn.Address{}, - ethcmn.BytesToHash([]byte("foo")), - ethcmn.Hash{}, - }, - } - for _, tc := range testCase { - value := suite.app.EvmKeeper.GetState(suite.ctx, tc.address, tc.key) - suite.Require().Equal(tc.value, value, tc.name) - } -} - -func (suite *KeeperTestSuite) TestStateDB_Code() { - testCase := []struct { - name string - address ethcmn.Address - code []byte - malleate func() - }{ - { - "no stored code for state object", - suite.address, - nil, - func() {}, - }, - { - "existing address", - suite.address, - []byte("code"), - func() { - suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, []byte("code")) - }, - }, - { - "state object not found", - ethcmn.Address{}, - nil, - func() {}, - }, - } - - for _, tc := range testCase { - tc.malleate() - - suite.Require().Equal(tc.code, suite.app.EvmKeeper.GetCode(suite.ctx, tc.address), tc.name) - suite.Require().Equal(len(tc.code), suite.app.EvmKeeper.GetCodeSize(suite.ctx, tc.address), tc.name) - } -} - -func (suite *KeeperTestSuite) TestStateDB_Logs() { - testCase := []struct { - name string - log *ethtypes.Log - }{ - { - "state db log", - ðtypes.Log{ - Address: suite.address, - Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))}, - Data: []byte("data"), - BlockNumber: 1, - TxHash: ethcmn.Hash{}, - TxIndex: 1, - BlockHash: ethcmn.Hash{}, - Index: 0, - Removed: false, - }, - }, - } - - for _, tc := range testCase { - hash := ethcmn.BytesToHash([]byte("hash")) - logs := []*ethtypes.Log{tc.log} - - err := suite.app.EvmKeeper.SetLogs(suite.ctx, hash, logs) - suite.Require().NoError(err, tc.name) - dbLogs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, hash) - suite.Require().NoError(err, tc.name) - suite.Require().Equal(logs, dbLogs, tc.name) - - suite.app.EvmKeeper.DeleteLogs(suite.ctx, hash) - dbLogs, err = suite.app.EvmKeeper.GetLogs(suite.ctx, hash) - suite.Require().NoError(err, tc.name) - suite.Require().Empty(dbLogs, tc.name) - - suite.app.EvmKeeper.AddLog(suite.ctx, tc.log) - suite.Require().Equal(logs, suite.app.EvmKeeper.AllLogs(suite.ctx), tc.name) - - //resets state but checking to see if storekey still persists. - err = suite.app.EvmKeeper.Reset(suite.ctx, hash) - suite.Require().NoError(err, tc.name) - suite.Require().Equal(logs, suite.app.EvmKeeper.AllLogs(suite.ctx), tc.name) - } -} - -func (suite *KeeperTestSuite) TestStateDB_Preimage() { - hash := ethcmn.BytesToHash([]byte("hash")) - preimage := []byte("preimage") - - suite.app.EvmKeeper.AddPreimage(suite.ctx, hash, preimage) - suite.Require().Equal(preimage, suite.app.EvmKeeper.Preimages(suite.ctx)[hash]) -} - -func (suite *KeeperTestSuite) TestStateDB_Refund() { - testCase := []struct { - name string - addAmount uint64 - subAmount uint64 - expRefund uint64 - expPanic bool - }{ - { - "refund 0", - 0, 0, 0, - false, - }, - { - "refund positive amount", - 100, 0, 100, - false, - }, - { - "refund panic", - 100, 200, 100, - true, - }, - } - - for _, tc := range testCase { - suite.Run(tc.name, func() { - suite.SetupTest() // reset - - suite.app.EvmKeeper.AddRefund(suite.ctx, tc.addAmount) - suite.Require().Equal(tc.addAmount, suite.app.EvmKeeper.GetRefund(suite.ctx)) - - if tc.expPanic { - suite.Panics(func() { - suite.app.EvmKeeper.SubRefund(suite.ctx, tc.subAmount) - }) - } else { - suite.app.EvmKeeper.SubRefund(suite.ctx, tc.subAmount) - suite.Require().Equal(tc.expRefund, suite.app.EvmKeeper.GetRefund(suite.ctx)) - } - }) - } -} - -func (suite *KeeperTestSuite) TestStateDB_CreateAccount() { - prevBalance := big.NewInt(12) - - testCase := []struct { - name string - address ethcmn.Address - malleate func() - }{ - { - "existing account", - suite.address, - func() { - suite.app.EvmKeeper.AddBalance(suite.ctx, suite.address, prevBalance) - }, - }, - { - "new account", - ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b4c1"), - func() { - prevBalance = big.NewInt(0) - }, - }, - } - - for _, tc := range testCase { - suite.Run(tc.name, func() { - suite.SetupTest() // reset - tc.malleate() - - suite.app.EvmKeeper.CreateAccount(suite.ctx, tc.address) - suite.Require().True(suite.app.EvmKeeper.Exist(suite.ctx, tc.address)) - suite.Require().Equal(prevBalance, suite.app.EvmKeeper.GetBalance(suite.ctx, tc.address)) - }) - } -} - -func (suite *KeeperTestSuite) TestStateDB_ClearStateObj() { - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err) - - addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey) - - suite.app.EvmKeeper.CreateAccount(suite.ctx, addr) - suite.Require().True(suite.app.EvmKeeper.Exist(suite.ctx, addr)) - - suite.app.EvmKeeper.ClearStateObjects(suite.ctx) - suite.Require().False(suite.app.EvmKeeper.Exist(suite.ctx, addr)) -} - -func (suite *KeeperTestSuite) TestStateDB_Reset() { - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err) - - addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey) - - suite.app.EvmKeeper.CreateAccount(suite.ctx, addr) - suite.Require().True(suite.app.EvmKeeper.Exist(suite.ctx, addr)) - - err = suite.app.EvmKeeper.Reset(suite.ctx, ethcmn.BytesToHash(nil)) - suite.Require().NoError(err) - suite.Require().False(suite.app.EvmKeeper.Exist(suite.ctx, addr)) -} - -func (suite *KeeperTestSuite) TestSuiteDB_Prepare() { - thash := ethcmn.BytesToHash([]byte("thash")) - bhash := ethcmn.BytesToHash([]byte("bhash")) - txi := 1 - - suite.app.EvmKeeper.Prepare(suite.ctx, thash, bhash, txi) - - suite.Require().Equal(txi, suite.app.EvmKeeper.TxIndex(suite.ctx)) - suite.Require().Equal(bhash, suite.app.EvmKeeper.BlockHash(suite.ctx)) -} - -func (suite *KeeperTestSuite) TestSuiteDB_CopyState() { - testCase := []struct { - name string - log ethtypes.Log - }{ - { - "copy state", - ethtypes.Log{ - Address: suite.address, - Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))}, - Data: []byte("data"), - BlockNumber: 1, - TxHash: ethcmn.Hash{}, - TxIndex: 1, - BlockHash: ethcmn.Hash{}, - Index: 0, - Removed: false, - }, - }, - } - - for _, tc := range testCase { - hash := ethcmn.BytesToHash([]byte("hash")) - logs := []*ethtypes.Log{&tc.log} - - err := suite.app.EvmKeeper.SetLogs(suite.ctx, hash, logs) - suite.Require().NoError(err, tc.name) - - copyDB := suite.app.EvmKeeper.Copy(suite.ctx) - suite.Require().Equal(suite.app.EvmKeeper.Exist(suite.ctx, suite.address), copyDB.Exist(suite.address), tc.name) - } -} - -func (suite *KeeperTestSuite) TestSuiteDB_Empty() { - suite.Require().True(suite.app.EvmKeeper.Empty(suite.ctx, suite.address)) - - suite.app.EvmKeeper.SetBalance(suite.ctx, suite.address, big.NewInt(100)) - suite.Require().False(suite.app.EvmKeeper.Empty(suite.ctx, suite.address)) -} - -func (suite *KeeperTestSuite) TestSuiteDB_Suicide() { - testCase := []struct { - name string - amount *big.Int - expPass bool - delete bool - }{ - { - "suicide zero balance", - big.NewInt(0), - false, false, - }, - { - "suicide with balance", - big.NewInt(100), - true, false, - }, - { - "delete", - big.NewInt(0), - true, true, - }, - } - - for _, tc := range testCase { - if tc.delete { - _, err := suite.app.EvmKeeper.Commit(suite.ctx, tc.delete) - suite.Require().NoError(err, tc.name) - suite.Require().False(suite.app.EvmKeeper.Exist(suite.ctx, suite.address), tc.name) - continue - } - - if tc.expPass { - suite.app.EvmKeeper.SetBalance(suite.ctx, suite.address, tc.amount) - suicide := suite.app.EvmKeeper.Suicide(suite.ctx, suite.address) - suite.Require().True(suicide, tc.name) - suite.Require().True(suite.app.EvmKeeper.HasSuicided(suite.ctx, suite.address), tc.name) - } else { - //Suicide only works for an account with non-zero balance/nonce - priv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err) - - addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey) - suicide := suite.app.EvmKeeper.Suicide(suite.ctx, addr) - suite.Require().False(suicide, tc.name) - suite.Require().False(suite.app.EvmKeeper.HasSuicided(suite.ctx, addr), tc.name) - } - } -} - -func (suite *KeeperTestSuite) TestCommitStateDB_Commit() { - testCase := []struct { - name string - malleate func() - deleteObjs bool - expPass bool - }{ - { - "commit suicided", - func() { - ok := suite.app.EvmKeeper.Suicide(suite.ctx, suite.address) - suite.Require().True(ok) - }, - true, true, - }, - { - "commit with dirty value", - func() { - suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, []byte("code")) - }, - false, true, - }, - } - - for _, tc := range testCase { - tc.malleate() - - hash, err := suite.app.EvmKeeper.Commit(suite.ctx, tc.deleteObjs) - suite.Require().Equal(ethcmn.Hash{}, hash) - - if !tc.expPass { - suite.Require().Error(err, tc.name) - continue - } - - suite.Require().NoError(err, tc.name) - acc := suite.app.AccountKeeper.GetAccount(suite.ctx, sdk.AccAddress(suite.address.Bytes())) - - if tc.deleteObjs { - suite.Require().Nil(acc, tc.name) - continue - } - - suite.Require().NotNil(acc, tc.name) - ethAcc, ok := acc.(*ethermint.EthAccount) - suite.Require().True(ok) - suite.Require().Equal(ethcrypto.Keccak256([]byte("code")), ethAcc.CodeHash) - } -} - -func (suite *KeeperTestSuite) TestCommitStateDB_Finalize() { - testCase := []struct { - name string - malleate func() - deleteObjs bool - expPass bool - }{ - { - "finalize suicided", - func() { - ok := suite.app.EvmKeeper.Suicide(suite.ctx, suite.address) - suite.Require().True(ok) - }, - true, true, - }, - { - "finalize, not suicided", - func() { - suite.app.EvmKeeper.AddBalance(suite.ctx, suite.address, big.NewInt(5)) - }, - false, true, - }, - { - "finalize, dirty storage", - func() { - suite.app.EvmKeeper.SetState(suite.ctx, suite.address, ethcmn.BytesToHash([]byte("key")), ethcmn.BytesToHash([]byte("value"))) - }, - false, true, - }, - } - - for _, tc := range testCase { - tc.malleate() - - err := suite.app.EvmKeeper.Finalise(suite.ctx, tc.deleteObjs) - - if !tc.expPass { - suite.Require().Error(err, tc.name) - hash := suite.app.EvmKeeper.GetCommittedState(suite.ctx, suite.address, ethcmn.BytesToHash([]byte("key"))) - suite.Require().NotEqual(ethcmn.Hash{}, hash, tc.name) - continue - } - - suite.Require().NoError(err, tc.name) - acc := suite.app.AccountKeeper.GetAccount(suite.ctx, sdk.AccAddress(suite.address.Bytes())) - - if tc.deleteObjs { - suite.Require().Nil(acc, tc.name) - continue - } - - suite.Require().NotNil(acc, tc.name) - } -} -func (suite *KeeperTestSuite) TestCommitStateDB_GetCommittedState() { - hash := suite.app.EvmKeeper.GetCommittedState(suite.ctx, ethcmn.Address{}, ethcmn.BytesToHash([]byte("key"))) - suite.Require().Equal(ethcmn.Hash{}, hash) -} - -func (suite *KeeperTestSuite) TestCommitStateDB_Snapshot() { - id := suite.app.EvmKeeper.Snapshot(suite.ctx) - suite.Require().NotPanics(func() { - suite.app.EvmKeeper.RevertToSnapshot(suite.ctx, id) - }) - - suite.Require().Panics(func() { - suite.app.EvmKeeper.RevertToSnapshot(suite.ctx, -1) - }, "invalid revision should panic") -} - -func (suite *KeeperTestSuite) TestCommitStateDB_ForEachStorage() { - var storage types.Storage - - testCase := []struct { - name string - malleate func() - callback func(key, value ethcmn.Hash) (stop bool) - expValues []string - }{ - { - "aggregate state", - func() { - for i := 0; i < 5; i++ { - suite.app.EvmKeeper.SetState(suite.ctx, 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 - }, - []string{ - ethcmn.BytesToHash([]byte("value0")).String(), - ethcmn.BytesToHash([]byte("value1")).String(), - ethcmn.BytesToHash([]byte("value2")).String(), - ethcmn.BytesToHash([]byte("value3")).String(), - ethcmn.BytesToHash([]byte("value4")).String(), - }, - }, - { - "filter state", - func() { - suite.app.EvmKeeper.SetState(suite.ctx, suite.address, ethcmn.BytesToHash([]byte("key")), ethcmn.BytesToHash([]byte("value"))) - suite.app.EvmKeeper.SetState(suite.ctx, 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 - }, - []string{ - ethcmn.BytesToHash([]byte("filtervalue")).String(), - }, - }, - } - - for _, tc := range testCase { - suite.Run(tc.name, func() { - suite.SetupTest() // reset - tc.malleate() - suite.app.EvmKeeper.Finalise(suite.ctx, false) - - err := suite.app.EvmKeeper.ForEachStorage(suite.ctx, 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([]string, len(storage)) - for i := range storage { - vals[i] = storage[i].Value - } - - suite.Require().ElementsMatch(tc.expValues, vals) - }) - storage = types.Storage{} - } -} diff --git a/x/evm/types/access_list.go b/x/evm/types/access_list.go index c880f6b7..e1b8e860 100644 --- a/x/evm/types/access_list.go +++ b/x/evm/types/access_list.go @@ -6,22 +6,22 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" ) -// accessList is copied from go-ethereum +// AccessListMappings is copied from go-ethereum // https://github.com/ethereum/go-ethereum/blob/cf856ea1ad96ac39ea477087822479b63417036a/core/state/access_list.go#L23 -type accessList struct { +type AccessListMappings struct { addresses map[common.Address]int slots []map[common.Hash]struct{} } // ContainsAddress returns true if the address is in the access list. -func (al *accessList) ContainsAddress(address common.Address) bool { +func (al *AccessListMappings) ContainsAddress(address common.Address) bool { _, ok := al.addresses[address] return ok } // Contains checks if a slot within an account is present in the access list, returning // separate flags for the presence of the account and the slot respectively. -func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { +func (al *AccessListMappings) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { idx, ok := al.addresses[address] if !ok { // no such address (and hence zero slots) @@ -41,16 +41,16 @@ func (al *accessList) Contains(address common.Address, slot common.Hash) (addres return true, slotPresent } -// newAccessList creates a new accessList. -func newAccessList() *accessList { - return &accessList{ +// newAccessList creates a new AccessListMappings. +func NewAccessListMappings() *AccessListMappings { + return &AccessListMappings{ addresses: make(map[common.Address]int), } } -// Copy creates an independent copy of an accessList. -func (al *accessList) Copy() *accessList { - cp := newAccessList() +// Copy creates an independent copy of an AccessListMappings. +func (al *AccessListMappings) Copy() *AccessListMappings { + cp := NewAccessListMappings() for k, v := range al.addresses { cp.addresses[k] = v } @@ -67,7 +67,7 @@ func (al *accessList) Copy() *accessList { // AddAddress adds an address to the access list, and returns 'true' if the operation // caused a change (addr was not previously in the list). -func (al *accessList) AddAddress(address common.Address) bool { +func (al *AccessListMappings) AddAddress(address common.Address) bool { if _, present := al.addresses[address]; present { return false } @@ -80,7 +80,7 @@ func (al *accessList) AddAddress(address common.Address) bool { // - address added // - slot added // For any 'true' value returned, a corresponding journal entry must be made. -func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { +func (al *AccessListMappings) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { idx, addrPresent := al.addresses[address] if !addrPresent || idx == -1 { // Address not present, or addr present but no slots there @@ -99,7 +99,7 @@ func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrCha slotmap := al.slots[idx] if _, ok := slotmap[slot]; !ok { slotmap[slot] = struct{}{} - // Journal add slot change + // journal add slot change return false, true } // No changes required @@ -110,7 +110,7 @@ func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrCha // This operation needs to be performed in the same order as the addition happened. // This method is meant to be used by the journal, which maintains ordering of // operations. -func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { +func (al *AccessListMappings) DeleteSlot(address common.Address, slot common.Hash) { idx, addrOk := al.addresses[address] // There are two ways this can fail if !addrOk { @@ -131,7 +131,7 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { // needs to be performed in the same order as the addition happened. // This method is meant to be used by the journal, which maintains ordering of // operations. -func (al *accessList) DeleteAddress(address common.Address) { +func (al *AccessListMappings) DeleteAddress(address common.Address) { delete(al.addresses, address) } @@ -146,7 +146,7 @@ func NewAccessList(ethAccessList *ethtypes.AccessList) AccessList { return nil } - var accessList AccessList + var AccessListMappings AccessList for _, tuple := range *ethAccessList { storageKeys := make([]string, len(tuple.StorageKeys)) @@ -154,19 +154,19 @@ func NewAccessList(ethAccessList *ethtypes.AccessList) AccessList { storageKeys[i] = tuple.StorageKeys[i].String() } - accessList = append(accessList, AccessTuple{ + AccessListMappings = append(AccessListMappings, AccessTuple{ Address: tuple.Address.String(), StorageKeys: storageKeys, }) } - return accessList + return AccessListMappings } // ToEthAccessList is an utility function to convert the protobuf compatible // AccessList to eth core AccessList from go-ethereum func (al AccessList) ToEthAccessList() *ethtypes.AccessList { - var accessList ethtypes.AccessList + var AccessListMappings ethtypes.AccessList for _, tuple := range al { storageKeys := make([]ethcmn.Hash, len(tuple.StorageKeys)) @@ -175,11 +175,11 @@ func (al AccessList) ToEthAccessList() *ethtypes.AccessList { storageKeys[i] = ethcmn.HexToHash(tuple.StorageKeys[i]) } - accessList = append(accessList, ethtypes.AccessTuple{ + AccessListMappings = append(AccessListMappings, ethtypes.AccessTuple{ Address: ethcmn.HexToAddress(tuple.Address), StorageKeys: storageKeys, }) } - return &accessList + return &AccessListMappings } diff --git a/x/evm/types/access_list_test.go b/x/evm/types/access_list_test.go index 700c94fc..3929b921 100644 --- a/x/evm/types/access_list_test.go +++ b/x/evm/types/access_list_test.go @@ -15,7 +15,7 @@ type AccessListTestSuite struct { suite.Suite address ethcmn.Address - accessList *accessList + accessList *AccessListMappings } func (suite *AccessListTestSuite) SetupTest() { @@ -23,7 +23,7 @@ func (suite *AccessListTestSuite) SetupTest() { suite.Require().NoError(err) suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes()) - suite.accessList = newAccessList() + suite.accessList = NewAccessListMappings() suite.accessList.addresses[suite.address] = 1 } @@ -85,7 +85,7 @@ func (suite *AccessListTestSuite) TestContains() { } func (suite *AccessListTestSuite) TestCopy() { - expAccessList := newAccessList() + expAccessList := NewAccessListMappings() testCases := []struct { name string @@ -96,7 +96,7 @@ func (suite *AccessListTestSuite) TestCopy() { }}, { "single address", func() { - expAccessList = newAccessList() + expAccessList = NewAccessListMappings() expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 0) expAccessList.addresses[suite.address] = -1 }, @@ -104,7 +104,7 @@ func (suite *AccessListTestSuite) TestCopy() { { "single address, single slot", func() { - expAccessList = newAccessList() + expAccessList = NewAccessListMappings() expAccessList.addresses[suite.address] = 0 expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 1) expAccessList.slots[0] = make(map[ethcmn.Hash]struct{}) @@ -114,7 +114,7 @@ func (suite *AccessListTestSuite) TestCopy() { { "multiple addresses, single slot each", func() { - expAccessList = newAccessList() + expAccessList = NewAccessListMappings() expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10) for i := 0; i < 10; i++ { expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i @@ -126,7 +126,7 @@ func (suite *AccessListTestSuite) TestCopy() { { "multiple addresses, multiple slots each", func() { - expAccessList = newAccessList() + expAccessList = NewAccessListMappings() expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10) for i := 0; i < 10; i++ { expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i diff --git a/x/evm/types/expected_keepers.go b/x/evm/types/interfaces.go similarity index 81% rename from x/evm/types/expected_keepers.go rename to x/evm/types/interfaces.go index 5978a7ee..5337352c 100644 --- a/x/evm/types/expected_keepers.go +++ b/x/evm/types/interfaces.go @@ -10,6 +10,7 @@ type AccountKeeper interface { NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI GetAllAccounts(ctx sdk.Context) (accounts []authtypes.AccountI) IterateAccounts(ctx sdk.Context, cb func(account authtypes.AccountI) bool) + GetSequence(sdk.Context, sdk.AccAddress) (uint64, error) GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI SetAccount(ctx sdk.Context, account authtypes.AccountI) RemoveAccount(ctx sdk.Context, account authtypes.AccountI) @@ -18,5 +19,7 @@ type AccountKeeper interface { // BankKeeper defines the expected interface needed to retrieve account balances. type BankKeeper interface { GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin + AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error + SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error SetBalance(ctx sdk.Context, addr sdk.AccAddress, balance sdk.Coin) error } diff --git a/x/evm/types/key.go b/x/evm/types/key.go index 879f8b31..744fd3f9 100644 --- a/x/evm/types/key.go +++ b/x/evm/types/key.go @@ -4,6 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ethcmn "github.com/ethereum/go-ethereum/common" + ethcrypto "github.com/ethereum/go-ethereum/crypto" ) const ( @@ -15,21 +16,51 @@ const ( // The EVM module should use a prefix store. StoreKey = ModuleName + // Transient Key is the key to access the EVM transient store, that is reset + // during the Commit phase. + TransientKey = "transient_" + ModuleName + // RouterKey uses module name for routing RouterKey = ModuleName ) +const ( + prefixBlockHash = iota + 1 + prefixBloom + prefixLogs + prefixCode + prefixStorage + prefixChainConfig + prefixBlockHeightHash + prefixHashTxReceipt + prefixBlockHeightTxs +) + +const ( + prefixTransientSuicided = iota + 1 + prefixTransientBloom + prefixTransientTxIndex + prefixTransientRefund +) + // KVStore key prefixes var ( - KeyPrefixBlockHash = []byte{0x01} - KeyPrefixBloom = []byte{0x02} - KeyPrefixLogs = []byte{0x03} - KeyPrefixCode = []byte{0x04} - KeyPrefixStorage = []byte{0x05} - KeyPrefixChainConfig = []byte{0x06} - KeyPrefixBlockHeightHash = []byte{0x07} - KeyPrefixHashTxReceipt = []byte{0x08} - KeyPrefixBlockHeightTxs = []byte{0x09} + KeyPrefixBlockHash = []byte{prefixBlockHash} + KeyPrefixBloom = []byte{prefixBloom} + KeyPrefixLogs = []byte{prefixLogs} + KeyPrefixCode = []byte{prefixCode} + KeyPrefixStorage = []byte{prefixStorage} + KeyPrefixChainConfig = []byte{prefixChainConfig} + KeyPrefixBlockHeightHash = []byte{prefixBlockHeightHash} + KeyPrefixHashTxReceipt = []byte{prefixHashTxReceipt} + KeyPrefixBlockHeightTxs = []byte{prefixBlockHeightTxs} +) + +var ( + KeyPrefixTransientSuicided = []byte{prefixTransientSuicided} + KeyPrefixTransientBloom = []byte{prefixTransientBloom} + KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex} + KeyPrefixTransientRefund = []byte{prefixTransientRefund} ) // BloomKey defines the store key for a block Bloom @@ -69,3 +100,17 @@ func KeyBlockHeightTxs(height uint64) []byte { heightBytes := sdk.Uint64ToBigEndian(height) return append(KeyPrefixBlockHeightTxs, heightBytes...) } + +// KeyAddressStorage returns the key hash to access a given account state. The composite key +// (address + hash) is hashed using Keccak256. +func KeyAddressStorage(address ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash { + prefix := address.Bytes() + key := hash.Bytes() + + compositeKey := make([]byte, len(prefix)+len(key)) + + copy(compositeKey, prefix) + copy(compositeKey[len(prefix):], key) + + return ethcrypto.Keccak256Hash(compositeKey) +} diff --git a/x/evm/types/state_object.go b/x/evm/types/state_object.go index 097f5c16..78599fde 100644 --- a/x/evm/types/state_object.go +++ b/x/evm/types/state_object.go @@ -21,7 +21,7 @@ import ( var ( _ StateObject = (*stateObject)(nil) - emptyCodeHash = ethcrypto.Keccak256(nil) + EmptyCodeHash = ethcrypto.Keccak256(nil) ) // StateObject interface for interacting with state object @@ -90,7 +90,7 @@ func newStateObject(db *CommitStateDB, accProto authtypes.AccountI, balance sdk. // set empty code hash if ethAccount.CodeHash == nil { - ethAccount.CodeHash = emptyCodeHash + ethAccount.CodeHash = EmptyCodeHash } return &stateObject{ @@ -307,7 +307,7 @@ func (so *stateObject) Balance() *big.Int { // CodeHash returns the state object's code hash. func (so *stateObject) CodeHash() []byte { if so.account == nil || len(so.account.CodeHash) == 0 { - return emptyCodeHash + return EmptyCodeHash } return so.account.CodeHash } @@ -326,7 +326,7 @@ func (so *stateObject) Code(_ ethstate.Database) []byte { return so.code } - if bytes.Equal(so.CodeHash(), emptyCodeHash) { + if bytes.Equal(so.CodeHash(), EmptyCodeHash) { return nil } @@ -416,7 +416,7 @@ func (so *stateObject) empty() bool { (so.account != nil && so.account.Sequence == 0 && (so.balance.BigInt() == nil || so.balance.IsZero()) && - bytes.Equal(so.account.CodeHash, emptyCodeHash)) + bytes.Equal(so.account.CodeHash, EmptyCodeHash)) } // EncodeRLP implements rlp.Encoder. diff --git a/x/evm/types/state_transition.go b/x/evm/types/state_transition.go index 5079e22d..3407243d 100644 --- a/x/evm/types/state_transition.go +++ b/x/evm/types/state_transition.go @@ -98,7 +98,8 @@ func (st *StateTransition) newEVM( } vmConfig := vm.Config{ - ExtraEips: eips, + EnablePreimageRecording: false, // no need for StateDB.AddPreimage + ExtraEips: eips, } if st.Debug { diff --git a/x/evm/types/state_transition_test.go b/x/evm/types/state_transition_test.go index 9e511887..dffe0fc3 100644 --- a/x/evm/types/state_transition_test.go +++ b/x/evm/types/state_transition_test.go @@ -179,8 +179,8 @@ func (suite *StateDBTestSuite) TestTransitionDb() { if tc.expPass { suite.Require().NoError(err, tc.name) - fromBalance := suite.app.EvmKeeper.GetBalance(suite.ctx, suite.address) - toBalance := suite.app.EvmKeeper.GetBalance(suite.ctx, recipient) + fromBalance := suite.app.EvmKeeper.CommitStateDB.GetBalance(suite.address) + toBalance := suite.app.EvmKeeper.CommitStateDB.GetBalance(recipient) suite.Require().Equal(fromBalance, big.NewInt(4950), tc.name) suite.Require().Equal(toBalance, big.NewInt(50), tc.name) } else { diff --git a/x/evm/types/statedb.go b/x/evm/types/statedb.go index 076a485b..e8a20cc3 100644 --- a/x/evm/types/statedb.go +++ b/x/evm/types/statedb.go @@ -72,14 +72,14 @@ type CommitStateDB struct { // by StateDB.Commit. dbErr error - // Journal of state modifications. This is the backbone of + // journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. journal *journal validRevisions []revision nextRevisionID int // Per-transaction access list - accessList *accessList + accessList *AccessListMappings // mutex for state deep copying lock sync.Mutex @@ -106,7 +106,7 @@ func NewCommitStateDB( preimages: []preimageEntry{}, hashToPreimageIndex: make(map[ethcmn.Hash]int), journal: newJournal(), - accessList: newAccessList(), + accessList: NewAccessListMappings(), } } @@ -727,7 +727,7 @@ func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error { csdb.logSize = 0 csdb.preimages = []preimageEntry{} csdb.hashToPreimageIndex = make(map[ethcmn.Hash]int) - csdb.accessList = newAccessList() + csdb.accessList = NewAccessListMappings() csdb.clearJournalAndRefund() return nil