feat(server/v2): add min gas price and check with tx fee (#21173)
Co-authored-by: Julien Robert <julien@rbrt.fr>
This commit is contained in:
parent
78de1a2549
commit
81a225e6a2
@ -38,13 +38,14 @@ func AddCommands[T transaction.Tx](
|
||||
rootCmd *cobra.Command,
|
||||
newApp AppCreator[T],
|
||||
logger log.Logger,
|
||||
serverCfg ServerConfig,
|
||||
components ...ServerComponent[T],
|
||||
) error {
|
||||
if len(components) == 0 {
|
||||
return errors.New("no components provided")
|
||||
}
|
||||
|
||||
server := NewServer(logger, components...)
|
||||
server := NewServer(logger, serverCfg, components...)
|
||||
originalPersistentPreRunE := rootCmd.PersistentPreRunE
|
||||
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
// set the default command outputs
|
||||
|
||||
@ -7,6 +7,18 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// ServerConfig defines configuration for the server component.
|
||||
type ServerConfig struct {
|
||||
MinGasPrices string `mapstructure:"minimum-gas-prices" toml:"minimum-gas-prices" comment:"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)."`
|
||||
}
|
||||
|
||||
// DefaultServerConfig returns the default config of server component
|
||||
func DefaultServerConfig() ServerConfig {
|
||||
return ServerConfig{
|
||||
MinGasPrices: "0stake",
|
||||
}
|
||||
}
|
||||
|
||||
// ReadConfig returns a viper instance of the config file
|
||||
func ReadConfig(configPath string) (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
// Package serverv2 defines constants for server configuration flags and output formats.
|
||||
package serverv2
|
||||
|
||||
import "fmt"
|
||||
|
||||
// start flags are prefixed with the server name
|
||||
// this allows viper to properly bind the flags
|
||||
func prefix(f string) string {
|
||||
return fmt.Sprintf("%s.%s", serverName, f)
|
||||
}
|
||||
|
||||
var FlagMinGasPrices = prefix("minimum-gas-prices")
|
||||
|
||||
const (
|
||||
// FlagHome specifies the home directory flag.
|
||||
FlagHome = "home"
|
||||
|
||||
@ -56,25 +56,32 @@ type CLIConfig struct {
|
||||
Txs []*cobra.Command
|
||||
}
|
||||
|
||||
const (
|
||||
serverName = "server"
|
||||
)
|
||||
|
||||
var _ ServerComponent[transaction.Tx] = (*Server[transaction.Tx])(nil)
|
||||
|
||||
type Server[T transaction.Tx] struct {
|
||||
logger log.Logger
|
||||
components []ServerComponent[T]
|
||||
config ServerConfig
|
||||
}
|
||||
|
||||
func NewServer[T transaction.Tx](
|
||||
logger log.Logger,
|
||||
config ServerConfig,
|
||||
components ...ServerComponent[T],
|
||||
) *Server[T] {
|
||||
return &Server[T]{
|
||||
logger: logger,
|
||||
config: config,
|
||||
components: components,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server[T]) Name() string {
|
||||
return "server"
|
||||
return serverName
|
||||
}
|
||||
|
||||
// Start starts all components concurrently.
|
||||
@ -151,9 +158,19 @@ func (s *Server[T]) CLICommands() CLIConfig {
|
||||
return commands
|
||||
}
|
||||
|
||||
// Config returns config of the server component
|
||||
func (s *Server[T]) Config() ServerConfig {
|
||||
return s.config
|
||||
}
|
||||
|
||||
// Configs returns all configs of all server components.
|
||||
func (s *Server[T]) Configs() map[string]any {
|
||||
cfgs := make(map[string]any)
|
||||
|
||||
// add server component config
|
||||
cfgs[s.Name()] = s.config
|
||||
|
||||
// add other components' config
|
||||
for _, mod := range s.components {
|
||||
if configmod, ok := mod.(HasConfig); ok {
|
||||
cfg := configmod.Config()
|
||||
@ -164,9 +181,22 @@ func (s *Server[T]) Configs() map[string]any {
|
||||
return cfgs
|
||||
}
|
||||
|
||||
func (s *Server[T]) StartCmdFlags() *pflag.FlagSet {
|
||||
flags := pflag.NewFlagSet(s.Name(), pflag.ExitOnError)
|
||||
flags.String(FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)")
|
||||
return flags
|
||||
}
|
||||
|
||||
// Init initializes all server components with the provided application, configuration, and logger.
|
||||
// It returns an error if any component fails to initialize.
|
||||
func (s *Server[T]) Init(appI AppI[T], v *viper.Viper, logger log.Logger) error {
|
||||
cfg := s.config
|
||||
if v != nil {
|
||||
if err := UnmarshalSubConfig(v, s.Name(), &cfg); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var components []ServerComponent[T]
|
||||
for _, mod := range s.components {
|
||||
mod := mod
|
||||
@ -177,6 +207,7 @@ func (s *Server[T]) Init(appI AppI[T], v *viper.Viper, logger log.Logger) error
|
||||
components = append(components, mod)
|
||||
}
|
||||
|
||||
s.config = cfg
|
||||
s.components = components
|
||||
return nil
|
||||
}
|
||||
@ -217,6 +248,11 @@ func (s *Server[T]) WriteConfig(configPath string) error {
|
||||
// StartFlags returns all flags of all server components.
|
||||
func (s *Server[T]) StartFlags() []*pflag.FlagSet {
|
||||
flags := []*pflag.FlagSet{}
|
||||
|
||||
// add server component flags
|
||||
flags = append(flags, s.StartCmdFlags())
|
||||
|
||||
// add other components' start cmd flags
|
||||
for _, mod := range s.components {
|
||||
if startmod, ok := mod.(HasStartFlags); ok {
|
||||
flags = append(flags, startmod.StartCmdFlags())
|
||||
|
||||
@ -65,6 +65,7 @@ func TestServer(t *testing.T) {
|
||||
|
||||
server := serverv2.NewServer(
|
||||
logger,
|
||||
serverv2.DefaultServerConfig(),
|
||||
grpcServer,
|
||||
mockServer,
|
||||
)
|
||||
|
||||
4
server/v2/testdata/app.toml
vendored
4
server/v2/testdata/app.toml
vendored
@ -10,6 +10,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'
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user