From 87c4ea2dc2533a457bae8faea9d44576e483caa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= <31522760+fedekunze@users.noreply.github.com> Date: Tue, 31 Aug 2021 14:50:31 +0200 Subject: [PATCH] evm: benchmark state DB (#514) * evm: benchmark state DB * add more bech funcs * rm func from interface --- app/ante/eth.go | 1 - .../{benchevm_test.go => benchmark_test.go} | 0 x/evm/keeper/grpc_query.go | 4 +- x/evm/keeper/state_transition.go | 37 ++-- x/evm/keeper/state_transition_test.go | 2 +- x/evm/keeper/statedb.go | 177 +++++++++++------- x/evm/keeper/statedb_benchmark_test.go | 128 +++++++++++++ 7 files changed, 264 insertions(+), 85 deletions(-) rename x/evm/keeper/{benchevm_test.go => benchmark_test.go} (100%) create mode 100644 x/evm/keeper/statedb_benchmark_test.go diff --git a/app/ante/eth.go b/app/ante/eth.go index 69089444..959ab0df 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -26,7 +26,6 @@ type EVMKeeper interface { GetParams(ctx sdk.Context) evmtypes.Params WithContext(ctx sdk.Context) ResetRefundTransient(ctx sdk.Context) - GetCoinbaseAddress() (common.Address, error) NewEVM(msg core.Message, config *params.ChainConfig, params evmtypes.Params, coinbase common.Address, tracer vm.Tracer) *vm.EVM GetCodeHash(addr common.Address) common.Hash } diff --git a/x/evm/keeper/benchevm_test.go b/x/evm/keeper/benchmark_test.go similarity index 100% rename from x/evm/keeper/benchevm_test.go rename to x/evm/keeper/benchmark_test.go diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index 457b84a1..b706b2f7 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -330,7 +330,7 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms params := k.GetParams(ctx) ethCfg := params.ChainConfig.EthereumConfig(k.eip155ChainID) - coinbase, err := k.GetCoinbaseAddress() + coinbase, err := k.GetCoinbaseAddress(ctx) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -394,7 +394,7 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type params := k.GetParams(ctx) ethCfg := params.ChainConfig.EthereumConfig(k.eip155ChainID) - coinbase, err := k.GetCoinbaseAddress() + coinbase, err := k.GetCoinbaseAddress(ctx) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 755b002d..077b0526 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -69,39 +69,40 @@ func (k Keeper) VMConfig(msg core.Message, params types.Params, tracer vm.Tracer func (k Keeper) GetHashFn() vm.GetHashFunc { return func(height uint64) common.Hash { h := int64(height) + ctx := k.Ctx() switch { - case k.Ctx().BlockHeight() == h: + case ctx.BlockHeight() == h: // Case 1: The requested height matches the one from the context so we can retrieve the header // hash directly from the context. // Note: The headerHash is only set at begin block, it will be nil in case of a query context - headerHash := k.Ctx().HeaderHash() + headerHash := ctx.HeaderHash() if len(headerHash) != 0 { return common.BytesToHash(headerHash) } // only recompute the hash if not set (eg: checkTxState) - contextBlockHeader := k.Ctx().BlockHeader() + contextBlockHeader := ctx.BlockHeader() header, err := tmtypes.HeaderFromProto(&contextBlockHeader) if err != nil { - k.Logger(k.Ctx()).Error("failed to cast tendermint header from proto", "error", err) + k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err) return common.Hash{} } headerHash = header.Hash() return common.BytesToHash(headerHash) - case k.Ctx().BlockHeight() > h: + case ctx.BlockHeight() > h: // Case 2: if the chain is not the current height we need to retrieve the hash from the store for the // current chain epoch. This only applies if the current height is greater than the requested height. - histInfo, found := k.stakingKeeper.GetHistoricalInfo(k.Ctx(), h) + histInfo, found := k.stakingKeeper.GetHistoricalInfo(ctx, h) if !found { - k.Logger(k.Ctx()).Debug("historical info not found", "height", h) + k.Logger(ctx).Debug("historical info not found", "height", h) return common.Hash{} } header, err := tmtypes.HeaderFromProto(&histInfo.Header) if err != nil { - k.Logger(k.Ctx()).Error("failed to cast tendermint header from proto", "error", err) + k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err) return common.Hash{} } @@ -133,7 +134,8 @@ func (k Keeper) GetHashFn() vm.GetHashFunc { func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricKeyTransitionDB) - params := k.GetParams(k.Ctx()) + ctx := k.Ctx() + params := k.GetParams(ctx) // return error if contract creation or call are disabled through governance if !params.EnableCreate && tx.To() == nil { @@ -145,7 +147,7 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT ethCfg := params.ChainConfig.EthereumConfig(k.eip155ChainID) // get the latest signer according to the chain rules from the config - signer := ethtypes.MakeSigner(ethCfg, big.NewInt(k.Ctx().BlockHeight())) + signer := ethtypes.MakeSigner(ethCfg, big.NewInt(ctx.BlockHeight())) msg, err := tx.AsMessage(signer) if err != nil { @@ -153,13 +155,13 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT } // get the coinbase address from the block proposer - coinbase, err := k.GetCoinbaseAddress() + coinbase, err := k.GetCoinbaseAddress(ctx) if err != nil { return nil, stacktrace.Propagate(err, "failed to obtain coinbase address") } // create an ethereum EVM instance and run the message - tracer := types.NewTracer(k.tracer, msg, ethCfg, k.Ctx().BlockHeight(), k.debug) + tracer := types.NewTracer(k.tracer, msg, ethCfg, ctx.BlockHeight(), k.debug) evm := k.NewEVM(msg, ethCfg, params, coinbase, tracer) txHash := tx.Hash() @@ -368,14 +370,15 @@ func (k *Keeper) RefundGas(msg core.Message, leftoverGas, refundQuotient uint64) // 'gasUsed' func (k *Keeper) resetGasMeterAndConsumeGas(gasUsed uint64) { // reset the gas count - k.Ctx().GasMeter().RefundGas(k.Ctx().GasMeter().GasConsumed(), "reset the gas count") - k.Ctx().GasMeter().ConsumeGas(gasUsed, "apply evm transaction") + ctx := k.Ctx() + ctx.GasMeter().RefundGas(ctx.GasMeter().GasConsumed(), "reset the gas count") + ctx.GasMeter().ConsumeGas(gasUsed, "apply evm transaction") } // GetCoinbaseAddress returns the block proposer's validator operator address. -func (k Keeper) GetCoinbaseAddress() (common.Address, error) { - consAddr := sdk.ConsAddress(k.Ctx().BlockHeader().ProposerAddress) - validator, found := k.stakingKeeper.GetValidatorByConsAddr(k.Ctx(), consAddr) +func (k Keeper) GetCoinbaseAddress(ctx sdk.Context) (common.Address, error) { + consAddr := sdk.ConsAddress(ctx.BlockHeader().ProposerAddress) + validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, consAddr) if !found { return common.Address{}, stacktrace.Propagate( sdkerrors.Wrap(stakingtypes.ErrNoValidatorFound, consAddr.String()), diff --git a/x/evm/keeper/state_transition_test.go b/x/evm/keeper/state_transition_test.go index 05a18c24..bf6aa91f 100644 --- a/x/evm/keeper/state_transition_test.go +++ b/x/evm/keeper/state_transition_test.go @@ -65,7 +65,7 @@ func (suite *KeeperTestSuite) TestGetCoinbaseAddress() { tc.malleate() - coinbase, err := suite.app.EvmKeeper.GetCoinbaseAddress() + coinbase, err := suite.app.EvmKeeper.GetCoinbaseAddress(suite.ctx) if tc.expPass { suite.Require().NoError(err) suite.Require().Equal(valOpAddr, coinbase) diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index a99e08dc..dae08f04 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -29,8 +29,8 @@ var _ vm.StateDB = &Keeper{} // address. func (k *Keeper) CreateAccount(addr common.Address) { cosmosAddr := sdk.AccAddress(addr.Bytes()) - - account := k.accountKeeper.GetAccount(k.Ctx(), cosmosAddr) + ctx := k.Ctx() + account := k.accountKeeper.GetAccount(ctx, cosmosAddr) log := "" if account == nil { log = "account created" @@ -39,10 +39,10 @@ func (k *Keeper) CreateAccount(addr common.Address) { k.ResetAccount(addr) } - account = k.accountKeeper.NewAccountWithAddress(k.Ctx(), cosmosAddr) - k.accountKeeper.SetAccount(k.Ctx(), account) + account = k.accountKeeper.NewAccountWithAddress(ctx, cosmosAddr) + k.accountKeeper.SetAccount(ctx, account) - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( log, "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -57,8 +57,10 @@ func (k *Keeper) CreateAccount(addr common.Address) { // coins and transferring them to the address. The coin denomination is obtained // from the module parameters. func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) { + ctx := k.Ctx() + if amount.Sign() != 1 { - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( "ignored non-positive amount addition", "ethereum-address", addr.Hex(), "amount", amount.Int64(), @@ -67,12 +69,18 @@ func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) { } cosmosAddr := sdk.AccAddress(addr.Bytes()) + params := k.GetParams(ctx) - params := k.GetParams(k.Ctx()) - coins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(amount))} + // Coin denom and amount already validated + coins := sdk.Coins{ + { + Denom: params.EvmDenom, + Amount: sdk.NewIntFromBigInt(amount), + }, + } - if err := k.bankKeeper.MintCoins(k.Ctx(), types.ModuleName, coins); err != nil { - k.Logger(k.Ctx()).Error( + if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { + k.Logger(ctx).Error( "failed to mint coins when adding balance", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -81,8 +89,8 @@ func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) { return } - if err := k.bankKeeper.SendCoinsFromModuleToAccount(k.Ctx(), types.ModuleName, cosmosAddr, coins); err != nil { - k.Logger(k.Ctx()).Error( + if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, cosmosAddr, coins); err != nil { + k.Logger(ctx).Error( "failed to send from module to account when adding balance", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -91,7 +99,7 @@ func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) { return } - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( "balance addition", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -103,8 +111,10 @@ func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) { // from the module parameters. This function performs a no-op if the amount is negative // or the user doesn't have enough funds for the transfer. func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) { + ctx := k.Ctx() + if amount.Sign() != 1 { - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( "ignored non-positive amount addition", "ethereum-address", addr.Hex(), "amount", amount.Int64(), @@ -114,11 +124,18 @@ 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))} + params := k.GetParams(ctx) - if err := k.bankKeeper.SendCoinsFromAccountToModule(k.Ctx(), cosmosAddr, types.ModuleName, coins); err != nil { - k.Logger(k.Ctx()).Debug( + // Coin denom and amount already validated + coins := sdk.Coins{ + { + Denom: params.EvmDenom, + Amount: sdk.NewIntFromBigInt(amount), + }, + } + + if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, cosmosAddr, types.ModuleName, coins); err != nil { + k.Logger(ctx).Debug( "failed to send from account to module when subtracting balance", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -128,8 +145,8 @@ func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) { return } - if err := k.bankKeeper.BurnCoins(k.Ctx(), types.ModuleName, coins); err != nil { - k.Logger(k.Ctx()).Error( + if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, coins); err != nil { + k.Logger(ctx).Error( "failed to burn coins when subtracting balance", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -138,7 +155,7 @@ func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) { return } - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( "balance subtraction", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -148,9 +165,11 @@ func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) { // GetBalance returns the EVM denomination balance of the provided address. The // denomination is obtained from the module parameters. func (k *Keeper) GetBalance(addr common.Address) *big.Int { + ctx := k.Ctx() + cosmosAddr := sdk.AccAddress(addr.Bytes()) - params := k.GetParams(k.Ctx()) - balance := k.bankKeeper.GetBalance(k.Ctx(), cosmosAddr, params.EvmDenom) + params := k.GetParams(ctx) + balance := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom) return balance.Amount.BigInt() } @@ -162,10 +181,12 @@ func (k *Keeper) GetBalance(addr common.Address) *big.Int { // GetNonce retrieves the account with the given address and returns the tx // sequence (i.e nonce). The function performs a no-op if the account is not found. func (k *Keeper) GetNonce(addr common.Address) uint64 { + ctx := k.Ctx() + cosmosAddr := sdk.AccAddress(addr.Bytes()) - nonce, err := k.accountKeeper.GetSequence(k.Ctx(), cosmosAddr) + nonce, err := k.accountKeeper.GetSequence(ctx, cosmosAddr) if err != nil { - k.Logger(k.Ctx()).Error( + k.Logger(ctx).Error( "account not found", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -179,21 +200,23 @@ func (k *Keeper) GetNonce(addr common.Address) uint64 { // SetNonce sets the given nonce as the sequence of the address' account. If the // account doesn't exist, a new one will be created from the address. func (k *Keeper) SetNonce(addr common.Address, nonce uint64) { + ctx := k.Ctx() + cosmosAddr := sdk.AccAddress(addr.Bytes()) - account := k.accountKeeper.GetAccount(k.Ctx(), cosmosAddr) + account := k.accountKeeper.GetAccount(ctx, cosmosAddr) if account == nil { - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( "account not found", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), ) // create address if it doesn't exist - account = k.accountKeeper.NewAccountWithAddress(k.Ctx(), cosmosAddr) + account = k.accountKeeper.NewAccountWithAddress(ctx, cosmosAddr) } if err := account.SetSequence(nonce); err != nil { - k.Logger(k.Ctx()).Error( + k.Logger(ctx).Error( "failed to set nonce", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -204,9 +227,9 @@ func (k *Keeper) SetNonce(addr common.Address, nonce uint64) { return } - k.accountKeeper.SetAccount(k.Ctx(), account) + k.accountKeeper.SetAccount(ctx, account) - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( "nonce set", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -221,8 +244,10 @@ func (k *Keeper) SetNonce(addr common.Address, nonce uint64) { // GetCodeHash fetches the account from the store and returns its code hash. If the account doesn't // exist or is not an EthAccount type, GetCodeHash returns the empty code hash value. func (k *Keeper) GetCodeHash(addr common.Address) common.Hash { + ctx := k.Ctx() cosmosAddr := sdk.AccAddress(addr.Bytes()) - account := k.accountKeeper.GetAccount(k.Ctx(), cosmosAddr) + + account := k.accountKeeper.GetAccount(ctx, cosmosAddr) if account == nil { return common.BytesToHash(types.EmptyCodeHash) } @@ -238,17 +263,18 @@ func (k *Keeper) GetCodeHash(addr common.Address) common.Hash { // GetCode returns the code byte array associated with the given address. // If the code hash from the account is empty, this function returns nil. func (k *Keeper) GetCode(addr common.Address) []byte { + ctx := k.Ctx() 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) + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixCode) code := store.Get(hash.Bytes()) if len(code) == 0 { - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( "code not found", "ethereum-address", addr.Hex(), "code-hash", hash.Hex(), @@ -261,21 +287,23 @@ func (k *Keeper) GetCode(addr common.Address) []byte { // SetCode stores the code byte array to the application KVStore and sets the // code hash to the given account. The code is deleted from the store if it is empty. func (k *Keeper) SetCode(addr common.Address, code []byte) { + ctx := k.Ctx() + if bytes.Equal(code, types.EmptyCodeHash) { - k.Logger(k.Ctx()).Debug("passed in EmptyCodeHash, but expected empty code") + k.Logger(ctx).Debug("passed in EmptyCodeHash, but expected empty code") } hash := crypto.Keccak256Hash(code) // update account code hash - account := k.accountKeeper.GetAccount(k.Ctx(), addr.Bytes()) + account := k.accountKeeper.GetAccount(ctx, addr.Bytes()) if account == nil { - account = k.accountKeeper.NewAccountWithAddress(k.Ctx(), addr.Bytes()) - k.accountKeeper.SetAccount(k.Ctx(), account) + account = k.accountKeeper.NewAccountWithAddress(ctx, addr.Bytes()) + k.accountKeeper.SetAccount(ctx, account) } ethAccount, isEthAccount := account.(*ethermint.EthAccount) if !isEthAccount { - k.Logger(k.Ctx()).Error( + k.Logger(ctx).Error( "invalid account type", "ethereum-address", addr.Hex(), "code-hash", hash.Hex(), @@ -284,9 +312,9 @@ func (k *Keeper) SetCode(addr common.Address, code []byte) { } ethAccount.CodeHash = hash.Hex() - k.accountKeeper.SetAccount(k.Ctx(), ethAccount) + k.accountKeeper.SetAccount(ctx, ethAccount) - store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.KeyPrefixCode) + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixCode) action := "updated" @@ -298,7 +326,7 @@ func (k *Keeper) SetCode(addr common.Address, code []byte) { store.Set(hash.Bytes(), code) } - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( fmt.Sprintf("code %s", action), "ethereum-address", addr.Hex(), "code-hash", hash.Hex(), @@ -323,17 +351,19 @@ func (k *Keeper) GetCodeSize(addr common.Address) int { // AddRefund adds the given amount of gas to the refund transient value. func (k *Keeper) AddRefund(gas uint64) { + ctx := k.Ctx() refund := k.GetRefund() refund += gas - store := k.Ctx().TransientStore(k.transientKey) + store := ctx.TransientStore(k.transientKey) store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund)) } // SubRefund subtracts the given amount of gas from the transient refund value. This function // will panic if gas amount is greater than the stored refund. func (k *Keeper) SubRefund(gas uint64) { + ctx := k.Ctx() refund := k.GetRefund() if gas > refund { @@ -343,14 +373,15 @@ func (k *Keeper) SubRefund(gas uint64) { refund -= gas - store := k.Ctx().TransientStore(k.transientKey) + store := ctx.TransientStore(k.transientKey) store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund)) } // GetRefund returns the amount of gas available for return after the tx execution // finalizes. This value is reset to 0 on every transaction. func (k *Keeper) GetRefund() uint64 { - store := k.Ctx().TransientStore(k.transientKey) + ctx := k.Ctx() + store := ctx.TransientStore(k.transientKey) bz := store.Get(types.KeyPrefixTransientRefund) if len(bz) == 0 { @@ -385,13 +416,15 @@ func (k *Keeper) GetCommittedState(addr common.Address, hash common.Hash) common // GetState returns the committed state for the given key hash, as all changes are committed directly // to the KVStore. func (k *Keeper) GetState(addr common.Address, hash common.Hash) common.Hash { - return doGetState(k.Ctx(), k.storeKey, addr, hash) + ctx := k.Ctx() + return doGetState(ctx, k.storeKey, addr, hash) } // SetState sets the given hashes (key, value) to the KVStore. If the value hash is empty, this // function deletes the key from the store. func (k *Keeper) SetState(addr common.Address, key, value common.Hash) { - store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.AddressStoragePrefix(addr)) + ctx := k.Ctx() + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) key = types.KeyAddressStorage(addr, key) action := "updated" @@ -402,7 +435,7 @@ func (k *Keeper) SetState(addr common.Address, key, value common.Hash) { store.Set(key.Bytes(), value.Bytes()) } - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( fmt.Sprintf("state %s", action), "ethereum-address", addr.Hex(), "key", key.Hex(), @@ -416,6 +449,8 @@ func (k *Keeper) SetState(addr common.Address, key, value common.Hash) { // Suicide marks the given account as suicided and clears the account balance of // the EVM tokens. func (k *Keeper) Suicide(addr common.Address) bool { + ctx := k.Ctx() + prev := k.HasSuicided(addr) if prev { return true @@ -425,7 +460,7 @@ func (k *Keeper) Suicide(addr common.Address) bool { _, err := k.ClearBalance(cosmosAddr) if err != nil { - k.Logger(k.Ctx()).Error( + k.Logger(ctx).Error( "failed to subtract balance on suicide", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -436,12 +471,9 @@ func (k *Keeper) Suicide(addr common.Address) bool { } // TODO: (@fedekunze) do we also need to delete the storage state and the code? + k.setSuicided(ctx, addr) - // 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( + k.Logger(ctx).Debug( "account suicided", "ethereum-address", addr.Hex(), "cosmos-address", cosmosAddr.String(), @@ -450,11 +482,19 @@ func (k *Keeper) Suicide(addr common.Address) bool { return true } +// setSuicided sets a single byte to the transient store and marks the address as suicided +func (k Keeper) setSuicided(ctx sdk.Context, addr common.Address) { + + store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided) + store.Set(addr.Bytes(), []byte{1}) +} + // 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) + ctx := k.Ctx() + store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided) return store.Has(addr.Bytes()) } @@ -465,13 +505,14 @@ func (k *Keeper) HasSuicided(addr common.Address) bool { // 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 { + ctx := k.Ctx() // 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) + account := k.accountKeeper.GetAccount(ctx, cosmosAddr) return account != nil } @@ -482,11 +523,12 @@ func (k *Keeper) Exist(addr common.Address) bool { // // Non-ethereum accounts are considered not empty func (k *Keeper) Empty(addr common.Address) bool { + ctx := k.Ctx() nonce := uint64(0) codeHash := types.EmptyCodeHash cosmosAddr := sdk.AccAddress(addr.Bytes()) - account := k.accountKeeper.GetAccount(k.Ctx(), cosmosAddr) + account := k.accountKeeper.GetAccount(ctx, cosmosAddr) if account != nil { nonce = account.GetSequence() @@ -537,7 +579,8 @@ func (k *Keeper) PrepareAccessList(sender common.Address, dest *common.Address, // AddressInAccessList returns true if the address is registered on the transient store. func (k *Keeper) AddressInAccessList(addr common.Address) bool { - ts := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress) + ctx := k.Ctx() + ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress) return ts.Has(addr.Bytes()) } @@ -550,7 +593,8 @@ func (k *Keeper) SlotInAccessList(addr common.Address, slot common.Hash) (addres // addressSlotInAccessList returns true if the address's slot is registered on the transient store. func (k *Keeper) addressSlotInAccessList(addr common.Address, slot common.Hash) bool { - ts := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot) + ctx := k.Ctx() + ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot) key := append(addr.Bytes(), slot.Bytes()...) return ts.Has(key) } @@ -562,7 +606,8 @@ func (k *Keeper) AddAddressToAccessList(addr common.Address) { return } - ts := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress) + ctx := k.Ctx() + ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress) ts.Set(addr.Bytes(), []byte{0x1}) } @@ -574,7 +619,8 @@ func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) { return } - ts := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot) + ctx := k.Ctx() + ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot) key := append(addr.Bytes(), slot.Bytes()...) ts.Set(key, []byte{0x1}) } @@ -601,7 +647,9 @@ func (k *Keeper) RevertToSnapshot(target int) { // 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) { - log.BlockHash = common.BytesToHash(k.Ctx().HeaderHash()) + ctx := k.Ctx() + + log.BlockHash = common.BytesToHash(ctx.HeaderHash()) log.TxIndex = uint(k.GetTxIndexTransient()) log.TxHash = k.GetTxHashTransient() @@ -609,7 +657,7 @@ func (k *Keeper) AddLog(log *ethtypes.Log) { k.IncreaseLogSizeTransient() k.SetLog(log) - k.Logger(k.Ctx()).Debug( + k.Logger(ctx).Debug( "log added", "tx-hash-ethereum", log.TxHash.Hex(), "log-index", int(log.Index), @@ -632,7 +680,8 @@ func (k *Keeper) AddPreimage(_ common.Hash, _ []byte) {} // ForEachStorage uses the store iterator to iterate over all the state keys and perform a callback // function on each of them. func (k *Keeper) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { - store := k.Ctx().KVStore(k.storeKey) + ctx := k.Ctx() + store := ctx.KVStore(k.storeKey) prefix := types.AddressStoragePrefix(addr) iterator := sdk.KVStorePrefixIterator(store, prefix) diff --git a/x/evm/keeper/statedb_benchmark_test.go b/x/evm/keeper/statedb_benchmark_test.go new file mode 100644 index 00000000..10ddf7fb --- /dev/null +++ b/x/evm/keeper/statedb_benchmark_test.go @@ -0,0 +1,128 @@ +package keeper_test + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/tharsis/ethermint/tests" +) + +func BenchmarkCreateAccountNew(b *testing.B) { + suite := KeeperTestSuite{} + suite.DoSetupTest(b) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + b.StopTimer() + addr := tests.GenerateAddress() + b.StartTimer() + suite.app.EvmKeeper.CreateAccount(addr) + } +} + +func BenchmarkCreateAccountExisting(b *testing.B) { + suite := KeeperTestSuite{} + suite.DoSetupTest(b) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + suite.app.EvmKeeper.CreateAccount(suite.address) + } +} + +func BenchmarkAddBalance(b *testing.B) { + suite := KeeperTestSuite{} + suite.DoSetupTest(b) + + amt := big.NewInt(10) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + suite.app.EvmKeeper.AddBalance(suite.address, amt) + } +} + +func BenchmarkSetCode(b *testing.B) { + suite := KeeperTestSuite{} + suite.DoSetupTest(b) + + hash := crypto.Keccak256Hash([]byte("code")).Bytes() + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + suite.app.EvmKeeper.SetCode(suite.address, hash) + } +} + +func BenchmarkSetState(b *testing.B) { + suite := KeeperTestSuite{} + suite.DoSetupTest(b) + + hash := crypto.Keccak256Hash([]byte("topic")).Bytes() + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + suite.app.EvmKeeper.SetCode(suite.address, hash) + } +} + +func BenchmarkAddLog(b *testing.B) { + suite := KeeperTestSuite{} + suite.DoSetupTest(b) + + topic := crypto.Keccak256Hash([]byte("topic")) + txHash := crypto.Keccak256Hash([]byte("tx_hash")) + blockHash := crypto.Keccak256Hash([]byte("block_hash")) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + suite.app.EvmKeeper.AddLog(ðtypes.Log{ + Address: suite.address, + Topics: []common.Hash{topic}, + Data: []byte("data"), + BlockNumber: 1, + TxHash: txHash, + TxIndex: 1, + BlockHash: blockHash, + Index: 1, + Removed: false, + }) + } +} + +func BenchmarkSnapshot(b *testing.B) { + suite := KeeperTestSuite{} + suite.DoSetupTest(b) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + target := suite.app.EvmKeeper.Snapshot() + require.Equal(b, i, target) + } + + for i := b.N - 1; i >= 0; i-- { + require.NotPanics(b, func() { + suite.app.EvmKeeper.RevertToSnapshot(i) + }) + } +}