<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: #10264 This PR makes 2 changes in the CLI: ### 1. Add an `--aux` flag to all tx command. ```bash simd tx bank send <from> <to> <amount> --aux (optional: --tip <tipAmt> --tipper <tipper>) ``` This will print an AuxSignerData instead of broadcasting the tx. ### 2. Add a new `aux-to-fee` subcommand ```bash simd tx aux-to-fee <aux_signer_data.json> ``` This takes the output of the previous command, and broadcasts a 2-signer tx. <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
335 lines
11 KiB
Go
335 lines
11 KiB
Go
package client
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
"github.com/tendermint/tendermint/libs/cli"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
// ClientContextKey defines the context key used to retrieve a client.Context from
|
|
// a command's Context.
|
|
const ClientContextKey = sdk.ContextKey("client.context")
|
|
|
|
// SetCmdClientContextHandler is to be used in a command pre-hook execution to
|
|
// read flags that populate a Context and sets that to the command's Context.
|
|
func SetCmdClientContextHandler(clientCtx Context, cmd *cobra.Command) (err error) {
|
|
clientCtx, err = ReadPersistentCommandFlags(clientCtx, cmd.Flags())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return SetCmdClientContext(cmd, clientCtx)
|
|
}
|
|
|
|
// ValidateCmd returns unknown command error or Help display if help flag set
|
|
func ValidateCmd(cmd *cobra.Command, args []string) error {
|
|
var unknownCmd string
|
|
var skipNext bool
|
|
|
|
for _, arg := range args {
|
|
// search for help flag
|
|
if arg == "--help" || arg == "-h" {
|
|
return cmd.Help()
|
|
}
|
|
|
|
// check if the current arg is a flag
|
|
switch {
|
|
case len(arg) > 0 && (arg[0] == '-'):
|
|
// the next arg should be skipped if the current arg is a
|
|
// flag and does not use "=" to assign the flag's value
|
|
if !strings.Contains(arg, "=") {
|
|
skipNext = true
|
|
} else {
|
|
skipNext = false
|
|
}
|
|
case skipNext:
|
|
// skip current arg
|
|
skipNext = false
|
|
case unknownCmd == "":
|
|
// unknown command found
|
|
// continue searching for help flag
|
|
unknownCmd = arg
|
|
}
|
|
}
|
|
|
|
// return the help screen if no unknown command is found
|
|
if unknownCmd != "" {
|
|
err := fmt.Sprintf("unknown command \"%s\" for \"%s\"", unknownCmd, cmd.CalledAs())
|
|
|
|
// build suggestions for unknown argument
|
|
if suggestions := cmd.SuggestionsFor(unknownCmd); len(suggestions) > 0 {
|
|
err += "\n\nDid you mean this?\n"
|
|
for _, s := range suggestions {
|
|
err += fmt.Sprintf("\t%v\n", s)
|
|
}
|
|
}
|
|
return errors.New(err)
|
|
}
|
|
|
|
return cmd.Help()
|
|
}
|
|
|
|
// ReadPersistentCommandFlags returns a Context with fields set for "persistent"
|
|
// or common flags that do not necessarily change with context.
|
|
//
|
|
// Note, the provided clientCtx may have field pre-populated. The following order
|
|
// of precedence occurs:
|
|
//
|
|
// - client.Context field not pre-populated & flag not set: uses default flag value
|
|
// - client.Context field not pre-populated & flag set: uses set flag value
|
|
// - client.Context field pre-populated & flag not set: uses pre-populated value
|
|
// - client.Context field pre-populated & flag set: uses set flag value
|
|
func ReadPersistentCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) {
|
|
if clientCtx.OutputFormat == "" || flagSet.Changed(cli.OutputFlag) {
|
|
output, _ := flagSet.GetString(cli.OutputFlag)
|
|
clientCtx = clientCtx.WithOutputFormat(output)
|
|
}
|
|
|
|
if clientCtx.HomeDir == "" || flagSet.Changed(flags.FlagHome) {
|
|
homeDir, _ := flagSet.GetString(flags.FlagHome)
|
|
clientCtx = clientCtx.WithHomeDir(homeDir)
|
|
}
|
|
|
|
if !clientCtx.Simulate || flagSet.Changed(flags.FlagDryRun) {
|
|
dryRun, _ := flagSet.GetBool(flags.FlagDryRun)
|
|
clientCtx = clientCtx.WithSimulation(dryRun)
|
|
}
|
|
|
|
if clientCtx.KeyringDir == "" || flagSet.Changed(flags.FlagKeyringDir) {
|
|
keyringDir, _ := flagSet.GetString(flags.FlagKeyringDir)
|
|
|
|
// The keyring directory is optional and falls back to the home directory
|
|
// if omitted.
|
|
if keyringDir == "" {
|
|
keyringDir = clientCtx.HomeDir
|
|
}
|
|
|
|
clientCtx = clientCtx.WithKeyringDir(keyringDir)
|
|
}
|
|
|
|
if clientCtx.ChainID == "" || flagSet.Changed(flags.FlagChainID) {
|
|
chainID, _ := flagSet.GetString(flags.FlagChainID)
|
|
clientCtx = clientCtx.WithChainID(chainID)
|
|
}
|
|
|
|
if clientCtx.Keyring == nil || flagSet.Changed(flags.FlagKeyringBackend) {
|
|
keyringBackend, _ := flagSet.GetString(flags.FlagKeyringBackend)
|
|
|
|
if keyringBackend != "" {
|
|
kr, err := NewKeyringFromBackend(clientCtx, keyringBackend)
|
|
if err != nil {
|
|
return clientCtx, err
|
|
}
|
|
|
|
clientCtx = clientCtx.WithKeyring(kr)
|
|
}
|
|
}
|
|
|
|
if clientCtx.Client == nil || flagSet.Changed(flags.FlagNode) {
|
|
rpcURI, _ := flagSet.GetString(flags.FlagNode)
|
|
if rpcURI != "" {
|
|
clientCtx = clientCtx.WithNodeURI(rpcURI)
|
|
|
|
client, err := NewClientFromNode(rpcURI)
|
|
if err != nil {
|
|
return clientCtx, err
|
|
}
|
|
|
|
clientCtx = clientCtx.WithClient(client)
|
|
}
|
|
}
|
|
|
|
return clientCtx, nil
|
|
}
|
|
|
|
// readQueryCommandFlags returns an updated Context with fields set based on flags
|
|
// defined in AddQueryFlagsToCmd. An error is returned if any flag query fails.
|
|
//
|
|
// Note, the provided clientCtx may have field pre-populated. The following order
|
|
// of precedence occurs:
|
|
//
|
|
// - client.Context field not pre-populated & flag not set: uses default flag value
|
|
// - client.Context field not pre-populated & flag set: uses set flag value
|
|
// - client.Context field pre-populated & flag not set: uses pre-populated value
|
|
// - client.Context field pre-populated & flag set: uses set flag value
|
|
func readQueryCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) {
|
|
if clientCtx.Height == 0 || flagSet.Changed(flags.FlagHeight) {
|
|
height, _ := flagSet.GetInt64(flags.FlagHeight)
|
|
clientCtx = clientCtx.WithHeight(height)
|
|
}
|
|
|
|
if !clientCtx.UseLedger || flagSet.Changed(flags.FlagUseLedger) {
|
|
useLedger, _ := flagSet.GetBool(flags.FlagUseLedger)
|
|
clientCtx = clientCtx.WithUseLedger(useLedger)
|
|
}
|
|
|
|
return ReadPersistentCommandFlags(clientCtx, flagSet)
|
|
}
|
|
|
|
// readTxCommandFlags returns an updated Context with fields set based on flags
|
|
// defined in AddTxFlagsToCmd. An error is returned if any flag query fails.
|
|
//
|
|
// Note, the provided clientCtx may have field pre-populated. The following order
|
|
// of precedence occurs:
|
|
//
|
|
// - client.Context field not pre-populated & flag not set: uses default flag value
|
|
// - client.Context field not pre-populated & flag set: uses set flag value
|
|
// - client.Context field pre-populated & flag not set: uses pre-populated value
|
|
// - client.Context field pre-populated & flag set: uses set flag value
|
|
func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, error) {
|
|
clientCtx, err := ReadPersistentCommandFlags(clientCtx, flagSet)
|
|
if err != nil {
|
|
return clientCtx, err
|
|
}
|
|
|
|
if !clientCtx.GenerateOnly || flagSet.Changed(flags.FlagGenerateOnly) {
|
|
genOnly, _ := flagSet.GetBool(flags.FlagGenerateOnly)
|
|
clientCtx = clientCtx.WithGenerateOnly(genOnly)
|
|
}
|
|
|
|
if !clientCtx.Offline || flagSet.Changed(flags.FlagOffline) {
|
|
offline, _ := flagSet.GetBool(flags.FlagOffline)
|
|
clientCtx = clientCtx.WithOffline(offline)
|
|
}
|
|
|
|
if !clientCtx.UseLedger || flagSet.Changed(flags.FlagUseLedger) {
|
|
useLedger, _ := flagSet.GetBool(flags.FlagUseLedger)
|
|
clientCtx = clientCtx.WithUseLedger(useLedger)
|
|
}
|
|
|
|
if clientCtx.BroadcastMode == "" || flagSet.Changed(flags.FlagBroadcastMode) {
|
|
bMode, _ := flagSet.GetString(flags.FlagBroadcastMode)
|
|
clientCtx = clientCtx.WithBroadcastMode(bMode)
|
|
}
|
|
|
|
if !clientCtx.SkipConfirm || flagSet.Changed(flags.FlagSkipConfirmation) {
|
|
skipConfirm, _ := flagSet.GetBool(flags.FlagSkipConfirmation)
|
|
clientCtx = clientCtx.WithSkipConfirmation(skipConfirm)
|
|
}
|
|
|
|
if clientCtx.SignModeStr == "" || flagSet.Changed(flags.FlagSignMode) {
|
|
signModeStr, _ := flagSet.GetString(flags.FlagSignMode)
|
|
clientCtx = clientCtx.WithSignModeStr(signModeStr)
|
|
}
|
|
|
|
if clientCtx.FeePayer == nil || flagSet.Changed(flags.FlagFeePayer) {
|
|
payer, _ := flagSet.GetString(flags.FlagFeePayer)
|
|
|
|
if payer != "" {
|
|
payerAcc, err := sdk.AccAddressFromBech32(payer)
|
|
if err != nil {
|
|
return clientCtx, err
|
|
}
|
|
|
|
clientCtx = clientCtx.WithFeePayerAddress(payerAcc)
|
|
}
|
|
}
|
|
|
|
if clientCtx.FeeGranter == nil || flagSet.Changed(flags.FlagFeeGranter) {
|
|
granter, _ := flagSet.GetString(flags.FlagFeeGranter)
|
|
|
|
if granter != "" {
|
|
granterAcc, err := sdk.AccAddressFromBech32(granter)
|
|
if err != nil {
|
|
return clientCtx, err
|
|
}
|
|
|
|
clientCtx = clientCtx.WithFeeGranterAddress(granterAcc)
|
|
}
|
|
}
|
|
|
|
if clientCtx.From == "" || flagSet.Changed(flags.FlagFrom) {
|
|
from, _ := flagSet.GetString(flags.FlagFrom)
|
|
fromAddr, fromName, keyType, err := GetFromFields(clientCtx.Keyring, from, clientCtx.GenerateOnly)
|
|
if err != nil {
|
|
return clientCtx, err
|
|
}
|
|
|
|
clientCtx = clientCtx.WithFrom(from).WithFromAddress(fromAddr).WithFromName(fromName)
|
|
|
|
// If the `from` signer account is a ledger key, we need to use
|
|
// SIGN_MODE_AMINO_JSON, because ledger doesn't support proto yet.
|
|
// ref: https://github.com/cosmos/cosmos-sdk/issues/8109
|
|
if keyType == keyring.TypeLedger && clientCtx.SignModeStr != flags.SignModeLegacyAminoJSON {
|
|
fmt.Println("Default sign-mode 'direct' not supported by Ledger, using sign-mode 'amino-json'.")
|
|
clientCtx = clientCtx.WithSignModeStr(flags.SignModeLegacyAminoJSON)
|
|
}
|
|
}
|
|
|
|
if !clientCtx.IsAux || flagSet.Changed(flags.FlagAux) {
|
|
isAux, _ := flagSet.GetBool(flags.FlagAux)
|
|
clientCtx = clientCtx.WithAux(isAux)
|
|
if isAux {
|
|
// If the user didn't explicity set an --output flag, use JSON by
|
|
// default.
|
|
if clientCtx.OutputFormat == "" || !flagSet.Changed(cli.OutputFlag) {
|
|
clientCtx = clientCtx.WithOutputFormat("json")
|
|
}
|
|
|
|
// If the user didn't explicity set a --sign-mode flag, use
|
|
// DIRECT_AUX by default.
|
|
if clientCtx.SignModeStr == "" || !flagSet.Changed(flags.FlagSignMode) {
|
|
clientCtx = clientCtx.WithSignModeStr(flags.SignModeDirectAux)
|
|
}
|
|
}
|
|
}
|
|
|
|
return clientCtx, nil
|
|
}
|
|
|
|
// GetClientQueryContext returns a Context from a command with fields set based on flags
|
|
// defined in AddQueryFlagsToCmd. An error is returned if any flag query fails.
|
|
//
|
|
// - client.Context field not pre-populated & flag not set: uses default flag value
|
|
// - client.Context field not pre-populated & flag set: uses set flag value
|
|
// - client.Context field pre-populated & flag not set: uses pre-populated value
|
|
// - client.Context field pre-populated & flag set: uses set flag value
|
|
func GetClientQueryContext(cmd *cobra.Command) (Context, error) {
|
|
ctx := GetClientContextFromCmd(cmd)
|
|
return readQueryCommandFlags(ctx, cmd.Flags())
|
|
}
|
|
|
|
// GetClientTxContext returns a Context from a command with fields set based on flags
|
|
// defined in AddTxFlagsToCmd. An error is returned if any flag query fails.
|
|
//
|
|
// - client.Context field not pre-populated & flag not set: uses default flag value
|
|
// - client.Context field not pre-populated & flag set: uses set flag value
|
|
// - client.Context field pre-populated & flag not set: uses pre-populated value
|
|
// - client.Context field pre-populated & flag set: uses set flag value
|
|
func GetClientTxContext(cmd *cobra.Command) (Context, error) {
|
|
ctx := GetClientContextFromCmd(cmd)
|
|
return readTxCommandFlags(ctx, cmd.Flags())
|
|
}
|
|
|
|
// GetClientContextFromCmd returns a Context from a command or an empty Context
|
|
// if it has not been set.
|
|
func GetClientContextFromCmd(cmd *cobra.Command) Context {
|
|
if v := cmd.Context().Value(ClientContextKey); v != nil {
|
|
clientCtxPtr := v.(*Context)
|
|
return *clientCtxPtr
|
|
}
|
|
|
|
return Context{}
|
|
}
|
|
|
|
// SetCmdClientContext sets a command's Context value to the provided argument.
|
|
func SetCmdClientContext(cmd *cobra.Command, clientCtx Context) error {
|
|
v := cmd.Context().Value(ClientContextKey)
|
|
if v == nil {
|
|
return errors.New("client context not set")
|
|
}
|
|
|
|
clientCtxPtr := v.(*Context)
|
|
*clientCtxPtr = clientCtx
|
|
|
|
return nil
|
|
}
|