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

refactor global fee zero coins #2327

Merged
merged 50 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
7ca8bf4
- Update doc to follow Go conventions
sainoe Feb 21, 2023
52efa9d
update params
sainoe Feb 22, 2023
d47a2af
rebase and fix nits
sainoe Mar 16, 2023
2d82576
add GetMinGasPrice UT
sainoe Mar 17, 2023
90be614
remove unused feetestsuite
sainoe Mar 17, 2023
c29fbfe
rebase
sainoe Mar 20, 2023
f41c3a3
update doc
sainoe Mar 20, 2023
b086f14
nits
sainoe Mar 20, 2023
2ff680a
nits
sainoe Mar 21, 2023
4c26632
refactor fee tests setup
sainoe Mar 21, 2023
5d3a6cd
remove comments
sainoe Mar 21, 2023
0070306
Update x/globalfee/ante/antetest/fee_test.go
sainoe Mar 21, 2023
e9c4bf8
Update x/globalfee/ante/antetest/fee_test.go
sainoe Mar 21, 2023
6c3a5a6
Update x/globalfee/ante/antetest/fee_test.go
sainoe Mar 21, 2023
cbfa419
Update x/globalfee/ante/antetest/fee_test.go
sainoe Mar 21, 2023
547c55a
Update x/globalfee/ante/antetest/fee_test.go
sainoe Mar 21, 2023
76dea00
Update x/globalfee/ante/antetest/fee_test.go
sainoe Mar 21, 2023
935452a
Merge remote-tracking branch 'origin/sainoe/global_fee' into sainoe/g…
sainoe Mar 21, 2023
fdaa76f
fix GetMinGasPrice to not return nil coins
sainoe Mar 22, 2023
ed13007
min gas price zero coins edge cases
sainoe Mar 22, 2023
8f20d53
min gas price zero coins edge cases
sainoe Mar 22, 2023
0922810
remove debug logs
sainoe Mar 22, 2023
679c2e5
udpate comments
sainoe Mar 22, 2023
3bb55cf
udpate comments
sainoe Mar 22, 2023
9ca89a4
add comment
sainoe Mar 22, 2023
dc73b60
Merge branch 'sainoe/global_fee_updated' into sainoe/global_fee
sainoe Mar 22, 2023
b923ed2
Update x/globalfee/types/params.go
sainoe Mar 22, 2023
81d7e49
Update x/globalfee/types/params.go
sainoe Mar 22, 2023
c6bde62
fix: lint
Mar 23, 2023
10dd1d0
Merge branch 'main' into sainoe/global_fee
MSalopek Mar 23, 2023
b658d27
refactor: global fee zero coins
Mar 23, 2023
37f4aec
refactor: add splitGlobalFees()
Mar 23, 2023
274983c
fix: globalfee antehandler
Mar 23, 2023
d3c381e
fix: add test helper func
Mar 24, 2023
d046942
update according to PR comments
Mar 24, 2023
5eeaac6
test: add test for splitGlobalFees()
Mar 24, 2023
4971089
merge sainoe/global_fee
Mar 24, 2023
211a99e
code improvement
Mar 24, 2023
0e677aa
refactor: globalfee antehandler
Mar 24, 2023
6bd7872
refactor: bypass logic
Mar 24, 2023
ac3d5c8
refactor: fee check logic
Mar 28, 2023
7f8dd99
fix: lint
Mar 28, 2023
cc9254e
fix: return err when required fees are not set up
Mar 30, 2023
2c1f0fd
merge main
Apr 3, 2023
3a252bd
chore: clean comments
Apr 3, 2023
3e4c0a5
Sainoe/globalfee coins (#2378)
sainoe Apr 6, 2023
3bb7e80
Merge branch 'main' into yaru/globalfee-coins
sainoe Apr 6, 2023
2452c27
Merge branch 'main' into yaru/globalfee-coins
mmulji-ic Apr 6, 2023
663c718
fix nits
sainoe Apr 12, 2023
6a91609
Merge branch 'main' into yaru/globalfee-coins
mmulji-ic Apr 12, 2023
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
95 changes: 56 additions & 39 deletions x/globalfee/ante/fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/cosmos/gaia/v9/x/globalfee/types"

tmstrings "github.com/tendermint/tendermint/libs/strings"

"github.com/cosmos/gaia/v9/x/globalfee"
"github.com/cosmos/gaia/v9/x/globalfee/types"
)

