-
Notifications
You must be signed in to change notification settings - Fork 697
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: bypass min fee check for custom message types (#1447)
- Loading branch information
1 parent
e8032ba
commit 0e94267
Showing
26 changed files
with
448 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.