refactor: optimize AnteHandler gas consumption (#1388)

* refactor: antehandler order and params optimization

* removed additional individual params for the feemarket module

* typo fix

* Apply suggestions from code review - Fede

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* typo fix

* formatted using gofumpt

* typo fix - missed negate operator

* missed to negate conditions

* added unit tests for the new param getter methods

* updated changelog

* Apply suggestions from code review

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* moved to improvements

* Converted unit tests into table-driven tests

* added Require().Equal() to test case

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
Vladislav Varadinov 2022-10-19 19:21:59 +03:00 committed by GitHub
parent 5879750b30
commit 83e509bc57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 258 additions and 66 deletions

View File

@ -52,6 +52,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements
* (ante) [\#1388](https://github.com/evmos/ethermint/pull/1388) Optimize AnteHandler gas consumption
* (lint) [#1298](https://github.com/evmos/ethermint/pull/1298) 150 character line length limit, `gofumpt`, and linting
* (feemarket) [\#1165](https://github.com/evmos/ethermint/pull/1165) Add hint in specs about different gas terminology in Cosmos and Ethereum.
* (cli) [#1226](https://github.com/evmos/ethermint/pull/1226) Add custom app db backend flag.

View File

@ -40,10 +40,8 @@ func NewEthSigVerificationDecorator(ek EVMKeeper) EthSigVerificationDecorator {
// won't see the error message.
func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
chainID := esvd.evmKeeper.ChainID()
params := esvd.evmKeeper.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(chainID)
chainCfg := esvd.evmKeeper.GetChainConfig(ctx)
ethCfg := chainCfg.EthereumConfig(chainID)
blockNum := big.NewInt(ctx.BlockHeight())
signer := ethtypes.MakeSigner(ethCfg, blockNum)
@ -53,8 +51,9 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}
allowUnprotectedTxs := esvd.evmKeeper.GetAllowUnprotectedTxs(ctx)
ethTx := msgEthTx.AsTransaction()
if !params.AllowUnprotectedTxs && !ethTx.Protected() {
if !allowUnprotectedTxs && !ethTx.Protected() {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrNotSupported,
"rejected unprotected Ethereum txs. Please EIP155 sign your transaction to protect it against replay-attacks")
@ -176,15 +175,13 @@ func NewEthGasConsumeDecorator(
// - transaction or block gas meter runs out of gas
// - sets the gas meter limit
func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
params := egcd.evmKeeper.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(egcd.evmKeeper.ChainID())
chainCfg := egcd.evmKeeper.GetChainConfig(ctx)
ethCfg := chainCfg.EthereumConfig(egcd.evmKeeper.ChainID())
blockHeight := big.NewInt(ctx.BlockHeight())
homestead := ethCfg.IsHomestead(blockHeight)
istanbul := ethCfg.IsIstanbul(blockHeight)
london := ethCfg.IsLondon(blockHeight)
evmDenom := params.EvmDenom
gasWanted := uint64(0)
var events sdk.Events
@ -213,6 +210,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
gasWanted += txData.GetGas()
}
evmDenom := egcd.evmKeeper.GetEVMDenom(ctx)
fees, priority, err := egcd.evmKeeper.DeductTxCostsFromUserBalance(
ctx,
*msgEthTx,
@ -436,9 +434,9 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
txFee := sdk.Coins{}
txGasLimit := uint64(0)
params := vbd.evmKeeper.GetParams(ctx)
chainCfg := vbd.evmKeeper.GetChainConfig(ctx)
chainID := vbd.evmKeeper.ChainID()
ethCfg := params.ChainConfig.EthereumConfig(chainID)
ethCfg := chainCfg.EthereumConfig(chainID)
baseFee := vbd.evmKeeper.GetBaseFee(ctx, ethCfg)
for _, msg := range protoTx.GetMsgs() {
@ -460,9 +458,12 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
}
// return error if contract creation or call are disabled through governance
if !params.EnableCreate && txData.GetTo() == nil {
enableCreate := vbd.evmKeeper.GetEnableCreate(ctx)
enableCall := vbd.evmKeeper.GetEnableCall(ctx)
if !enableCreate && txData.GetTo() == nil {
return ctx, sdkerrors.Wrap(evmtypes.ErrCreateDisabled, "failed to create new contract")
} else if !params.EnableCall && txData.GetTo() != nil {
} else if !enableCall && txData.GetTo() != nil {
return ctx, sdkerrors.Wrap(evmtypes.ErrCallDisabled, "failed to call contract")
}
@ -470,7 +471,8 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported")
}
txFee = txFee.Add(sdk.NewCoin(params.EvmDenom, sdkmath.NewIntFromBigInt(txData.Fee())))
evmDenom := vbd.evmKeeper.GetEVMDenom(ctx)
txFee = txFee.Add(sdk.NewCoin(evmDenom, sdkmath.NewIntFromBigInt(txData.Fee())))
}
authInfo := protoTx.AuthInfo
@ -546,8 +548,8 @@ func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator {
// It only do the check if london hardfork not enabled or feemarket not enabled, because in that case feemarket will take over the task.
func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if ctx.IsCheckTx() && !simulate {
params := mfd.evmKeeper.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(mfd.evmKeeper.ChainID())
chainCfg := mfd.evmKeeper.GetChainConfig(ctx)
ethCfg := chainCfg.EthereumConfig(mfd.evmKeeper.ChainID())
baseFee := mfd.evmKeeper.GetBaseFee(ctx, ethCfg)
if baseFee == nil {
for _, msg := range tx.GetMsgs() {
@ -556,7 +558,7 @@ func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulat
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}
evmDenom := params.EvmDenom
evmDenom := mfd.evmKeeper.GetEVMDenom(ctx)
feeAmt := ethMsg.GetFee()
glDec := sdk.NewDec(int64(ethMsg.GetGas()))
requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec)

View File

@ -27,8 +27,8 @@ func NewGasWantedDecorator(
}
func (gwd GasWantedDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
params := gwd.evmKeeper.GetParams(ctx)
ethCfg := params.ChainConfig.EthereumConfig(gwd.evmKeeper.ChainID())
chainCfg := gwd.evmKeeper.GetChainConfig(ctx)
ethCfg := chainCfg.EthereumConfig(gwd.evmKeeper.ChainID())
blockHeight := big.NewInt(ctx.BlockHeight())
isLondon := ethCfg.IsLondon(blockHeight)
@ -39,10 +39,10 @@ func (gwd GasWantedDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo
}
gasWanted := feeTx.GetGas()
feeMktParams := gwd.feeMarketKeeper.GetParams(ctx)
isBaseFeeEnabled := gwd.feeMarketKeeper.GetBaseFeeEnabled(ctx)
// Add total gasWanted to cumulative in block transientStore in FeeMarket module
if feeMktParams.IsBaseFeeEnabled(ctx.BlockHeight()) {
if isBaseFeeEnabled {
if _, err := gwd.feeMarketKeeper.AddTransientGasWanted(ctx, gasWanted); err != nil {
return ctx, sdkerrors.Wrapf(err, "failed to add gas wanted to transient store")
}

View File

@ -37,10 +37,10 @@ func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
return next(ctx, tx, simulate)
}
evmParams := mpd.evmKeeper.GetParams(ctx)
evmDenom := mpd.evmKeeper.GetEVMDenom(ctx)
minGasPrices := sdk.DecCoins{
{
Denom: evmParams.EvmDenom,
Denom: evmDenom,
Amount: minGasPrice,
},
}
@ -93,8 +93,8 @@ func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
return next(ctx, tx, simulate)
}
paramsEvm := empd.evmKeeper.GetParams(ctx)
ethCfg := paramsEvm.ChainConfig.EthereumConfig(empd.evmKeeper.ChainID())
chainCfg := empd.evmKeeper.GetChainConfig(ctx)
ethCfg := chainCfg.EthereumConfig(empd.evmKeeper.ChainID())
baseFee := empd.evmKeeper.GetBaseFee(ctx, ethCfg)
for _, msg := range tx.GetMsgs() {

View File

@ -20,7 +20,7 @@ type HandlerOptions struct {
AccountKeeper evmtypes.AccountKeeper
BankKeeper evmtypes.BankKeeper
IBCKeeper *ibckeeper.Keeper
FeeMarketKeeper evmtypes.FeeMarketKeeper
FeeMarketKeeper FeeMarketKeeper
EvmKeeper EVMKeeper
FeegrantKeeper ante.FeegrantKeeper
SignModeHandler authsigning.SignModeHandler
@ -70,9 +70,9 @@ func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler {
RejectMessagesDecorator{}, // reject MsgEthereumTxs
ante.NewSetUpContextDecorator(),
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
@ -93,9 +93,9 @@ func newCosmosAnteHandlerEip712(options HandlerOptions) sdk.AnteHandler {
ante.NewSetUpContextDecorator(),
// NOTE: extensions option decorator removed
// ante.NewRejectExtensionOptionsDecorator(),
NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),

View File

@ -34,6 +34,11 @@ type EVMKeeper interface {
GetBalance(ctx sdk.Context, addr common.Address) *big.Int
ResetTransientGasUsed(ctx sdk.Context)
GetTxIndexTransient(ctx sdk.Context) uint64
GetChainConfig(ctx sdk.Context) evmtypes.ChainConfig
GetEVMDenom(ctx sdk.Context) string
GetEnableCreate(ctx sdk.Context) bool
GetEnableCall(ctx sdk.Context) bool
GetAllowUnprotectedTxs(ctx sdk.Context) bool
}
type protoTxProvider interface {
@ -44,4 +49,5 @@ type protoTxProvider interface {
type FeeMarketKeeper interface {
GetParams(ctx sdk.Context) (params feemarkettypes.Params)
AddTransientGasWanted(ctx sdk.Context, gasWanted uint64) (uint64, error)
GetBaseFeeEnabled(ctx sdk.Context) bool
}

View File

@ -23,10 +23,12 @@ import (
)
// Testing Constants
var chainId = "ethermint_9000-1"
var ctx = client.Context{}.WithTxConfig(
var (
chainId = "ethermint_9000-1"
ctx = client.Context{}.WithTxConfig(
encoding.MakeConfig(app.ModuleBasics).TxConfig,
)
)
var feePayerAddress = "ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl"
type TestCaseStruct struct {

View File

@ -3,13 +3,14 @@ package rpc
import (
"encoding/json"
"fmt"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/require"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/require"
)
var (

View File

@ -21,3 +21,38 @@ func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSpace.SetParamSet(ctx, &params)
}
// GetChainConfig returns the chain configuration parameter.
func (k Keeper) GetChainConfig(ctx sdk.Context) types.ChainConfig {
chainCfg := types.ChainConfig{}
k.paramSpace.GetIfExists(ctx, types.ParamStoreKeyChainConfig, &chainCfg)
return chainCfg
}
// GetEVMDenom returns the EVM denom.
func (k Keeper) GetEVMDenom(ctx sdk.Context) string {
evmDenom := ""
k.paramSpace.GetIfExists(ctx, types.ParamStoreKeyEVMDenom, &evmDenom)
return evmDenom
}
// GetEnableCall returns true if the EVM Call operation is enabled.
func (k Keeper) GetEnableCall(ctx sdk.Context) bool {
enableCall := false
k.paramSpace.GetIfExists(ctx, types.ParamStoreKeyEnableCall, &enableCall)
return enableCall
}
// GetEnableCreate returns true if the EVM Create contract operation is enabled.
func (k Keeper) GetEnableCreate(ctx sdk.Context) bool {
enableCreate := false
k.paramSpace.GetIfExists(ctx, types.ParamStoreKeyEnableCreate, &enableCreate)
return enableCreate
}
// GetAllowUnprotectedTxs returns true if unprotected txs (i.e non-replay protected as per EIP-155) are supported by the chain.
func (k Keeper) GetAllowUnprotectedTxs(ctx sdk.Context) bool {
allowUnprotectedTx := false
k.paramSpace.GetIfExists(ctx, types.ParamStoreKeyAllowUnprotectedTxs, &allowUnprotectedTx)
return allowUnprotectedTx
}

View File

@ -2,13 +2,94 @@ package keeper_test
import (
"github.com/evmos/ethermint/x/evm/types"
"reflect"
)
func (suite *KeeperTestSuite) TestParams() {
params := suite.app.EvmKeeper.GetParams(suite.ctx)
suite.Require().Equal(types.DefaultParams(), params)
suite.app.EvmKeeper.SetParams(suite.ctx, params)
testCases := []struct {
name string
paramsFun func() interface{}
getFun func() interface{}
expected bool
}{
{
"success - Checks if the default params are set correctly",
func() interface{} {
return types.DefaultParams()
},
func() interface{} {
return suite.app.EvmKeeper.GetParams(suite.ctx)
},
true,
},
{
"success - EvmDenom param is set to \"inj\" and can be retrieved correctly",
func() interface{} {
params.EvmDenom = "inj"
suite.app.EvmKeeper.SetParams(suite.ctx, params)
newParams := suite.app.EvmKeeper.GetParams(suite.ctx)
suite.Require().Equal(newParams, params)
return params.EvmDenom
},
func() interface{} {
return suite.app.EvmKeeper.GetEVMDenom(suite.ctx)
},
true,
},
{
"success - Check EnableCreate param is set to false and can be retrieved correctly",
func() interface{} {
params.EnableCreate = false
suite.app.EvmKeeper.SetParams(suite.ctx, params)
return params.EnableCreate
},
func() interface{} {
return suite.app.EvmKeeper.GetEnableCreate(suite.ctx)
},
true,
},
{
"success - Check EnableCall param is set to false and can be retrieved correctly",
func() interface{} {
params.EnableCall = false
suite.app.EvmKeeper.SetParams(suite.ctx, params)
return params.EnableCall
},
func() interface{} {
return suite.app.EvmKeeper.GetEnableCall(suite.ctx)
},
true,
},
{
"success - Check AllowUnprotectedTxs param is set to false and can be retrieved correctly",
func() interface{} {
params.AllowUnprotectedTxs = false
suite.app.EvmKeeper.SetParams(suite.ctx, params)
return params.AllowUnprotectedTxs
},
func() interface{} {
return suite.app.EvmKeeper.GetAllowUnprotectedTxs(suite.ctx)
},
true,
},
{
"success - Check ChainConfig param is set to the default value and can be retrieved correctly",
func() interface{} {
params.ChainConfig = types.DefaultChainConfig()
suite.app.EvmKeeper.SetParams(suite.ctx, params)
return params.ChainConfig
},
func() interface{} {
return suite.app.EvmKeeper.GetChainConfig(suite.ctx)
},
true,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
outcome := reflect.DeepEqual(tc.paramsFun(), tc.getFun())
suite.Require().Equal(tc.expected, outcome)
})
}
}

View File

@ -198,4 +198,3 @@ func TestConversionFunctions(t *testing.T) {
require.Nil(t, conversionErr)
require.Nil(t, copyErr)
}

View File

@ -28,6 +28,15 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
// Required by EIP1559 base fee calculation.
// ----------------------------------------------------------------------------
// GetBaseFeeEnabled returns true if base fee is enabled
func (k Keeper) GetBaseFeeEnabled(ctx sdk.Context) bool {
noBaseFee := false
enableHeight := int64(0)
k.paramSpace.GetIfExists(ctx, types.ParamStoreKeyNoBaseFee, &noBaseFee)
k.paramSpace.GetIfExists(ctx, types.ParamStoreKeyEnableHeight, &enableHeight)
return !noBaseFee && ctx.BlockHeight() >= enableHeight
}
// GetBaseFee get's the base fee from the paramSpace
// return nil if base fee is not enabled
func (k Keeper) GetBaseFee(ctx sdk.Context) *big.Int {

View File

@ -2,13 +2,69 @@ package keeper_test
import (
"github.com/evmos/ethermint/x/feemarket/types"
"reflect"
)
func (suite *KeeperTestSuite) TestSetGetParams() {
params := suite.app.FeeMarketKeeper.GetParams(suite.ctx)
suite.Require().Equal(types.DefaultParams(), params)
suite.app.FeeMarketKeeper.SetParams(suite.ctx, params)
testCases := []struct {
name string
paramsFun func() interface{}
getFun func() interface{}
expected bool
}{
{
"success - Checks if the default params are set correctly",
func() interface{} {
return types.DefaultParams()
},
func() interface{} {
return suite.app.FeeMarketKeeper.GetParams(suite.ctx)
},
true,
},
{
"success - Check ElasticityMultiplier is set to 3 and can be retrieved correctly",
func() interface{} {
params.ElasticityMultiplier = 3
suite.app.FeeMarketKeeper.SetParams(suite.ctx, params)
newParams := suite.app.FeeMarketKeeper.GetParams(suite.ctx)
suite.Require().Equal(newParams, params)
return params.ElasticityMultiplier
},
func() interface{} {
return suite.app.FeeMarketKeeper.GetParams(suite.ctx).ElasticityMultiplier
},
true,
},
{
"success - Check BaseFeeEnabled is computed with its default params and can be retrieved correctly",
func() interface{} {
suite.app.FeeMarketKeeper.SetParams(suite.ctx, types.DefaultParams())
return true
},
func() interface{} {
return suite.app.FeeMarketKeeper.GetBaseFeeEnabled(suite.ctx)
},
true,
},
{
"success - Check BaseFeeEnabled is computed with alternate params and can be retrieved correctly",
func() interface{} {
params.NoBaseFee = true
params.EnableHeight = 5
suite.app.FeeMarketKeeper.SetParams(suite.ctx, params)
return true
},
func() interface{} {
return suite.app.FeeMarketKeeper.GetBaseFeeEnabled(suite.ctx)
},
false,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
outcome := reflect.DeepEqual(tc.paramsFun(), tc.getFun())
suite.Require().Equal(tc.expected, outcome)
})
}
}