// FeeWithBypassDecorator checks if the transaction's fee is at least as large
Expand Down Expand Up @@ -54,28 +52,51 @@ 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")
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must implement the sdk.FeeTx interface")
}

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

// Get required Global Fee and min gas price
requiredGlobalFees, err := mfd.getGlobalFee(ctx, feeTx)
// Sort fee tx's coins, zero coins in feeCoins are already removed
feeCoins := feeTx.GetFee().Sort()
gas := feeTx.GetGas()
msgs := feeTx.GetMsgs()

// Get required Global Fee
requiredGlobalFees, err := mfd.GetGlobalFee(ctx, feeTx)
if err != nil {
return ctx, err
}
requiredFees := GetMinGasPrice(ctx, int64(feeTx.GetGas()))

// sort fee tx's coins
feeCoins := feeTx.GetFee().Sort()
gas := feeTx.GetGas()
msgs := feeTx.GetMsgs()
// Get local minimum-gas-prices
localFees := GetMinGasPrice(ctx, int64(feeTx.GetGas()))

// CombinedFeeRequirement should never be empty since
// global fee is set to its default value, i.e. 0uatom, if empty
combinedFeeRequirement := CombinedFeeRequirement(requiredGlobalFees, localFees)
if len(combinedFeeRequirement) == 0 {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "required fees are not setup.")
mmulji-ic marked this conversation as resolved.
Show resolved Hide resolved
}

nonZeroCoinFeesReq, zeroCoinFeesDenomReq := getNonZeroFees(combinedFeeRequirement)

// feeCoinsNonZeroDenom contains non-zero denominations from the combinedFeeRequirement
//
// feeCoinsNoZeroDenom is used to check if the fees meets the requirement imposed by nonZeroCoinFeesReq
// when feeCoins does not contain zero coins' denoms in combinedFeeRequirement
feeCoinsNonZeroDenom, feeCoinsZeroDenom := splitCoinsByDenoms(feeCoins, zeroCoinFeesDenomReq)

// Check that the fees are in expected denominations.
// if feeCoinsNoZeroDenom=[], DenomsSubsetOf returns true
// if feeCoinsNoZeroDenom is not empty, but nonZeroCoinFeesReq empty, return false
if !feeCoinsNonZeroDenom.DenomsSubsetOf(nonZeroCoinFeesReq) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee is not a subset of required fees; got %s, required: %s", feeCoins, combinedFeeRequirement)
}

