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

feat: global fee module #1488

Merged
merged 32 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
64c71bf
remove non-46 packages until go mod tidy runs
okwme May 11, 2022
1ced43c
builds but had to remove IBC and E2E
okwme May 11, 2022
e5b6d6e
fix: undeclared identifier 'kIOMainPortDefault'
May 11, 2022
a431aef
restore E2E
okwme May 11, 2022
b33add8
add back minimal upgrade handler
okwme May 11, 2022
e9949ff
Add local e2e docker
May 11, 2022
c2968aa
fix: keys list
May 12, 2022
2b2fe5f
fix: e2e test
May 13, 2022
4be0ac5
Fix e2e (#1479)
okwme May 14, 2022
96e4484
add global fee module
May 2, 2022
66425ff
add glob fee ante handler
May 13, 2022
cd00f8f
add glob fee to app
May 16, 2022
e6d6819
add proto files
May 18, 2022
8fb7539
add proto script and docs
May 18, 2022
e9b935e
fix tests
May 18, 2022
8ccd349
add globalfee README
May 18, 2022
99c0bd1
Update README.md
yaruwangway May 24, 2022
e026fe7
merge main
Jun 7, 2022
8511127
fix: lint
Jun 7, 2022
b6a4cb9
add: globfee antehandler
Jun 8, 2022
1671c82
Merge branch 'main' into yaru/rho-glob-fee
okwme Jun 21, 2022
49f68d5
Merge branch 'main' into yaru/rho-glob-fee
Jun 22, 2022
ea602bf
Merge branch 'main' into yaru/rho-glob-fee
Jun 23, 2022
6d39e4a
Merge branch 'main' into yaru/rho-glob-fee
okwme Jun 30, 2022
4b2fb82
fix lint
Jun 30, 2022
1161ab2
Merge branch 'main' into yaru/rho-glob-fee
okwme Jul 4, 2022
7d264ee
custom global fee: make global accept fee bypass, define fee denoms (…
yaruwangway Aug 8, 2022
d5bd5e8
test: global fee e2e tests, bypass fee e2e (#1594)
yaruwangway Aug 10, 2022
50367a9
merge main
Aug 11, 2022
2102541
fix: dead link
Aug 11, 2022
ca8dc6d
change disable all link to disable nextline
Aug 11, 2022
442fac6
Update proto/gaia/globalfee/v1beta1/genesis.proto
yaruwangway Aug 11, 2022
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ start-localnet-ci:
./build/gaiad keys add val --home ~/.gaiad-liveness
./build/gaiad add-genesis-account val 10000000000000000000000000stake --home ~/.gaiad-liveness --keyring-backend test
./build/gaiad gentx val 1000000000stake --home ~/.gaiad-liveness --chain-id liveness
./build/gaiad collect-gentxs --home ~/.gaiad-liveness
./build/gaiad collect-gentxs --home ~/.gaiad-liveness
sed -i'' 's/minimum-gas-prices = ""/minimum-gas-prices = "0uatom"/' ~/.gaiad-liveness/config/app.toml
./build/gaiad start --home ~/.gaiad-liveness --x-crisis-skip-assert-invariants

Expand Down
12 changes: 8 additions & 4 deletions ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
ibcante "github.com/cosmos/ibc-go/v5/modules/core/ante"
ibckeeper "github.com/cosmos/ibc-go/v5/modules/core/keeper"
)
Expand All @@ -14,6 +15,7 @@ type HandlerOptions struct {
ante.HandlerOptions
IBCkeeper *ibckeeper.Keeper
BypassMinFeeMsgTypes []string
GlobalFeeSubspace paramtypes.Subspace
}

func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) {
Expand All @@ -26,10 +28,12 @@ func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) {
if opts.SignModeHandler == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder")
}

if opts.IBCkeeper == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "IBC keeper is required for middlewares")
}
if opts.GlobalFeeSubspace.Name() == "" {
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "param store is required for ante builder")
}

