From 1260f6703f5dc68073b7d506e9424326c3086f86 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 08:22:04 +0000 Subject: [PATCH] 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 --- server/v2/cometbft/go.mod | 4 +- server/v2/cometbft/go.sum | 8 +-- simapp/v2/go.mod | 4 +- simapp/v2/go.sum | 8 +-- simapp/v2/simdv2/cmd/commands.go | 1 + simapp/v2/simdv2/cmd/config.go | 19 ++++++ simapp/v2/simdv2/cmd/testnet.go | 10 +-- tests/systemtests/staking_test.go | 1 - tools/confix/data/v2-app.toml | 4 ++ tools/confix/migrations.go | 11 +-- x/auth/ante/fee.go | 108 +++++++++++++++++------------- x/auth/ante/fee_test.go | 5 ++ x/auth/ante/validator_tx_fee.go | 8 ++- x/auth/tx/config/depinject.go | 59 +++++++++++----- x/auth/tx/config/module.go | 7 ++ 15 files changed, 168 insertions(+), 89 deletions(-) diff --git a/server/v2/cometbft/go.mod b/server/v2/cometbft/go.mod index 7f131220d4df..e67c8dc473a8 100644 --- a/server/v2/cometbft/go.mod +++ b/server/v2/cometbft/go.mod @@ -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 diff --git a/server/v2/cometbft/go.sum b/server/v2/cometbft/go.sum index 64b4795ec574..bc6f46513293 100644 --- a/server/v2/cometbft/go.sum +++ b/server/v2/cometbft/go.sum @@ -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= diff --git a/simapp/v2/go.mod b/simapp/v2/go.mod index 5e65823aa15a..8ac8d316af90 100644 --- a/simapp/v2/go.mod +++ b/simapp/v2/go.mod @@ -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 diff --git a/simapp/v2/go.sum b/simapp/v2/go.sum index 9a7fe1c7a7dc..8c6a0793eafc 100644 --- a/simapp/v2/go.sum +++ b/simapp/v2/go.sum @@ -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= diff --git a/simapp/v2/simdv2/cmd/commands.go b/simapp/v2/simdv2/cmd/commands.go index b4d13aa7a78c..d9fa1fa54983 100644 --- a/simapp/v2/simdv2/cmd/commands.go +++ b/simapp/v2/simdv2/cmd/commands.go @@ -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), diff --git a/simapp/v2/simdv2/cmd/config.go b/simapp/v2/simdv2/cmd/config.go index 0794a8a42a79..c7f6b707c89c 100644 --- a/simapp/v2/simdv2/cmd/config.go +++ b/simapp/v2/simdv2/cmd/config.go @@ -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 +} diff --git a/simapp/v2/simdv2/cmd/testnet.go b/simapp/v2/simdv2/cmd/testnet.go index 91c1a30a12a6..5ec782389036 100644 --- a/simapp/v2/simdv2/cmd/testnet.go +++ b/simapp/v2/simdv2/cmd/testnet.go @@ -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 diff --git a/tests/systemtests/staking_test.go b/tests/systemtests/staking_test.go index 0b18408410df..f197bff1a0ac 100644 --- a/tests/systemtests/staking_test.go +++ b/tests/systemtests/staking_test.go @@ -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 diff --git a/tools/confix/data/v2-app.toml b/tools/confix/data/v2-app.toml index 6a93006eccfb..8f7f7f9940d2 100644 --- a/tools/confix/data/v2-app.toml +++ b/tools/confix/data/v2-app.toml @@ -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' diff --git a/tools/confix/migrations.go b/tools/confix/migrations.go index 4a500898bc04..437c628d7b25 100644 --- a/tools/confix/migrations.go +++ b/tools/confix/migrations.go @@ -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", diff --git a/x/auth/ante/fee.go b/x/auth/ante/fee.go index 298fae30a4cc..248d7163471b 100644 --- a/x/auth/ante/fee.go +++ b/x/auth/ante/fee.go @@ -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 - } - newCtx := ctx.WithPriority(priority) + if err := dfd.checkDeductFee(ctx, feeTx, fee); err != nil { + return 0, err + } - 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) } diff --git a/x/auth/ante/fee_test.go b/x/auth/ante/fee_test.go index 0c9fa299a5e4..6736f17aed99 100644 --- a/x/auth/ante/fee_test.go +++ b/x/auth/ante/fee_test.go @@ -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) diff --git a/x/auth/ante/validator_tx_fee.go b/x/auth/ante/validator_tx_fee.go index af4f02fed05f..421068175bea 100644 --- a/x/auth/ante/validator_tx_fee.go +++ b/x/auth/ante/validator_tx_fee.go @@ -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)) diff --git a/x/auth/tx/config/depinject.go b/x/auth/tx/config/depinject.go index bb573f044f6a..e4e43ac3d80f 100644 --- a/x/auth/tx/config/depinject.go +++ b/x/auth/tx/config/depinject.go @@ -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) { diff --git a/x/auth/tx/config/module.go b/x/auth/tx/config/module.go index 6e94e0e8cfac..f4a1207fa7b1 100644 --- a/x/auth/tx/config/module.go +++ b/x/auth/tx/config/module.go @@ -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) }