// Accept zero fee transactions only if both of the following statements are true:
//
Expand All @@ -88,45 +109,41 @@ func (mfd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, ne
doesNotExceedMaxGasUsage := gas <= mfd.MaxTotalBypassMinFeeMsgGasUsage
allowedToBypassMinFee := mfd.ContainsOnlyBypassMinFeeMsgs(msgs) && doesNotExceedMaxGasUsage

if allowedToBypassMinFee {
// Transactions with zero fees are accepted
if len(feeCoins) == 0 {
// Either the transaction contains at least one message of a type
// that cannot bypass the minimum fee or the total gas limit exceeds
// the imposed threshold. As a result, besides check the fees are in
// expected denominations, check the amounts are greater or equal than
// the expected amounts.

// only check feeCoinsNoZeroDenom has coins IsAnyGTE than nonZeroCoinFeesReq
// when feeCoins does not contain denoms of zero denoms in combinedFeeRequirement
if !allowedToBypassMinFee && len(feeCoinsZeroDenom) == 0 {
// special case: when feeCoins=[] and there is zero coin in fee requirement
if len(feeCoins) == 0 && len(zeroCoinFeesDenomReq) != 0 {
return next(ctx, tx, simulate)
}
// If the transaction fee is non-zero, then check that the fees are in
// expected denominations.
if !DenomsSubsetOfIncludingZero(feeCoins, requiredGlobalFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fees denom is wrong; got: %s required: %s", feeCoins, requiredGlobalFees)
}
} else {
// Either the transaction contains at least one 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.

combinedFees := 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, combinedFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee is not a subset of required fees; got %s, required: %s", feeCoins, combinedFees)
}

// 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, combinedFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, combinedFees)

// if feeCoinsNoZeroDenom=[], return false
// if nonZeroCoinFeesReq=[], return false (this situation should not happen
// because when nonZeroCoinFeesReq empty, and DenomsSubsetOf check passed,
// the tx should already passed before)
if !feeCoinsNonZeroDenom.IsAnyGTE(nonZeroCoinFeesReq) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, combinedFeeRequirement)
}
}

return next(ctx, tx, simulate)
}

