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 }