diff --git a/.pending/breaking/sdk/4305-GenerateOrBroad b/.pending/breaking/sdk/4305-GenerateOrBroad new file mode 100644 index 000000000000..6975f6a2e71d --- /dev/null +++ b/.pending/breaking/sdk/4305-GenerateOrBroad @@ -0,0 +1 @@ +#4305 `GenerateOrBroadcastMsgs` no longer takes an `offline` parameter. diff --git a/.pending/improvements/sdk/4305-The---generate- b/.pending/improvements/sdk/4305-The---generate- new file mode 100644 index 000000000000..42e309027ebc --- /dev/null +++ b/.pending/improvements/sdk/4305-The---generate- @@ -0,0 +1 @@ +#4305 The `--generate-only` CLI flag fully respects offline tx processing. diff --git a/client/context/context.go b/client/context/context.go index 0a42ff4afe26..7050e9de0cb0 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -62,13 +62,9 @@ type CLIContext struct { // command line using Viper. It takes a key name or address and populates the FromName and // FromAddress field accordingly. func NewCLIContextWithFrom(from string) CLIContext { + var nodeURI string var rpc rpcclient.Client - nodeURI := viper.GetString(client.FlagNode) - if nodeURI != "" { - rpc = rpcclient.NewHTTP(nodeURI, "/websocket") - } - genOnly := viper.GetBool(client.FlagGenerateOnly) fromAddress, fromName, err := GetFromFields(from, genOnly) if err != nil { @@ -76,6 +72,13 @@ func NewCLIContextWithFrom(from string) CLIContext { os.Exit(1) } + if !genOnly { + nodeURI = viper.GetString(client.FlagNode) + if nodeURI != "" { + rpc = rpcclient.NewHTTP(nodeURI, "/websocket") + } + } + // We need to use a single verifier for all contexts if verifier == nil || verifierHome != viper.GetString(cli.HomeFlag) { verifier = createVerifier() diff --git a/client/flags.go b/client/flags.go index 26ff68e82c23..580db1e488e8 100644 --- a/client/flags.go +++ b/client/flags.go @@ -93,7 +93,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)") c.Flags().Bool(FlagTrustNode, true, "Trust connected full node (don't verify proofs for responses)") c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it") - c.Flags().Bool(FlagGenerateOnly, false, "Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase is not accessible)") + c.Flags().Bool(FlagGenerateOnly, false, "Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase is not accessible and the node operates offline)") c.Flags().BoolP(FlagSkipConfirmation, "y", false, "Skip tx broadcasting prompt confirmation") // --gas can accept integers and "simulate" diff --git a/client/utils/utils.go b/client/utils/utils.go index 25d81c4518e4..b3de2e16887c 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" + "github.com/pkg/errors" "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/client" @@ -30,11 +31,15 @@ func (gr GasEstimateResponse) String() string { return fmt.Sprintf("gas estimate: %d", gr.GasEstimate) } -// GenerateOrBroadcastMsgs respects CLI flags and outputs a message -func GenerateOrBroadcastMsgs(cliCtx context.CLIContext, txBldr authtxb.TxBuilder, msgs []sdk.Msg, offline bool) error { +// GenerateOrBroadcastMsgs creates a StdTx given a series of messages. If +// the provided context has generate-only enabled, the tx will only be printed +// to STDOUT in a fully offline manner. Otherwise, the tx will be signed and +// broadcasted. +func GenerateOrBroadcastMsgs(cliCtx context.CLIContext, txBldr authtxb.TxBuilder, msgs []sdk.Msg) error { if cliCtx.GenerateOnly { - return PrintUnsignedStdTx(txBldr, cliCtx, msgs, offline) + return PrintUnsignedStdTx(txBldr, cliCtx, msgs) } + return CompleteAndBroadcastTxCLI(txBldr, cliCtx, msgs) } @@ -141,29 +146,19 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), } // PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout. -// Don't perform online validation or lookups if offline is true. -func PrintUnsignedStdTx( - txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg, offline bool, -) (err error) { - - var stdTx auth.StdTx - - if offline { - stdTx, err = buildUnsignedStdTxOffline(txBldr, cliCtx, msgs) - } else { - stdTx, err = buildUnsignedStdTx(txBldr, cliCtx, msgs) - } - +func PrintUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { + stdTx, err := buildUnsignedStdTxOffline(txBldr, cliCtx, msgs) if err != nil { - return + return err } json, err := cliCtx.Codec.MarshalJSON(stdTx) - if err == nil { - fmt.Fprintf(cliCtx.Output, "%s\n", json) + if err != nil { + return err } - return + _, _ = fmt.Fprintf(cliCtx.Output, "%s\n", json) + return nil } // SignStdTx appends a signature to a StdTx and returns a copy of it. If appendSig @@ -327,21 +322,15 @@ func PrepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth return txBldr, nil } -// buildUnsignedStdTx builds a StdTx as per the parameters passed in the -// contexts. Gas is automatically estimated if gas wanted is set to 0. -func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { - txBldr, err = PrepareTxBuilder(txBldr, cliCtx) - if err != nil { - return - } - return buildUnsignedStdTxOffline(txBldr, cliCtx, msgs) -} - func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { if txBldr.SimulateAndExecute() { + if cliCtx.GenerateOnly { + return stdTx, errors.New("cannot estimate gas with generate-only") + } + txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs) if err != nil { - return + return stdTx, err } fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas()) @@ -349,7 +338,7 @@ func buildUnsignedStdTxOffline(txBldr authtxb.TxBuilder, cliCtx context.CLIConte stdSignMsg, err := txBldr.BuildSignMsg(msgs) if err != nil { - return + return stdTx, nil } return auth.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 0e2ab0ff39bf..722fb643ea91 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -812,9 +812,9 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { require.Equal(t, 0, len(msg.GetSignatures())) // Test generate sendTx, estimate gas - success, stdout, stderr = f.TxSend(fooAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--gas=auto", "--generate-only") + success, stdout, stderr = f.TxSend(fooAddr.String(), barAddr, sdk.NewCoin(denom, sendTokens), "--generate-only") require.True(t, success) - require.NotEmpty(t, stderr) + require.Empty(t, stderr) msg = unmarshalStdTx(t, stdout) require.True(t, msg.Fee.Gas > 0) require.Equal(t, len(msg.Msgs), 1) @@ -854,13 +854,6 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test broadcast success, stdout, _ = f.TxBroadcast(signedTxFile.Name()) require.True(t, success) - - var result sdk.TxResponse - - // Unmarshal the response and ensure that gas was properly used - require.Nil(t, app.MakeCodec().UnmarshalJSON([]byte(stdout), &result)) - require.Equal(t, msg.Fee.Gas, uint64(result.GasUsed)) - require.Equal(t, msg.Fee.Gas, uint64(result.GasWanted)) tests.WaitForNextNBlocksTM(1, f.Port) // Ensure account state diff --git a/cmd/gaia/init/gentx.go b/cmd/gaia/init/gentx.go index b873cd88b58f..67865426da65 100644 --- a/cmd/gaia/init/gentx.go +++ b/cmd/gaia/init/gentx.go @@ -155,14 +155,14 @@ following delegation and commission default parameters: if info.GetType() == kbkeys.TypeOffline || info.GetType() == kbkeys.TypeMulti { fmt.Println("Offline key passed in. Use `gaiacli tx sign` command to sign:") - return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true) + return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // write the unsigned transaction to the buffer w := bytes.NewBuffer([]byte{}) cliCtx = cliCtx.WithOutput(w) - if err = utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true); err != nil { + if err = utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}); err != nil { return err } diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 900189d9f2d4..094b5edcd7d1 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -42,7 +42,7 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint msg := bank.NewMsgSend(cliCtx.GetFromAddress(), to, coins) - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } diff --git a/x/crisis/client/cli/tx.go b/x/crisis/client/cli/tx.go index d44759ea4509..8f1147793301 100644 --- a/x/crisis/client/cli/tx.go +++ b/x/crisis/client/cli/tx.go @@ -26,7 +26,7 @@ func GetCmdInvariantBroken(cdc *codec.Codec) *cobra.Command { senderAddr := cliCtx.GetFromAddress() moduleName, route := args[0], args[1] msg := crisis.NewMsgVerifyInvariant(senderAddr, moduleName, route) - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } return cmd diff --git a/x/distribution/client/cli/tx.go b/x/distribution/client/cli/tx.go index 8bf143ce953f..a51394568ef8 100644 --- a/x/distribution/client/cli/tx.go +++ b/x/distribution/client/cli/tx.go @@ -68,7 +68,7 @@ $ gaiacli tx distr withdraw-rewards cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9 msgs = append(msgs, types.NewMsgWithdrawValidatorCommission(valAddr)) } - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, msgs, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, msgs) }, } cmd.Flags().Bool(flagComission, false, "also withdraw validator's commission") @@ -98,7 +98,7 @@ $ gaiacli tx distr withdraw-all-rewards --from mykey return err } - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, msgs, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, msgs) }, } } @@ -127,7 +127,7 @@ $ gaiacli tx set-withdraw-addr cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p --f } msg := types.NewMsgSetWithdrawAddress(delAddr, withdrawAddr) - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } return cmd diff --git a/x/gov/client/cli/query.go b/x/gov/client/cli/query.go index 9aa52383dc46..8c95595fec51 100644 --- a/x/gov/client/cli/query.go +++ b/x/gov/client/cli/query.go @@ -155,7 +155,7 @@ $ gaiacli query gov vote 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk // check to see if the proposal is in the store _, err = gcutils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { - return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + return fmt.Errorf("failed to fetch proposal-id %d: %s", proposalID, err) } voterAddr, err := sdk.AccAddressFromBech32(args[1]) @@ -224,7 +224,7 @@ $ gaiacli query gov votes 1 // check to see if the proposal is in the store res, err := gcutils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) if err != nil { - return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) + return fmt.Errorf("failed to fetch proposal-id %d: %s", proposalID, err) } var proposal gov.Proposal diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index a488be2f4c2e..63e22b8fcb86 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -94,7 +94,7 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome return err } - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } @@ -108,14 +108,15 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome } // GetCmdDeposit implements depositing tokens for an active proposal. -func GetCmdDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { +func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "deposit [proposal-id] [deposit]", Args: cobra.ExactArgs(2), - Short: "Deposit tokens for activing proposal", + Short: "Deposit tokens for an active proposal", Long: strings.TrimSpace(` -Submit a deposit for an acive proposal. You can find the proposal-id by running gaiacli query gov proposals: +Submit a deposit for an active proposal. You can find the proposal-id by running "gaiacli query gov proposals": +Example: $ gaiacli tx gov deposit 1 10stake --from mykey `), RunE: func(cmd *cobra.Command, args []string) error { @@ -130,12 +131,6 @@ $ gaiacli tx gov deposit 1 10stake --from mykey return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0]) } - // check to see if the proposal is in the store - _, err = govClientUtils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) - if err != nil { - return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) - } - // Get depositor address from := cliCtx.GetFromAddress() @@ -151,20 +146,21 @@ $ gaiacli tx gov deposit 1 10stake --from mykey return err } - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } } // GetCmdVote implements creating a new vote command. -func GetCmdVote(queryRoute string, cdc *codec.Codec) *cobra.Command { +func GetCmdVote(cdc *codec.Codec) *cobra.Command { return &cobra.Command{ Use: "vote [proposal-id] [option]", Args: cobra.ExactArgs(2), Short: "Vote for an active proposal, options: yes/no/no_with_veto/abstain", Long: strings.TrimSpace(` -Submit a vote for an acive proposal. You can find the proposal-id by running gaiacli query gov proposals: +Submit a vote for an active proposal. You can find the proposal-id by running "gaiacli query gov proposals": +Example: $ gaiacli tx gov vote 1 yes --from mykey `), RunE: func(cmd *cobra.Command, args []string) error { @@ -182,12 +178,6 @@ $ gaiacli tx gov vote 1 yes --from mykey return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0]) } - // check to see if the proposal is in the store - _, err = govClientUtils.QueryProposalByID(proposalID, cliCtx, cdc, queryRoute) - if err != nil { - return fmt.Errorf("Failed to fetch proposal-id %d: %s", proposalID, err) - } - // Find out which vote option user chose byteVoteOption, err := gov.VoteOptionFromString(govClientUtils.NormalizeVoteOption(args[1])) if err != nil { @@ -201,7 +191,7 @@ $ gaiacli tx gov vote 1 yes --from mykey return err } - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } } diff --git a/x/gov/client/module_client.go b/x/gov/client/module_client.go index 80858ff13f22..dbdf3aca99d5 100644 --- a/x/gov/client/module_client.go +++ b/x/gov/client/module_client.go @@ -60,8 +60,8 @@ func (mc ModuleClient) GetTxCmd() *cobra.Command { } govTxCmd.AddCommand(client.PostCommands( - govCli.GetCmdDeposit(mc.storeKey, mc.cdc), - govCli.GetCmdVote(mc.storeKey, mc.cdc), + govCli.GetCmdDeposit(mc.cdc), + govCli.GetCmdVote(mc.cdc), cmdSubmitProp, )...) diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index a1575257903d..9a5ac05ee661 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -37,7 +37,7 @@ func IBCTransferCmd(cdc *codec.Codec) *cobra.Command { return err } - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } diff --git a/x/params/client/cli/tx.go b/x/params/client/cli/tx.go index 1997512f1cb1..ae4717a83eb7 100644 --- a/x/params/client/cli/tx.go +++ b/x/params/client/cli/tx.go @@ -74,7 +74,7 @@ where proposal.json contains: return err } - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index 1da2dc651142..9097975f952c 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -30,7 +30,7 @@ $ gaiacli tx slashing unjail --from mykey valAddr := cliCtx.GetFromAddress() msg := slashing.NewMsgUnjail(sdk.ValAddress(valAddr)) - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } } diff --git a/x/staking/client/cli/tx.go b/x/staking/client/cli/tx.go index 0507fb7eaee3..d58b8bfb228b 100644 --- a/x/staking/client/cli/tx.go +++ b/x/staking/client/cli/tx.go @@ -35,7 +35,7 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { return err } - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, true) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } @@ -102,7 +102,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { msg := staking.NewMsgEditValidator(sdk.ValAddress(valAddr), description, newRate, newMinSelfDelegation) // build and sign the transaction, then broadcast to Tendermint - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } @@ -140,7 +140,7 @@ $ gaiacli tx staking delegate cosmosvaloper1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59 } msg := staking.NewMsgDelegate(delAddr, valAddr, amount) - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } } @@ -178,7 +178,7 @@ $ gaiacli tx staking redelegate cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmq } msg := staking.NewMsgBeginRedelegate(delAddr, valSrcAddr, valDstAddr, amount) - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } } @@ -211,7 +211,7 @@ $ gaiacli tx staking unbond cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj } msg := staking.NewMsgUndelegate(delAddr, valAddr, amount) - return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } }