feat(server/v2): add min gas price and check with tx fee (backport #21173) (#21453)

Co-authored-by: Akhil Kumar P <36399231+akhilkumarpilli@users.noreply.github.com>
Co-authored-by: Julien Robert <julien@rbrt.fr>
This commit is contained in:
mergify[bot] 2024-08-29 08:22:04 +00:00 committed by GitHub
parent 69878d8e6f
commit 1260f6703f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 168 additions and 89 deletions

View File

@ -23,7 +23,7 @@ require (
cosmossdk.io/core v1.0.0 // main
cosmossdk.io/errors v1.0.1
cosmossdk.io/log v1.4.1
cosmossdk.io/server/v2 v2.0.0-20240827095516-355f748add9e // main
cosmossdk.io/server/v2 v2.0.0-20240829074658-81a225e6a29b // main
cosmossdk.io/server/v2/appmanager v0.0.0-20240827095516-355f748add9e // main
cosmossdk.io/store/v2 v2.0.0-20240731205446-aee9803a0af6 // main
cosmossdk.io/x/consensus v0.0.0-00010101000000-000000000000
@ -135,7 +135,7 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/common v0.57.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect

View File

@ -24,8 +24,8 @@ cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/schema v0.1.1 h1:I0M6pgI7R10nq+/HCQfbO6BsGBZA8sQy+duR1Y3aKcA=
cosmossdk.io/schema v0.1.1/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
cosmossdk.io/server/v2 v2.0.0-20240827095516-355f748add9e h1:5B5vereMsxfQsk8eXchBQPB1sc5POgYxZOBaUlST6WQ=
cosmossdk.io/server/v2 v2.0.0-20240827095516-355f748add9e/go.mod h1:rUve692/2QXV8ulpAG1mlytqiyW5nhZySY4ngmgJyQA=
cosmossdk.io/server/v2 v2.0.0-20240829074658-81a225e6a29b h1:FFixNVq2SbtRlYvr1fB/WikfYTRrA0o/35ImIhhZzrE=
cosmossdk.io/server/v2 v2.0.0-20240829074658-81a225e6a29b/go.mod h1:MgjYKtPEW4FPEYxh1h0idVBjgblFgdrAWkeqn7UOrVA=
cosmossdk.io/server/v2/appmanager v0.0.0-20240827095516-355f748add9e h1:9eB2si1gT6c5ea8Jsh498Ei6E3Th1yMyuJgSLiT6D4Y=
cosmossdk.io/server/v2/appmanager v0.0.0-20240827095516-355f748add9e/go.mod h1:fJDDnWJCBRxLLIyu2byqtf3KTRYIVS4OxKwdZozJi20=
cosmossdk.io/store v1.0.0-rc.0.0.20240815194237-858ec2fcb897 h1:o024zaPHYtmUGL2BCX1ns9rfZmMc19U4hQ2CAPt2Xgg=
@ -415,8 +415,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY=
github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=

View File

@ -11,7 +11,7 @@ require (
cosmossdk.io/log v1.4.1
cosmossdk.io/math v1.3.0
cosmossdk.io/runtime/v2 v2.0.0-20240827121911-e98b8e96174f // main
cosmossdk.io/server/v2 v2.0.0-20240827095516-355f748add9e // main
cosmossdk.io/server/v2 v2.0.0-20240829074658-81a225e6a29b // main
cosmossdk.io/server/v2/cometbft v0.0.0-00010101000000-000000000000
cosmossdk.io/store/v2 v2.0.0-20240815194237-858ec2fcb897 // main
cosmossdk.io/tools/confix v0.0.0-00010101000000-000000000000
@ -185,7 +185,7 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/common v0.57.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rivo/uniseg v0.2.0 // indirect

View File

@ -214,8 +214,8 @@ cosmossdk.io/runtime/v2 v2.0.0-20240827121911-e98b8e96174f h1:k/naA1PbdzhQ6EdiPh
cosmossdk.io/runtime/v2 v2.0.0-20240827121911-e98b8e96174f/go.mod h1:SoCx1xyZn+tVPAtTAuLSEYjocVtZips+O2QLGBj99/c=
cosmossdk.io/schema v0.1.1 h1:I0M6pgI7R10nq+/HCQfbO6BsGBZA8sQy+duR1Y3aKcA=
cosmossdk.io/schema v0.1.1/go.mod h1:RDAhxIeNB4bYqAlF4NBJwRrgtnciMcyyg0DOKnhNZQQ=
cosmossdk.io/server/v2 v2.0.0-20240827095516-355f748add9e h1:5B5vereMsxfQsk8eXchBQPB1sc5POgYxZOBaUlST6WQ=
cosmossdk.io/server/v2 v2.0.0-20240827095516-355f748add9e/go.mod h1:rUve692/2QXV8ulpAG1mlytqiyW5nhZySY4ngmgJyQA=
cosmossdk.io/server/v2 v2.0.0-20240829074658-81a225e6a29b h1:FFixNVq2SbtRlYvr1fB/WikfYTRrA0o/35ImIhhZzrE=
cosmossdk.io/server/v2 v2.0.0-20240829074658-81a225e6a29b/go.mod h1:MgjYKtPEW4FPEYxh1h0idVBjgblFgdrAWkeqn7UOrVA=
cosmossdk.io/server/v2/appmanager v0.0.0-20240827095516-355f748add9e h1:9eB2si1gT6c5ea8Jsh498Ei6E3Th1yMyuJgSLiT6D4Y=
cosmossdk.io/server/v2/appmanager v0.0.0-20240827095516-355f748add9e/go.mod h1:fJDDnWJCBRxLLIyu2byqtf3KTRYIVS4OxKwdZozJi20=
cosmossdk.io/server/v2/stf v0.0.0-20240827095516-355f748add9e h1:A2JxCwJuoMRiBF8ZjpxRf+JO5cTSYw8Vc2iHImfHoKg=
@ -764,8 +764,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY=
github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=

View File

@ -75,6 +75,7 @@ func initRootCmd[T transaction.Tx](
rootCmd,
newApp,
logger,
initServerConfig(),
cometbft.New(&genericTxDecoder[T]{txConfig}, cometbft.DefaultServerOptions[T]()),
grpc.New[T](),
store.New[T](newApp),

View File

@ -3,6 +3,8 @@ package cmd
import (
"strings"
serverv2 "cosmossdk.io/server/v2"
clientconfig "github.com/cosmos/cosmos-sdk/client/config"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
)
@ -49,3 +51,20 @@ gas-adjustment = {{ .GasConfig.GasAdjustment }}
return customClientConfigTemplate, customClientConfig
}
// Allow the chain developer to overwrite the server default app toml config.
func initServerConfig() serverv2.ServerConfig {
serverCfg := serverv2.DefaultServerConfig()
// The server's default minimum gas price is set to "0stake" inside
// app.toml. However, the chain developer can set a default app.toml value for their
// validators here. Please update value based on chain denom.
//
// In summary:
// - if you set serverCfg.MinGasPrices value, validators CAN tweak their
// own app.toml to override, or use this default value.
//
// In simapp, we set the min gas prices to 0.
serverCfg.MinGasPrices = "0stake"
return serverCfg
}

View File

@ -41,7 +41,6 @@ import (
)
var (
flagMinGasPrices = "minimum-gas-prices"
flagNodeDirPrefix = "node-dir-prefix"
flagNumValidators = "validator-count"
flagOutputDir = "output-dir"
@ -72,7 +71,7 @@ func addTestnetFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().IntP(flagNumValidators, "n", 4, "Number of validators to initialize the testnet with")
cmd.Flags().StringP(flagOutputDir, "o", "./.testnets", "Directory to store initialization data for the testnet")
cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().String(flagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)")
cmd.Flags().String(serverv2.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)")
cmd.Flags().String(flags.FlagKeyType, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for")
// support old flags name for backwards compatibility
@ -129,7 +128,7 @@ Example:
args.outputDir, _ = cmd.Flags().GetString(flagOutputDir)
args.keyringBackend, _ = cmd.Flags().GetString(flags.FlagKeyringBackend)
args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID)
args.minGasPrices, _ = cmd.Flags().GetString(flagMinGasPrices)
args.minGasPrices, _ = cmd.Flags().GetString(serverv2.FlagMinGasPrices)
args.nodeDirPrefix, _ = cmd.Flags().GetString(flagNodeDirPrefix)
args.nodeDaemonHome, _ = cmd.Flags().GetString(flagNodeDaemonHome)
args.startingIPAddress, _ = cmd.Flags().GetString(flagStartingIPAddress)
@ -337,6 +336,9 @@ func initTestnetFiles[T transaction.Tx](
return err
}
serverCfg := serverv2.DefaultServerConfig()
serverCfg.MinGasPrices = args.minGasPrices
// Write server config
cometServer := cometbft.New[T](
&genericTxDecoder[T]{clientCtx.TxConfig},
@ -345,7 +347,7 @@ func initTestnetFiles[T transaction.Tx](
)
storeServer := store.New[T](newApp)
grpcServer := grpc.New[T](grpc.OverwriteDefaultConfig(grpcConfig))
server := serverv2.NewServer(log.NewNopLogger(), cometServer, grpcServer, storeServer)
server := serverv2.NewServer(log.NewNopLogger(), serverCfg, cometServer, grpcServer, storeServer)
err = server.WriteConfig(filepath.Join(nodeDir, "config"))
if err != nil {
return err

View File

@ -10,7 +10,6 @@ import (
)
func TestStakeUnstake(t *testing.T) {
t.Skip("The fee deduction is not yet implemented in v2")
// Scenario:
// delegate tokens to validator
// undelegate some tokens

View File

@ -28,6 +28,10 @@ max-recv-msg-size = 10485760
# The default value is math.MaxInt32.
max-send-msg-size = 2147483647
[server]
# minimum-gas-prices defines the price which a validator is willing to accept for processing a transaction. A transaction's fees must meet the minimum of any denomination specified in this config (e.g. 0.25token1;0.0001token2).
minimum-gas-prices = '0stake'
[store]
# The type of database for application and snapshots databases.
app-db-backend = 'goleveldb'

View File

@ -39,11 +39,12 @@ type v2KeyChangesMap map[string][]string
// list all the keys which are need to be modified in v2
var v2KeyChanges = v2KeyChangesMap{
"min-retain-blocks": []string{"comet.min-retain-blocks"},
"index-events": []string{"comet.index-events"},
"halt-height": []string{"comet.halt-height"},
"halt-time": []string{"comet.halt-time"},
"app-db-backend": []string{"store.app-db-backend"},
"minimum-gas-prices": []string{"server.minimum-gas-prices"},
"min-retain-blocks": []string{"comet.min-retain-blocks"},
"index-events": []string{"comet.index-events"},
"halt-height": []string{"comet.halt-height"},
"halt-time": []string{"comet.halt-time"},
"app-db-backend": []string{"store.app-db-backend"},
"pruning-keep-recent": []string{
"store.options.ss-pruning-option.keep-recent",
"store.options.sc-pruning-option.keep-recent",

View File

@ -2,8 +2,10 @@ package ante
import (
"bytes"
"context"
"fmt"
"cosmossdk.io/core/event"
"cosmossdk.io/core/transaction"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/auth/types"
@ -14,7 +16,7 @@ import (
// TxFeeChecker checks if the provided fee is enough and returns the effective fee and tx priority.
// The effective fee should be deducted later, and the priority should be returned in the ABCI response.
type TxFeeChecker func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error)
type TxFeeChecker func(ctx context.Context, tx transaction.Tx) (sdk.Coins, int64, error)
// DeductFeeDecorator deducts fees from the fee payer. The fee payer is the fee granter (if specified) or first signer of the tx.
// If the fee payer does not have the funds to pay for the fees, return an InsufficientFunds error.
@ -25,60 +27,78 @@ type DeductFeeDecorator struct {
bankKeeper types.BankKeeper
feegrantKeeper FeegrantKeeper
txFeeChecker TxFeeChecker
minGasPrices sdk.DecCoins
}
func NewDeductFeeDecorator(ak AccountKeeper, bk types.BankKeeper, fk FeegrantKeeper, tfc TxFeeChecker) DeductFeeDecorator {
if tfc == nil {
tfc = checkTxFeeWithValidatorMinGasPrices
}
return DeductFeeDecorator{
func NewDeductFeeDecorator(ak AccountKeeper, bk types.BankKeeper, fk FeegrantKeeper, tfc TxFeeChecker) *DeductFeeDecorator {
dfd := &DeductFeeDecorator{
accountKeeper: ak,
bankKeeper: bk,
feegrantKeeper: fk,
txFeeChecker: tfc,
minGasPrices: sdk.NewDecCoins(),
}
if tfc == nil {
dfd.txFeeChecker = dfd.checkTxFeeWithValidatorMinGasPrices
}
return dfd
}
func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (sdk.Context, error) {
// SetMinGasPrices sets the minimum-gas-prices value in the state of DeductFeeDecorator
func (dfd *DeductFeeDecorator) SetMinGasPrices(minGasPrices sdk.DecCoins) {
dfd.minGasPrices = minGasPrices
}
// AnteHandle implements an AnteHandler decorator for the DeductFeeDecorator
func (dfd *DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (sdk.Context, error) {
dfd.minGasPrices = ctx.MinGasPrices()
txPriority, err := dfd.innerValidateTx(ctx, tx)
if err != nil {
return ctx, err
}
newCtx := ctx.WithPriority(txPriority)
return next(newCtx, tx, false)
}
func (dfd *DeductFeeDecorator) innerValidateTx(ctx context.Context, tx transaction.Tx) (priority int64, err error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must implement the FeeTx interface")
return 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must implement the FeeTx interface")
}
txService := dfd.accountKeeper.GetEnvironment().TransactionService
execMode := txService.ExecMode(ctx)
if execMode != transaction.ExecModeSimulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 {
return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas")
}
execMode := dfd.accountKeeper.GetEnvironment().TransactionService.ExecMode(ctx)
headerInfo := dfd.accountKeeper.GetEnvironment().HeaderService.HeaderInfo(ctx)
var (
priority int64
err error
)
if execMode != transaction.ExecModeSimulate && headerInfo.Height > 0 && feeTx.GetGas() == 0 {
return 0, errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas")
}
fee := feeTx.GetFee()
if execMode != transaction.ExecModeSimulate {
fee, priority, err = dfd.txFeeChecker(ctx, tx)
if err != nil {
return ctx, err
return 0, err
}
}
if err := dfd.checkDeductFee(ctx, tx, fee); err != nil {
return ctx, err
if err := dfd.checkDeductFee(ctx, feeTx, fee); err != nil {
return 0, err
}
newCtx := ctx.WithPriority(priority)
return next(newCtx, tx, false)
return priority, nil
}
func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee sdk.Coins) error {
feeTx, ok := sdkTx.(sdk.FeeTx)
if !ok {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must implement the FeeTx interface")
}
// ValidateTx implements an TxValidator for DeductFeeDecorator
// Note: This method is applicable only for transactions that implement the sdk.FeeTx interface.
func (dfd *DeductFeeDecorator) ValidateTx(ctx context.Context, tx transaction.Tx) error {
_, err := dfd.innerValidateTx(ctx, tx)
return err
}
func (dfd *DeductFeeDecorator) checkDeductFee(ctx context.Context, feeTx sdk.FeeTx, fee sdk.Coins) error {
addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName)
if len(addr) == 0 {
return fmt.Errorf("fee collector module account (%s) has not been set", types.FeeCollectorName)
@ -91,12 +111,10 @@ func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee
// if feegranter set, deduct fee from feegranter account.
// this works only when feegrant is enabled.
if feeGranter != nil {
feeGranterAddr := sdk.AccAddress(feeGranter)
if dfd.feegrantKeeper == nil {
return sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled")
} else if !bytes.Equal(feeGranterAddr, feePayer) {
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranterAddr, feePayer, fee, sdkTx.GetMsgs())
} else if !bytes.Equal(feeGranter, feePayer) {
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, feeTx.GetMsgs())
if err != nil {
granterAddr, acErr := dfd.accountKeeper.AddressCodec().BytesToString(feeGranter)
if acErr != nil {
@ -109,38 +127,34 @@ func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee
return errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", granterAddr, payerAddr)
}
}
deductFeesFrom = feeGranterAddr
deductFeesFrom = feeGranter
}
// deduct the fees
if !fee.IsZero() {
err := DeductFees(dfd.bankKeeper, ctx, deductFeesFrom, fee)
if err != nil {
if err := DeductFees(dfd.bankKeeper, ctx, deductFeesFrom, fee); err != nil {
return err
}
}
events := sdk.Events{
sdk.NewEvent(
sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()),
sdk.NewAttribute(sdk.AttributeKeyFeePayer, sdk.AccAddress(deductFeesFrom).String()),
),
if err := dfd.accountKeeper.GetEnvironment().EventService.EventManager(ctx).EmitKV(
sdk.EventTypeTx,
event.NewAttribute(sdk.AttributeKeyFee, fee.String()),
event.NewAttribute(sdk.AttributeKeyFeePayer, sdk.AccAddress(deductFeesFrom).String()),
); err != nil {
return err
}
ctx.EventManager().EmitEvents(events)
return nil
}
// DeductFees deducts fees from the given account.
func DeductFees(bankKeeper types.BankKeeper, ctx sdk.Context, acc []byte, fees sdk.Coins) error {
func DeductFees(bankKeeper types.BankKeeper, ctx context.Context, acc []byte, fees sdk.Coins) error {
if !fees.IsValid() {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}
err := bankKeeper.SendCoinsFromAccountToModule(ctx, sdk.AccAddress(acc), types.FeeCollectorName, fees)
if err != nil {
if err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc, types.FeeCollectorName, fees); err != nil {
return fmt.Errorf("failed to deduct fees: %w", err)
}

View File

@ -41,6 +41,11 @@ func TestDeductFeeDecorator_ZeroGas(t *testing.T) {
// Set IsCheckTx to true
s.ctx = s.ctx.WithIsCheckTx(true)
// Set current block height in headerInfo
headerInfo := s.ctx.HeaderInfo()
headerInfo.Height = s.ctx.BlockHeight()
s.ctx = s.ctx.WithHeaderInfo(headerInfo)
_, err = antehandler(s.ctx, tx, false)
require.Error(t, err)

View File

@ -1,8 +1,10 @@
package ante
import (
"context"
"math"
"cosmossdk.io/core/transaction"
errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
@ -12,7 +14,7 @@ import (
// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per
// unit of gas is fixed and set by each validator, can the tx priority is computed from the gas price.
func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) {
func (dfd *DeductFeeDecorator) checkTxFeeWithValidatorMinGasPrices(ctx context.Context, tx transaction.Tx) (sdk.Coins, int64, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return nil, 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
@ -24,8 +26,8 @@ func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.Tx) (sdk.Coins,
// Ensure that the provided fees meet a minimum threshold for the validator,
// if this is a CheckTx. This is only for local mempool purposes, and thus
// is only ran on check tx.
if ctx.ExecMode() == sdk.ExecModeCheck { // NOTE: using environment here breaks the API of fee logic, an alternative must be found for server/v2. ref: https://github.com/cosmos/cosmos-sdk/issues/19640
minGasPrices := ctx.MinGasPrices()
if dfd.accountKeeper.GetEnvironment().TransactionService.ExecMode(ctx) == transaction.ExecModeCheck {
minGasPrices := dfd.minGasPrices
if !minGasPrices.IsZero() {
requiredFees := make(sdk.Coins, len(minGasPrices))

View File

@ -6,6 +6,7 @@ import (
"fmt"
gogoproto "github.com/cosmos/gogoproto/proto"
"github.com/spf13/viper"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
grpcstatus "google.golang.org/grpc/status"
@ -34,6 +35,9 @@ import (
signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
)
// flagMinGasPricesV2 is the flag name for the minimum gas prices in the main server v2 component.
const flagMinGasPricesV2 = "server.minimum-gas-prices"
func init() {
appconfig.RegisterModule(&txconfigv1.Config{},
appconfig.Provide(ProvideModule),
@ -50,7 +54,7 @@ type ModuleInputs struct {
Codec codec.Codec
ProtoFileResolver txsigning.ProtoFileResolver
Environment appmodule.Environment
// BankKeeper is the expected bank keeper to be passed to AnteHandlers
// BankKeeper is the expected bank keeper to be passed to AnteHandlers / Tx Validators
BankKeeper authtypes.BankKeeper `optional:"true"`
MetadataBankKeeper BankKeeper `optional:"true"`
AccountKeeper ante.AccountKeeper `optional:"true"`
@ -58,8 +62,10 @@ type ModuleInputs struct {
AccountAbstractionKeeper ante.AccountAbstractionKeeper `optional:"true"`
CustomSignModeHandlers func() []txsigning.SignModeHandler `optional:"true"`
CustomGetSigners []txsigning.CustomGetSigner `optional:"true"`
UnorderedTxManager *unorderedtx.Manager `optional:"true"`
ExtraTxValidators []appmodule.TxValidator[transaction.Tx] `optional:"true"`
UnorderedTxManager *unorderedtx.Manager `optional:"true"`
TxFeeChecker ante.TxFeeChecker `optional:"true"`
Viper *viper.Viper `optional:"true"` // server v2
}
type ModuleOutputs struct {
@ -107,7 +113,40 @@ func ProvideModule(in ModuleInputs) ModuleOutputs {
panic(err)
}
baseAppOption := func(app *baseapp.BaseApp) {
svd := ante.NewSigVerificationDecorator(
in.AccountKeeper,
txConfig.SignModeHandler(),
ante.DefaultSigVerificationGasConsumer,
in.AccountAbstractionKeeper,
)
var (
minGasPrices sdk.DecCoins
feeTxValidator *ante.DeductFeeDecorator
)
if in.AccountKeeper != nil && in.BankKeeper != nil && in.Viper != nil {
minGasPricesStr := in.Viper.GetString(flagMinGasPricesV2)
minGasPrices, err = sdk.ParseDecCoins(minGasPricesStr)
if err != nil {
panic(fmt.Sprintf("invalid minimum gas prices: %v", err))
}
feeTxValidator = ante.NewDeductFeeDecorator(in.AccountKeeper, in.BankKeeper, in.FeeGrantKeeper, in.TxFeeChecker)
feeTxValidator.SetMinGasPrices(minGasPrices) // set min gas price in deduct fee decorator
}
return ModuleOutputs{
Module: NewAppModule(svd, feeTxValidator, in.ExtraTxValidators...),
BaseAppOption: newBaseAppOption(txConfig, in),
TxConfig: txConfig,
TxConfigOptions: txConfigOptions,
}
}
// newBaseAppOption returns baseapp option that sets the ante handler and post handler
// and set the tx encoder and decoder on baseapp.
func newBaseAppOption(txConfig client.TxConfig, in ModuleInputs) func(app *baseapp.BaseApp) {
return func(app *baseapp.BaseApp) {
// AnteHandlers
if !in.Config.SkipAnteHandler {
anteHandler, err := newAnteHandler(txConfig, in)
@ -145,20 +184,6 @@ func ProvideModule(in ModuleInputs) ModuleOutputs {
app.SetTxDecoder(txConfig.TxDecoder())
app.SetTxEncoder(txConfig.TxEncoder())
}
svd := ante.NewSigVerificationDecorator(
in.AccountKeeper,
txConfig.SignModeHandler(),
ante.DefaultSigVerificationGasConsumer,
in.AccountAbstractionKeeper,
)
return ModuleOutputs{
Module: NewAppModule(svd, in.ExtraTxValidators...),
TxConfig: txConfig,
TxConfigOptions: txConfigOptions,
BaseAppOption: baseAppOption,
}
}
func newAnteHandler(txConfig client.TxConfig, in ModuleInputs) (sdk.AnteHandler, error) {

View File

@ -19,6 +19,7 @@ var (
// This module is only useful for chains using server/v2. Ante/Post handlers are setup via baseapp options in depinject.
type AppModule struct {
sigVerification ante.SigVerificationDecorator
feeTxValidator *ante.DeductFeeDecorator
// txValidators contains tx validator that can be injected into the module via depinject.
// tx validators should be module based, but it can happen that you do not want to create a new module
// and simply depinject-it.
@ -28,10 +29,12 @@ type AppModule struct {
// NewAppModule creates a new AppModule object.
func NewAppModule(
sigVerification ante.SigVerificationDecorator,
feeTxValidator *ante.DeductFeeDecorator,
txValidators ...appmodulev2.TxValidator[transaction.Tx],
) AppModule {
return AppModule{
sigVerification: sigVerification,
feeTxValidator: feeTxValidator,
txValidators: txValidators,
}
}
@ -50,5 +53,9 @@ func (a AppModule) TxValidator(ctx context.Context, tx transaction.Tx) error {
}
}
if err := a.feeTxValidator.ValidateTx(ctx, tx); err != nil {
return err
}
return a.sigVerification.ValidateTx(ctx, tx)
}