imp(ante): refactor AnteHandler (#1455)

* fix(ante): block gas check

* refactor

* rename

* use gas wanted

* remove consume gas logic on ante handler

* comment

* c++

* move min gas price

* comment

* Update app/ante/eth.go

Co-authored-by: Vladislav Varadinov <vladislav.varadinov@gmail.com>

* fix build

* fix integration test script

Co-authored-by: Vladislav Varadinov <vladislav.varadinov@gmail.com>
Co-authored-by: Freddy Caceres <facs95@gmail.com>
This commit is contained in:
Federico Kunze Küllmer 2022-11-18 22:41:16 +01:00 committed by GitHub
parent 37e394f309
commit feed84b09c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 53 deletions

View File

@ -58,6 +58,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements ### Improvements
* (ante) [#1455](https://github.com/evmos/ethermint/pull/1455) Refactor `AnteHandler` logic
* (evm) [#1444](https://github.com/evmos/ethermint/pull/1444) Improve performance of `eth_estimateGas` * (evm) [#1444](https://github.com/evmos/ethermint/pull/1444) Improve performance of `eth_estimateGas`
* (ante) [\#1388](https://github.com/evmos/ethermint/pull/1388) Optimize AnteHandler gas consumption * (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 * (lint) [#1298](https://github.com/evmos/ethermint/pull/1298) 150 character line length limit, `gofumpt`, and linting

View File

@ -165,7 +165,7 @@ func NewEthGasConsumeDecorator(
// (during CheckTx only) and that the sender has enough balance to pay for the gas cost. // (during CheckTx only) and that the sender has enough balance to pay for the gas cost.
// //
// Intrinsic gas for a transaction is the amount of gas that the transaction uses before the // Intrinsic gas for a transaction is the amount of gas that the transaction uses before the
// transaction is executed. The gas is a constant value plus any cost inccured by additional bytes // transaction is executed. The gas is a constant value plus any cost incurred by additional bytes
// of data supplied with the transaction. // of data supplied with the transaction.
// //
// This AnteHandler decorator will fail if: // This AnteHandler decorator will fail if:
@ -175,7 +175,14 @@ func NewEthGasConsumeDecorator(
// - user doesn't have enough balance to deduct the transaction fees (gas_limit * gas_price) // - user doesn't have enough balance to deduct the transaction fees (gas_limit * gas_price)
// - transaction or block gas meter runs out of gas // - transaction or block gas meter runs out of gas
// - sets the gas meter limit // - sets the gas meter limit
// - gas limit is greater than the block gas meter limit
func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// gas consumption limit already checked during CheckTx so there's no need to
// verify it again during ReCheckTx
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
}
chainCfg := egcd.evmKeeper.GetChainConfig(ctx) chainCfg := egcd.evmKeeper.GetChainConfig(ctx)
ethCfg := chainCfg.EthereumConfig(egcd.evmKeeper.ChainID()) ethCfg := chainCfg.EthereumConfig(egcd.evmKeeper.ChainID())
@ -225,32 +232,45 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
return ctx, sdkerrors.Wrapf(err, "failed to deduct transaction costs from user balance") return ctx, sdkerrors.Wrapf(err, "failed to deduct transaction costs from user balance")
} }
events = append(events, sdk.NewEvent(sdk.EventTypeTx, sdk.NewAttribute(sdk.AttributeKeyFee, fees.String()))) events = append(events,
sdk.NewEvent(
sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyFee, fees.String()),
),
)
if priority < minPriority { if priority < minPriority {
minPriority = priority minPriority = priority
} }
} }
// TODO: change to typed events
ctx.EventManager().EmitEvents(events) ctx.EventManager().EmitEvents(events)
// TODO: deprecate after https://github.com/cosmos/cosmos-sdk/issues/9514 is fixed on SDK
blockGasLimit := ethermint.BlockGasLimit(ctx) blockGasLimit := ethermint.BlockGasLimit(ctx)
// NOTE: safety check // return error if the tx gas is greater than the block limit (max gas)
if blockGasLimit > 0 {
// generate a copy of the gas pool (i.e block gas meter) to see if we've run out of gas for this block // NOTE: it's important here to use the gas wanted instead of the gas consumed
// if current gas consumed is greater than the limit, this funcion panics and the error is recovered on the Baseapp // from the tx gas pool. The later only has the value so far since the
gasPool := sdk.NewGasMeter(blockGasLimit) // EthSetupContextDecorator so it will never exceed the block gas limit.
gasPool.ConsumeGas(ctx.GasMeter().GasConsumedToLimit(), "gas pool check") if gasWanted > blockGasLimit {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrOutOfGas,
"tx gas (%d) exceeds block gas limit (%d)",
gasWanted,
blockGasLimit,
)
} }
// Set ctx.GasMeter with a limit of GasWanted (gasLimit) // Set tx GasMeter with a limit of GasWanted (i.e gas limit from the Ethereum tx).
gasConsumed := ctx.GasMeter().GasConsumed() // The gas consumed will be then reset to the gas used by the state transition
ctx = ctx.WithGasMeter(ethermint.NewInfiniteGasMeterWithLimit(gasWanted)) // in the EVM.
ctx.GasMeter().ConsumeGas(gasConsumed, "copy gas consumed")
newCtx := ctx.WithPriority(minPriority) // FIXME: use a custom gas configuration that doesn't add any additional gas and only
// takes into account the gas consumed at the end of the EVM transaction.
newCtx := ctx.
WithGasMeter(ethermint.NewInfiniteGasMeterWithLimit(gasWanted)).
WithPriority(minPriority)
// we know that we have enough gas on the pool to cover the intrinsic gas // we know that we have enough gas on the pool to cover the intrinsic gas
return next(newCtx, tx, simulate) return next(newCtx, tx, simulate)
@ -292,6 +312,22 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
) )
} }
if evmtypes.IsLondon(ethCfg, ctx.BlockHeight()) {
if baseFee == nil {
return ctx, sdkerrors.Wrap(
evmtypes.ErrInvalidBaseFee,
"base fee is supported but evm block context value is nil",
)
}
if coreMsg.GasFeeCap().Cmp(baseFee) < 0 {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"max fee per gas less than block base fee (%s < %s)",
coreMsg.GasFeeCap(), baseFee,
)
}
}
// NOTE: pass in an empty coinbase address and nil tracer as we don't need them for the check below // NOTE: pass in an empty coinbase address and nil tracer as we don't need them for the check below
cfg := &evmtypes.EVMConfig{ cfg := &evmtypes.EVMConfig{
ChainConfig: ethCfg, ChainConfig: ethCfg,
@ -312,22 +348,6 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
coreMsg.From(), coreMsg.From(),
) )
} }
if evmtypes.IsLondon(ethCfg, ctx.BlockHeight()) {
if baseFee == nil {
return ctx, sdkerrors.Wrap(
evmtypes.ErrInvalidBaseFee,
"base fee is supported but evm block context value is nil",
)
}
if coreMsg.GasFeeCap().Cmp(baseFee) < 0 {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"max fee per gas less than block base fee (%s < %s)",
coreMsg.GasFeeCap(), baseFee,
)
}
}
} }
return next(ctx, tx, simulate) return next(ctx, tx, simulate)
@ -546,31 +566,41 @@ func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator {
} }
} }
// AnteHandle ensures that the provided fees meet a minimum threshold for the validator, // AnteHandle ensures that the provided fees meet a minimum threshold for the validator.
// if this is a CheckTx. This is only for local mempool purposes, and thus // This check only for local mempool purposes, and thus it is only run on (Re)CheckTx.
// is only ran on check tx. // The logic is also skipped if the London hard fork and EIP-1559 are enabled.
// 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) { func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if ctx.IsCheckTx() && !simulate { if !ctx.IsCheckTx() || simulate {
chainCfg := mfd.evmKeeper.GetChainConfig(ctx) return next(ctx, tx, simulate)
ethCfg := chainCfg.EthereumConfig(mfd.evmKeeper.ChainID()) }
baseFee := mfd.evmKeeper.GetBaseFee(ctx, ethCfg) chainCfg := mfd.evmKeeper.GetChainConfig(ctx)
evmDenom := mfd.evmKeeper.GetEVMDenom(ctx) ethCfg := chainCfg.EthereumConfig(mfd.evmKeeper.ChainID())
if baseFee == nil { baseFee := mfd.evmKeeper.GetBaseFee(ctx, ethCfg)
for _, msg := range tx.GetMsgs() { // skip check as the London hard fork and EIP-1559 are enabled
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if baseFee != nil {
if !ok { return next(ctx, tx, simulate)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) }
}
feeAmt := ethMsg.GetFee() evmDenom := mfd.evmKeeper.GetEVMDenom(ctx)
glDec := sdk.NewDec(int64(ethMsg.GetGas())) minGasPrice := ctx.MinGasPrices().AmountOf(evmDenom)
requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec)
if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) { for _, msg := range tx.GetMsgs() {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee) ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
} if !ok {
} return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}
fee := sdk.NewDecFromBigInt(ethMsg.GetFee())
gasLimit := sdk.NewDecFromBigInt(new(big.Int).SetUint64(ethMsg.GetGas()))
requiredFee := minGasPrice.Mul(gasLimit)
if fee.LT(requiredFee) {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"insufficient fees; got: %s required: %s",
fee, requiredFee,
)
} }
} }

