-
Notifications
You must be signed in to change notification settings - Fork 717
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
Reformat global fee #2229
Changes from 2 commits
b26588a
9ace1a4
34459c9
ed407c0
3654bbf
d9f5e41
7be1e72
fdc666b
8477af7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,9 +10,10 @@ import ( | |
"github.com/cosmos/gaia/v9/x/globalfee/types" | ||
|
||
"github.com/cosmos/gaia/v9/x/globalfee" | ||
tmstrings "github.com/tendermint/tendermint/libs/strings" | ||
) | ||
|
||
// 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. | ||
|
@@ -49,59 +50,43 @@ func NewFeeDecorator(bypassMsgTypes []string, globalfeeSubspace, stakingSubspace | |
} | ||
} | ||
|
||
// 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() | ||
|
||
// Get required Global Fee and min gas price | ||
requiredGlobalFees, err := mfd.getGlobalFee(ctx, feeTx) | ||
if err != nil { | ||
panic(err) | ||
} | ||
requiredFees := getMinGasPrice(ctx, feeTx) | ||
|
||
// 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 | ||
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. | ||
allowedToBypassMinFee := mfd.containsOnlyBypassMinFeeMsgs(msgs) && doesNotExceedMaxGasUsage | ||
|
||
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) | ||
} | ||
} else { | ||
if allowedToBypassMinFee { | ||
// Transactions with zero fees are accepted | ||
if len(feeCoins) == 0 { | ||
return next(ctx, tx, simulate) | ||
|
@@ -113,10 +98,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 | ||
|
@@ -129,6 +136,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 | ||
|
@@ -139,7 +149,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) { | ||
|
@@ -159,3 +169,16 @@ func (mfd FeeDecorator) getBondDenom(ctx sdk.Context) string { | |
|
||
return bondDenom | ||
} | ||
|
||
// containsOnlyBypassMinFeeMsgs returns true if all the given msgs type are listed | ||
// in the BypassMinFeeMsgTypes of the FeeDecorator. | ||
func (mfd FeeDecorator) containsOnlyBypassMinFeeMsgs(msgs []sdk.Msg) bool { | ||
for _, msg := range msgs { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if tmstrings.StringInSlice(sdk.MsgTypeURL(msg), mfd.BypassMinFeeMsgTypes) { | ||
continue | ||
} | ||
return false | ||
} | ||
|
||
return true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,7 +58,11 @@ func (a AppModuleBasic) RegisterRESTRoutes(context client.Context, router *mux.R | |
} | ||
|
||
func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { | ||
_ = types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) | ||
err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) | ||
if err != nil { | ||
// same behavior as in cosmos-sdk | ||
panic(err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch! |
||
} | ||
} | ||
|
||
func (a AppModuleBasic) GetTxCmd() *cobra.Command { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,53 +45,37 @@ func validateMinimumGasPrices(i interface{}) error { | |
return dec.Validate() | ||
} | ||
|
||
// Validate checks that the DecCoins are sorted, have nonnegtive amount, with a valid and unique | ||
// denomination (i.e no duplicates). Otherwise, it returns an error. | ||
type DecCoins sdk.DecCoins | ||
|
||
// Validate checks that the DecCoins are sorted, have nonnegtive amount, with a valid and unique | ||
// denomination (i.e no duplicates). Otherwise, it returns an error. | ||
func (coins DecCoins) Validate() error { | ||
switch len(coins) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this |
||
case 0: | ||
if len(coins) == 0 { | ||
return nil | ||
} | ||
|
||
case 1: | ||
// match the denom reg expr | ||
if err := sdk.ValidateDenom(coins[0].Denom); err != nil { | ||
return err | ||
} | ||
if coins[0].IsNegative() { | ||
return fmt.Errorf("coin %s amount is negtive", coins[0]) | ||
lowDenom := "" | ||
seenDenoms := make(map[string]bool) | ||
|
||
for _, coin := range coins { | ||
if seenDenoms[coin.Denom] { | ||
return fmt.Errorf("duplicate denomination %s", coin.Denom) | ||
} | ||
return nil | ||
default: | ||
// check single coin case | ||
if err := (DecCoins{coins[0]}).Validate(); err != nil { | ||
if err := sdk.ValidateDenom(coin.Denom); err != nil { | ||
return err | ||
} | ||
|
||
lowDenom := coins[0].Denom | ||
seenDenoms := make(map[string]bool) | ||
seenDenoms[lowDenom] = true | ||
|
||
for _, coin := range coins[1:] { | ||
if seenDenoms[coin.Denom] { | ||
return fmt.Errorf("duplicate denomination %s", coin.Denom) | ||
} | ||
if err := sdk.ValidateDenom(coin.Denom); err != nil { | ||
return err | ||
} | ||
if coin.Denom <= lowDenom { | ||
return fmt.Errorf("denomination %s is not sorted", coin.Denom) | ||
} | ||
if coin.IsNegative() { | ||
return fmt.Errorf("coin %s amount is negtive", coin.Denom) | ||
} | ||
|
||
// we compare each coin against the last denom | ||
lowDenom = coin.Denom | ||
seenDenoms[coin.Denom] = true | ||
if coin.Denom <= lowDenom { | ||
return fmt.Errorf("denomination %s is not sorted", coin.Denom) | ||
} | ||
if coin.IsNegative() { | ||
return fmt.Errorf("coin %s amount is negative", coin.Amount) | ||
} | ||
|
||
return nil | ||
// we compare each coin against the last denom | ||
lowDenom = coin.Denom | ||
seenDenoms[coin.Denom] = true | ||
} | ||
|
||
return nil | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yaruwangway did I get it right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"1stake" is ignore due to the denom is not in globalfee fee
"uatom" is not ignored, but rather when combining global fee, it is being "merged", so globalfee provides 0.1uatom, while minimum-gas-prices wants 0.2 uatom, so 0.2 uatom is taken to check the paid fees. since this testcase is for bypass msg, only the denom "uatom" is taken to check if the paid fee is zero, if not zero, if the paid fee denom is in "uatom'