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(server/v2): add min gas price and check with tx fee (backport #21173) #21453

Merged
merged 2 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
213 changes: 213 additions & 0 deletions server/v2/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package serverv2

import (
"context"
"errors"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"cosmossdk.io/core/transaction"
"cosmossdk.io/log"
)

// Execute executes the root command of an application.
// It handles adding core CLI flags, specifically the logging flags.
func Execute(rootCmd *cobra.Command, envPrefix, defaultHome string) error {
rootCmd.PersistentFlags().String(FlagLogLevel, "info", "The logging level (trace|debug|info|warn|error|fatal|panic|disabled or '*:<level>,<key>:<level>')")
rootCmd.PersistentFlags().String(FlagLogFormat, "plain", "The logging format (json|plain)")
rootCmd.PersistentFlags().Bool(FlagLogNoColor, false, "Disable colored logs")
rootCmd.PersistentFlags().StringP(FlagHome, "", defaultHome, "directory for config and data")

// update the global viper with the root command's configuration
viper.SetEnvPrefix(envPrefix)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
viper.AutomaticEnv()

return rootCmd.Execute()
}

// AddCommands add the server commands to the root command
// It configure the config handling and the logger handling
func AddCommands[T transaction.Tx](
rootCmd *cobra.Command,
newApp AppCreator[T],

Check failure on line 39 in server/v2/commands.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: AppCreator
logger log.Logger,
serverCfg ServerConfig,
components ...ServerComponent[T],
) error {
if len(components) == 0 {
return errors.New("no components provided")
}

server := NewServer(logger, serverCfg, components...)
originalPersistentPreRunE := rootCmd.PersistentPreRunE
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
// set the default command outputs
cmd.SetOut(cmd.OutOrStdout())
cmd.SetErr(cmd.ErrOrStderr())

if err := configHandle(server, cmd); err != nil {
return err
}

// call the original PersistentPreRun(E) if it exists
if rootCmd.PersistentPreRun != nil {
rootCmd.PersistentPreRun(cmd, args)
return nil
}

return originalPersistentPreRunE(cmd, args)
}

cmds := server.CLICommands()
startCmd := createStartCommand(server, newApp)
startCmd.SetContext(rootCmd.Context())
cmds.Commands = append(cmds.Commands, startCmd)
rootCmd.AddCommand(cmds.Commands...)

if len(cmds.Queries) > 0 {
if queryCmd := findSubCommand(rootCmd, "query"); queryCmd != nil {
queryCmd.AddCommand(cmds.Queries...)
} else {
queryCmd := topLevelCmd(rootCmd.Context(), "query", "Querying subcommands")
queryCmd.Aliases = []string{"q"}
queryCmd.AddCommand(cmds.Queries...)
rootCmd.AddCommand(queryCmd)
}
}

if len(cmds.Txs) > 0 {
if txCmd := findSubCommand(rootCmd, "tx"); txCmd != nil {
txCmd.AddCommand(cmds.Txs...)
} else {
txCmd := topLevelCmd(rootCmd.Context(), "tx", "Transactions subcommands")
txCmd.AddCommand(cmds.Txs...)
rootCmd.AddCommand(txCmd)
}
}

return nil
}

// createStartCommand creates the start command for the application.
func createStartCommand[T transaction.Tx](
server *Server[T],
newApp AppCreator[T],

Check failure on line 101 in server/v2/commands.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: AppCreator
) *cobra.Command {
flags := server.StartFlags()

cmd := &cobra.Command{
Use: "start",
Short: "Run the application",
RunE: func(cmd *cobra.Command, args []string) error {
v := GetViperFromCmd(cmd)

Check failure on line 109 in server/v2/commands.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: GetViperFromCmd
l := GetLoggerFromCmd(cmd)

Check failure on line 110 in server/v2/commands.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: GetLoggerFromCmd
if err := v.BindPFlags(cmd.Flags()); err != nil {
return err
}

if err := server.Init(newApp(l, v), v, l); err != nil {
return err
}

ctx, cancelFn := context.WithCancel(cmd.Context())
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigCh
cancelFn()
cmd.Printf("caught %s signal\n", sig.String())

if err := server.Stop(ctx); err != nil {
cmd.PrintErrln("failed to stop servers:", err)
}
}()

if err := server.Start(ctx); err != nil {
return err
}

return nil
},
}