View File

@ -65,12 +65,15 @@ fi
echo "compiling ethermint" echo "compiling ethermint"
make build make build
# PID array declaration # PID array declaration
arr=() arr=()
init_func() { init_func() {
"$PWD"/build/ethermintd keys add $KEY"$i" --keyring-backend test --home "$DATA_DIR$i" --no-backup --algo "eth_secp256k1" "$PWD"/build/ethermintd keys add $KEY"$i" --keyring-backend test --home "$DATA_DIR$i" --no-backup --algo "eth_secp256k1"
"$PWD"/build/ethermintd init $MONIKER --chain-id $CHAINID --home "$DATA_DIR$i" "$PWD"/build/ethermintd init $MONIKER --chain-id $CHAINID --home "$DATA_DIR$i"
# Set gas limit in genesis
cat $DATA_DIR$i/config/genesis.json | jq '.consensus_params["block"]["max_gas"]="10000000"' > $DATA_DIR$i/config/tmp_genesis.json && mv $DATA_DIR$i/config/tmp_genesis.json $DATA_DIR$i/config/genesis.json
"$PWD"/build/ethermintd add-genesis-account \ "$PWD"/build/ethermintd add-genesis-account \
"$("$PWD"/build/ethermintd keys show "$KEY$i" --keyring-backend test -a --home "$DATA_DIR$i")" 1000000000000000000aphoton,1000000000000000000stake \ "$("$PWD"/build/ethermintd keys show "$KEY$i" --keyring-backend test -a --home "$DATA_DIR$i")" 1000000000000000000aphoton,1000000000000000000stake \
--keyring-backend test --home "$DATA_DIR$i" --keyring-backend test --home "$DATA_DIR$i"