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
anteHandler = sdk.ChainAnteDecorators(
NewEthSetupContextDecorator(), // outermost AnteDecorator. EthSetUpContext must be called first
NewEthSetupContextDecorator(evmKeeper), // outermost AnteDecorator. EthSetUpContext must be called first
NewEthMempoolFeeDecorator(evmKeeper),
NewEthValidateBasicDecorator(),
authante.TxTimeoutHeightDecorator{},

View File

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

View File

@ -252,7 +252,8 @@ func NewEthermintApp(
evmtypes.StoreKey,
)
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey)
// Add the EVM transient store key
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey, evmtypes.TransientKey)
memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey)
app := &EthermintApp{
@ -310,7 +311,7 @@ func NewEthermintApp(
// Create Ethermint keepers
app.EvmKeeper = evmkeeper.NewKeeper(
appCodec, keys[evmtypes.StoreKey], app.GetSubspace(evmtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], app.GetSubspace(evmtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
)
// Create IBC Keeper

View File

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

View File

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

View File

@ -47,6 +47,7 @@ func (suite *EvmTestSuite) SetupTest() {
suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "ethermint-888", Time: time.Now().UTC()})
suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx)
suite.handler = evm.NewHandler(suite.app.EvmKeeper)
suite.codec = suite.app.AppCodec()
suite.chainID = suite.chainID
@ -80,7 +81,7 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() {
{
"passed",
func() {
suite.app.EvmKeeper.SetBalance(suite.ctx, suite.from, big.NewInt(100))
suite.app.EvmKeeper.CommitStateDB.SetBalance(suite.from, big.NewInt(100))
to := ethcmn.BytesToAddress(suite.to)
tx = types.NewMsgEthereumTx(suite.chainID, 0, &to, big.NewInt(100), 0, big.NewInt(10000), nil, nil)
tx.From = suite.from.String()
@ -193,10 +194,10 @@ func (suite *EvmTestSuite) TestHandlerLogs() {
suite.Require().Equal(len(txResponse.TxLogs.Logs[0].Topics), 2)
hash := []byte{1}
err = suite.app.EvmKeeper.SetLogs(suite.ctx, ethcmn.BytesToHash(hash), txResponse.TxLogs.EthLogs())
err = suite.app.EvmKeeper.CommitStateDB.SetLogs(ethcmn.BytesToHash(hash), txResponse.TxLogs.EthLogs())
suite.Require().NoError(err)
logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, ethcmn.BytesToHash(hash))
logs, err := suite.app.EvmKeeper.CommitStateDB.GetLogs(ethcmn.BytesToHash(hash))
suite.Require().NoError(err, "failed to get logs")
suite.Require().Equal(logs, txResponse.TxLogs.Logs)
@ -227,7 +228,7 @@ func (suite *EvmTestSuite) TestQueryTxLogs() {
// get logs by tx hash
hash := txResponse.TxLogs.Hash
logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, ethcmn.HexToHash(hash))
logs, err := suite.app.EvmKeeper.CommitStateDB.GetLogs(ethcmn.HexToHash(hash))
suite.Require().NoError(err, "failed to get logs")
suite.Require().Equal(logs, txResponse.TxLogs.EthLogs())
@ -345,7 +346,7 @@ func (suite *EvmTestSuite) TestSendTransaction() {
gasLimit := uint64(21000)
gasPrice := big.NewInt(0x55ae82600)
suite.app.EvmKeeper.SetBalance(suite.ctx, suite.from, big.NewInt(100))
suite.app.EvmKeeper.CommitStateDB.SetBalance(suite.from, big.NewInt(100))
// send simple value transfer with gasLimit=21000
tx := types.NewMsgEthereumTx(suite.chainID, 1, &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
k.SetHeightHash(ctx, uint64(req.Header.Height), common.BytesToHash(req.Hash))
// reset counters that are used on CommitStateDB.Prepare
k.Bloom = big.NewInt(0)
k.TxCount = 0
}
// EndBlock updates the accounts and commits state objects to the KV Store, while
@ -48,11 +44,12 @@ func (k Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.Valid
// Gas costs are handled within msg handler so costs should be ignored
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
k.CommitStateDB.WithContext(ctx)
// Update account balances before committing other parts of state
k.UpdateAccounts(ctx)
k.CommitStateDB.UpdateAccounts()
root, err := k.Commit(ctx, true)
root, err := k.CommitStateDB.Commit(true)
// Commit state objects to KV store
if err != nil {
k.Logger(ctx).Error("failed to commit state objects", "error", err, "height", ctx.BlockHeight())
@ -60,12 +57,17 @@ func (k Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.Valid
}
// reset all cache after account data has been committed, that make sure node state consistent
if err = k.Reset(ctx, root); err != nil {
if err = k.CommitStateDB.Reset(root); err != nil {
panic(err)
}
// set the block bloom filter bytes to store
bloom := ethtypes.BytesToBloom(k.Bloom.Bytes())
// get the block bloom bytes from the transient store and set it to the persistent storage
bloomBig, found := k.GetBlockBloomTransient()
if !found {
bloomBig = big.NewInt(0)
}
bloom := ethtypes.BytesToBloom(bloomBig.Bytes())
k.SetBlockBloom(ctx, req.Height, bloom)
return []abci.ValidatorUpdate{}

View File

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

View File

@ -247,7 +247,7 @@ func (suite *KeeperTestSuite) TestQueryStorage() {
key := ethcmn.BytesToHash([]byte("key"))
value := ethcmn.BytesToHash([]byte("value"))
expValue = value.String()
suite.app.EvmKeeper.SetState(suite.ctx, suite.address, key, value)
suite.app.EvmKeeper.CommitStateDB.SetState(suite.address, key, value)
req = &types.QueryStorageRequest{
Address: suite.address.String(),
Key: key.String(),
@ -302,7 +302,7 @@ func (suite *KeeperTestSuite) TestQueryCode() {
"success",
func() {
expCode = []byte("code")
suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, expCode)
suite.app.EvmKeeper.CommitStateDB.SetCode(suite.address, expCode)
req = &types.QueryCodeRequest{
Address: suite.address.String(),
@ -379,7 +379,7 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() {
},
}
suite.app.EvmKeeper.SetLogs(suite.ctx, hash, types.LogsToEthereum(expLogs))
suite.app.EvmKeeper.CommitStateDB.SetLogs(hash, types.LogsToEthereum(expLogs))
req = &types.QueryTxLogsRequest{
Hash: hash.String(),
@ -488,8 +488,8 @@ func (suite *KeeperTestSuite) TestQueryBlockLogs() {
},
}
suite.app.EvmKeeper.SetLogs(suite.ctx, ethcmn.BytesToHash([]byte("tx_hash_0")), types.LogsToEthereum(expLogs[0].Logs))
suite.app.EvmKeeper.SetLogs(suite.ctx, ethcmn.BytesToHash([]byte("tx_hash_1")), types.LogsToEthereum(expLogs[1].Logs))
suite.app.EvmKeeper.CommitStateDB.SetLogs(ethcmn.BytesToHash([]byte("tx_hash_0")), types.LogsToEthereum(expLogs[0].Logs))
suite.app.EvmKeeper.CommitStateDB.SetLogs(ethcmn.BytesToHash([]byte("tx_hash_1")), types.LogsToEthereum(expLogs[1].Logs))
req = &types.QueryBlockLogsRequest{
Hash: hash.String(),

View File

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

View File

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

View File

@ -1,9 +1,11 @@
package keeper
import (
"bytes"
"math/big"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/ethereum/go-ethereum/common"
@ -26,25 +28,28 @@ type Keeper struct {
// - storing block hash -> block height map. Needed for the Web3 API. TODO: remove
storeKey sdk.StoreKey
// key to access the transient store, which is reset on every block during Commit
transientKey sdk.StoreKey
paramSpace paramtypes.Subspace
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
ctx sdk.Context
// Ethermint concrete implementation on the EVM StateDB interface
CommitStateDB *types.CommitStateDB
// Transaction counter in a block. Used on StateSB's Prepare function.
// It is reset to 0 every block on BeginBlock so there's no point in storing the counter
// on the KVStore or adding it as a field on the EVM genesis state.
TxCount int
Bloom *big.Int
// LogsCache keeps mapping of contract address -> eth logs emitted
// during EVM execution in the current block.
LogsCache map[common.Address][]*ethtypes.Log
// Per-transaction access list
// See EIP-2930 for more info: https://eips.ethereum.org/EIPS/eip-2930
// TODO: (@fedekunze) for how long should we persist the entries in the access list?
// same block (i.e Transient Store)? 2 or more (KVStore with module Parameter which resets the state after that window)?
accessList *types.AccessListMappings
}
// NewKeeper generates new evm module keeper
func NewKeeper(
cdc codec.BinaryMarshaler, storeKey sdk.StoreKey, paramSpace paramtypes.Subspace,
cdc codec.BinaryMarshaler, storeKey, transientKey sdk.StoreKey, paramSpace paramtypes.Subspace,
ak types.AccountKeeper, bankKeeper types.BankKeeper,
) *Keeper {
// set KeyTable if it has not already been set
@ -55,13 +60,13 @@ func NewKeeper(
// NOTE: we pass in the parameter space to the CommitStateDB in order to use custom denominations for the EVM operations
return &Keeper{
cdc: cdc,
paramSpace: paramSpace,
accountKeeper: ak,
bankKeeper: bankKeeper,
storeKey: storeKey,
transientKey: transientKey,
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, paramSpace, ak, bankKeeper),
TxCount: 0,
Bloom: big.NewInt(0),
LogsCache: map[common.Address][]*ethtypes.Log{},
accessList: types.NewAccessListMappings(),
}
}
@ -70,8 +75,13 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", types.ModuleName)
}
// WithContext sets an updated SDK context to the keeper
func (k *Keeper) WithContext(ctx sdk.Context) {
k.ctx = ctx
}
// ----------------------------------------------------------------------------
// Block bloom bits mapping functions
// Block Bloom
// Required by Web3 API.
// ----------------------------------------------------------------------------
@ -94,6 +104,28 @@ func (k Keeper) SetBlockBloom(ctx sdk.Context, height int64, bloom ethtypes.Bloo
store.Set(key, bloom.Bytes())
}
// GetBlockBloomTransient returns bloom bytes for the current block height
func (k Keeper) GetBlockBloomTransient() (*big.Int, bool) {
store := k.ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientBloom)
if len(bz) == 0 {
return nil, false
}
return new(big.Int).SetBytes(bz), true
}
// SetBlockBloomTransient sets the given bloom bytes to the transient store. This value is reset on
// every block.
func (k Keeper) SetBlockBloomTransient(bloom *big.Int) {
store := k.ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientBloom, bloom.Bytes())
}
// ----------------------------------------------------------------------------
// Block
// ----------------------------------------------------------------------------
// GetBlockHash gets block height from block consensus hash
func (k Keeper) GetBlockHashFromHeight(ctx sdk.Context, height int64) (common.Hash, bool) {
store := ctx.KVStore(k.storeKey)
@ -150,11 +182,40 @@ func (k Keeper) SetHeightHash(ctx sdk.Context, height uint64, hash common.Hash)
k.CommitStateDB.WithContext(ctx).SetHeightHash(height, hash)
}
// ----------------------------------------------------------------------------
// Tx
// ----------------------------------------------------------------------------
// GetTxIndexTransient returns EVM transaction index on the current block.
func (k Keeper) GetTxIndexTransient() uint64 {
store := k.ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientBloom)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// IncreaseTxIndexTransient fetches the current EVM tx index from the transient store, increases its
// value by one and then sets the new index back to the transient store.
func (k Keeper) IncreaseTxIndexTransient() {
txIndex := k.GetTxIndexTransient()
store := k.ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientBloom, sdk.Uint64ToBigEndian(txIndex+1))
}
// ResetRefundTransient resets the refund gas value.
func (k Keeper) ResetRefundTransient(ctx sdk.Context) {
store := ctx.TransientStore(k.transientKey)
store.Delete(types.KeyPrefixTransientRefund)
}
// GetTxReceiptFromHash gets tx receipt by tx hash.
func (k Keeper) GetTxReceiptFromHash(ctx sdk.Context, hash common.Hash) (*types.TxReceipt, bool) {
store := ctx.KVStore(k.storeKey)
data := store.Get(types.KeyHashTxReceipt(hash))
if data == nil || len(data) == 0 {
if len(data) == 0 {
return nil, false
}
@ -216,7 +277,7 @@ func (k Keeper) GetTxReceiptsByBlockHeight(ctx sdk.Context, blockHeight int64) [
for idx, txHash := range txs {
data := store.Get(types.KeyHashTxReceipt(txHash))
if data == nil || len(data) == 0 {
if len(data) == 0 {
continue
}
@ -239,6 +300,10 @@ func (k Keeper) GetTxReceiptsByBlockHash(ctx sdk.Context, hash common.Hash) []*t
return k.GetTxReceiptsByBlockHeight(ctx, blockHeight)
}
// ----------------------------------------------------------------------------
// Log
// ----------------------------------------------------------------------------
// GetAllTxLogs return all the transaction logs from the store.
func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs {
store := ctx.KVStore(k.storeKey)
@ -256,14 +321,51 @@ func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs {
return txsLogs
}
// GetLogs returns the current logs for a given transaction hash from the KVStore.
// This function returns an empty, non-nil slice if no logs are found.
func (k Keeper) GetTxLogs(txHash common.Hash) []*ethtypes.Log {
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixLogs)
bz := store.Get(txHash.Bytes())
if len(bz) == 0 {
return []*ethtypes.Log{}
}
var logs types.TransactionLogs
k.cdc.MustUnmarshalBinaryBare(bz, &logs)
return logs.EthLogs()
}
// SetLogs sets the logs for a transaction in the KVStore.
func (k Keeper) SetLogs(txHash common.Hash, logs []*ethtypes.Log) {
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixLogs)
txLogs := types.NewTransactionLogsFromEth(txHash, logs)
bz := k.cdc.MustMarshalBinaryBare(&txLogs)
store.Set(txHash.Bytes(), bz)
}
// DeleteLogs removes the logs from the KVStore. It is used during journal.Revert.
func (k Keeper) DeleteTxLogs(ctx sdk.Context, txHash common.Hash) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixLogs)
store.Delete(txHash.Bytes())
}
// ----------------------------------------------------------------------------
// Storage
// ----------------------------------------------------------------------------
// GetAccountStorage return state storage associated with an account
func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (types.Storage, error) {
storage := types.Storage{}
err := k.ForEachStorage(ctx, address, func(key, value common.Hash) bool {
err := k.ForEachStorage(address, func(key, value common.Hash) bool {
storage = append(storage, types.NewState(key, value))
return false
})
if err != nil {
return types.Storage{}, err
}
@ -271,22 +373,62 @@ func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (type
return storage, nil
}
// GetChainConfig gets block height from block consensus hash
func (k Keeper) GetChainConfig(ctx sdk.Context) (types.ChainConfig, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.KeyPrefixChainConfig)
if len(bz) == 0 {
return types.ChainConfig{}, false
// ----------------------------------------------------------------------------
// Account
// ----------------------------------------------------------------------------
func (k Keeper) DeleteState(addr common.Address, key common.Hash) {
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr))
key = types.KeyAddressStorage(addr, key)
store.Delete(key.Bytes())
}
// DeleteAccountStorage clears all the storage state associated with the given address.
func (k Keeper) DeleteAccountStorage(addr common.Address) {
_ = k.ForEachStorage(addr, func(key, _ common.Hash) bool {
k.DeleteState(addr, key)
return false
})
}
// DeleteCode removes the contract code byte array from the store associated with
// the given address.
func (k Keeper) DeleteCode(addr common.Address) {
hash := k.GetCodeHash(addr)
if bytes.Equal(hash.Bytes(), common.BytesToHash(types.EmptyCodeHash).Bytes()) {
return
}
var config types.ChainConfig
k.cdc.MustUnmarshalBinaryBare(bz, &config)
return config, true
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode)
store.Delete(hash.Bytes())
}
// SetChainConfig sets the mapping from block consensus hash to block height
func (k Keeper) SetChainConfig(ctx sdk.Context, config types.ChainConfig) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinaryBare(&config)
store.Set(types.KeyPrefixChainConfig, bz)
// ClearBalance subtracts the EVM all the balance denomination from the address
// balance while also updating the total supply.
func (k Keeper) ClearBalance(addr sdk.AccAddress) (prevBalance sdk.Coin, err error) {
params := k.GetParams(k.ctx)
prevBalance = k.bankKeeper.GetBalance(k.ctx, addr, params.EvmDenom)
if prevBalance.IsPositive() {
err := k.bankKeeper.SubtractCoins(k.ctx, addr, sdk.Coins{prevBalance})
if err != nil {
return sdk.Coin{}, err
}
}
return prevBalance, nil
}
// ResetAccount removes the code, storage state and evm denom balance coins stored
// with the given address.
func (k Keeper) ResetAccount(addr common.Address) {
k.DeleteCode(addr)
k.DeleteAccountStorage(addr)
_, err := k.ClearBalance(addr.Bytes())
if err != nil {
k.Logger(k.ctx).Error(
"failed to clear balance during account reset",
"ethereum-address", addr.Hex(),
)
}
}

View File

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

View File

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

View File

@ -8,10 +8,31 @@ import (
// GetParams returns the total set of evm parameters.
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
return k.CommitStateDB.WithContext(ctx).GetParams()
k.paramSpace.GetParamSet(ctx, &params)
return params
}
// SetParams sets the evm parameters to the param space.
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.CommitStateDB.WithContext(ctx).SetParams(params)
k.paramSpace.SetParamSet(ctx, &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
import (
"bytes"
"fmt"
"math/big"
"github.com/cosmos/ethermint/x/evm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethvm "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
)
var _ vm.StateDB = &Keeper{}
// ----------------------------------------------------------------------------
// Setters
// Account
// ----------------------------------------------------------------------------
// SetBalance calls CommitStateDB.SetBalance using the passed in context
func (k *Keeper) SetBalance(ctx sdk.Context, addr ethcmn.Address, amount *big.Int) {
k.CommitStateDB.WithContext(ctx).SetBalance(addr, amount)
// CreateAccount creates a new EthAccount instance from the provided address and
// sets the value to store.
func (k *Keeper) CreateAccount(addr common.Address) {
cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr)
log := ""
if account == nil {
log = "account created"
} else {
log = "account overwritten"
k.ResetAccount(addr)
}
_ = k.accountKeeper.NewAccountWithAddress(k.ctx, cosmosAddr)
k.Logger(k.ctx).Debug(
log,
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
)
}
// ----------------------------------------------------------------------------
// Balance
// ----------------------------------------------------------------------------
// AddBalance calls CommitStateDB.AddBalance using the passed in context
func (k *Keeper) AddBalance(ctx sdk.Context, addr ethcmn.Address, amount *big.Int) {
k.CommitStateDB.WithContext(ctx).AddBalance(addr, amount)
func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) {
cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(k.ctx)
coins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(amount))}
if err := k.bankKeeper.AddCoins(k.ctx, cosmosAddr, coins); err != nil {
k.Logger(k.ctx).Error(
"failed to add balance",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
"error", err,
)
return
}
k.Logger(k.ctx).Debug(
"balance addition",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
)
}
// SubBalance calls CommitStateDB.SubBalance using the passed in context
func (k *Keeper) SubBalance(ctx sdk.Context, addr ethcmn.Address, amount *big.Int) {
k.CommitStateDB.WithContext(ctx).SubBalance(addr, amount)
func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) {
cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(k.ctx)
coins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(amount))}
if err := k.bankKeeper.SubtractCoins(k.ctx, cosmosAddr, coins); err != nil {
k.Logger(k.ctx).Error(
"failed to subtract balance",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
"error", err,
)
return
}
k.Logger(k.ctx).Debug(
"balance subtraction",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
)
}
// GetBalance calls CommitStateDB.GetBalance using the passed in context
func (k *Keeper) GetBalance(addr common.Address) *big.Int {
cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(k.ctx)
balance := k.bankKeeper.GetBalance(k.ctx, cosmosAddr, params.EvmDenom)
return balance.Amount.BigInt()
}
// ----------------------------------------------------------------------------
// Nonce
// ----------------------------------------------------------------------------
// GetNonce calls CommitStateDB.GetNonce using the passed in context
func (k *Keeper) GetNonce(addr common.Address) uint64 {
cosmosAddr := sdk.AccAddress(addr.Bytes())
nonce, err := k.accountKeeper.GetSequence(k.ctx, cosmosAddr)
if err != nil {
k.Logger(k.ctx).Error(
"account not found",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
"error", err,
)
}
return nonce
}
// SetNonce calls CommitStateDB.SetNonce using the passed in context
func (k *Keeper) SetNonce(ctx sdk.Context, addr ethcmn.Address, nonce uint64) {
k.CommitStateDB.WithContext(ctx).SetNonce(addr, nonce)
}
func (k *Keeper) SetNonce(addr common.Address, nonce uint64) {
cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr)
if account == nil {
k.Logger(k.ctx).Debug(
"account not found",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
)
// SetState calls CommitStateDB.SetState using the passed in context
func (k *Keeper) SetState(ctx sdk.Context, addr ethcmn.Address, key, value ethcmn.Hash) {
k.CommitStateDB.WithContext(ctx).SetState(addr, key, value)
}
// create address if it doesn't exist
account = k.accountKeeper.NewAccountWithAddress(k.ctx, cosmosAddr)
}
// SetCode calls CommitStateDB.SetCode using the passed in context
func (k *Keeper) SetCode(ctx sdk.Context, addr ethcmn.Address, code []byte) {
k.CommitStateDB.WithContext(ctx).SetCode(addr, code)
}
if err := account.SetSequence(nonce); err != nil {
k.Logger(k.ctx).Error(
"failed to set nonce",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
"nonce", nonce,
"error", err,
)
// SetLogs calls CommitStateDB.SetLogs using the passed in context
func (k *Keeper) SetLogs(ctx sdk.Context, hash ethcmn.Hash, logs []*ethtypes.Log) error {
// TODO:@albert
// since SetLogs is only called for non-simulation mode, GasEstimation is quite not correct
// I suggest using ctx.ConsumeGas(XXX) where XXX is max of gas consume for set logs.
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
return
}
return k.CommitStateDB.WithContext(ctx).SetLogs(hash, logs)
}
k.accountKeeper.SetAccount(k.ctx, account)
// DeleteLogs calls CommitStateDB.DeleteLogs using the passed in context
func (k *Keeper) DeleteLogs(ctx sdk.Context, hash ethcmn.Hash) {
k.CommitStateDB.WithContext(ctx).DeleteLogs(hash)
}
// AddLog calls CommitStateDB.AddLog using the passed in context
func (k *Keeper) AddLog(ctx sdk.Context, log *ethtypes.Log) {
k.CommitStateDB.WithContext(ctx).AddLog(log)
}
// AddPreimage calls CommitStateDB.AddPreimage using the passed in context
func (k *Keeper) AddPreimage(ctx sdk.Context, hash ethcmn.Hash, preimage []byte) {
k.CommitStateDB.WithContext(ctx).AddPreimage(hash, preimage)
}
// AddRefund calls CommitStateDB.AddRefund using the passed in context
func (k *Keeper) AddRefund(ctx sdk.Context, gas uint64) {
k.CommitStateDB.WithContext(ctx).AddRefund(gas)
}
// SubRefund calls CommitStateDB.SubRefund using the passed in context
func (k *Keeper) SubRefund(ctx sdk.Context, gas uint64) {
k.CommitStateDB.WithContext(ctx).SubRefund(gas)
k.Logger(k.ctx).Debug(
"nonce set",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
"nonce", nonce,
)
}
// ----------------------------------------------------------------------------
// Getters
// Code
// ----------------------------------------------------------------------------
// GetBalance calls CommitStateDB.GetBalance using the passed in context
func (k *Keeper) GetBalance(ctx sdk.Context, addr ethcmn.Address) *big.Int {
return k.CommitStateDB.WithContext(ctx).GetBalance(addr)
}
// GetCodeHash calls CommitStateDB.GetCodeHash using the passed in context
func (k *Keeper) GetCodeHash(addr common.Address) common.Hash {
cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr)
if account == nil {
return common.BytesToHash(types.EmptyCodeHash)
}
// GetNonce calls CommitStateDB.GetNonce using the passed in context
func (k *Keeper) GetNonce(ctx sdk.Context, addr ethcmn.Address) uint64 {
return k.CommitStateDB.WithContext(ctx).GetNonce(addr)
}
ethAccount, isEthAccount := account.(*ethermint.EthAccount)
if !isEthAccount {
return common.BytesToHash(types.EmptyCodeHash)
}
// TxIndex calls CommitStateDB.TxIndex using the passed in context
func (k *Keeper) TxIndex(ctx sdk.Context) int {
return k.CommitStateDB.WithContext(ctx).TxIndex()
}
// BlockHash calls CommitStateDB.BlockHash using the passed in context
func (k *Keeper) BlockHash(ctx sdk.Context) ethcmn.Hash {
return k.CommitStateDB.WithContext(ctx).BlockHash()
return common.BytesToHash(ethAccount.CodeHash)
}
// GetCode calls CommitStateDB.GetCode using the passed in context
func (k *Keeper) GetCode(ctx sdk.Context, addr ethcmn.Address) []byte {
return k.CommitStateDB.WithContext(ctx).GetCode(addr)
func (k *Keeper) GetCode(addr common.Address) []byte {
hash := k.GetCodeHash(addr)
if bytes.Equal(hash.Bytes(), common.BytesToHash(types.EmptyCodeHash).Bytes()) {
return nil
}
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode)
code := store.Get(hash.Bytes())
if len(code) == 0 {
k.Logger(k.ctx).Debug(
"code not found",
"ethereum-address", addr.Hex(),
"code-hash", hash.Hex(),
)
}
return code
}
// GetCodeSize calls CommitStateDB.GetCodeSize using the passed in context
func (k *Keeper) GetCodeSize(ctx sdk.Context, addr ethcmn.Address) int {
return k.CommitStateDB.WithContext(ctx).GetCodeSize(addr)
// SetCode calls CommitStateDB.SetCode using the passed in context
func (k *Keeper) SetCode(addr common.Address, code []byte) {
hash := crypto.Keccak256Hash(code)
// update account code hash
account := k.accountKeeper.GetAccount(k.ctx, addr.Bytes())
if account == nil {
account = k.accountKeeper.NewAccountWithAddress(k.ctx, addr.Bytes())
}
ethAccount, isEthAccount := account.(*ethermint.EthAccount)
if !isEthAccount {
k.Logger(k.ctx).Error(
"invalid account type",
"ethereum-address", addr.Hex(),
"code-hash", hash.Hex(),
)
return
}
ethAccount.CodeHash = hash.Bytes()
k.accountKeeper.SetAccount(k.ctx, ethAccount)
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode)
action := "updated"
// store or delete code
if len(code) == 0 {
store.Delete(hash.Bytes())
action = "deleted"
} else {
store.Set(hash.Bytes(), code)
}
k.Logger(k.ctx).Debug(
fmt.Sprintf("code %s", action),
"ethereum-address", addr.Hex(),
"code-hash", hash.Hex(),
)
}
// GetCodeHash calls CommitStateDB.GetCodeHash using the passed in context
func (k *Keeper) GetCodeHash(ctx sdk.Context, addr ethcmn.Address) ethcmn.Hash {
return k.CommitStateDB.WithContext(ctx).GetCodeHash(addr)
// GetCodeSize returns the code hash stored in the address account.
func (k *Keeper) GetCodeSize(addr common.Address) int {
return len(k.GetCode(addr))
}
// ----------------------------------------------------------------------------
// Refund
// ----------------------------------------------------------------------------
// NOTE: gas refunded needs to be tracked and stored in a separate variable in
// order to add it subtract/add it from/to the gas used value after the EVM
// execution has finalised. The refund value is cleared on every transaction and
// at the end of every block.
// AddRefund adds the given amount of gas to the refund cached value.
func (k *Keeper) AddRefund(gas uint64) {
refund := k.GetRefund()
refund += gas
store := k.ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund))
}
// SubRefund subtracts the given amount of gas from the refund value. This function
// will panic if gas amount is greater than the stored refund.
func (k *Keeper) SubRefund(gas uint64) {
refund := k.GetRefund()
if gas > refund {
// TODO: (@fedekunze) set to 0?? Geth panics here
panic("refund counter below zero")
}
refund -= gas
store := k.ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund))
}
// GetRefund returns the amount of gas available for return after the tx execution
// finalises. This value is reset to 0 on every transaction.
func (k *Keeper) GetRefund() uint64 {
store := k.ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientRefund)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// ----------------------------------------------------------------------------
// State
// ----------------------------------------------------------------------------
// GetCommittedState calls CommitStateDB.GetCommittedState using the passed in context
func (k *Keeper) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr))
key := types.KeyAddressStorage(addr, hash)
value := store.Get(key.Bytes())
if len(value) == 0 {
return common.Hash{}
}
return common.BytesToHash(value)
}
// GetState calls CommitStateDB.GetState using the passed in context
func (k *Keeper) GetState(ctx sdk.Context, addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash {
return k.CommitStateDB.WithContext(ctx).GetState(addr, hash)
func (k *Keeper) GetState(addr common.Address, hash common.Hash) common.Hash {
// All state is committed directly
return k.GetCommittedState(addr, hash)
}
// GetCommittedState calls CommitStateDB.GetCommittedState using the passed in context
func (k *Keeper) GetCommittedState(ctx sdk.Context, addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash {
return k.CommitStateDB.WithContext(ctx).GetCommittedState(addr, hash)
}
// SetState calls CommitStateDB.SetState using the passed in context
func (k *Keeper) SetState(addr common.Address, key, value common.Hash) {
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr))
key = types.KeyAddressStorage(addr, key)
// GetLogs calls CommitStateDB.GetLogs using the passed in context
func (k *Keeper) GetLogs(ctx sdk.Context, hash ethcmn.Hash) ([]*ethtypes.Log, error) {
return k.CommitStateDB.WithContext(ctx).GetLogs(hash)
}
action := "updated"
if ethermint.IsEmptyHash(value.Hex()) {
store.Delete(key.Bytes())
action = "deleted"
} else {
store.Set(key.Bytes(), value.Bytes())
}
// AllLogs calls CommitStateDB.AllLogs using the passed in context
func (k *Keeper) AllLogs(ctx sdk.Context) []*ethtypes.Log {
return k.CommitStateDB.WithContext(ctx).AllLogs()
}
// GetRefund calls CommitStateDB.GetRefund using the passed in context
func (k *Keeper) GetRefund(ctx sdk.Context) uint64 {
return k.CommitStateDB.WithContext(ctx).GetRefund()
}
// Preimages calls CommitStateDB.Preimages using the passed in context
func (k *Keeper) Preimages(ctx sdk.Context) map[ethcmn.Hash][]byte {
return k.CommitStateDB.WithContext(ctx).Preimages()
}
// HasSuicided calls CommitStateDB.HasSuicided using the passed in context
func (k *Keeper) HasSuicided(ctx sdk.Context, addr ethcmn.Address) bool {
return k.CommitStateDB.WithContext(ctx).HasSuicided(addr)
}
// StorageTrie calls CommitStateDB.StorageTrie using the passed in context
func (k *Keeper) StorageTrie(ctx sdk.Context, addr ethcmn.Address) ethstate.Trie {
return k.CommitStateDB.WithContext(ctx).StorageTrie(addr)
k.Logger(k.ctx).Debug(
fmt.Sprintf("state %s", action),
"ethereum-address", addr.Hex(),
"key", key.Hex(),
)
}
// ----------------------------------------------------------------------------
// Persistence
// Suicide
// ----------------------------------------------------------------------------
// Commit calls CommitStateDB.Commit using the passed in context
func (k *Keeper) Commit(ctx sdk.Context, deleteEmptyObjects bool) (root ethcmn.Hash, err error) {
return k.CommitStateDB.WithContext(ctx).Commit(deleteEmptyObjects)
// Suicide marks the given account as suicided and clears the account balance of
// the EVM tokens.
func (k *Keeper) Suicide(addr common.Address) bool {
prev := k.HasSuicided(addr)
if prev {
return true
}
cosmosAddr := sdk.AccAddress(addr.Bytes())
_, err := k.ClearBalance(cosmosAddr)
if err != nil {
k.Logger(k.ctx).Error(
"failed to subtract balance on suicide",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
"error", err,
)
return false
}
// TODO: (@fedekunze) do we also need to delete the storage state and the code?
// Set a single byte to the transient store
store := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided)
store.Set(addr.Bytes(), []byte{1})
k.Logger(k.ctx).Debug(
"account suicided",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
)
return true
}
// Finalise calls CommitStateDB.Finalise using the passed in context
func (k *Keeper) Finalise(ctx sdk.Context, deleteEmptyObjects bool) error {
return k.CommitStateDB.WithContext(ctx).Finalise(deleteEmptyObjects)
// HasSuicided queries the transient store to check if the account has been marked as suicided in the
// current block. Accounts that are suicided will be returned as non-nil during queries and "cleared"
// after the block has been committed.
func (k *Keeper) HasSuicided(addr common.Address) bool {
store := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided)
return store.Has(addr.Bytes())
}
// IntermediateRoot calls CommitStateDB.IntermediateRoot using the passed in context
func (k *Keeper) IntermediateRoot(ctx sdk.Context, deleteEmptyObjects bool) error {
_, err := k.CommitStateDB.WithContext(ctx).IntermediateRoot(deleteEmptyObjects)
return err
// ----------------------------------------------------------------------------
// Account Exist / Empty
// ----------------------------------------------------------------------------
// Exist returns true if the given account exists in store or if it has been
// marked as suicided in the transient store.
func (k *Keeper) Exist(addr common.Address) bool {
// return true if the account has suicided
if k.HasSuicided(addr) {
return true
}
cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr)
return account != nil
}
// Empty returns true if the address meets the following conditions:
// - nonce is 0
// - balance amount for evm denom is 0
// - account code hash is empty
func (k *Keeper) Empty(addr common.Address) bool {
nonce := uint64(0)
codeHash := types.EmptyCodeHash
cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr)
if account != nil {
nonce = account.GetSequence()
ethAccount, isEthAccount := account.(*ethermint.EthAccount)
if !isEthAccount {
// NOTE: non-ethereum accounts are considered not empty
return false
}
codeHash = ethAccount.CodeHash
}
balance := k.GetBalance(addr)
hasZeroBalance := balance.Sign() == 0
hasEmptyCodeHash := bytes.Equal(codeHash, types.EmptyCodeHash)
return hasZeroBalance && nonce == 0 && hasEmptyCodeHash
}
// ----------------------------------------------------------------------------
// Access List
// ----------------------------------------------------------------------------
// PrepareAccessList handles the preparatory steps for executing a state transition with
// regards to both EIP-2929 and EIP-2930:
//
// - Add sender to access list (2929)
// - Add destination to access list (2929)
// - Add precompiles to access list (2929)
// - Add the contents of the optional tx access list (2930)
//
// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number.
func (k *Keeper) PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses ethtypes.AccessList) {
// NOTE: only update the access list during DeliverTx
if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() {
return
}
k.AddAddressToAccessList(sender)
if dest != nil {
k.AddAddressToAccessList(*dest)
// If it's a create-tx, the destination will be added inside evm.create
}
for _, addr := range precompiles {
k.AddAddressToAccessList(addr)
}
for _, tuple := range txAccesses {
k.AddAddressToAccessList(tuple.Address)
for _, key := range tuple.StorageKeys {
k.AddSlotToAccessList(tuple.Address, key)
}
}
}
// AddressInAccessList returns true if the address is registered on the access list map.
func (k *Keeper) AddressInAccessList(addr common.Address) bool {
return k.accessList.ContainsAddress(addr)
}
func (k *Keeper) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) {
return k.accessList.Contains(addr, slot)
}
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
func (k *Keeper) AddAddressToAccessList(addr common.Address) {
// NOTE: only update the access list during DeliverTx
if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() {
return
}
// NOTE: ignore change return bool because we don't have to keep a journal for state changes
_ = k.accessList.AddAddress(addr)
}
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) {
// NOTE: only update the access list during DeliverTx
if k.ctx.IsCheckTx() || k.ctx.IsReCheckTx() {
return
}
// NOTE: ignore change return booleans because we don't have to keep a journal for state changes
_, _ = k.accessList.AddSlot(addr, slot)
}
// ----------------------------------------------------------------------------
// Snapshotting
// ----------------------------------------------------------------------------
// Snapshot calls CommitStateDB.Snapshot using the passed in context
func (k *Keeper) Snapshot(ctx sdk.Context) int {
return k.CommitStateDB.WithContext(ctx).Snapshot()
// Snapshot return zero as the state changes won't be committed if the state transition fails. So there
// is no need to snapshot before the VM execution.
// See Cosmos SDK docs for more info: https://docs.cosmos.network/master/core/baseapp.html#delivertx-state-updates
func (k *Keeper) Snapshot() int {
return 0
}
// RevertToSnapshot calls CommitStateDB.RevertToSnapshot using the passed in context
func (k *Keeper) RevertToSnapshot(ctx sdk.Context, revID int) {
k.CommitStateDB.WithContext(ctx).RevertToSnapshot(revID)
// RevertToSnapshot performs a no-op because when a transaction execution fails on the EVM, the state
// won't be persisted during ABCI DeliverTx.
func (k *Keeper) RevertToSnapshot(_ int) {}
// ----------------------------------------------------------------------------
// Log
// ----------------------------------------------------------------------------
// AddLog appends the given ethereum Log to the list of Logs associated with the transaction hash kept in the current
// context. This function also fills in the tx hash, block hash, tx index and log index fields before setting the log
// to store.
func (k *Keeper) AddLog(log *ethtypes.Log) {
txHash := common.BytesToHash(tmtypes.Tx(k.ctx.TxBytes()).Hash())
blockHash, found := k.GetBlockHashFromHeight(k.ctx, k.ctx.BlockHeight())
if found {
log.BlockHash = blockHash
}
log.TxHash = txHash
log.TxIndex = uint(k.GetTxIndexTransient())
logs := k.GetTxLogs(txHash)
log.Index = uint(len(logs))
logs = append(logs, log)
k.SetLogs(txHash, logs)
k.Logger(k.ctx).Debug(
"log added",
"tx-hash", txHash.Hex(),
"log-index", int(log.Index),
)
}
// ----------------------------------------------------------------------------
// Auxiliary
// Trie
// ----------------------------------------------------------------------------
// Database calls CommitStateDB.Database using the passed in context
func (k *Keeper) Database(ctx sdk.Context) ethstate.Database {
return k.CommitStateDB.WithContext(ctx).Database()
}
// AddPreimage performs a no-op since the EnablePreimageRecording flag is disabled
// on the vm.Config during state transitions. No store trie preimages are written
// to the database.
func (k *Keeper) AddPreimage(_ common.Hash, _ []byte) {}
// Empty calls CommitStateDB.Empty using the passed in context
func (k *Keeper) Empty(ctx sdk.Context, addr ethcmn.Address) bool {
return k.CommitStateDB.WithContext(ctx).Empty(addr)
}
// Exist calls CommitStateDB.Exist using the passed in context
func (k *Keeper) Exist(ctx sdk.Context, addr ethcmn.Address) bool {
return k.CommitStateDB.WithContext(ctx).Exist(addr)
}
// Error calls CommitStateDB.Error using the passed in context
func (k *Keeper) Error(ctx sdk.Context) error {
return k.CommitStateDB.WithContext(ctx).Error()
}
// Suicide calls CommitStateDB.Suicide using the passed in context
func (k *Keeper) Suicide(ctx sdk.Context, addr ethcmn.Address) bool {
return k.CommitStateDB.WithContext(ctx).Suicide(addr)
}
// Reset calls CommitStateDB.Reset using the passed in context
func (k *Keeper) Reset(ctx sdk.Context, root ethcmn.Hash) error {
return k.CommitStateDB.WithContext(ctx).Reset(root)
}
// Prepare calls CommitStateDB.Prepare using the passed in context
func (k *Keeper) Prepare(ctx sdk.Context, thash, bhash ethcmn.Hash, txi int) {
k.CommitStateDB.WithContext(ctx).Prepare(thash, bhash, txi)
}
// CreateAccount calls CommitStateDB.CreateAccount using the passed in context
func (k *Keeper) CreateAccount(ctx sdk.Context, addr ethcmn.Address) {
k.CommitStateDB.WithContext(ctx).CreateAccount(addr)
}
// UpdateAccounts calls CommitStateDB.UpdateAccounts using the passed in context
func (k *Keeper) UpdateAccounts(ctx sdk.Context) {
k.CommitStateDB.WithContext(ctx).UpdateAccounts()
}
// ClearStateObjects calls CommitStateDB.ClearStateObjects using the passed in context
func (k *Keeper) ClearStateObjects(ctx sdk.Context) {
k.CommitStateDB.WithContext(ctx).ClearStateObjects()
}
// Copy calls CommitStateDB.Copy using the passed in context
func (k *Keeper) Copy(ctx sdk.Context) ethvm.StateDB {
return k.CommitStateDB.WithContext(ctx).Copy()
}
// ----------------------------------------------------------------------------
// Iterator
// ----------------------------------------------------------------------------
// ForEachStorage calls CommitStateDB.ForEachStorage using passed in context
func (k *Keeper) ForEachStorage(ctx sdk.Context, addr ethcmn.Address, cb func(key, value ethcmn.Hash) bool) error {
return k.CommitStateDB.WithContext(ctx).ForEachStorage(addr, cb)
}
func (k *Keeper) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
store := k.ctx.KVStore(k.storeKey)
prefix := types.AddressStoragePrefix(addr)
// GetOrNewStateObject calls CommitStateDB.GetOrNetStateObject using the passed in context
func (k *Keeper) GetOrNewStateObject(ctx sdk.Context, addr ethcmn.Address) types.StateObject {
return k.CommitStateDB.WithContext(ctx).GetOrNewStateObject(addr)
iterator := sdk.KVStorePrefixIterator(store, prefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
// TODO: check if the key prefix needs to be trimmed
key := common.BytesToHash(iterator.Key())
value := common.BytesToHash(iterator.Value())
// check if iteration stops
if cb(key, value) {
return nil
}
}
return nil
}

View File

@ -1,644 +1 @@
package keeper_test
import (
"fmt"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
)
func (suite *KeeperTestSuite) TestBloomFilter() {
// Prepare db for logs
tHash := ethcmn.BytesToHash([]byte{0x1})
suite.app.EvmKeeper.Prepare(suite.ctx, tHash, ethcmn.Hash{}, 0)
contractAddress := ethcmn.BigToAddress(big.NewInt(1))
log := ethtypes.Log{Address: contractAddress, Topics: []ethcmn.Hash{}}
testCase := []struct {
name string
malleate func()
numLogs int
isBloom bool
}{
{
"no logs",
func() {},
0,
false,
},
{
"add log",
func() {
suite.app.EvmKeeper.AddLog(suite.ctx, &log)
},
1,
false,
},
{
"bloom",
func() {},
0,
true,
},
}
for _, tc := range testCase {
tc.malleate()
logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, tHash)
if !tc.isBloom {
suite.Require().NoError(err, tc.name)
suite.Require().Len(logs, tc.numLogs, tc.name)
if len(logs) != 0 {
suite.Require().Equal(log, *logs[0], tc.name)
}
} else {
// get logs bloom from the log
bloomInt := ethtypes.LogsBloom(logs)
bloomFilter := ethtypes.BytesToBloom(bloomInt)
suite.Require().True(ethtypes.BloomLookup(bloomFilter, contractAddress), tc.name)
suite.Require().False(ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2))), tc.name)
}
}
}
func (suite *KeeperTestSuite) TestStateDB_Balance() {
testCase := []struct {
name string
malleate func()
balance *big.Int
}{
{
"set balance",
func() {
suite.app.EvmKeeper.SetBalance(suite.ctx, suite.address, big.NewInt(100))
},
big.NewInt(100),
},
{
"sub balance",
func() {
suite.app.EvmKeeper.SubBalance(suite.ctx, suite.address, big.NewInt(100))
},
big.NewInt(0),
},
{
"add balance",
func() {
suite.app.EvmKeeper.AddBalance(suite.ctx, suite.address, big.NewInt(200))
},
big.NewInt(200),
},
}
for _, tc := range testCase {
tc.malleate()
suite.Require().Equal(tc.balance, suite.app.EvmKeeper.GetBalance(suite.ctx, suite.address), tc.name)
}
}
func (suite *KeeperTestSuite) TestStateDBNonce() {
nonce := uint64(123)
suite.app.EvmKeeper.SetNonce(suite.ctx, suite.address, nonce)
suite.Require().Equal(nonce, suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address))
}
func (suite *KeeperTestSuite) TestStateDB_Error() {
nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, ethcmn.Address{})
suite.Require().Equal(0, int(nonce))
suite.Require().Error(suite.app.EvmKeeper.Error(suite.ctx))
}
func (suite *KeeperTestSuite) TestStateDB_Database() {
suite.Require().Nil(suite.app.EvmKeeper.Database(suite.ctx))
}
func (suite *KeeperTestSuite) TestStateDB_State() {
key := ethcmn.BytesToHash([]byte("foo"))
val := ethcmn.BytesToHash([]byte("bar"))
suite.app.EvmKeeper.SetState(suite.ctx, suite.address, key, val)
testCase := []struct {
name string
address ethcmn.Address
key ethcmn.Hash
value ethcmn.Hash
}{
{
"found state",
suite.address,
ethcmn.BytesToHash([]byte("foo")),
ethcmn.BytesToHash([]byte("bar")),
},
{
"state not found",
suite.address,
ethcmn.BytesToHash([]byte("key")),
ethcmn.Hash{},
},
{
"object not found",
ethcmn.Address{},
ethcmn.BytesToHash([]byte("foo")),
ethcmn.Hash{},
},
}
for _, tc := range testCase {
value := suite.app.EvmKeeper.GetState(suite.ctx, tc.address, tc.key)
suite.Require().Equal(tc.value, value, tc.name)
}
}
func (suite *KeeperTestSuite) TestStateDB_Code() {
testCase := []struct {
name string
address ethcmn.Address
code []byte
malleate func()
}{
{
"no stored code for state object",
suite.address,
nil,
func() {},
},
{
"existing address",
suite.address,
[]byte("code"),
func() {
suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, []byte("code"))
},
},
{
"state object not found",
ethcmn.Address{},
nil,
func() {},
},
}
for _, tc := range testCase {
tc.malleate()
suite.Require().Equal(tc.code, suite.app.EvmKeeper.GetCode(suite.ctx, tc.address), tc.name)
suite.Require().Equal(len(tc.code), suite.app.EvmKeeper.GetCodeSize(suite.ctx, tc.address), tc.name)
}
}
func (suite *KeeperTestSuite) TestStateDB_Logs() {
testCase := []struct {
name string
log *ethtypes.Log
}{
{
"state db log",
&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"
)
// accessList is copied from go-ethereum
// AccessListMappings is copied from go-ethereum
// https://github.com/ethereum/go-ethereum/blob/cf856ea1ad96ac39ea477087822479b63417036a/core/state/access_list.go#L23
type accessList struct {
type AccessListMappings struct {
addresses map[common.Address]int
slots []map[common.Hash]struct{}
}
// ContainsAddress returns true if the address is in the access list.
func (al *accessList) ContainsAddress(address common.Address) bool {
func (al *AccessListMappings) ContainsAddress(address common.Address) bool {
_, ok := al.addresses[address]
return ok
}
// Contains checks if a slot within an account is present in the access list, returning
// separate flags for the presence of the account and the slot respectively.
func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
func (al *AccessListMappings) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
idx, ok := al.addresses[address]
if !ok {
// no such address (and hence zero slots)
@ -41,16 +41,16 @@ func (al *accessList) Contains(address common.Address, slot common.Hash) (addres
return true, slotPresent
}
// newAccessList creates a new accessList.
func newAccessList() *accessList {
return &accessList{
// newAccessList creates a new AccessListMappings.
func NewAccessListMappings() *AccessListMappings {
return &AccessListMappings{
addresses: make(map[common.Address]int),
}
}
// Copy creates an independent copy of an accessList.
func (al *accessList) Copy() *accessList {
cp := newAccessList()
// Copy creates an independent copy of an AccessListMappings.
func (al *AccessListMappings) Copy() *AccessListMappings {
cp := NewAccessListMappings()
for k, v := range al.addresses {
cp.addresses[k] = v
}
@ -67,7 +67,7 @@ func (al *accessList) Copy() *accessList {
// AddAddress adds an address to the access list, and returns 'true' if the operation
// caused a change (addr was not previously in the list).
func (al *accessList) AddAddress(address common.Address) bool {
func (al *AccessListMappings) AddAddress(address common.Address) bool {
if _, present := al.addresses[address]; present {
return false
}
@ -80,7 +80,7 @@ func (al *accessList) AddAddress(address common.Address) bool {
// - address added
// - slot added
// For any 'true' value returned, a corresponding journal entry must be made.
func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) {
func (al *AccessListMappings) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) {
idx, addrPresent := al.addresses[address]
if !addrPresent || idx == -1 {
// Address not present, or addr present but no slots there
@ -99,7 +99,7 @@ func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrCha
slotmap := al.slots[idx]
if _, ok := slotmap[slot]; !ok {
slotmap[slot] = struct{}{}
// Journal add slot change
// journal add slot change
return false, true
}
// No changes required
@ -110,7 +110,7 @@ func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrCha
// This operation needs to be performed in the same order as the addition happened.
// This method is meant to be used by the journal, which maintains ordering of
// operations.
func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
func (al *AccessListMappings) DeleteSlot(address common.Address, slot common.Hash) {
idx, addrOk := al.addresses[address]
// There are two ways this can fail
if !addrOk {
@ -131,7 +131,7 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
// needs to be performed in the same order as the addition happened.
// This method is meant to be used by the journal, which maintains ordering of
// operations.
func (al *accessList) DeleteAddress(address common.Address) {
func (al *AccessListMappings) DeleteAddress(address common.Address) {
delete(al.addresses, address)
}
@ -146,7 +146,7 @@ func NewAccessList(ethAccessList *ethtypes.AccessList) AccessList {
return nil
}
var accessList AccessList
var AccessListMappings AccessList
for _, tuple := range *ethAccessList {
storageKeys := make([]string, len(tuple.StorageKeys))
@ -154,19 +154,19 @@ func NewAccessList(ethAccessList *ethtypes.AccessList) AccessList {
storageKeys[i] = tuple.StorageKeys[i].String()
}
accessList = append(accessList, AccessTuple{
AccessListMappings = append(AccessListMappings, AccessTuple{
Address: tuple.Address.String(),
StorageKeys: storageKeys,
})
}
return accessList
return AccessListMappings
}
// ToEthAccessList is an utility function to convert the protobuf compatible
// AccessList to eth core AccessList from go-ethereum
func (al AccessList) ToEthAccessList() *ethtypes.AccessList {
var accessList ethtypes.AccessList
var AccessListMappings ethtypes.AccessList
for _, tuple := range al {
storageKeys := make([]ethcmn.Hash, len(tuple.StorageKeys))
@ -175,11 +175,11 @@ func (al AccessList) ToEthAccessList() *ethtypes.AccessList {
storageKeys[i] = ethcmn.HexToHash(tuple.StorageKeys[i])
}
accessList = append(accessList, ethtypes.AccessTuple{
AccessListMappings = append(AccessListMappings, ethtypes.AccessTuple{
Address: ethcmn.HexToAddress(tuple.Address),
StorageKeys: storageKeys,
})
}
return &accessList
return &AccessListMappings
}

View File

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

View File

@ -10,6 +10,7 @@ type AccountKeeper interface {
NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
GetAllAccounts(ctx sdk.Context) (accounts []authtypes.AccountI)
IterateAccounts(ctx sdk.Context, cb func(account authtypes.AccountI) bool)
GetSequence(sdk.Context, sdk.AccAddress) (uint64, error)
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
SetAccount(ctx sdk.Context, account authtypes.AccountI)
RemoveAccount(ctx sdk.Context, account authtypes.AccountI)
@ -18,5 +19,7 @@ type AccountKeeper interface {
// BankKeeper defines the expected interface needed to retrieve account balances.
type BankKeeper interface {
GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error
SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error
SetBalance(ctx sdk.Context, addr sdk.AccAddress, balance sdk.Coin) error
}

View File

@ -4,6 +4,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)
const (
@ -15,21 +16,51 @@ const (
// The EVM module should use a prefix store.
StoreKey = ModuleName
// Transient Key is the key to access the EVM transient store, that is reset
// during the Commit phase.
TransientKey = "transient_" + ModuleName
// RouterKey uses module name for routing
RouterKey = ModuleName
)
const (
prefixBlockHash = iota + 1
prefixBloom
prefixLogs
prefixCode
prefixStorage
prefixChainConfig
prefixBlockHeightHash
prefixHashTxReceipt
prefixBlockHeightTxs
)
const (
prefixTransientSuicided = iota + 1
prefixTransientBloom
prefixTransientTxIndex
prefixTransientRefund
)
// KVStore key prefixes
var (
KeyPrefixBlockHash = []byte{0x01}
KeyPrefixBloom = []byte{0x02}
KeyPrefixLogs = []byte{0x03}
KeyPrefixCode = []byte{0x04}
KeyPrefixStorage = []byte{0x05}
KeyPrefixChainConfig = []byte{0x06}
KeyPrefixBlockHeightHash = []byte{0x07}
KeyPrefixHashTxReceipt = []byte{0x08}
KeyPrefixBlockHeightTxs = []byte{0x09}
KeyPrefixBlockHash = []byte{prefixBlockHash}
KeyPrefixBloom = []byte{prefixBloom}
KeyPrefixLogs = []byte{prefixLogs}
KeyPrefixCode = []byte{prefixCode}
KeyPrefixStorage = []byte{prefixStorage}
KeyPrefixChainConfig = []byte{prefixChainConfig}
KeyPrefixBlockHeightHash = []byte{prefixBlockHeightHash}
KeyPrefixHashTxReceipt = []byte{prefixHashTxReceipt}
KeyPrefixBlockHeightTxs = []byte{prefixBlockHeightTxs}
)
var (
KeyPrefixTransientSuicided = []byte{prefixTransientSuicided}
KeyPrefixTransientBloom = []byte{prefixTransientBloom}
KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex}
KeyPrefixTransientRefund = []byte{prefixTransientRefund}
)
// BloomKey defines the store key for a block Bloom
@ -69,3 +100,17 @@ func KeyBlockHeightTxs(height uint64) []byte {
heightBytes := sdk.Uint64ToBigEndian(height)
return append(KeyPrefixBlockHeightTxs, heightBytes...)
}
// KeyAddressStorage returns the key hash to access a given account state. The composite key
// (address + hash) is hashed using Keccak256.
func KeyAddressStorage(address ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash {
prefix := address.Bytes()
key := hash.Bytes()
compositeKey := make([]byte, len(prefix)+len(key))
copy(compositeKey, prefix)
copy(compositeKey[len(prefix):], key)
return ethcrypto.Keccak256Hash(compositeKey)
}

View File

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

View File

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

View File

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

View File

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