github.com/cosmos/cosmos-sdk@v0.50.10/client/cmd.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/cockroachdb/errors"
    10  	"github.com/spf13/cobra"
    11  	"github.com/spf13/pflag"
    12  	"golang.org/x/exp/slices"
    13  	"google.golang.org/grpc"
    14  	"google.golang.org/grpc/credentials"
    15  	"google.golang.org/grpc/credentials/insecure"
    16  
    17  	signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
    18  
    19  	"github.com/cosmos/cosmos-sdk/client/flags"
    20  	"github.com/cosmos/cosmos-sdk/crypto/keyring"
    21  	sdk "github.com/cosmos/cosmos-sdk/types"
    22  )
    23  
    24  // ClientContextKey defines the context key used to retrieve a client.Context from
    25  // a command's Context.
    26  const ClientContextKey = sdk.ContextKey("client.context")
    27  
    28  // SetCmdClientContextHandler is to be used in a command pre-hook execution to
    29  // read flags that populate a Context and sets that to the command's Context.
    30  func SetCmdClientContextHandler(clientCtx Context, cmd *cobra.Command) (err error) {
    31  	clientCtx, err = ReadPersistentCommandFlags(clientCtx, cmd.Flags())
    32  	if err != nil {
    33  		return err
    34  	}
    35  
    36  	return SetCmdClientContext(cmd, clientCtx)
    37  }
    38  
    39  // ValidateCmd returns unknown command error or Help display if help flag set
    40  func ValidateCmd(cmd *cobra.Command, args []string) error {
    41  	var unknownCmd string
    42  	var skipNext bool
    43  
    44  	for _, arg := range args {
    45  		// search for help flag
    46  		if arg == "--help" || arg == "-h" {
    47  			return cmd.Help()
    48  		}
    49  
    50  		// check if the current arg is a flag
    51  		switch {
    52  		case len(arg) > 0 && (arg[0] == '-'):
    53  			// the next arg should be skipped if the current arg is a
    54  			// flag and does not use "=" to assign the flag's value
    55  			if !strings.Contains(arg, "=") {
    56  				skipNext = true
    57  			} else {
    58  				skipNext = false
    59  			}
    60  		case skipNext:
    61  			// skip current arg
    62  			skipNext = false
    63  		case unknownCmd == "":
    64  			// unknown command found
    65  			// continue searching for help flag
    66  			unknownCmd = arg
    67  		}
    68  	}
    69  
    70  	// return the help screen if no unknown command is found
    71  	if unknownCmd != "" {
    72  		err := fmt.Sprintf("unknown command \"%s\" for \"%s\"", unknownCmd, cmd.CalledAs())
    73  
    74  		// build suggestions for unknown argument
    75  		if suggestions := cmd.SuggestionsFor(unknownCmd); len(suggestions) > 0 {
    76  			err += "\n\nDid you mean this?\n"
    77  			for _, s := range suggestions {
    78  				err += fmt.Sprintf("\t%v\n", s)
    79  			}
    80  		}
    81  		return errors.New(err)
    82  	}
    83  
    84  	return cmd.Help()
    85  }
    86  
    87  // ReadPersistentCommandFlags returns a Context with fields set for "persistent"
    88  // or common flags that do not necessarily change with context.
    89  //
    90  // Note, the provided clientCtx may have field pre-populated. The following order
    91  // of precedence occurs:
    92  //
    93  // - client.Context field not pre-populated & flag not set: uses default flag value
    94  // - client.Context field not pre-populated & flag set: uses set flag value
    95  // - client.Context field pre-populated & flag not set: uses pre-populated value
    96  // - client.Context field pre-populated & flag set: uses set flag value
    97  func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) {
    98  	if clientCtx.OutputFormat == "" || flagSet.Changed(flags.FlagOutput) {
    99  		output, _ := flagSet.GetString(flags.FlagOutput)
   100  		clientCtx = clientCtx.WithOutputFormat(output)
   101  	}
   102  
   103  	if clientCtx.HomeDir == "" || flagSet.Changed(flags.FlagHome) {
   104  		homeDir, _ := flagSet.GetString(flags.FlagHome)
   105  		clientCtx = clientCtx.WithHomeDir(homeDir)
   106  	}
   107  
   108  	if !clientCtx.Simulate || flagSet.Changed(flags.FlagDryRun) {
   109  		dryRun, _ := flagSet.GetBool(flags.FlagDryRun)
   110  		clientCtx = clientCtx.WithSimulation(dryRun)
   111  	}
   112  
   113  	if clientCtx.KeyringDir == "" || flagSet.Changed(flags.FlagKeyringDir) {
   114  		keyringDir, _ := flagSet.GetString(flags.FlagKeyringDir)
   115  
   116  		// The keyring directory is optional and falls back to the home directory
   117  		// if omitted.
   118  		if keyringDir == "" {
   119  			keyringDir = clientCtx.HomeDir
   120  		}
   121  
   122  		clientCtx = clientCtx.WithKeyringDir(keyringDir)
   123  	}
   124  
   125  	if clientCtx.ChainID == "" || flagSet.Changed(flags.FlagChainID) {
   126  		chainID, _ := flagSet.GetString(flags.FlagChainID)
   127  		clientCtx = clientCtx.WithChainID(chainID)
   128  	}
   129  
   130  	if clientCtx.Keyring == nil || flagSet.Changed(flags.FlagKeyringBackend) {
   131  		keyringBackend, _ := flagSet.GetString(flags.FlagKeyringBackend)
   132  
   133  		if keyringBackend != "" {
   134  			kr, err := NewKeyringFromBackend(clientCtx, keyringBackend)
   135  			if err != nil {
   136  				return clientCtx, err
   137  			}
   138  
   139  			clientCtx = clientCtx.WithKeyring(kr)
   140  		}
   141  	}
   142  
   143  	if clientCtx.Client == nil || flagSet.Changed(flags.FlagNode) {
   144  		rpcURI, _ := flagSet.GetString(flags.FlagNode)
   145  		if rpcURI != "" {
   146  			clientCtx = clientCtx.WithNodeURI(rpcURI)
   147  
   148  			client, err := NewClientFromNode(rpcURI)
   149  			if err != nil {
   150  				return clientCtx, err
   151  			}
   152  
   153  			clientCtx = clientCtx.WithClient(client)
   154  		}
   155  	}
   156  
   157  	if clientCtx.GRPCClient == nil || flagSet.Changed(flags.FlagGRPC) {
   158  		grpcURI, _ := flagSet.GetString(flags.FlagGRPC)
   159  		if grpcURI != "" {
   160  			var dialOpts []grpc.DialOption
   161  
   162  			useInsecure, _ := flagSet.GetBool(flags.FlagGRPCInsecure)
   163  			if useInsecure {
   164  				dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
   165  			} else {
   166  				dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
   167  					MinVersion: tls.VersionTLS12,
   168  				})))
   169  			}
   170  
   171  			grpcClient, err := grpc.Dial(grpcURI, dialOpts...) // nolint:staticcheck // grpc.Dial is deprecated but we still use it
   172  			if err != nil {
   173  				return Context{}, err
   174  			}
   175  			clientCtx = clientCtx.WithGRPCClient(grpcClient)
   176  		}
   177  	}
   178  
   179  	return clientCtx, nil
   180  }
   181  
   182  // readQueryCommandFlags returns an updated Context with fields set based on flags
   183  // defined in AddQueryFlagsToCmd. An error is returned if any flag query fails.
   184  //
   185  // Note, the provided clientCtx may have field pre-populated. The following order
   186  // of precedence occurs:
   187  //
   188  // - client.Context field not pre-populated & flag not set: uses default flag value
   189  // - client.Context field not pre-populated & flag set: uses set flag value
   190  // - client.Context field pre-populated & flag not set: uses pre-populated value
   191  // - client.Context field pre-populated & flag set: uses set flag value
   192  func readQueryCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) {
   193  	if clientCtx.Height == 0 || flagSet.Changed(flags.FlagHeight) {
   194  		height, _ := flagSet.GetInt64(flags.FlagHeight)
   195  		clientCtx = clientCtx.WithHeight(height)
   196  	}
   197  
   198  	if !clientCtx.UseLedger || flagSet.Changed(flags.FlagUseLedger) {
   199  		useLedger, _ := flagSet.GetBool(flags.FlagUseLedger)
   200  		clientCtx = clientCtx.WithUseLedger(useLedger)
   201  	}
   202  
   203  	return ReadPersistentCommandFlags(clientCtx, flagSet)
   204  }
   205  
   206  // readTxCommandFlags returns an updated Context with fields set based on flags
   207  // defined in AddTxFlagsToCmd. An error is returned if any flag query fails.
   208  //
   209  // Note, the provided clientCtx may have field pre-populated. The following order
   210  // of precedence occurs:
   211  //
   212  // - client.Context field not pre-populated & flag not set: uses default flag value
   213  // - client.Context field not pre-populated & flag set: uses set flag value
   214  // - client.Context field pre-populated & flag not set: uses pre-populated value
   215  // - client.Context field pre-populated & flag set: uses set flag value
   216  func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) {
   217  	clientCtx, err := ReadPersistentCommandFlags(clientCtx, flagSet)
   218  	if err != nil {
   219  		return clientCtx, err
   220  	}
   221  
   222  	if !clientCtx.GenerateOnly || flagSet.Changed(flags.FlagGenerateOnly) {
   223  		genOnly, _ := flagSet.GetBool(flags.FlagGenerateOnly)
   224  		clientCtx = clientCtx.WithGenerateOnly(genOnly)
   225  	}
   226  
   227  	if !clientCtx.Offline || flagSet.Changed(flags.FlagOffline) {
   228  		offline, _ := flagSet.GetBool(flags.FlagOffline)
   229  		clientCtx = clientCtx.WithOffline(offline)
   230  	}
   231  
   232  	if !clientCtx.UseLedger || flagSet.Changed(flags.FlagUseLedger) {
   233  		useLedger, _ := flagSet.GetBool(flags.FlagUseLedger)
   234  		clientCtx = clientCtx.WithUseLedger(useLedger)
   235  	}
   236  
   237  	if clientCtx.BroadcastMode == "" || flagSet.Changed(flags.FlagBroadcastMode) {
   238  		bMode, _ := flagSet.GetString(flags.FlagBroadcastMode)
   239  		clientCtx = clientCtx.WithBroadcastMode(bMode)
   240  	}
   241  
   242  	if !clientCtx.SkipConfirm || flagSet.Changed(flags.FlagSkipConfirmation) {
   243  		skipConfirm, _ := flagSet.GetBool(flags.FlagSkipConfirmation)
   244  		clientCtx = clientCtx.WithSkipConfirmation(skipConfirm)
   245  	}
   246  
   247  	if clientCtx.SignModeStr == "" || flagSet.Changed(flags.FlagSignMode) {
   248  		signModeStr, _ := flagSet.GetString(flags.FlagSignMode)
   249  		clientCtx = clientCtx.WithSignModeStr(signModeStr)
   250  	}
   251  
   252  	if clientCtx.FeePayer == nil || flagSet.Changed(flags.FlagFeePayer) {
   253  		payer, _ := flagSet.GetString(flags.FlagFeePayer)
   254  
   255  		if payer != "" {
   256  			payerAcc, err := sdk.AccAddressFromBech32(payer)
   257  			if err != nil {
   258  				return clientCtx, err
   259  			}
   260  
   261  			clientCtx = clientCtx.WithFeePayerAddress(payerAcc)
   262  		}
   263  	}
   264  
   265  	if clientCtx.FeeGranter == nil || flagSet.Changed(flags.FlagFeeGranter) {
   266  		granter, _ := flagSet.GetString(flags.FlagFeeGranter)
   267  
   268  		if granter != "" {
   269  			granterAcc, err := sdk.AccAddressFromBech32(granter)
   270  			if err != nil {
   271  				return clientCtx, err
   272  			}
   273  
   274  			clientCtx = clientCtx.WithFeeGranterAddress(granterAcc)
   275  		}
   276  	}
   277  
   278  	if clientCtx.From == "" || flagSet.Changed(flags.FlagFrom) {
   279  		from, _ := flagSet.GetString(flags.FlagFrom)
   280  		fromAddr, fromName, keyType, err := GetFromFields(clientCtx, clientCtx.Keyring, from)
   281  		if err != nil {
   282  			return clientCtx, fmt.Errorf("failed to convert address field to address: %w", err)
   283  		}
   284  
   285  		clientCtx = clientCtx.WithFrom(from).WithFromAddress(fromAddr).WithFromName(fromName)
   286  
   287  		if keyType == keyring.TypeLedger && clientCtx.SignModeStr == flags.SignModeTextual {
   288  			if !slices.Contains(clientCtx.TxConfig.SignModeHandler().SupportedModes(), signingv1beta1.SignMode_SIGN_MODE_TEXTUAL) {
   289  				return clientCtx, fmt.Errorf("SIGN_MODE_TEXTUAL is not available")
   290  			}
   291  		}
   292  
   293  		// If the `from` signer account is a ledger key, we need to use
   294  		// SIGN_MODE_AMINO_JSON, because ledger doesn't support proto yet.
   295  		// ref: https://github.com/cosmos/cosmos-sdk/issues/8109
   296  		if keyType == keyring.TypeLedger &&
   297  			clientCtx.SignModeStr != flags.SignModeLegacyAminoJSON &&
   298  			clientCtx.SignModeStr != flags.SignModeTextual &&
   299  			!clientCtx.LedgerHasProtobuf {
   300  			fmt.Println("Default sign-mode 'direct' not supported by Ledger, using sign-mode 'amino-json'.")
   301  			clientCtx = clientCtx.WithSignModeStr(flags.SignModeLegacyAminoJSON)
   302  		}
   303  	}
   304  
   305  	if !clientCtx.IsAux || flagSet.Changed(flags.FlagAux) {
   306  		isAux, _ := flagSet.GetBool(flags.FlagAux)
   307  		clientCtx = clientCtx.WithAux(isAux)
   308  		if isAux {
   309  			// If the user didn't explicitly set an --output flag, use JSON by default.
   310  			if clientCtx.OutputFormat == "" || !flagSet.Changed(flags.FlagOutput) {
   311  				clientCtx = clientCtx.WithOutputFormat(flags.OutputFormatJSON)
   312  			}
   313  
   314  			// If the user didn't explicitly set a --sign-mode flag, use DIRECT_AUX by default.
   315  			if clientCtx.SignModeStr == "" || !flagSet.Changed(flags.FlagSignMode) {
   316  				clientCtx = clientCtx.WithSignModeStr(flags.SignModeDirectAux)
   317  			}
   318  		}
   319  	}
   320  
   321  	return clientCtx, nil
   322  }
   323  
   324  // GetClientQueryContext returns a Context from a command with fields set based on flags
   325  // defined in AddQueryFlagsToCmd. An error is returned if any flag query fails.
   326  //
   327  // - client.Context field not pre-populated & flag not set: uses default flag value
   328  // - client.Context field not pre-populated & flag set: uses set flag value
   329  // - client.Context field pre-populated & flag not set: uses pre-populated value
   330  // - client.Context field pre-populated & flag set: uses set flag value
   331  func GetClientQueryContext(cmd *cobra.Command) (Context, error) {
   332  	ctx := GetClientContextFromCmd(cmd)
   333  	return readQueryCommandFlags(ctx, cmd.Flags())
   334  }
   335  
   336  // GetClientTxContext returns a Context from a command with fields set based on flags
   337  // defined in AddTxFlagsToCmd. An error is returned if any flag query fails.
   338  //
   339  // - client.Context field not pre-populated & flag not set: uses default flag value
   340  // - client.Context field not pre-populated & flag set: uses set flag value
   341  // - client.Context field pre-populated & flag not set: uses pre-populated value
   342  // - client.Context field pre-populated & flag set: uses set flag value
   343  func GetClientTxContext(cmd *cobra.Command) (Context, error) {
   344  	ctx := GetClientContextFromCmd(cmd)
   345  	return readTxCommandFlags(ctx, cmd.Flags())
   346  }
   347  
   348  // GetClientContextFromCmd returns a Context from a command or an empty Context
   349  // if it has not been set.
   350  func GetClientContextFromCmd(cmd *cobra.Command) Context {
   351  	if v := cmd.Context().Value(ClientContextKey); v != nil {
   352  		clientCtxPtr := v.(*Context)
   353  		return *clientCtxPtr
   354  	}
   355  
   356  	return Context{}
   357  }
   358  
   359  // SetCmdClientContext sets a command's Context value to the provided argument.
   360  // If the context has not been set, set the given context as the default.
   361  func SetCmdClientContext(cmd *cobra.Command, clientCtx Context) error {
   362  	cmdCtx := cmd.Context()
   363  	if cmdCtx == nil {
   364  		cmdCtx = context.Background()
   365  	}
   366  
   367  	v := cmd.Context().Value(ClientContextKey)
   368  	if clientCtxPtr, ok := v.(*Context); ok {
   369  		*clientCtxPtr = clientCtx
   370  	} else {
   371  		cmd.SetContext(context.WithValue(cmdCtx, ClientContextKey, &clientCtx))
   372  	}
   373  
   374  	return nil
   375  }