Skip to content

Commit

Permalink
feat: bypass min fee check for custom message types (#1447)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez authored Apr 27, 2022
1 parent e8032ba commit 0e94267
Show file tree
Hide file tree
Showing 26 changed files with 448 additions and 71 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

- [#1447](https://github.com/cosmos/gaia/pull/1447) Support custom message types to bypass minimum fee checks for.
If a transaction contains only bypassed message types, the transaction will not have minimum fee
checks performed during `CheckTx`. Operators can supply these message types via the `bypass-min-fee-msg-types`
configuration in `app.toml`. Note, by default they include various IBC message types.

## [v7.0.0] - 2022-03-24

- (gaia) bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.45.1](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.45.1). See [CHANGELOG.md](https://github.com/cosmos/cosmos-sdk/blob/v0.45.1/CHANGELOG.md#v0451---2022-02-03) for details.
- (gaia) bump [ibc-go](https://github.com/cosmos/ibc-go) module to [v3.0.0](https://github.com/cosmos/ibc-go/releases/tag/v3.0.0). See [CHANGELOG.md](https://github.com/cosmos/ibc-go/blob/v3.0.0/CHANGELOG.md#v300---2022-03-15) for details.
- (gaia) add [interchain account](https://github.com/cosmos/ibc-go/tree/main/modules/apps/27-interchain-accounts) module (interhchain-account module is part of ibc-go module).
Expand All @@ -45,6 +51,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (gaia) add migration logs for upgrade process.

## [v6.0.4] - 2022-03-10

* (gaia) Bump [Liquidity](https://github.com/gravity-devs/liquidity) module to [v1.4.6](https://github.com/Gravity-Devs/liquidity/releases/tag/v1.4.6).
* (gaia) Bump [IBC](https://github.com/cosmos/ibc-go) module to [2.0.3](https://github.com/cosmos/ibc-go/releases/tag/v2.0.3).
* (gaia) [#1230](https://github.com/cosmos/gaia/pull/1230) Fix: update gRPC Web Configuration in `contrib/testnets/test_platform`.
Expand Down
36 changes: 19 additions & 17 deletions app/ante_handler.go → ante/ante.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gaia
package ante

import (
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -12,41 +12,43 @@ import (
// channel keeper.
type HandlerOptions struct {
ante.HandlerOptions
IBCkeeper *ibckeeper.Keeper

IBCkeeper *ibckeeper.Keeper
BypassMinFeeMsgTypes []string
}

func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if options.AccountKeeper == nil {
func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) {
if opts.AccountKeeper == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler")
}
if options.BankKeeper == nil {
if opts.BankKeeper == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler")
}
if options.SignModeHandler == nil {
if opts.SignModeHandler == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder")
}

var sigGasConsumer = options.SigGasConsumer
var sigGasConsumer = opts.SigGasConsumer
if sigGasConsumer == nil {
sigGasConsumer = ante.DefaultSigVerificationGasConsumer
}

anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(),
ante.NewRejectExtensionOptionsDecorator(),
ante.NewMempoolFeeDecorator(),
NewMempoolFeeDecorator(opts.BypassMinFeeMsgTypes),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper),
ante.NewValidateMemoDecorator(opts.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(opts.AccountKeeper),
ante.NewDeductFeeDecorator(opts.AccountKeeper, opts.BankKeeper, opts.FeegrantKeeper),
// SetPubKeyDecorator must be called before all signature verification decorators
ante.NewSetPubKeyDecorator(options.AccountKeeper),
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewAnteDecorator(options.IBCkeeper),
ante.NewSetPubKeyDecorator(opts.AccountKeeper),
ante.NewValidateSigCountDecorator(opts.AccountKeeper),
ante.NewSigGasConsumeDecorator(opts.AccountKeeper, sigGasConsumer),
ante.NewSigVerificationDecorator(opts.AccountKeeper, opts.SignModeHandler),
ante.NewIncrementSequenceDecorator(opts.AccountKeeper),
ibcante.NewAnteDecorator(opts.IBCkeeper),
}

return sdk.ChainAnteDecorators(anteDecorators...), nil
Expand Down
99 changes: 99 additions & 0 deletions ante/ante_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package ante_test

import (
"fmt"
"testing"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/stretchr/testify/suite"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

gaiaapp "github.com/cosmos/gaia/v7/app"
gaiahelpers "github.com/cosmos/gaia/v7/app/helpers"
)

type IntegrationTestSuite struct {
suite.Suite

app *gaiaapp.GaiaApp
anteHandler sdk.AnteHandler
ctx sdk.Context
clientCtx client.Context
txBuilder client.TxBuilder
}

func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

func (s *IntegrationTestSuite) SetupTest() {
app := gaiahelpers.Setup(s.T(), false, 1)
ctx := app.BaseApp.NewContext(false, tmproto.Header{
ChainID: fmt.Sprintf("test-chain-%s", tmrand.Str(4)),
Height: 1,
})

encodingConfig := simapp.MakeTestEncodingConfig()
encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil)
testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry)

s.app = app
s.ctx = ctx
s.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig)
}

func (s *IntegrationTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) {
var sigsV2 []signing.SignatureV2
for i, priv := range privs {
sigV2 := signing.SignatureV2{
PubKey: priv.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: s.clientCtx.TxConfig.SignModeHandler().DefaultMode(),
Signature: nil,
},
Sequence: accSeqs[i],
}

sigsV2 = append(sigsV2, sigV2)
}

if err := s.txBuilder.SetSignatures(sigsV2...); err != nil {
return nil, err
}

sigsV2 = []signing.SignatureV2{}
for i, priv := range privs {
signerData := xauthsigning.SignerData{
ChainID: chainID,
AccountNumber: accNums[i],
Sequence: accSeqs[i],
}
sigV2, err := tx.SignWithPrivKey(
s.clientCtx.TxConfig.SignModeHandler().DefaultMode(),
signerData,
s.txBuilder,
priv,
s.clientCtx.TxConfig,
accSeqs[i],
)
if err != nil {
return nil, err
}

sigsV2 = append(sigsV2, sigV2)
}

if err := s.txBuilder.SetSignatures(sigsV2...); err != nil {
return nil, err
}

return s.txBuilder.GetTx(), nil
}
75 changes: 75 additions & 0 deletions ante/fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ante

import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
tmstrings "github.com/tendermint/tendermint/libs/strings"
)

const maxBypassMinFeeMsgGasUsage = uint64(200_000)

// MempoolFeeDecorator will check if the transaction's 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 MempoolFeeDecorator struct {
BypassMinFeeMsgTypes []string
}

func NewMempoolFeeDecorator(bypassMsgTypes []string) MempoolFeeDecorator {
return MempoolFeeDecorator{
BypassMinFeeMsgTypes: bypassMsgTypes,
}
}

func (mfd MempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}

feeCoins := feeTx.GetFee()
gas := feeTx.GetGas()
msgs := feeTx.GetMsgs()

// Only check for minimum fees if the execution mode is CheckTx and the tx does
// not contain operator configured bypass messages. If the tx does contain
// operator configured bypass messages only, it's total gas must be less than
// or equal to a constant, otherwise minimum fees are checked to prevent spam.
if ctx.IsCheckTx() && !simulate && !(mfd.bypassMinFeeMsgs(msgs) && gas <= uint64(len(msgs))*maxBypassMinFeeMsgGasUsage) {
minGasPrices := ctx.MinGasPrices()
if !minGasPrices.IsZero() {
requiredFees := make(sdk.Coins, len(minGasPrices))

// Determine the required fees by multiplying each required minimum gas
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
glDec := sdk.NewDec(int64(gas))
for i, gp := range minGasPrices {
fee := gp.Amount.Mul(glDec)
requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}

if !feeCoins.IsAnyGTE(requiredFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
}
}
}

return next(ctx, tx, simulate)
}

func (mfd MempoolFeeDecorator) bypassMinFeeMsgs(msgs []sdk.Msg) bool {
for _, msg := range msgs {
if tmstrings.StringInSlice(sdk.MsgTypeURL(msg), mfd.BypassMinFeeMsgTypes) {
continue
}

return false
}

return true
}
59 changes: 59 additions & 0 deletions ante/fee_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package ante_test

import (
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
ibcclienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
ibcchanneltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"

"github.com/cosmos/gaia/v7/ante"
)

func (s *IntegrationTestSuite) TestMempoolFeeDecorator() {
s.SetupTest()
s.txBuilder = s.clientCtx.TxConfig.NewTxBuilder()

mfd := ante.NewMempoolFeeDecorator([]string{
sdk.MsgTypeURL(&ibcchanneltypes.MsgRecvPacket{}),
sdk.MsgTypeURL(&ibcchanneltypes.MsgAcknowledgement{}),
sdk.MsgTypeURL(&ibcclienttypes.MsgUpdateClient{}),
})
antehandler := sdk.ChainAnteDecorators(mfd)
priv1, _, addr1 := testdata.KeyTestPubAddr()

msg := testdata.NewTestMsg(addr1)
feeAmount := testdata.NewTestFeeAmount()
gasLimit := testdata.NewTestGasLimit()
s.Require().NoError(s.txBuilder.SetMsgs(msg))
s.txBuilder.SetFeeAmount(feeAmount)
s.txBuilder.SetGasLimit(gasLimit)

privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
tx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID())
s.Require().NoError(err)

// Set high gas price so standard test fee fails
feeAmt := sdk.NewDecCoinFromDec("uatom", sdk.NewDec(200).Quo(sdk.NewDec(100000)))
minGasPrice := []sdk.DecCoin{feeAmt}
s.ctx = s.ctx.WithMinGasPrices(minGasPrice).WithIsCheckTx(true)

// antehandler errors with insufficient fees
_, err = antehandler(s.ctx, tx, false)
s.Require().Error(err, "expected error due to low fee")

// ensure no fees for certain IBC msgs
s.Require().NoError(s.txBuilder.SetMsgs(
ibcchanneltypes.NewMsgRecvPacket(ibcchanneltypes.Packet{}, nil, ibcclienttypes.Height{}, ""),
))

oracleTx, err := s.CreateTestTx(privs, accNums, accSeqs, s.ctx.ChainID())
_, err = antehandler(s.ctx, oracleTx, false)
s.Require().NoError(err, "expected min fee bypass for IBC messages")

s.ctx = s.ctx.WithIsCheckTx(false)

// antehandler should not error since we do not check min gas prices in DeliverTx
_, err = antehandler(s.ctx, tx, false)
s.Require().NoError(err, "unexpected error during DeliverTx")
}
11 changes: 7 additions & 4 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@ import (
tmos "github.com/tendermint/tendermint/libs/os"
dbm "github.com/tendermint/tm-db"

gaiaappparams "github.com/cosmos/gaia/v7/app/params"
"github.com/strangelove-ventures/packet-forward-middleware/v2/router"
routerkeeper "github.com/strangelove-ventures/packet-forward-middleware/v2/router/keeper"
routertypes "github.com/strangelove-ventures/packet-forward-middleware/v2/router/types"

gaiaante "github.com/cosmos/gaia/v7/ante"
gaiaappparams "github.com/cosmos/gaia/v7/app/params"

// unnamed import of statik for swagger UI support
_ "github.com/cosmos/cosmos-sdk/client/docs/statik"
)
Expand Down Expand Up @@ -602,16 +604,17 @@ func NewGaiaApp(
app.MountTransientStores(tkeys)
app.MountMemoryStores(memKeys)

anteHandler, err := NewAnteHandler(
HandlerOptions{
anteHandler, err := gaiaante.NewAnteHandler(
gaiaante.HandlerOptions{
HandlerOptions: ante.HandlerOptions{
AccountKeeper: app.AccountKeeper,
BankKeeper: app.BankKeeper,
FeegrantKeeper: app.FeeGrantKeeper,
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
},
IBCkeeper: app.IBCKeeper,
IBCkeeper: app.IBCKeeper,
BypassMinFeeMsgTypes: cast.ToStringSlice(appOpts.Get(gaiaappparams.BypassMinFeeMsgTypesKey)),
},
)
if err != nil {
Expand Down
Loading

0 comments on commit 0e94267

Please sign in to comment.