sigGasConsumer := opts.SigGasConsumer
if sigGasConsumer == nil {
Expand All @@ -43,15 +47,15 @@ func NewAnteHandler(opts HandlerOptions) (sdk.AnteHandler, error) {
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(opts.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(opts.AccountKeeper),
NewBypassMinFeeDecorator(opts.BypassMinFeeMsgTypes),
NewBypassMinFeeDecorator(opts.BypassMinFeeMsgTypes, opts.GlobalFeeSubspace),
// if opts.TxFeeCheck is nil, it is the default fee check
ante.NewDeductFeeDecorator(opts.AccountKeeper, opts.BankKeeper, opts.FeegrantKeeper, opts.TxFeeChecker),
// SetPubKeyDecorator must be called before all signature verification decorators
ante.NewSetPubKeyDecorator(opts.AccountKeeper),
ante.NewSetPubKeyDecorator(opts.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(opts.AccountKeeper),
ante.NewSigGasConsumeDecorator(opts.AccountKeeper, sigGasConsumer),
ante.NewSigVerificationDecorator(opts.AccountKeeper, opts.SignModeHandler),
ante.NewIncrementSequenceDecorator(opts.AccountKeeper),
// todo check
ibcante.NewRedundantRelayDecorator(opts.IBCkeeper),
}

Expand Down
16 changes: 13 additions & 3 deletions ante/ante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ import (
"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/cosmos/cosmos-sdk/x/params/types"
"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/v8/app"
gaiahelpers "github.com/cosmos/gaia/v8/app/helpers"
"github.com/cosmos/gaia/v8/x/globalfee"
globfeetypes "github.com/cosmos/gaia/v8/x/globalfee/types"
)

type IntegrationTestSuite struct {
Expand All @@ -41,7 +43,7 @@ func (s *IntegrationTestSuite) SetupTest() {
Height: 1,
})

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

Expand All @@ -50,7 +52,15 @@ func (s *IntegrationTestSuite) SetupTest() {
s.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig)
}

func (s *IntegrationTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) {
func (s *IntegrationTestSuite) setupTestGlobalFeeStoreAndMinGasPrice(minGasPrice []sdk.DecCoin, globalFeeParams *globfeetypes.Params) types.Subspace {
subspace := s.app.GetSubspace(globalfee.ModuleName)
subspace.SetParamSet(s.ctx, globalFeeParams)
s.ctx = s.ctx.WithMinGasPrices(minGasPrice).WithIsCheckTx(true)

return subspace
}

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{
Expand Down
106 changes: 55 additions & 51 deletions ante/fee.go
Original file line number Diff line number Diff line change
@@ -1,95 +1,99 @@
package ante

import (
"math"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
tmstrings "github.com/tendermint/tendermint/libs/strings"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"

"github.com/cosmos/gaia/v8/x/globalfee"
)

const maxBypassMinFeeMsgGasUsage = uint64(200_000)

// FeeWithBypassDecorator will check if the transaction's fee is at least as large
// as the local validator's minimum gasFee (defined in validator config).
// 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.
// 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 FeeWithBypassDecorator
// CONTRACT: Tx must implement FeeTx to use BypassMinFeeDecorator
// If the tx msg type is one of the bypass msg types, the tx is valid even if the min fee is lower than normally required.
// If the bypass tx still carries fees, the fee denom should be the same as global fee required.

var _ sdk.AnteDecorator = BypassMinFeeDecorator{}

type BypassMinFeeDecorator struct {
BypassMinFeeMsgTypes []string
GlobalMinFee globalfee.ParamSource
}

func NewBypassMinFeeDecorator(bypassMsgTypes []string) BypassMinFeeDecorator {
const defaultZeroGlobalFeeDenom = "uatom"

func DefaultZeroGlobalFee() []sdk.DecCoin {
return []sdk.DecCoin{sdk.NewDecCoinFromDec(defaultZeroGlobalFeeDenom, sdk.NewDec(0))}
}

func NewBypassMinFeeDecorator(bypassMsgTypes []string, paramSpace paramtypes.Subspace) BypassMinFeeDecorator {
if !paramSpace.HasKeyTable() {
panic("global fee paramspace was not set up via module")
}

return BypassMinFeeDecorator{
BypassMinFeeMsgTypes: bypassMsgTypes,
GlobalMinFee: paramSpace,
}
}

func (mfd BypassMinFeeDecorator) 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")
}

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

// Only check for minimum fees if the execution mode is CheckTx and the tx does
// Only check for minimum fees and global fee 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)
}
}
}
// or equal to a constant, otherwise minimum fees and global fees are checked to prevent spam.
containsOnlyBypassMinFeeMsgs := mfd.bypassMinFeeMsgs(msgs)
doesNotExceedMaxGasUsage := gas <= uint64(len(msgs))*maxBypassMinFeeMsgGasUsage
allowedToBypassMinFee := containsOnlyBypassMinFeeMsgs && doesNotExceedMaxGasUsage

return next(ctx, tx, simulate)
}
var allFees sdk.Coins
requiredFees := getMinGasPrice(ctx, feeTx)

func (mfd BypassMinFeeDecorator) bypassMinFeeMsgs(msgs []sdk.Msg) bool {
for _, msg := range msgs {
if tmstrings.StringInSlice(sdk.MsgTypeURL(msg), mfd.BypassMinFeeMsgTypes) {
continue
}
if ctx.IsCheckTx() && !simulate && !allowedToBypassMinFee {

return false
}
requiredGlobalFees := mfd.getGlobalFee(ctx, feeTx)
allFees = CombinedFeeRequirement(requiredGlobalFees, requiredFees)

return true
}
// this is to ban 1stake passing if the globalfee is 1photon or 0photon
// if feeCoins=[] and requiredGlobalFees has 0denom coins then it should pass.
if !DenomsSubsetOfIncludingZero(feeCoins, allFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fees %s is not a subset of required fees %s", feeCoins, allFees)
}
// At least one feeCoin amount must be GTE to one of the requiredGlobalFees
if !IsAnyGTEIncludingZero(feeCoins, allFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees for global fee; got: %s required: %s", feeCoins, allFees)
}
}

// utils function: GetTxPriority
// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the fee
// provided in a transaction.
func GetTxPriority(fee sdk.Coins) int64 {
var priority int64
for _, c := range fee {
p := int64(math.MaxInt64)
if c.Amount.IsInt64() {
p = c.Amount.Int64()
// when the tx is bypass msg type, still need to check the denom is not some unknown denom
if ctx.IsCheckTx() && !simulate && allowedToBypassMinFee {
requiredGlobalFees := mfd.getGlobalFee(ctx, feeTx)
// bypass tx without pay fee
if len(feeCoins) == 0 {
return next(ctx, tx, simulate)
}
if priority == 0 || p < priority {
priority = p
// bypass with fee, fee denom must in requiredGlobalFees
if !DenomsSubsetOfIncludingZero(feeCoins, requiredGlobalFees) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fees denom is wrong; got: %s required: %s", feeCoins, requiredGlobalFees)
}
}

return priority
return next(ctx, tx, simulate)
}
Loading