Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reformat global fee #2229

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) {
// so that a transaction that contains only message types that can
// bypass the minimum fee can be accepted with a zero fee.
// For details, see gaiafeeante.NewFeeDecorator()
var maxBypassMinFeeMsgGasUsage uint64 = 200_000
var maxTotalBypassMinFeeMsgGasUsage uint64 = 1_000_000

anteDecorators := []sdk.AnteDecorator{
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
Expand All @@ -59,7 +59,7 @@ func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) {
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(opts.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(opts.AccountKeeper),
gaiafeeante.NewFeeDecorator(opts.BypassMinFeeMsgTypes, opts.GlobalFeeSubspace, opts.StakingSubspace, maxBypassMinFeeMsgGasUsage),
gaiafeeante.NewFeeDecorator(opts.BypassMinFeeMsgTypes, opts.GlobalFeeSubspace, opts.StakingSubspace, maxTotalBypassMinFeeMsgGasUsage),

ante.NewDeductFeeDecorator(opts.AccountKeeper, opts.BankKeeper, opts.FeegrantKeeper),
ante.NewSetPubKeyDecorator(opts.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
Expand Down
2 changes: 2 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ func GetDefaultBypassFeeMessages() []string {
sdk.MsgTypeURL(&ibcchanneltypes.MsgRecvPacket{}),
sdk.MsgTypeURL(&ibcchanneltypes.MsgAcknowledgement{}),
sdk.MsgTypeURL(&ibcclienttypes.MsgUpdateClient{}),
sdk.MsgTypeURL(&ibcchanneltypes.MsgTimeout{}),
sdk.MsgTypeURL(&ibcchanneltypes.MsgTimeoutOnClose{}),
}
}

Expand Down
15 changes: 8 additions & 7 deletions docs/modules/globalfee.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ The `minimum-gas-prices` config parameter allows node operators to impose additi
Bypass messages are messages that are exempt from paying fees. The above global fees and `minimum-gas-prices` checks do not apply for transactions that satisfy the following conditions:

- Contains only bypass message types, i.e., bypass transactions.
- The total gas used is less than or equal to `len(messages) * MaxBypassMinFeeMsgGasUsage`. Note: the current `MaxBypassMinFeeMsgGasUsage` is set to `200,000`.
- The total gas used is less than or equal to `MaxTotalBypassMinFeeMsgGasUsage`. Note: the current `MaxTotalBypassMinFeeMsgGasUsage` is set to `1,000,000`.
- In case of non-zero transaction fees, the denom has to be a subset of denoms defined in the global fees list.

Node operators can configure `bypass-min-fee-msg-types` in `config/app.toml`.

Nodes created using Gaiad `v7.0.2` or later use `["/ibc.core.channel.v1.MsgRecvPacket", "/ibc.core.channel.v1.MsgAcknowledgement","/ibc.applications.transfer.v1.MsgTransfer"]` as defaults. Nodes with `bypass-min-fee-msg-types = []` or missing this field in `app.toml` also use default bypass message types.

Nodes created using Gaiad `v7.0.1` or earlier do not have `bypass-min-fee-msg-types` configured in `config/app.toml` - they are also using default values. The `bypass-min-fee-msg-types` config option can be added to `config/app.toml` before the `[telemetry]` field.
- Nodes created using Gaiad `v7.0.2` or later use `["/ibc.core.channel.v1.MsgRecvPacket", "/ibc.core.channel.v1.MsgAcknowledgement","/ibc.applications.transfer.v1.MsgTransfer"]` as defaults.
- Nodes created using Gaiad `v9.0.1` or later use `["/ibc.core.channel.v1.MsgRecvPacket", "/ibc.core.channel.v1.MsgAcknowledgement","/ibc.applications.transfer.v1.MsgTransfer", "/ibc.core.channel.v1.MsgTimeout", "/ibc.core.channel.v1.MsgTimeoutOnClose"]` as defaults.
- Node Nodes with `bypass-min-fee-msg-types = []` or missing this field in `app.toml` also use default bypass message types.
- Nodes created using Gaiad `v7.0.1` and `v7.0.0` do not have `bypass-min-fee-msg-types` configured in `config/app.toml` - they are also using same default values as in `v7.0.2`. The `bypass-min-fee-msg-types` config option can be added to `config/app.toml` before the `[telemetry]` field.

An example of `bypass-min-fee-msg-types` in `app.toml`:

Expand All @@ -72,7 +73,7 @@ An example of `bypass-min-fee-msg-types` in `app.toml`:
#
# Example:
# ["/ibc.core.channel.v1.MsgRecvPacket", "/ibc.core.channel.v1.MsgAcknowledgement", ...]
bypass-min-fee-msg-types = ["/ibc.core.channel.v1.MsgRecvPacket", "/ibc.core.channel.v1.MsgAcknowledgement","/ibc.applications.transfer.v1.MsgTransfer"]
bypass-min-fee-msg-types = ["/ibc.core.channel.v1.MsgRecvPacket", "/ibc.core.channel.v1.MsgAcknowledgement","/ibc.applications.transfer.v1.MsgTransfer", "/ibc.core.channel.v1.MsgTimeout", "/ibc.core.channel.v1.MsgTimeoutOnClose"]
```


Expand Down Expand Up @@ -200,9 +201,9 @@ Note that the required amount of `uatom` in globalfee is overwritten by the amou

### Case 7

**Setting:** globalfee=[0.1uatom], minimum-gas-prices=0.2uatom,1stake, gas=200000, bypass-min-fee-msg-types = ["/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward"]
**Setting:** globalfee=[0.1uatom], minimum-gas-prices=[0.2uatom, 1stake], gas=200000, bypass-min-fee-msg-types = ["/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward"]

Note that the required amount of `uatom` in globalfee is overwritten by the amount in minimum-gas-prices.
Note that the required amount of `uatom` in globalfee is overwritten by the amount in minimum-gas-prices.
Also, the `1stake` in minimum-gas-prices is ignored.

- msg withdraw-all-rewards with paidfee="", `pass`
Expand Down
50 changes: 46 additions & 4 deletions x/globalfee/ante/antetest/fee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,8 +468,7 @@ func (s *IntegrationTestSuite) TestGlobalFeeMinimumGasFeeAnteHandler() {
txCheck: true,
expErr: false,
},
// test bypass msg
"msg type ibc, zero fee in globalfee denom": {
"bypass msg type: ibc.core.channel.v1.MsgRecvPacket": {
minGasPrice: minGasPrice,
globalFeeParams: globalfeeParamsLow,
gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt())),
Expand All @@ -479,6 +478,46 @@ func (s *IntegrationTestSuite) TestGlobalFeeMinimumGasFeeAnteHandler() {
txCheck: true,
expErr: false,
},
"bypass msg type: ibc.core.channel.v1.MsgTimeout": {
minGasPrice: minGasPrice,
globalFeeParams: globalfeeParamsLow,
gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt())),
gasLimit: newTestGasLimit(),
txMsg: ibcchanneltypes.NewMsgTimeout(
ibcchanneltypes.Packet{}, 1, nil, ibcclienttypes.Height{}, ""),
txCheck: true,
expErr: false,
},
"bypass msg type: ibc.core.channel.v1.MsgTimeoutOnClose": {
minGasPrice: minGasPrice,
globalFeeParams: globalfeeParamsLow,
gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt())),
gasLimit: newTestGasLimit(),
txMsg: ibcchanneltypes.NewMsgTimeout(
ibcchanneltypes.Packet{}, 2, nil, ibcclienttypes.Height{}, ""),
txCheck: true,
expErr: false,
},
"bypass msg gas usage exceeds maxTotalBypassMinFeeMsgGasUsage": {
minGasPrice: minGasPrice,
globalFeeParams: globalfeeParamsLow,
gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt())),
gasLimit: 2 * newTestMaxTotalBypassMinFeeMsgGasUsage(),
txMsg: ibcchanneltypes.NewMsgTimeout(
ibcchanneltypes.Packet{}, 2, nil, ibcclienttypes.Height{}, ""),
txCheck: true,
expErr: true,
},
"bypass msg gas usage equals to maxTotalBypassMinFeeMsgGasUsage": {
minGasPrice: minGasPrice,
globalFeeParams: globalfeeParamsLow,
gasPrice: sdk.NewCoins(sdk.NewCoin("uatom", sdk.ZeroInt())),
gasLimit: newTestMaxTotalBypassMinFeeMsgGasUsage(),
txMsg: ibcchanneltypes.NewMsgTimeout(
ibcchanneltypes.Packet{}, 3, nil, ibcclienttypes.Height{}, ""),
txCheck: true,
expErr: false,
},
"msg type ibc, zero fee not in globalfee denom": {
minGasPrice: minGasPrice,
globalFeeParams: globalfeeParamsLow,
Expand Down Expand Up @@ -573,9 +612,8 @@ func (s *IntegrationTestSuite) TestGlobalFeeMinimumGasFeeAnteHandler() {
stakingParam.BondDenom = "uatom"
stakingSubspace := s.SetupTestStakingSubspace(stakingParam)
// setup antehandler
mfd := gaiafeeante.NewFeeDecorator(gaiaapp.GetDefaultBypassFeeMessages(), globalfeeSubspace, stakingSubspace, newTestGasLimit())
mfd := gaiafeeante.NewFeeDecorator(gaiaapp.GetDefaultBypassFeeMessages(), globalfeeSubspace, stakingSubspace, newTestMaxTotalBypassMinFeeMsgGasUsage())
antehandler := sdk.ChainAnteDecorators(mfd)

s.Require().NoError(s.txBuilder.SetMsgs(testCase.txMsg))
s.txBuilder.SetFeeAmount(testCase.gasPrice)
s.txBuilder.SetGasLimit(testCase.gasLimit)
Expand All @@ -597,3 +635,7 @@ func (s *IntegrationTestSuite) TestGlobalFeeMinimumGasFeeAnteHandler() {
func newTestGasLimit() uint64 {
return 200000
}

func newTestMaxTotalBypassMinFeeMsgGasUsage() uint64 {
return 1000000
}
104 changes: 56 additions & 48 deletions x/globalfee/ante/fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/cosmos/gaia/v9/x/globalfee"
)

// FeeWithBypassDecorator will check if the transaction's fee is at least as large
// FeeWithBypassDecorator checks if the transaction's fee is at least as large
// as the local validator's minimum gasFee (defined in validator config) and global fee, and the fee denom should be in the global fees' denoms.
//
// If fee is too low, decorator returns error and tx is rejected from mempool.
Expand All @@ -26,13 +26,13 @@ import (
var _ sdk.AnteDecorator = FeeDecorator{}

type FeeDecorator struct {
BypassMinFeeMsgTypes []string
GlobalMinFee globalfee.ParamSource
StakingSubspace paramtypes.Subspace
MaxBypassMinFeeMsgGasUsage uint64
BypassMinFeeMsgTypes []string
GlobalMinFee globalfee.ParamSource
StakingSubspace paramtypes.Subspace
MaxTotalBypassMinFeeMsgGasUsage uint64
}

func NewFeeDecorator(bypassMsgTypes []string, globalfeeSubspace, stakingSubspace paramtypes.Subspace, maxBypassMinFeeMsgGasUsage uint64) FeeDecorator {
func NewFeeDecorator(bypassMsgTypes []string, globalfeeSubspace, stakingSubspace paramtypes.Subspace, maxTotalBypassMinFeeMsgGasUsage uint64) FeeDecorator {
if !globalfeeSubspace.HasKeyTable() {
panic("global fee paramspace was not set up via module")
}
Expand All @@ -42,66 +42,49 @@ func NewFeeDecorator(bypassMsgTypes []string, globalfeeSubspace, stakingSubspace
}

return FeeDecorator{
BypassMinFeeMsgTypes: bypassMsgTypes,
GlobalMinFee: globalfeeSubspace,
StakingSubspace: stakingSubspace,
MaxBypassMinFeeMsgGasUsage: maxBypassMinFeeMsgGasUsage,
BypassMinFeeMsgTypes: bypassMsgTypes,
GlobalMinFee: globalfeeSubspace,
StakingSubspace: stakingSubspace,
MaxTotalBypassMinFeeMsgGasUsage: maxTotalBypassMinFeeMsgGasUsage,
}
}

// AnteHandle implements the AnteDecorator interface
func (mfd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
// please note: after parsing feeflag, the zero fees are removed already
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}

// Only check for minimum fees and global fee if the execution mode is CheckTx
if !ctx.IsCheckTx() || simulate {
return next(ctx, tx, simulate)
}

// sort fee tx's coins
feeCoins := feeTx.GetFee().Sort()
gas := feeTx.GetGas()
msgs := feeTx.GetMsgs()

// Accept zero fee transactions only if both of the following statements are true:
// - the tx contains only message types that can bypass the minimum fee,
// see BypassMinFeeMsgTypes;
// - the total gas limit per message does not exceed MaxBypassMinFeeMsgGasUsage,
// i.e., totalGas <= len(msgs) * MaxBypassMinFeeMsgGasUsage
// Otherwise, minimum fees and global fees are checked to prevent spam.
containsOnlyBypassMinFeeMsgs := mfd.bypassMinFeeMsgs(msgs)
doesNotExceedMaxGasUsage := gas <= uint64(len(msgs))*mfd.MaxBypassMinFeeMsgGasUsage
allowedToBypassMinFee := containsOnlyBypassMinFeeMsgs && doesNotExceedMaxGasUsage

var allFees sdk.Coins
// Get required Global Fee and min gas price
requiredGlobalFees, err := mfd.getGlobalFee(ctx, feeTx)
if err != nil {
panic(err)
}
requiredFees := getMinGasPrice(ctx, feeTx)

// Only check for minimum fees and global fee if the execution mode is CheckTx
if !ctx.IsCheckTx() || simulate {
return next(ctx, tx, simulate)
}

if !allowedToBypassMinFee {
// Either the transaction contains at least on message of a type
// that cannot bypass the minimum fee or the total gas limit exceeds
// the imposed threshold. As a result, check that the fees are in
// expected denominations and the amounts are greater or equal than
// the expected amounts.

allFees = CombinedFeeRequirement(requiredGlobalFees, requiredFees)
// Accept zero fee transactions only if both of the following statements are true:
//
// - the tx contains only message types that can bypass the minimum fee,
// see BypassMinFeeMsgTypes;
// - the total gas limit per message does not exceed MaxTotalBypassMinFeeMsgGasUsage,
// i.e., totalGas <= MaxTotalBypassMinFeeMsgGasUsage
// Otherwise, minimum fees and global fees are checked to prevent spam.
doesNotExceedMaxGasUsage := gas <= mfd.MaxTotalBypassMinFeeMsgGasUsage
allowedToBypassMinFee := mfd.containsOnlyBypassMinFeeMsgs(msgs) && doesNotExceedMaxGasUsage

// Check that the fees are in expected denominations. Note that a zero fee
// is accepted if the global fee has an entry with a zero amount, e.g., 0uatoms.
if !DenomsSubsetOfIncludingZero(feeCoins, allFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee is not a subset of required fees; got %s, required: %s", feeCoins, allFees)
}
// Check that the amounts of the fees are greater or equal than
// the expected amounts, i.e., at least one feeCoin amount must
// be greater or equal to one of the combined required fees.
if !IsAnyGTEIncludingZero(feeCoins, allFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, allFees)
}
} else {
if allowedToBypassMinFee {
// Transactions with zero fees are accepted
if len(feeCoins) == 0 {
return next(ctx, tx, simulate)
Expand All @@ -113,10 +96,32 @@ func (mfd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, ne
}
}

// Either the transaction contains at least on message of a type
// that cannot bypass the minimum fee or the total gas limit exceeds
// the imposed threshold. As a result, check that the fees are in
// expected denominations and the amounts are greater or equal than
// the expected amounts.

allFees := CombinedFeeRequirement(requiredGlobalFees, requiredFees)

// Check that the fees are in expected denominations. Note that a zero fee
// is accepted if the global fee has an entry with a zero amount, e.g., 0uatoms.
if !DenomsSubsetOfIncludingZero(feeCoins, allFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee is not a subset of required fees; got %s, required: %s", feeCoins, allFees)
}
// Check that the amounts of the fees are greater or equal than
// the expected amounts, i.e., at least one feeCoin amount must
// be greater or equal to one of the combined required fees.
if !IsAnyGTEIncludingZero(feeCoins, allFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, allFees)
}

return next(ctx, tx, simulate)
}

// ParamStoreKeyMinGasPrices type require coins sorted. getGlobalFee will also return sorted coins (might return 0denom if globalMinGasPrice is 0)
// getGlobalFee returns the global fees for a given fee tx's gas (might also returns 0denom if globalMinGasPrice is 0)
// sorted in ascending order.
// Note that ParamStoreKeyMinGasPrices type requires coins sorted.
func (mfd FeeDecorator) getGlobalFee(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coins, error) {
var (
globalMinGasPrices sdk.DecCoins
Expand All @@ -129,6 +134,9 @@ func (mfd FeeDecorator) getGlobalFee(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coin
// global fee is empty set, set global fee to 0uatom
if len(globalMinGasPrices) == 0 {
globalMinGasPrices, err = mfd.DefaultZeroGlobalFee(ctx)
if err != nil {
return sdk.Coins{}, err
}
}
requiredGlobalFees := make(sdk.Coins, len(globalMinGasPrices))
// Determine the required fees by multiplying each required minimum gas
Expand All @@ -139,7 +147,7 @@ func (mfd FeeDecorator) getGlobalFee(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coin
requiredGlobalFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}

return requiredGlobalFees.Sort(), err
return requiredGlobalFees.Sort(), nil
}

func (mfd FeeDecorator) DefaultZeroGlobalFee(ctx sdk.Context) ([]sdk.DecCoin, error) {
Expand Down
Loading