Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

imp(ante): refactor AnteHandler #1479

Merged
merged 13 commits into from
Nov 25, 2022
296 changes: 6 additions & 290 deletions app/ante/eth.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
package ante

import (
"errors"
"math"
"math/big"
"strconv"

sdkmath "cosmossdk.io/math"

storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"

ethermint "github.com/evmos/ethermint/types"
evmkeeper "github.com/evmos/ethermint/x/evm/keeper"
Expand All @@ -22,60 +18,6 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
)

// EthSigVerificationDecorator validates an ethereum signatures
type EthSigVerificationDecorator struct {
evmKeeper EVMKeeper
}

// NewEthSigVerificationDecorator creates a new EthSigVerificationDecorator
func NewEthSigVerificationDecorator(ek EVMKeeper) EthSigVerificationDecorator {
return EthSigVerificationDecorator{
evmKeeper: ek,
}
}

// AnteHandle validates checks that the registered chain id is the same as the one on the message, and
// that the signer address matches the one defined on the message.
// It's not skipped for RecheckTx, because it set `From` address which is critical from other ante handler to work.
// Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user
// 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()
chainCfg := esvd.evmKeeper.GetChainConfig(ctx)
ethCfg := chainCfg.EthereumConfig(chainID)
blockNum := big.NewInt(ctx.BlockHeight())
signer := ethtypes.MakeSigner(ethCfg, blockNum)

for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
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 !allowUnprotectedTxs && !ethTx.Protected() {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrNotSupported,
"rejected unprotected Ethereum txs. Please EIP155 sign your transaction to protect it against replay-attacks")
}

sender, err := signer.Sender(ethTx)
if err != nil {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrorInvalidSigner,
"couldn't retrieve sender address from the ethereum transaction: %s",
err.Error(),
)
}

// set up the sender to the transaction field if not already
msgEthTx.From = sender.Hex()
}

return next(ctx, tx, simulate)
}

// EthAccountVerificationDecorator validates an account balance checks
type EthAccountVerificationDecorator struct {
ak evmtypes.AccountKeeper
Expand Down Expand Up @@ -195,6 +137,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula

// Use the lowest priority of all the messages as the final one.
minPriority := int64(math.MaxInt64)
baseFee := egcd.evmKeeper.GetBaseFee(ctx, ethCfg)

for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
Expand All @@ -219,11 +162,13 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
}

evmDenom := egcd.evmKeeper.GetEVMDenom(ctx)
fees, priority, err := egcd.evmKeeper.DeductTxCostsFromUserBalance(

fees, err := egcd.evmKeeper.DeductTxCostsFromUserBalance(
ctx,
*msgEthTx,
txData,
evmDenom,
baseFee,
homestead,
istanbul,
london,
Expand All @@ -239,6 +184,8 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
),
)

priority := evmtypes.GetTxPriority(txData, baseFee)

if priority < minPriority {
minPriority = priority
}
Expand Down Expand Up @@ -408,234 +355,3 @@ func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx s

return next(ctx, tx, simulate)
}

// EthValidateBasicDecorator is adapted from ValidateBasicDecorator from cosmos-sdk, it ignores ErrNoSignatures
type EthValidateBasicDecorator struct {
evmKeeper EVMKeeper
}

// NewEthValidateBasicDecorator creates a new EthValidateBasicDecorator
func NewEthValidateBasicDecorator(ek EVMKeeper) EthValidateBasicDecorator {
return EthValidateBasicDecorator{
evmKeeper: ek,
}
}

// AnteHandle handles basic validation of tx
func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// no need to validate basic on recheck tx, call next antehandler
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
}

err := tx.ValidateBasic()
// ErrNoSignatures is fine with eth tx
if err != nil && !errors.Is(err, sdkerrors.ErrNoSignatures) {
return ctx, sdkerrors.Wrap(err, "tx basic validation failed")
}

// For eth type cosmos tx, some fields should be veified as zero values,
// since we will only verify the signature against the hash of the MsgEthereumTx.Data
wrapperTx, ok := tx.(protoTxProvider)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid tx type %T, didn't implement interface protoTxProvider", tx)
}

protoTx := wrapperTx.GetProtoTx()
body := protoTx.Body
if body.Memo != "" || body.TimeoutHeight != uint64(0) || len(body.NonCriticalExtensionOptions) > 0 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest,
"for eth tx body Memo TimeoutHeight NonCriticalExtensionOptions should be empty")
}

if len(body.ExtensionOptions) != 1 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1")
}

txFee := sdk.Coins{}
txGasLimit := uint64(0)

chainCfg := vbd.evmKeeper.GetChainConfig(ctx)
chainID := vbd.evmKeeper.ChainID()
ethCfg := chainCfg.EthereumConfig(chainID)
baseFee := vbd.evmKeeper.GetBaseFee(ctx, ethCfg)
enableCreate := vbd.evmKeeper.GetEnableCreate(ctx)
enableCall := vbd.evmKeeper.GetEnableCall(ctx)
evmDenom := vbd.evmKeeper.GetEVMDenom(ctx)

for _, msg := range protoTx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}

// Validate `From` field
if msgEthTx.From != "" {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid From %s, expect empty string", msgEthTx.From)
}

txGasLimit += msgEthTx.GetGas()

txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
if err != nil {
return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data")
}

