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
This commit is contained in:
Federico Kunze 2021-05-25 08:56:36 -04:00 committed by GitHub
parent bd89830d64
commit 6c1e7fec01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 924 additions and 991 deletions

View File

@ -67,7 +67,7 @@ func NewAnteHandler(
// handle as *evmtypes.MsgEthereumTx // handle as *evmtypes.MsgEthereumTx
anteHandler = sdk.ChainAnteDecorators( anteHandler = sdk.ChainAnteDecorators(
NewEthSetupContextDecorator(), // outermost AnteDecorator. EthSetUpContext must be called first NewEthSetupContextDecorator(evmKeeper), // outermost AnteDecorator. EthSetUpContext must be called first
NewEthMempoolFeeDecorator(evmKeeper), NewEthMempoolFeeDecorator(evmKeeper),
NewEthValidateBasicDecorator(), NewEthValidateBasicDecorator(),
authante.TxTimeoutHeightDecorator{}, authante.TxTimeoutHeightDecorator{},

View File

@ -22,6 +22,8 @@ import (
type EVMKeeper interface { type EVMKeeper interface {
GetParams(ctx sdk.Context) evmtypes.Params GetParams(ctx sdk.Context) evmtypes.Params
GetChainConfig(ctx sdk.Context) (evmtypes.ChainConfig, bool) 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 // EthSetupContextDecorator sets the infinite GasMeter in the Context and wraps
@ -30,11 +32,15 @@ type EVMKeeper interface {
// on gas provided and gas used. // on gas provided and gas used.
// CONTRACT: Must be first decorator in the chain // CONTRACT: Must be first decorator in the chain
// CONTRACT: Tx must implement GasTx interface // CONTRACT: Tx must implement GasTx interface
type EthSetupContextDecorator struct{} type EthSetupContextDecorator struct {
evmKeeper EVMKeeper
}
// NewEthSetupContextDecorator creates a new EthSetupContextDecorator // NewEthSetupContextDecorator creates a new EthSetupContextDecorator
func NewEthSetupContextDecorator() EthSetupContextDecorator { func NewEthSetupContextDecorator(ek EVMKeeper) EthSetupContextDecorator {
return EthSetupContextDecorator{} return EthSetupContextDecorator{
evmKeeper: ek,
}
} }
// AnteHandle sets the infinite gas meter to done to ignore costs in AnteHandler checks. // 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) { 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()) ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
// reset the refund gas value for the current transaction
escd.evmKeeper.ResetRefundTransient(ctx)
// all transactions must implement GasTx // all transactions must implement GasTx
gasTx, ok := tx.(authante.GasTx) gasTx, ok := tx.(authante.GasTx)
if !ok { 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 // Set gas meter after ante handler to ignore gaskv costs
newCtx = authante.SetGasMeter(simulate, ctx, gasLimit) newCtx = authante.SetGasMeter(simulate, ctx, gasLimit)
egcd.evmKeeper.WithContext(newCtx)
return next(newCtx, tx, simulate) return next(newCtx, tx, simulate)
} }

View File

@ -252,7 +252,8 @@ func NewEthermintApp(
evmtypes.StoreKey, 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) memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey)
app := &EthermintApp{ app := &EthermintApp{
@ -310,7 +311,7 @@ func NewEthermintApp(
// Create Ethermint keepers // Create Ethermint keepers
app.EvmKeeper = evmkeeper.NewKeeper( 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 // Create IBC Keeper

View File

@ -105,6 +105,7 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak authkeeper.
genBlock := ethcore.DefaultGenesisBlock() genBlock := ethcore.DefaultGenesisBlock()
ms := cms.CacheMultiStore() ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, tmproto.Header{}, false, logger) ctx := sdk.NewContext(ms, tmproto.Header{}, false, logger)
evmKeeper.CommitStateDB.WithContext(ctx)
// Set the default Ethermint parameters to the parameter keeper store // Set the default Ethermint parameters to the parameter keeper store
evmKeeper.SetParams(ctx, evmtypes.DefaultParams()) evmKeeper.SetParams(ctx, evmtypes.DefaultParams())
@ -123,23 +124,23 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak authkeeper.
addr := ethcmn.HexToAddress(addrStr) addr := ethcmn.HexToAddress(addrStr)
acc := genBlock.Alloc[addr] acc := genBlock.Alloc[addr]
evmKeeper.AddBalance(ctx, addr, acc.Balance) evmKeeper.CommitStateDB.AddBalance(addr, acc.Balance)
evmKeeper.SetCode(ctx, addr, acc.Code) evmKeeper.CommitStateDB.SetCode(addr, acc.Code)
evmKeeper.SetNonce(ctx, addr, acc.Nonce) evmKeeper.CommitStateDB.SetNonce(addr, acc.Nonce)
for key, value := range acc.Storage { 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 // 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()) require.Equal(t, "200000000000000000000", b.String())
// commit the stateDB with 'false' to delete empty objects // commit the stateDB with 'false' to delete empty objects
// //
// NOTE: Commit does not yet return the intra merkle root (version) // 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) require.NoError(t, err)
// persist multi-store cache state // persist multi-store cache state
@ -257,13 +258,14 @@ func TestImportBlocks(t *testing.T) {
ms := cms.CacheMultiStore() ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, tmproto.Header{}, false, logger) ctx := sdk.NewContext(ms, tmproto.Header{}, false, logger)
ctx = ctx.WithBlockHeight(int64(block.NumberU64())) ctx = ctx.WithBlockHeight(int64(block.NumberU64()))
evmKeeper.CommitStateDB.WithContext(ctx)
if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 { if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 {
applyDAOHardFork(evmKeeper) applyDAOHardFork(evmKeeper)
} }
for i, tx := range block.Transactions() { 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()) // evmKeeper.CommitStateDB.Set(block.Hash())
receipt, gas, err := applyTransaction( receipt, gas, err := applyTransaction(

View File

@ -21,6 +21,8 @@ func InitGenesis(
bankKeeper types.BankKeeper, bankKeeper types.BankKeeper,
data types.GenesisState, data types.GenesisState,
) []abci.ValidatorUpdate { ) []abci.ValidatorUpdate {
k.CommitStateDB.WithContext(ctx)
k.SetParams(ctx, data.Params) k.SetParams(ctx, data.Params)
evmDenom := data.Params.EvmDenom evmDenom := data.Params.EvmDenom
@ -43,18 +45,18 @@ func InitGenesis(
} }
evmBalance := bankKeeper.GetBalance(ctx, accAddress, evmDenom) evmBalance := bankKeeper.GetBalance(ctx, accAddress, evmDenom)
k.SetBalance(ctx, address, evmBalance.Amount.BigInt()) k.CommitStateDB.SetBalance(address, evmBalance.Amount.BigInt())
k.SetNonce(ctx, address, acc.GetSequence()) k.CommitStateDB.SetNonce(address, acc.GetSequence())
k.SetCode(ctx, address, ethcmn.Hex2Bytes(account.Code)) k.CommitStateDB.SetCode(address, ethcmn.Hex2Bytes(account.Code))
for _, storage := range account.Storage { 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 var err error
for _, txLog := range data.TxsLogs { 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 { if err != nil {
panic(err) panic(err)
} }
@ -63,14 +65,14 @@ func InitGenesis(
k.SetChainConfig(ctx, data.ChainConfig) k.SetChainConfig(ctx, data.ChainConfig)
// set state objects and code to store // set state objects and code to store
_, err = k.Commit(ctx, false) _, err = k.CommitStateDB.Commit(false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// set storage to store // set storage to store
// NOTE: don't delete empty object to prevent import-export simulation failure // 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 { if err != nil {
panic(err) panic(err)
} }
@ -80,6 +82,8 @@ func InitGenesis(
// ExportGenesis exports genesis state of the EVM module // ExportGenesis exports genesis state of the EVM module
func ExportGenesis(ctx sdk.Context, k keeper.Keeper, ak types.AccountKeeper) *types.GenesisState { func ExportGenesis(ctx sdk.Context, k keeper.Keeper, ak types.AccountKeeper) *types.GenesisState {
k.CommitStateDB.WithContext(ctx)
// nolint: prealloc // nolint: prealloc
var ethGenAccounts []types.GenesisAccount var ethGenAccounts []types.GenesisAccount
ak.IterateAccounts(ctx, func(account authtypes.AccountI) bool { 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{ genAccount := types.GenesisAccount{
Address: addr.String(), Address: addr.String(),
Code: ethcmn.Bytes2Hex(k.GetCode(ctx, addr)), Code: ethcmn.Bytes2Hex(k.CommitStateDB.GetCode(addr)),
Storage: storage, Storage: storage,
} }

View File

@ -47,6 +47,7 @@ func (suite *EvmTestSuite) SetupTest() {
suite.app = app.Setup(checkTx) suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "ethermint-888", Time: time.Now().UTC()}) 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.handler = evm.NewHandler(suite.app.EvmKeeper)
suite.codec = suite.app.AppCodec() suite.codec = suite.app.AppCodec()
suite.chainID = suite.chainID suite.chainID = suite.chainID
@ -80,7 +81,7 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() {
{ {
"passed", "passed",
func() { 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) to := ethcmn.BytesToAddress(suite.to)
tx = types.NewMsgEthereumTx(suite.chainID, 0, &to, big.NewInt(100), 0, big.NewInt(10000), nil, nil) tx = types.NewMsgEthereumTx(suite.chainID, 0, &to, big.NewInt(100), 0, big.NewInt(10000), nil, nil)
tx.From = suite.from.String() tx.From = suite.from.String()
@ -193,10 +194,10 @@ func (suite *EvmTestSuite) TestHandlerLogs() {
suite.Require().Equal(len(txResponse.TxLogs.Logs[0].Topics), 2) suite.Require().Equal(len(txResponse.TxLogs.Logs[0].Topics), 2)
hash := []byte{1} 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) 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().NoError(err, "failed to get logs")
suite.Require().Equal(logs, txResponse.TxLogs.Logs) suite.Require().Equal(logs, txResponse.TxLogs.Logs)
@ -227,7 +228,7 @@ func (suite *EvmTestSuite) TestQueryTxLogs() {
// get logs by tx hash // get logs by tx hash
hash := txResponse.TxLogs.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().NoError(err, "failed to get logs")
suite.Require().Equal(logs, txResponse.TxLogs.EthLogs()) suite.Require().Equal(logs, txResponse.TxLogs.EthLogs())
@ -345,7 +346,7 @@ func (suite *EvmTestSuite) TestSendTransaction() {
gasLimit := uint64(21000) gasLimit := uint64(21000)
gasPrice := big.NewInt(0x55ae82600) 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 // send simple value transfer with gasLimit=21000
tx := types.NewMsgEthereumTx(suite.chainID, 1, &ethcmn.Address{0x1}, big.NewInt(1), gasLimit, gasPrice, nil, nil) tx := types.NewMsgEthereumTx(suite.chainID, 1, &ethcmn.Address{0x1}, big.NewInt(1), gasLimit, gasPrice, nil, nil)

View File

@ -33,10 +33,6 @@ func (k *Keeper) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
// special setter for csdb // special setter for csdb
k.SetHeightHash(ctx, uint64(req.Header.Height), common.BytesToHash(req.Hash)) 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 // 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 // Gas costs are handled within msg handler so costs should be ignored
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
k.CommitStateDB.WithContext(ctx)
// Update account balances before committing other parts of state // 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 // Commit state objects to KV store
if err != nil { if err != nil {
k.Logger(ctx).Error("failed to commit state objects", "error", err, "height", ctx.BlockHeight()) 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 // 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) panic(err)
} }
// set the block bloom filter bytes to store // get the block bloom bytes from the transient store and set it to the persistent storage
bloom := ethtypes.BytesToBloom(k.Bloom.Bytes()) bloomBig, found := k.GetBlockBloomTransient()
if !found {
bloomBig = big.NewInt(0)
}
bloom := ethtypes.BytesToBloom(bloomBig.Bytes())
k.SetBlockBloom(ctx, req.Height, bloom) k.SetBlockBloom(ctx, req.Height, bloom)
return []abci.ValidatorUpdate{} return []abci.ValidatorUpdate{}

View File

@ -32,7 +32,9 @@ func (k Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*typ
} }
ctx := sdk.UnwrapSDKContext(c) 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()) balance, err := ethermint.MarshalBigInt(so.Balance())
if err != nil { if err != nil {
return nil, err return nil, err
@ -57,6 +59,7 @@ func (k Keeper) CosmosAccount(c context.Context, req *types.QueryCosmosAccountRe
} }
ctx := sdk.UnwrapSDKContext(c) ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
ethAddr := ethcmn.HexToAddress(req.Address) ethAddr := ethcmn.HexToAddress(req.Address)
cosmosAddr := sdk.AccAddress(ethAddr.Bytes()) cosmosAddr := sdk.AccAddress(ethAddr.Bytes())
@ -88,8 +91,9 @@ func (k Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*typ
} }
ctx := sdk.UnwrapSDKContext(c) 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) balance, err := ethermint.MarshalBigInt(balanceInt)
if err != nil { if err != nil {
return nil, status.Error( return nil, status.Error(
@ -117,11 +121,12 @@ func (k Keeper) Storage(c context.Context, req *types.QueryStorageRequest) (*typ
} }
ctx := sdk.UnwrapSDKContext(c) ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
address := ethcmn.HexToAddress(req.Address) address := ethcmn.HexToAddress(req.Address)
key := ethcmn.HexToHash(req.Key) key := ethcmn.HexToHash(req.Key)
state := k.GetState(ctx, address, key) state := k.CommitStateDB.GetState(address, key)
return &types.QueryStorageResponse{ return &types.QueryStorageResponse{
Value: state.String(), Value: state.String(),
@ -142,9 +147,10 @@ func (k Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.Que
} }
ctx := sdk.UnwrapSDKContext(c) ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
address := ethcmn.HexToAddress(req.Address) address := ethcmn.HexToAddress(req.Address)
code := k.GetCode(ctx, address) code := k.CommitStateDB.GetCode(address)
return &types.QueryCodeResponse{ return &types.QueryCodeResponse{
Code: code, Code: code,
@ -165,9 +171,10 @@ func (k Keeper) TxLogs(c context.Context, req *types.QueryTxLogsRequest) (*types
} }
ctx := sdk.UnwrapSDKContext(c) ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
hash := ethcmn.HexToHash(req.Hash) hash := ethcmn.HexToHash(req.Hash)
logs, err := k.GetLogs(ctx, hash) logs, err := k.CommitStateDB.GetLogs(hash)
if err != nil { if err != nil {
return nil, status.Error( return nil, status.Error(
codes.Internal, codes.Internal,
@ -296,6 +303,7 @@ func (k Keeper) StaticCall(c context.Context, req *types.QueryStaticCallRequest)
} }
ctx := sdk.UnwrapSDKContext(c) ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
// parse the chainID from a string to a base-10 integer // parse the chainID from a string to a base-10 integer
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID()) chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
@ -312,7 +320,7 @@ func (k Keeper) StaticCall(c context.Context, req *types.QueryStaticCallRequest)
recipient = &addr recipient = &addr
} }
so := k.GetOrNewStateObject(ctx, *recipient) so := k.CommitStateDB.GetOrNewStateObject(*recipient)
sender := ethcmn.HexToAddress("0xaDd00275E3d9d213654Ce5223f0FADE8b106b707") sender := ethcmn.HexToAddress("0xaDd00275E3d9d213654Ce5223f0FADE8b106b707")
msg := types.NewMsgEthereumTx( msg := types.NewMsgEthereumTx(

View File

@ -247,7 +247,7 @@ func (suite *KeeperTestSuite) TestQueryStorage() {
key := ethcmn.BytesToHash([]byte("key")) key := ethcmn.BytesToHash([]byte("key"))
value := ethcmn.BytesToHash([]byte("value")) value := ethcmn.BytesToHash([]byte("value"))
expValue = value.String() 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{ req = &types.QueryStorageRequest{
Address: suite.address.String(), Address: suite.address.String(),
Key: key.String(), Key: key.String(),
@ -302,7 +302,7 @@ func (suite *KeeperTestSuite) TestQueryCode() {
"success", "success",
func() { func() {
expCode = []byte("code") expCode = []byte("code")
suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, expCode) suite.app.EvmKeeper.CommitStateDB.SetCode(suite.address, expCode)
req = &types.QueryCodeRequest{ req = &types.QueryCodeRequest{
Address: suite.address.String(), 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{ req = &types.QueryTxLogsRequest{
Hash: hash.String(), 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.CommitStateDB.SetLogs(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_1")), types.LogsToEthereum(expLogs[1].Logs))
req = &types.QueryBlockLogsRequest{ req = &types.QueryBlockLogsRequest{
Hash: hash.String(), Hash: hash.String(),

View File

@ -30,6 +30,8 @@ func (k Keeper) BalanceInvariant() sdk.Invariant {
count int count int
) )
k.CommitStateDB.WithContext(ctx)
k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) bool { k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) bool {
ethAccount, ok := account.(*ethermint.EthAccount) ethAccount, ok := account.(*ethermint.EthAccount)
if !ok { if !ok {
@ -40,7 +42,7 @@ func (k Keeper) BalanceInvariant() sdk.Invariant {
evmDenom := k.GetParams(ctx).EvmDenom evmDenom := k.GetParams(ctx).EvmDenom
accountBalance := k.bankKeeper.GetBalance(ctx, ethAccount.GetAddress(), 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 { if evmBalance.Cmp(accountBalance.Amount.BigInt()) != 0 {
count++ count++
@ -71,6 +73,8 @@ func (k Keeper) NonceInvariant() sdk.Invariant {
count int count int
) )
k.CommitStateDB.WithContext(ctx)
k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) bool { k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) bool {
ethAccount, ok := account.(*ethermint.EthAccount) ethAccount, ok := account.(*ethermint.EthAccount)
if !ok { if !ok {
@ -78,7 +82,7 @@ func (k Keeper) NonceInvariant() sdk.Invariant {
return false return false
} }
evmNonce := k.GetNonce(ctx, ethAccount.EthAddress()) evmNonce := k.CommitStateDB.GetNonce(ethAccount.EthAddress())
if evmNonce != ethAccount.Sequence { if evmNonce != ethAccount.Sequence {
count++ count++

View File

@ -29,7 +29,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() {
suite.Require().NoError(err) suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc) 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, true,
}, },
@ -42,7 +42,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() {
suite.Require().NoError(err) suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc) 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, false,
}, },
@ -60,6 +60,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
suite.SetupTest() // reset values suite.SetupTest() // reset values
suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx)
tc.malleate() tc.malleate()
_, broken := suite.app.EvmKeeper.BalanceInvariant()(suite.ctx) _, broken := suite.app.EvmKeeper.BalanceInvariant()(suite.ctx)
@ -92,7 +93,7 @@ func (suite *KeeperTestSuite) TestNonceInvariant() {
suite.Require().NoError(err) suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc) suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
suite.app.EvmKeeper.SetNonce(suite.ctx, address, 100) suite.app.EvmKeeper.CommitStateDB.SetNonce(address, 100)
}, },
true, true,
}, },
@ -105,7 +106,7 @@ func (suite *KeeperTestSuite) TestNonceInvariant() {
suite.Require().NoError(err) suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc) suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
suite.app.EvmKeeper.SetNonce(suite.ctx, address, 1) suite.app.EvmKeeper.CommitStateDB.SetNonce(address, 1)
}, },
false, false,
}, },
@ -123,6 +124,7 @@ func (suite *KeeperTestSuite) TestNonceInvariant() {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
suite.SetupTest() // reset values suite.SetupTest() // reset values
suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx)
tc.malleate() tc.malleate()
_, broken := suite.app.EvmKeeper.NonceInvariant()(suite.ctx) _, broken := suite.app.EvmKeeper.NonceInvariant()(suite.ctx)

View File

@ -1,9 +1,11 @@
package keeper package keeper
import ( import (
"bytes"
"math/big" "math/big"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/ethereum/go-ethereum/common" "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 // - storing block hash -> block height map. Needed for the Web3 API. TODO: remove
storeKey sdk.StoreKey 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 accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper bankKeeper types.BankKeeper
ctx sdk.Context
// Ethermint concrete implementation on the EVM StateDB interface // Ethermint concrete implementation on the EVM StateDB interface
CommitStateDB *types.CommitStateDB 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 // Per-transaction access list
// during EVM execution in the current block. // See EIP-2930 for more info: https://eips.ethereum.org/EIPS/eip-2930
LogsCache map[common.Address][]*ethtypes.Log // 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 // NewKeeper generates new evm module keeper
func NewKeeper( 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, ak types.AccountKeeper, bankKeeper types.BankKeeper,
) *Keeper { ) *Keeper {
// set KeyTable if it has not already been set // 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 // NOTE: we pass in the parameter space to the CommitStateDB in order to use custom denominations for the EVM operations
return &Keeper{ return &Keeper{
cdc: cdc, cdc: cdc,
paramSpace: paramSpace,
accountKeeper: ak, accountKeeper: ak,
bankKeeper: bankKeeper, bankKeeper: bankKeeper,
storeKey: storeKey, storeKey: storeKey,
transientKey: transientKey,
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, paramSpace, ak, bankKeeper), CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, paramSpace, ak, bankKeeper),
TxCount: 0, accessList: types.NewAccessListMappings(),
Bloom: big.NewInt(0),
LogsCache: map[common.Address][]*ethtypes.Log{},
} }
} }
@ -70,8 +75,13 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", types.ModuleName) 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. // 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()) 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 // GetBlockHash gets block height from block consensus hash
func (k Keeper) GetBlockHashFromHeight(ctx sdk.Context, height int64) (common.Hash, bool) { func (k Keeper) GetBlockHashFromHeight(ctx sdk.Context, height int64) (common.Hash, bool) {
store := ctx.KVStore(k.storeKey) 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) 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. // GetTxReceiptFromHash gets tx receipt by tx hash.
func (k Keeper) GetTxReceiptFromHash(ctx sdk.Context, hash common.Hash) (*types.TxReceipt, bool) { func (k Keeper) GetTxReceiptFromHash(ctx sdk.Context, hash common.Hash) (*types.TxReceipt, bool) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
data := store.Get(types.KeyHashTxReceipt(hash)) data := store.Get(types.KeyHashTxReceipt(hash))
if data == nil || len(data) == 0 { if len(data) == 0 {
return nil, false return nil, false
} }
@ -216,7 +277,7 @@ func (k Keeper) GetTxReceiptsByBlockHeight(ctx sdk.Context, blockHeight int64) [
for idx, txHash := range txs { for idx, txHash := range txs {
data := store.Get(types.KeyHashTxReceipt(txHash)) data := store.Get(types.KeyHashTxReceipt(txHash))
if data == nil || len(data) == 0 { if len(data) == 0 {
continue continue
} }
@ -239,6 +300,10 @@ func (k Keeper) GetTxReceiptsByBlockHash(ctx sdk.Context, hash common.Hash) []*t
return k.GetTxReceiptsByBlockHeight(ctx, blockHeight) return k.GetTxReceiptsByBlockHeight(ctx, blockHeight)
} }
// ----------------------------------------------------------------------------
// Log
// ----------------------------------------------------------------------------
// GetAllTxLogs return all the transaction logs from the store. // GetAllTxLogs return all the transaction logs from the store.
func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs { func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
@ -256,14 +321,51 @@ func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs {
return txsLogs 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 // GetAccountStorage return state storage associated with an account
func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (types.Storage, error) { func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (types.Storage, error) {
storage := types.Storage{} 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)) storage = append(storage, types.NewState(key, value))
return false return false
}) })
if err != nil { if err != nil {
return types.Storage{}, err return types.Storage{}, err
} }
@ -271,22 +373,62 @@ func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (type
return storage, nil return storage, nil
} }
// GetChainConfig gets block height from block consensus hash // ----------------------------------------------------------------------------
func (k Keeper) GetChainConfig(ctx sdk.Context) (types.ChainConfig, bool) { // Account
store := ctx.KVStore(k.storeKey) // ----------------------------------------------------------------------------
bz := store.Get(types.KeyPrefixChainConfig)
if len(bz) == 0 { func (k Keeper) DeleteState(addr common.Address, key common.Hash) {
return types.ChainConfig{}, false 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 store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode)
k.cdc.MustUnmarshalBinaryBare(bz, &config) store.Delete(hash.Bytes())
return config, true
} }
// SetChainConfig sets the mapping from block consensus hash to block height // ClearBalance subtracts the EVM all the balance denomination from the address
func (k Keeper) SetChainConfig(ctx sdk.Context, config types.ChainConfig) { // balance while also updating the total supply.
store := ctx.KVStore(k.storeKey) func (k Keeper) ClearBalance(addr sdk.AccAddress) (prevBalance sdk.Coin, err error) {
bz := k.cdc.MustMarshalBinaryBare(&config) params := k.GetParams(k.ctx)
store.Set(types.KeyPrefixChainConfig, bz)
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(),
)
}
} }

View File

@ -43,6 +43,8 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.app = app.Setup(checkTx) suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "ethermint-3", Time: time.Now().UTC()}) 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) suite.address = ethcmn.HexToAddress(addrHex)
queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry()) queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
@ -77,18 +79,18 @@ func (suite *KeeperTestSuite) TestTransactionLogs() {
} }
expLogs := []*ethtypes.Log{log} 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) 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().NoError(err)
suite.Require().Equal(expLogs, logs) suite.Require().Equal(expLogs, logs)
expLogs = []*ethtypes.Log{log2, log} expLogs = []*ethtypes.Log{log2, log}
// add another log under the zero hash // add another log under the zero hash
suite.app.EvmKeeper.AddLog(suite.ctx, log2) suite.app.EvmKeeper.CommitStateDB.AddLog(log2)
logs = suite.app.EvmKeeper.AllLogs(suite.ctx) logs = suite.app.EvmKeeper.CommitStateDB.AllLogs()
suite.Require().Equal(expLogs, logs) suite.Require().Equal(expLogs, logs)
// add another log under the zero hash // add another log under the zero hash
@ -97,7 +99,7 @@ func (suite *KeeperTestSuite) TestTransactionLogs() {
Data: []byte("log3"), Data: []byte("log3"),
BlockNumber: 10, BlockNumber: 10,
} }
suite.app.EvmKeeper.AddLog(suite.ctx, log3) suite.app.EvmKeeper.CommitStateDB.AddLog(log3)
txLogs := suite.app.EvmKeeper.GetAllTxLogs(suite.ctx) txLogs := suite.app.EvmKeeper.GetAllTxLogs(suite.ctx)
suite.Require().Equal(2, len(txLogs)) suite.Require().Equal(2, len(txLogs))
@ -111,28 +113,28 @@ func (suite *KeeperTestSuite) TestTransactionLogs() {
func (suite *KeeperTestSuite) TestDBStorage() { func (suite *KeeperTestSuite) TestDBStorage() {
// Perform state transitions // Perform state transitions
suite.app.EvmKeeper.CreateAccount(suite.ctx, suite.address) suite.app.EvmKeeper.CommitStateDB.CreateAccount(suite.address)
suite.app.EvmKeeper.SetBalance(suite.ctx, suite.address, big.NewInt(5)) suite.app.EvmKeeper.CommitStateDB.SetBalance(suite.address, big.NewInt(5))
suite.app.EvmKeeper.SetNonce(suite.ctx, suite.address, 4) suite.app.EvmKeeper.CommitStateDB.SetNonce(suite.address, 4)
suite.app.EvmKeeper.SetState(suite.ctx, suite.address, ethcmn.HexToHash("0x2"), ethcmn.HexToHash("0x3")) suite.app.EvmKeeper.CommitStateDB.SetState(suite.address, ethcmn.HexToHash("0x2"), ethcmn.HexToHash("0x3"))
suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, []byte{0x1}) suite.app.EvmKeeper.CommitStateDB.SetCode(suite.address, []byte{0x1})
// Test block height mapping functionality // Test block height mapping functionality
testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3}) testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3})
suite.app.EvmKeeper.SetBlockBloom(suite.ctx, 4, testBloom) suite.app.EvmKeeper.SetBlockBloom(suite.ctx, 4, testBloom)
// Get those state transitions // 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.CommitStateDB.GetBalance(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.CommitStateDB.GetNonce(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.CommitStateDB.GetState(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.GetCode(suite.address), []byte{0x1})
bloom, found := suite.app.EvmKeeper.GetBlockBloom(suite.ctx, 4) bloom, found := suite.app.EvmKeeper.GetBlockBloom(suite.ctx, 4)
suite.Require().True(found) suite.Require().True(found)
suite.Require().Equal(bloom, testBloom) suite.Require().Equal(bloom, testBloom)
// commit stateDB // 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") suite.Require().NoError(err, "failed to commit StateDB")
// simulate BaseApp EndBlocker commitment // simulate BaseApp EndBlocker commitment

View File

@ -3,6 +3,7 @@ package keeper
import ( import (
"context" "context"
"errors" "errors"
"math/big"
"time" "time"
"github.com/armon/go-metrics" "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) defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.TypeMsgEthereumTx)
ctx := sdk.UnwrapSDKContext(goCtx) ctx := sdk.UnwrapSDKContext(goCtx)
k.CommitStateDB.WithContext(ctx)
ethMsg, err := msg.AsMessage() ethMsg, err := msg.AsMessage()
if err != nil { if err != nil {
@ -67,8 +69,8 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
// other nodes, causing a consensus error // other nodes, causing a consensus error
if !st.Simulate { if !st.Simulate {
// Prepare db for logs // Prepare db for logs
k.Prepare(ctx, ethHash, blockHash, k.TxCount) k.CommitStateDB.Prepare(ethHash, blockHash, int(k.GetTxIndexTransient()))
k.TxCount++ k.IncreaseTxIndexTransient()
} }
executionResult, err := st.TransitionDb(ctx, config) executionResult, err := st.TransitionDb(ctx, config)
@ -102,11 +104,16 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
} }
if !st.Simulate { if !st.Simulate {
bloom, found := k.GetBlockBloomTransient()
if !found {
bloom = big.NewInt(0)
}
// update block bloom filter // 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 // update transaction logs in KVStore
err = k.SetLogs(ctx, ethHash, executionResult.Logs) err = k.CommitStateDB.SetLogs(ethHash, executionResult.Logs)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -130,10 +137,6 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
}) })
k.AddTxHashToBlock(ctx, ctx.BlockHeight(), ethHash) k.AddTxHashToBlock(ctx, ctx.BlockHeight(), ethHash)
for _, ethLog := range executionResult.Logs {
k.LogsCache[ethLog.Address] = append(k.LogsCache[ethLog.Address], ethLog)
}
} }
defer func() { defer func() {

View File

@ -8,10 +8,31 @@ import (
// GetParams returns the total set of evm parameters. // GetParams returns the total set of evm parameters.
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
return k.CommitStateDB.WithContext(ctx).GetParams() k.paramSpace.GetParamSet(ctx, &params)
return params
} }
// SetParams sets the evm parameters to the param space. // SetParams sets the evm parameters to the param space.
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.CommitStateDB.WithContext(ctx).SetParams(params) k.paramSpace.SetParamSet(ctx, &params)
}
// 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)
} }

View File

@ -1,265 +1,589 @@
package keeper package keeper
import ( import (
"bytes"
"fmt"
"math/big" "math/big"
"github.com/cosmos/ethermint/x/evm/types" "github.com/ethereum/go-ethereum/common"
sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethtypes "github.com/ethereum/go-ethereum/core/types" 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 // CreateAccount creates a new EthAccount instance from the provided address and
func (k *Keeper) SetBalance(ctx sdk.Context, addr ethcmn.Address, amount *big.Int) { // sets the value to store.
k.CommitStateDB.WithContext(ctx).SetBalance(addr, amount) 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 // AddBalance calls CommitStateDB.AddBalance using the passed in context
func (k *Keeper) AddBalance(ctx sdk.Context, addr ethcmn.Address, amount *big.Int) { func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) {
k.CommitStateDB.WithContext(ctx).AddBalance(addr, amount) 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 // SubBalance calls CommitStateDB.SubBalance using the passed in context
func (k *Keeper) SubBalance(ctx sdk.Context, addr ethcmn.Address, amount *big.Int) { func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) {
k.CommitStateDB.WithContext(ctx).SubBalance(addr, amount) 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 // SetNonce calls CommitStateDB.SetNonce using the passed in context
func (k *Keeper) SetNonce(ctx sdk.Context, addr ethcmn.Address, nonce uint64) { func (k *Keeper) SetNonce(addr common.Address, nonce uint64) {
k.CommitStateDB.WithContext(ctx).SetNonce(addr, nonce) 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 // create address if it doesn't exist
func (k *Keeper) SetState(ctx sdk.Context, addr ethcmn.Address, key, value ethcmn.Hash) { account = k.accountKeeper.NewAccountWithAddress(k.ctx, cosmosAddr)
k.CommitStateDB.WithContext(ctx).SetState(addr, key, value) }
}
// SetCode calls CommitStateDB.SetCode using the passed in context if err := account.SetSequence(nonce); err != nil {
func (k *Keeper) SetCode(ctx sdk.Context, addr ethcmn.Address, code []byte) { k.Logger(k.ctx).Error(
k.CommitStateDB.WithContext(ctx).SetCode(addr, code) "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 return
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 k.CommitStateDB.WithContext(ctx).SetLogs(hash, logs) k.accountKeeper.SetAccount(k.ctx, account)
}
// DeleteLogs calls CommitStateDB.DeleteLogs using the passed in context k.Logger(k.ctx).Debug(
func (k *Keeper) DeleteLogs(ctx sdk.Context, hash ethcmn.Hash) { "nonce set",
k.CommitStateDB.WithContext(ctx).DeleteLogs(hash) "ethereum-address", addr.Hex(),
} "cosmos-address", cosmosAddr.String(),
"nonce", nonce,
// 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)
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Getters // Code
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// GetBalance calls CommitStateDB.GetBalance using the passed in context // GetCodeHash calls CommitStateDB.GetCodeHash using the passed in context
func (k *Keeper) GetBalance(ctx sdk.Context, addr ethcmn.Address) *big.Int { func (k *Keeper) GetCodeHash(addr common.Address) common.Hash {
return k.CommitStateDB.WithContext(ctx).GetBalance(addr) 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 ethAccount, isEthAccount := account.(*ethermint.EthAccount)
func (k *Keeper) GetNonce(ctx sdk.Context, addr ethcmn.Address) uint64 { if !isEthAccount {
return k.CommitStateDB.WithContext(ctx).GetNonce(addr) return common.BytesToHash(types.EmptyCodeHash)
} }
// TxIndex calls CommitStateDB.TxIndex using the passed in context return common.BytesToHash(ethAccount.CodeHash)
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()
} }
// GetCode calls CommitStateDB.GetCode using the passed in context // GetCode calls CommitStateDB.GetCode using the passed in context
func (k *Keeper) GetCode(ctx sdk.Context, addr ethcmn.Address) []byte { func (k *Keeper) GetCode(addr common.Address) []byte {
return k.CommitStateDB.WithContext(ctx).GetCode(addr) 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 // SetCode calls CommitStateDB.SetCode using the passed in context
func (k *Keeper) GetCodeSize(ctx sdk.Context, addr ethcmn.Address) int { func (k *Keeper) SetCode(addr common.Address, code []byte) {
return k.CommitStateDB.WithContext(ctx).GetCodeSize(addr) 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 // GetCodeSize returns the code hash stored in the address account.
func (k *Keeper) GetCodeHash(ctx sdk.Context, addr ethcmn.Address) ethcmn.Hash { func (k *Keeper) GetCodeSize(addr common.Address) int {
return k.CommitStateDB.WithContext(ctx).GetCodeHash(addr) 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 // GetState calls CommitStateDB.GetState using the passed in context
func (k *Keeper) GetState(ctx sdk.Context, addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash { func (k *Keeper) GetState(addr common.Address, hash common.Hash) common.Hash {
return k.CommitStateDB.WithContext(ctx).GetState(addr, hash) // All state is committed directly
return k.GetCommittedState(addr, hash)
} }
// GetCommittedState calls CommitStateDB.GetCommittedState using the passed in context // SetState calls CommitStateDB.SetState using the passed in context
func (k *Keeper) GetCommittedState(ctx sdk.Context, addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash { func (k *Keeper) SetState(addr common.Address, key, value common.Hash) {
return k.CommitStateDB.WithContext(ctx).GetCommittedState(addr, 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 action := "updated"
func (k *Keeper) GetLogs(ctx sdk.Context, hash ethcmn.Hash) ([]*ethtypes.Log, error) { if ethermint.IsEmptyHash(value.Hex()) {
return k.CommitStateDB.WithContext(ctx).GetLogs(hash) store.Delete(key.Bytes())
} action = "deleted"
} else {
store.Set(key.Bytes(), value.Bytes())
}
// AllLogs calls CommitStateDB.AllLogs using the passed in context k.Logger(k.ctx).Debug(
func (k *Keeper) AllLogs(ctx sdk.Context) []*ethtypes.Log { fmt.Sprintf("state %s", action),
return k.CommitStateDB.WithContext(ctx).AllLogs() "ethereum-address", addr.Hex(),
} "key", key.Hex(),
)
// 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)
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Persistence // Suicide
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Commit calls CommitStateDB.Commit using the passed in context // Suicide marks the given account as suicided and clears the account balance of
func (k *Keeper) Commit(ctx sdk.Context, deleteEmptyObjects bool) (root ethcmn.Hash, err error) { // the EVM tokens.
return k.CommitStateDB.WithContext(ctx).Commit(deleteEmptyObjects) 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 // HasSuicided queries the transient store to check if the account has been marked as suicided in the
func (k *Keeper) Finalise(ctx sdk.Context, deleteEmptyObjects bool) error { // current block. Accounts that are suicided will be returned as non-nil during queries and "cleared"
return k.CommitStateDB.WithContext(ctx).Finalise(deleteEmptyObjects) // 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 { // Account Exist / Empty
_, err := k.CommitStateDB.WithContext(ctx).IntermediateRoot(deleteEmptyObjects) // ----------------------------------------------------------------------------
return err
// 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 // Snapshotting
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Snapshot calls CommitStateDB.Snapshot using the passed in context // Snapshot return zero as the state changes won't be committed if the state transition fails. So there
func (k *Keeper) Snapshot(ctx sdk.Context) int { // is no need to snapshot before the VM execution.
return k.CommitStateDB.WithContext(ctx).Snapshot() // 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 // RevertToSnapshot performs a no-op because when a transaction execution fails on the EVM, the state
func (k *Keeper) RevertToSnapshot(ctx sdk.Context, revID int) { // won't be persisted during ABCI DeliverTx.
k.CommitStateDB.WithContext(ctx).RevertToSnapshot(revID) 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 // AddPreimage performs a no-op since the EnablePreimageRecording flag is disabled
func (k *Keeper) Database(ctx sdk.Context) ethstate.Database { // on the vm.Config during state transitions. No store trie preimages are written
return k.CommitStateDB.WithContext(ctx).Database() // 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 { // Iterator
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()
}
// ForEachStorage calls CommitStateDB.ForEachStorage using passed in context // 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 { func (k *Keeper) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
return k.CommitStateDB.WithContext(ctx).ForEachStorage(addr, cb) store := k.ctx.KVStore(k.storeKey)
} prefix := types.AddressStoragePrefix(addr)
// GetOrNewStateObject calls CommitStateDB.GetOrNetStateObject using the passed in context iterator := sdk.KVStorePrefixIterator(store, prefix)
func (k *Keeper) GetOrNewStateObject(ctx sdk.Context, addr ethcmn.Address) types.StateObject { defer iterator.Close()
return k.CommitStateDB.WithContext(ctx).GetOrNewStateObject(addr)
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
} }

View File

@ -1,644 +1 @@
package keeper_test 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",
&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)
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{}
}
}

View File

@ -6,22 +6,22 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types" 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 // 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 addresses map[common.Address]int
slots []map[common.Hash]struct{} slots []map[common.Hash]struct{}
} }
// ContainsAddress returns true if the address is in the access list. // 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] _, ok := al.addresses[address]
return ok return ok
} }
// Contains checks if a slot within an account is present in the access list, returning // 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. // 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] idx, ok := al.addresses[address]
if !ok { if !ok {
// no such address (and hence zero slots) // 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 return true, slotPresent
} }
// newAccessList creates a new accessList. // newAccessList creates a new AccessListMappings.
func newAccessList() *accessList { func NewAccessListMappings() *AccessListMappings {
return &accessList{ return &AccessListMappings{
addresses: make(map[common.Address]int), addresses: make(map[common.Address]int),
} }
} }
// Copy creates an independent copy of an accessList. // Copy creates an independent copy of an AccessListMappings.
func (al *accessList) Copy() *accessList { func (al *AccessListMappings) Copy() *AccessListMappings {
cp := newAccessList() cp := NewAccessListMappings()
for k, v := range al.addresses { for k, v := range al.addresses {
cp.addresses[k] = v 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 // AddAddress adds an address to the access list, and returns 'true' if the operation
// caused a change (addr was not previously in the list). // 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 { if _, present := al.addresses[address]; present {
return false return false
} }
@ -80,7 +80,7 @@ func (al *accessList) AddAddress(address common.Address) bool {
// - address added // - address added
// - slot added // - slot added
// For any 'true' value returned, a corresponding journal entry must be made. // 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] idx, addrPresent := al.addresses[address]
if !addrPresent || idx == -1 { if !addrPresent || idx == -1 {
// Address not present, or addr present but no slots there // 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] slotmap := al.slots[idx]
if _, ok := slotmap[slot]; !ok { if _, ok := slotmap[slot]; !ok {
slotmap[slot] = struct{}{} slotmap[slot] = struct{}{}
// Journal add slot change // journal add slot change
return false, true return false, true
} }
// No changes required // 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 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 // This method is meant to be used by the journal, which maintains ordering of
// operations. // 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] idx, addrOk := al.addresses[address]
// There are two ways this can fail // There are two ways this can fail
if !addrOk { 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. // 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 // This method is meant to be used by the journal, which maintains ordering of
// operations. // operations.
func (al *accessList) DeleteAddress(address common.Address) { func (al *AccessListMappings) DeleteAddress(address common.Address) {
delete(al.addresses, address) delete(al.addresses, address)
} }
@ -146,7 +146,7 @@ func NewAccessList(ethAccessList *ethtypes.AccessList) AccessList {
return nil return nil
} }
var accessList AccessList var AccessListMappings AccessList
for _, tuple := range *ethAccessList { for _, tuple := range *ethAccessList {
storageKeys := make([]string, len(tuple.StorageKeys)) storageKeys := make([]string, len(tuple.StorageKeys))
@ -154,19 +154,19 @@ func NewAccessList(ethAccessList *ethtypes.AccessList) AccessList {
storageKeys[i] = tuple.StorageKeys[i].String() storageKeys[i] = tuple.StorageKeys[i].String()
} }
accessList = append(accessList, AccessTuple{ AccessListMappings = append(AccessListMappings, AccessTuple{
Address: tuple.Address.String(), Address: tuple.Address.String(),
StorageKeys: storageKeys, StorageKeys: storageKeys,
}) })
} }
return accessList return AccessListMappings
} }
// ToEthAccessList is an utility function to convert the protobuf compatible // ToEthAccessList is an utility function to convert the protobuf compatible
// AccessList to eth core AccessList from go-ethereum // AccessList to eth core AccessList from go-ethereum
func (al AccessList) ToEthAccessList() *ethtypes.AccessList { func (al AccessList) ToEthAccessList() *ethtypes.AccessList {
var accessList ethtypes.AccessList var AccessListMappings ethtypes.AccessList
for _, tuple := range al { for _, tuple := range al {
storageKeys := make([]ethcmn.Hash, len(tuple.StorageKeys)) storageKeys := make([]ethcmn.Hash, len(tuple.StorageKeys))
@ -175,11 +175,11 @@ func (al AccessList) ToEthAccessList() *ethtypes.AccessList {
storageKeys[i] = ethcmn.HexToHash(tuple.StorageKeys[i]) storageKeys[i] = ethcmn.HexToHash(tuple.StorageKeys[i])
} }
accessList = append(accessList, ethtypes.AccessTuple{ AccessListMappings = append(AccessListMappings, ethtypes.AccessTuple{
Address: ethcmn.HexToAddress(tuple.Address), Address: ethcmn.HexToAddress(tuple.Address),
StorageKeys: storageKeys, StorageKeys: storageKeys,
}) })
} }
return &accessList return &AccessListMappings
} }

View File

@ -15,7 +15,7 @@ type AccessListTestSuite struct {
suite.Suite suite.Suite
address ethcmn.Address address ethcmn.Address
accessList *accessList accessList *AccessListMappings
} }
func (suite *AccessListTestSuite) SetupTest() { func (suite *AccessListTestSuite) SetupTest() {
@ -23,7 +23,7 @@ func (suite *AccessListTestSuite) SetupTest() {
suite.Require().NoError(err) suite.Require().NoError(err)
suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes()) suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes())
suite.accessList = newAccessList() suite.accessList = NewAccessListMappings()
suite.accessList.addresses[suite.address] = 1 suite.accessList.addresses[suite.address] = 1
} }
@ -85,7 +85,7 @@ func (suite *AccessListTestSuite) TestContains() {
} }
func (suite *AccessListTestSuite) TestCopy() { func (suite *AccessListTestSuite) TestCopy() {
expAccessList := newAccessList() expAccessList := NewAccessListMappings()
testCases := []struct { testCases := []struct {
name string name string
@ -96,7 +96,7 @@ func (suite *AccessListTestSuite) TestCopy() {
}}, }},
{ {
"single address", func() { "single address", func() {
expAccessList = newAccessList() expAccessList = NewAccessListMappings()
expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 0) expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 0)
expAccessList.addresses[suite.address] = -1 expAccessList.addresses[suite.address] = -1
}, },
@ -104,7 +104,7 @@ func (suite *AccessListTestSuite) TestCopy() {
{ {
"single address, single slot", "single address, single slot",
func() { func() {
expAccessList = newAccessList() expAccessList = NewAccessListMappings()
expAccessList.addresses[suite.address] = 0 expAccessList.addresses[suite.address] = 0
expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 1) expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 1)
expAccessList.slots[0] = make(map[ethcmn.Hash]struct{}) expAccessList.slots[0] = make(map[ethcmn.Hash]struct{})
@ -114,7 +114,7 @@ func (suite *AccessListTestSuite) TestCopy() {
{ {
"multiple addresses, single slot each", "multiple addresses, single slot each",
func() { func() {
expAccessList = newAccessList() expAccessList = NewAccessListMappings()
expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10) expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i
@ -126,7 +126,7 @@ func (suite *AccessListTestSuite) TestCopy() {
{ {
"multiple addresses, multiple slots each", "multiple addresses, multiple slots each",
func() { func() {
expAccessList = newAccessList() expAccessList = NewAccessListMappings()
expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10) expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i

View File

@ -10,6 +10,7 @@ type AccountKeeper interface {
NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
GetAllAccounts(ctx sdk.Context) (accounts []authtypes.AccountI) GetAllAccounts(ctx sdk.Context) (accounts []authtypes.AccountI)
IterateAccounts(ctx sdk.Context, cb func(account authtypes.AccountI) bool) 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 GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
SetAccount(ctx sdk.Context, account authtypes.AccountI) SetAccount(ctx sdk.Context, account authtypes.AccountI)
RemoveAccount(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. // BankKeeper defines the expected interface needed to retrieve account balances.
type BankKeeper interface { type BankKeeper interface {
GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin 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 SetBalance(ctx sdk.Context, addr sdk.AccAddress, balance sdk.Coin) error
} }

View File

@ -4,6 +4,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
) )
const ( const (
@ -15,21 +16,51 @@ const (
// The EVM module should use a prefix store. // The EVM module should use a prefix store.
StoreKey = ModuleName 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 uses module name for routing
RouterKey = ModuleName RouterKey = ModuleName
) )
const (
prefixBlockHash = iota + 1
prefixBloom
prefixLogs
prefixCode
prefixStorage
prefixChainConfig
prefixBlockHeightHash
prefixHashTxReceipt
prefixBlockHeightTxs
)
const (
prefixTransientSuicided = iota + 1
prefixTransientBloom
prefixTransientTxIndex
prefixTransientRefund
)
// KVStore key prefixes // KVStore key prefixes
var ( var (
KeyPrefixBlockHash = []byte{0x01} KeyPrefixBlockHash = []byte{prefixBlockHash}
KeyPrefixBloom = []byte{0x02} KeyPrefixBloom = []byte{prefixBloom}
KeyPrefixLogs = []byte{0x03} KeyPrefixLogs = []byte{prefixLogs}
KeyPrefixCode = []byte{0x04} KeyPrefixCode = []byte{prefixCode}
KeyPrefixStorage = []byte{0x05} KeyPrefixStorage = []byte{prefixStorage}
KeyPrefixChainConfig = []byte{0x06} KeyPrefixChainConfig = []byte{prefixChainConfig}
KeyPrefixBlockHeightHash = []byte{0x07} KeyPrefixBlockHeightHash = []byte{prefixBlockHeightHash}
KeyPrefixHashTxReceipt = []byte{0x08} KeyPrefixHashTxReceipt = []byte{prefixHashTxReceipt}
KeyPrefixBlockHeightTxs = []byte{0x09} 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 // BloomKey defines the store key for a block Bloom
@ -69,3 +100,17 @@ func KeyBlockHeightTxs(height uint64) []byte {
heightBytes := sdk.Uint64ToBigEndian(height) heightBytes := sdk.Uint64ToBigEndian(height)
return append(KeyPrefixBlockHeightTxs, heightBytes...) 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)
}

View File

@ -21,7 +21,7 @@ import (
var ( var (
_ StateObject = (*stateObject)(nil) _ StateObject = (*stateObject)(nil)
emptyCodeHash = ethcrypto.Keccak256(nil) EmptyCodeHash = ethcrypto.Keccak256(nil)
) )
// StateObject interface for interacting with state object // StateObject interface for interacting with state object
@ -90,7 +90,7 @@ func newStateObject(db *CommitStateDB, accProto authtypes.AccountI, balance sdk.
// set empty code hash // set empty code hash
if ethAccount.CodeHash == nil { if ethAccount.CodeHash == nil {
ethAccount.CodeHash = emptyCodeHash ethAccount.CodeHash = EmptyCodeHash
} }
return &stateObject{ return &stateObject{
@ -307,7 +307,7 @@ func (so *stateObject) Balance() *big.Int {
// CodeHash returns the state object's code hash. // CodeHash returns the state object's code hash.
func (so *stateObject) CodeHash() []byte { func (so *stateObject) CodeHash() []byte {
if so.account == nil || len(so.account.CodeHash) == 0 { if so.account == nil || len(so.account.CodeHash) == 0 {
return emptyCodeHash return EmptyCodeHash
} }
return so.account.CodeHash return so.account.CodeHash
} }
@ -326,7 +326,7 @@ func (so *stateObject) Code(_ ethstate.Database) []byte {
return so.code return so.code
} }
if bytes.Equal(so.CodeHash(), emptyCodeHash) { if bytes.Equal(so.CodeHash(), EmptyCodeHash) {
return nil return nil
} }
@ -416,7 +416,7 @@ func (so *stateObject) empty() bool {
(so.account != nil && (so.account != nil &&
so.account.Sequence == 0 && so.account.Sequence == 0 &&
(so.balance.BigInt() == nil || so.balance.IsZero()) && (so.balance.BigInt() == nil || so.balance.IsZero()) &&
bytes.Equal(so.account.CodeHash, emptyCodeHash)) bytes.Equal(so.account.CodeHash, EmptyCodeHash))
} }
// EncodeRLP implements rlp.Encoder. // EncodeRLP implements rlp.Encoder.

View File

@ -98,6 +98,7 @@ func (st *StateTransition) newEVM(
} }
vmConfig := vm.Config{ vmConfig := vm.Config{
EnablePreimageRecording: false, // no need for StateDB.AddPreimage
ExtraEips: eips, ExtraEips: eips,
} }

View File

@ -179,8 +179,8 @@ func (suite *StateDBTestSuite) TestTransitionDb() {
if tc.expPass { if tc.expPass {
suite.Require().NoError(err, tc.name) suite.Require().NoError(err, tc.name)
fromBalance := suite.app.EvmKeeper.GetBalance(suite.ctx, suite.address) fromBalance := suite.app.EvmKeeper.CommitStateDB.GetBalance(suite.address)
toBalance := suite.app.EvmKeeper.GetBalance(suite.ctx, recipient) toBalance := suite.app.EvmKeeper.CommitStateDB.GetBalance(recipient)
suite.Require().Equal(fromBalance, big.NewInt(4950), tc.name) suite.Require().Equal(fromBalance, big.NewInt(4950), tc.name)
suite.Require().Equal(toBalance, big.NewInt(50), tc.name) suite.Require().Equal(toBalance, big.NewInt(50), tc.name)
} else { } else {

View File

@ -72,14 +72,14 @@ type CommitStateDB struct {
// by StateDB.Commit. // by StateDB.Commit.
dbErr error dbErr error
// Journal of state modifications. This is the backbone of // journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot. // Snapshot and RevertToSnapshot.
journal *journal journal *journal
validRevisions []revision validRevisions []revision
nextRevisionID int nextRevisionID int
// Per-transaction access list // Per-transaction access list
accessList *accessList accessList *AccessListMappings
// mutex for state deep copying // mutex for state deep copying
lock sync.Mutex lock sync.Mutex
@ -106,7 +106,7 @@ func NewCommitStateDB(
preimages: []preimageEntry{}, preimages: []preimageEntry{},
hashToPreimageIndex: make(map[ethcmn.Hash]int), hashToPreimageIndex: make(map[ethcmn.Hash]int),
journal: newJournal(), journal: newJournal(),
accessList: newAccessList(), accessList: NewAccessListMappings(),
} }
} }
@ -727,7 +727,7 @@ func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error {
csdb.logSize = 0 csdb.logSize = 0
csdb.preimages = []preimageEntry{} csdb.preimages = []preimageEntry{}
csdb.hashToPreimageIndex = make(map[ethcmn.Hash]int) csdb.hashToPreimageIndex = make(map[ethcmn.Hash]int)
csdb.accessList = newAccessList() csdb.accessList = NewAccessListMappings()
csdb.clearJournalAndRefund() csdb.clearJournalAndRefund()
return nil return nil