// add the start flags to the command
for _, startFlags := range flags {
cmd.Flags().AddFlagSet(startFlags)
}

return cmd
}

// configHandle writes the default config to the home directory if it does not exist and sets the server context
func configHandle[T transaction.Tx](s *Server[T], cmd *cobra.Command) error {
home, err := cmd.Flags().GetString(FlagHome)
if err != nil {
return err
}

configDir := filepath.Join(home, "config")

// we need to check app.toml as the config folder can already exist for the client.toml
if _, err := os.Stat(filepath.Join(configDir, "app.toml")); os.IsNotExist(err) {
if err = s.WriteConfig(configDir); err != nil {
return err
}
}

v, err := ReadConfig(configDir)
if err != nil {
return err
}

if err := v.BindPFlags(cmd.Flags()); err != nil {
return err
}

log, err := NewLogger(v, cmd.OutOrStdout())

Check failure on line 173 in server/v2/commands.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: NewLogger
if err != nil {
return err
}

return SetCmdServerContext(cmd, v, log)

Check failure on line 178 in server/v2/commands.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: SetCmdServerContext
}

// findSubCommand finds a sub-command of the provided command whose Use
// string is or begins with the provided subCmdName.
// It verifies the command's aliases as well.
func findSubCommand(cmd *cobra.Command, subCmdName string) *cobra.Command {
for _, subCmd := range cmd.Commands() {
use := subCmd.Use
if use == subCmdName || strings.HasPrefix(use, subCmdName+" ") {
return subCmd
}

for _, alias := range subCmd.Aliases {
if alias == subCmdName || strings.HasPrefix(alias, subCmdName+" ") {
return subCmd
}
}
}
return nil
}

// topLevelCmd creates a new top-level command with the provided name and
// description. The command will have DisableFlagParsing set to false and
// SuggestionsMinimumDistance set to 2.
func topLevelCmd(ctx context.Context, use, short string) *cobra.Command {
cmd := &cobra.Command{
Use: use,
Short: short,
DisableFlagParsing: false,
SuggestionsMinimumDistance: 2,
}
cmd.SetContext(ctx)

return cmd
}
73 changes: 73 additions & 0 deletions server/v2/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package serverv2

import (
"fmt"

"github.com/mitchellh/mapstructure"
"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()
v.SetConfigType("toml")
v.SetConfigName("config")
v.AddConfigPath(configPath)
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config: %s: %w", configPath, err)
}

v.SetConfigName("app")
if err := v.MergeInConfig(); err != nil {
return nil, fmt.Errorf("failed to merge configuration: %w", err)
}

v.WatchConfig()

return v, nil
}

// UnmarshalSubConfig unmarshals the given subconfig from the viper instance.
// It unmarshals the config, env, flags into the target struct.
// Use this instead of viper.Sub because viper does not unmarshal flags.
func UnmarshalSubConfig(v *viper.Viper, subName string, target any) error {
var sub any
for k, val := range v.AllSettings() {
if k == subName {
sub = val
break
}
}
Fixed Show fixed Hide fixed

// Create a new decoder with custom decoding options
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
),
Result: target,
WeaklyTypedInput: true,
})
if err != nil {
return fmt.Errorf("failed to create decoder: %w", err)
}

// Decode the sub-configuration
if err := decoder.Decode(sub); err != nil {
return fmt.Errorf("failed to decode sub-configuration: %w", err)
}

return nil
}
25 changes: 25 additions & 0 deletions server/v2/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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"

FlagLogLevel = "log_level" // Sets the logging level
FlagLogFormat = "log_format" // Specifies the log output format
FlagLogNoColor = "log_no_color" // Disables colored log output
FlagTrace = "trace" // Enables trace-level logging

// OutputFormatJSON defines the JSON output format option.
OutputFormatJSON = "json"
)
Loading
Loading