// return error if contract creation or call are disabled through governance
if !enableCreate && txData.GetTo() == nil {
return ctx, sdkerrors.Wrap(evmtypes.ErrCreateDisabled, "failed to create new contract")
} else if !enableCall && txData.GetTo() != nil {
return ctx, sdkerrors.Wrap(evmtypes.ErrCallDisabled, "failed to call contract")
}

if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType {
return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported")
}

txFee = txFee.Add(sdk.NewCoin(evmDenom, sdkmath.NewIntFromBigInt(txData.Fee())))
}

authInfo := protoTx.AuthInfo
if len(authInfo.SignerInfos) > 0 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty")
}

if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty")
}

if !authInfo.Fee.Amount.IsEqual(txFee) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee)
}

if authInfo.Fee.GasLimit != txGasLimit {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit)
}

sigs := protoTx.Signatures
if len(sigs) > 0 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx Signatures should be empty")
}

return next(ctx, tx, simulate)
}

// EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption
// by setting the gas meter to infinite
type EthSetupContextDecorator struct {
evmKeeper EVMKeeper
}

func NewEthSetUpContextDecorator(evmKeeper EVMKeeper) EthSetupContextDecorator {
return EthSetupContextDecorator{
evmKeeper: evmKeeper,
}
}

func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
// all transactions must implement GasTx
_, ok := tx.(authante.GasTx)
if !ok {
return newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx")
}

// We need to setup an empty gas config so that the gas is consistent with Ethereum.
newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()).
WithKVGasConfig(storetypes.GasConfig{}).
WithTransientKVGasConfig(storetypes.GasConfig{})

// Reset transient gas used to prepare the execution of current cosmos tx.
// Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs.
esc.evmKeeper.ResetTransientGasUsed(ctx)
return next(newCtx, tx, simulate)
}

// EthMempoolFeeDecorator will check if the transaction's effective fee is at least as large
// as the local validator's minimum gasFee (defined in validator config).
// If fee is too low, decorator returns error and tx is rejected from mempool.
// Note this only applies when ctx.CheckTx = true
// If fee is high enough or not CheckTx, then call next AnteHandler
// CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator
type EthMempoolFeeDecorator struct {
evmKeeper EVMKeeper
}

func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator {
return EthMempoolFeeDecorator{
evmKeeper: ek,
}
}

// AnteHandle ensures that the provided fees meet a minimum threshold for the validator.
// This check only for local mempool purposes, and thus it is only run on (Re)CheckTx.
// The logic is also skipped if the London hard fork and EIP-1559 are enabled.
func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if !ctx.IsCheckTx() || simulate {
return next(ctx, tx, simulate)
}
chainCfg := mfd.evmKeeper.GetChainConfig(ctx)
ethCfg := chainCfg.EthereumConfig(mfd.evmKeeper.ChainID())

baseFee := mfd.evmKeeper.GetBaseFee(ctx, ethCfg)
// skip check as the London hard fork and EIP-1559 are enabled
if baseFee != nil {
return next(ctx, tx, simulate)
}

evmDenom := mfd.evmKeeper.GetEVMDenom(ctx)
minGasPrice := ctx.MinGasPrices().AmountOf(evmDenom)

for _, msg := range tx.GetMsgs() {
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,
)
}
}

return next(ctx, tx, simulate)
}

// EthEmitEventDecorator emit events in ante handler in case of tx execution failed (out of block gas limit).
type EthEmitEventDecorator struct {
evmKeeper EVMKeeper
}

// NewEthEmitEventDecorator creates a new EthEmitEventDecorator
func NewEthEmitEventDecorator(evmKeeper EVMKeeper) EthEmitEventDecorator {
return EthEmitEventDecorator{evmKeeper}
}

// AnteHandle emits some basic events for the eth messages
func (eeed EthEmitEventDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
// After eth tx passed ante handler, the fee is deducted and nonce increased, it shouldn't be ignored by json-rpc,
// we need to emit some basic events at the very end of ante handler to be indexed by tendermint.
txIndex := eeed.evmKeeper.GetTxIndexTransient(ctx)
for i, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}

// emit ethereum tx hash as event, should be indexed by tm tx indexer for query purpose.
// it's emitted in ante handler so we can query failed transaction (out of block gas limit).
ctx.EventManager().EmitEvent(sdk.NewEvent(
evmtypes.EventTypeEthereumTx,
sdk.NewAttribute(evmtypes.AttributeKeyEthereumTxHash, msgEthTx.Hash),
sdk.NewAttribute(evmtypes.AttributeKeyTxIndex, strconv.FormatUint(txIndex+uint64(i), 10)),
))
}

return next(ctx, tx, simulate)
}
4 changes: 2 additions & 2 deletions app/ante/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ type EVMKeeper interface {

NewEVM(ctx sdk.Context, msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) evm.EVM
DeductTxCostsFromUserBalance(
ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool,
) (fees sdk.Coins, priority int64, err error)
ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, baseFee *big.Int, homestead, istanbul, london bool,
) (fees sdk.Coins, err error)
GetBalance(ctx sdk.Context, addr common.Address) *big.Int
ResetTransientGasUsed(ctx sdk.Context)
GetTxIndexTransient(ctx sdk.Context) uint64
Expand Down
Loading