// getGlobalFee returns the global fees for a given fee tx's gas (might also returns 0denom if globalMinGasPrice is 0)
// GetGlobalFee returns the global fees for a given fee tx's gas
// (might also return 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) {
func (mfd FeeDecorator) GetGlobalFee(ctx sdk.Context, feeTx sdk.FeeTx) (sdk.Coins, error) {
var (
globalMinGasPrices sdk.DecCoins
err error
Expand Down Expand Up @@ -185,7 +202,7 @@ func (mfd FeeDecorator) ContainsOnlyBypassMinFeeMsgs(msgs []sdk.Msg) bool {
return true
}

// GetMinGasPrice returns a nodes's local minimum gas prices
// GetMinGasPrice returns the validator's minimum gas prices
// fees given a gas limit
func GetMinGasPrice(ctx sdk.Context, gasLimit int64) sdk.Coins {
minGasPrices := ctx.MinGasPrices()
Expand Down
103 changes: 35 additions & 68 deletions x/globalfee/ante/fee_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,8 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// DenomsSubsetOfIncludingZero and IsAnyGTEIncludingZero are similar to DenomsSubsetOf and IsAnyGTE in sdk.
//
// DenomsSubsetOfIncludingZero overwrites DenomsSubsetOf from sdk, to allow zero amt coins in superset. e.g.
// e.g. [1stake] is the DenomsSubsetOfIncludingZero of [0stake] and
// [] is the DenomsSubsetOfIncludingZero of [0stake] but not [1stake].
// DenomsSubsetOfIncludingZero returns true if coins's denoms is subset of coinsB's denoms.
// If coins is empty set, empty set is any sets' subset
func DenomsSubsetOfIncludingZero(coins, coinsB sdk.Coins) bool {
// more denoms in B than in receiver
if len(coins) > len(coinsB) {
return false
}
// coins=[], coinsB=[0stake]
// let all len(coins) == 0 pass and reject later at IsAnyGTEIncludingZero
if len(coins) == 0 && ContainZeroCoins(coinsB) {
return true
}
// coins=1stake, coinsB=[0stake,1uatom]
for _, coin := range coins {
err := sdk.ValidateDenom(coin.Denom)
if err != nil {
panic(err)
}
if ok, _ := Find(coinsB, coin.Denom); !ok {
return false
}
}

return true
}

// IsAnyGTEIncludingZero overwrites the IsAnyGTE from sdk to allow zero coins in coins and coinsB.
// IsAnyGTEIncludingZero returns true if coins contain at least one denom that is present at a greater or equal amount in coinsB;
// it returns false otherwise. If CoinsB is emptyset, no coins sets are IsAnyGTEIncludingZero coinsB unless coins is also empty set.
// NOTE: IsAnyGTEIncludingZero operates under the invariant that both coin sets are sorted by denoms.
// contract !!!! coins must be DenomsSubsetOfIncludingZero of coinsB
func IsAnyGTEIncludingZero(coins, coinsB sdk.Coins) bool {
// no set is empty set's subset except empty set
// this is different from sdk, sdk return false for coinsB empty
if len(coinsB) == 0 && len(coins) == 0 {
return true
}
// nothing is gte empty coins
if len(coinsB) == 0 && len(coins) != 0 {
return false
}
// if feecoins empty (len(coins)==0 && len(coinsB) != 0 ), and globalfee has one denom of amt zero, return true
if len(coins) == 0 {
return ContainZeroCoins(coinsB)
}

// len(coinsB) != 0 && len(coins) != 0
// special case: coins=1stake, coinsB=[2stake,0uatom], fail
for _, coin := range coins {
// not find coin in CoinsB
if ok, _ := Find(coinsB, coin.Denom); ok {
// find coin in coinsB, and if the amt == 0, mean either coin=0denom or coinsB=0denom...both true
amt := coinsB.AmountOf(coin.Denom)
if coin.Amount.GTE(amt) {
return true
}
}
}

return false
}

// ContainZeroCoins returns true if the given coins are empty or contain zero coins,
// Note that the coins denoms must be validated, see sdk.ValidateDenom
func ContainZeroCoins(coins sdk.Coins) bool {
if len(coins) == 0 {
return true
Expand All @@ -88,14 +22,15 @@ func ContainZeroCoins(coins sdk.Coins) bool {
// CombinedFeeRequirement returns the global fee and min_gas_price combined and sorted.
// Both globalFees and minGasPrices must be valid, but CombinedFeeRequirement
// does not validate them, so it may return 0denom.
// if globalfee is empty, CombinedFeeRequirement return sdk.Coins{}
func CombinedFeeRequirement(globalFees, minGasPrices sdk.Coins) sdk.Coins {
// empty min_gas_price
if len(minGasPrices) == 0 {
return globalFees
}
// empty global fee is not possible if we set default global fee
mmulji-ic marked this conversation as resolved.
Show resolved Hide resolved
if len(globalFees) == 0 && len(minGasPrices) != 0 {
return globalFees
return sdk.Coins{}
}

// if min_gas_price denom is in globalfee, and the amount is higher than globalfee, add min_gas_price to allFees
Expand Down Expand Up @@ -139,3 +74,35 @@ func Find(coins sdk.Coins, denom string) (bool, sdk.Coin) {
}
}
}

// splitCoinsByDenoms returns the given coins split in two whether
// their demon is or isn't found in the given denom map.
func splitCoinsByDenoms(feeCoins sdk.Coins, denomMap map[string]bool) (feeCoinsNonZeroDenom sdk.Coins, feeCoinsZeroDenom sdk.Coins) {
for _, fc := range feeCoins {
_, found := denomMap[fc.Denom]
if found {
feeCoinsZeroDenom = append(feeCoinsZeroDenom, fc)
} else {
feeCoinsNonZeroDenom = append(feeCoinsNonZeroDenom, fc)
}
}

return feeCoinsNonZeroDenom.Sort(), feeCoinsZeroDenom.Sort()
}

// getNonZeroFees returns the given fees nonzero coins
// and a map storing the zero coins's denoms
func getNonZeroFees(fees sdk.Coins) (sdk.Coins, map[string]bool) {
requiredFeesNonZero := sdk.Coins{}
requiredFeesZeroDenom := map[string]bool{}

for _, gf := range fees {
if gf.IsZero() {
requiredFeesZeroDenom[gf.Denom] = true
} else {
requiredFeesNonZero = append(requiredFeesNonZero, gf)
}
}

return requiredFeesNonZero.Sort(), requiredFeesZeroDenom
}
Loading