From 6f4789937f7beaa94632864069df38e00e91d9a5 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Thu, 23 Aug 2018 22:19:52 +0100 Subject: [PATCH] Add --generate-only flag to CLI commands Closes: #966 --- client/context/context.go | 2 + client/flags.go | 2 + client/utils/utils.go | 85 +++++++++++++++++++++++++------------ x/auth/stdtx.go | 12 +++--- x/bank/client/cli/sendtx.go | 9 ++++ x/gov/client/cli/tx.go | 31 ++++++++++++-- x/gov/msgs.go | 10 ++--- x/ibc/client/cli/ibctx.go | 9 ++++ x/ibc/types.go | 10 ++--- x/slashing/client/cli/tx.go | 10 ++++- x/stake/client/cli/tx.go | 9 +++- 11 files changed, 140 insertions(+), 49 deletions(-) diff --git a/client/context/context.go b/client/context/context.go index 743c923552c8..52894521493b 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -32,6 +32,7 @@ type CLIContext struct { Async bool JSON bool PrintResponse bool + GenerateOnly bool } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -57,6 +58,7 @@ func NewCLIContext() CLIContext { Async: viper.GetBool(client.FlagAsync), JSON: viper.GetBool(client.FlagJson), PrintResponse: viper.GetBool(client.FlagPrintResponse), + GenerateOnly: viper.GetBool(client.FlagGenerateOnly), } } diff --git a/client/flags.go b/client/flags.go index 81e06706784a..aa9ad7a1b09a 100644 --- a/client/flags.go +++ b/client/flags.go @@ -23,6 +23,7 @@ const ( FlagAsync = "async" FlagJson = "json" FlagPrintResponse = "print-response" + FlagGenerateOnly = "generate-only" ) // LineBreak can be included in a command list to provide a blank line @@ -58,6 +59,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") c.Flags().Bool(FlagJson, false, "return output in json format") c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)") + c.Flags().Bool(FlagGenerateOnly, false, "build an unsigned transaction and write it to STDOUT") } return cmds } diff --git a/client/utils/utils.go b/client/utils/utils.go index fb5d6198871d..b82ef994f720 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/common" @@ -23,37 +24,11 @@ const DefaultGasAdjustment = 1.2 // addition, it builds and signs a transaction with the supplied messages. // Finally, it broadcasts the signed transaction to a node. func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) error { - if err := cliCtx.EnsureAccountExists(); err != nil { - return err - } - - from, err := cliCtx.GetFromAddress() + txCtx, err := prepareTxContext(txCtx, cliCtx) if err != nil { return err } - // TODO: (ref #1903) Allow for user supplied account number without - // automatically doing a manual lookup. - if txCtx.AccountNumber == 0 { - accNum, err := cliCtx.GetAccountNumber(from) - if err != nil { - return err - } - - txCtx = txCtx.WithAccountNumber(accNum) - } - - // TODO: (ref #1903) Allow for user supplied account sequence without - // automatically doing a manual lookup. - if txCtx.Sequence == 0 { - accSeq, err := cliCtx.GetAccountSequence(from) - if err != nil { - return err - } - - txCtx = txCtx.WithSequence(accSeq) - } - passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName) if err != nil { return err @@ -75,6 +50,28 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) return cliCtx.EnsureBroadcastTx(txBytes) } +// BuildTx builds a StdSignMsg as per the parameters passed in the contexts. +func BuildTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) (auth.StdSignMsg, error) { + txCtx, err := prepareTxContext(txCtx, cliCtx) + if err != nil { + return auth.StdSignMsg{}, err + } + return txCtx.Build(msgs) +} + +// MarshalJSON builds the transaction and returns its JSON encoding. +func MarshalJSON(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) ([]byte, error) { + stdSignMsg, err := BuildTx(txCtx, cliCtx, msgs) + if err != nil { + return nil, err + } + json, err := cliCtx.Codec.MarshalJSON(stdSignMsg) + if err != nil { + return nil, err + } + return json, nil +} + // EnrichCtxWithGas calculates the gas estimate that would be consumed by the // transaction and set the transaction's respective value accordingly. func EnrichCtxWithGas(txCtx authctx.TxContext, cliCtx context.CLIContext, name, passphrase string, msgs []sdk.Msg) (authctx.TxContext, error) { @@ -113,6 +110,40 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc * return } +func prepareTxContext(txCtx authctx.TxContext, cliCtx context.CLIContext) (authctx.TxContext, error) { + if err := cliCtx.EnsureAccountExists(); err != nil { + return txCtx, err + } + + from, err := cliCtx.GetFromAddress() + if err != nil { + return txCtx, err + } + + if txCtx.AccountNumber == 0 { + // TODO: (ref #1903) Allow for user supplied account number without + // automatically doing a manual lookup. + accNum, err := cliCtx.GetAccountNumber(from) + if err != nil { + return txCtx, err + } + + txCtx = txCtx.WithAccountNumber(accNum) + } + + // TODO: (ref #1903) Allow for user supplied account sequence without + // automatically doing a manual lookup. + if txCtx.Sequence == 0 { + accSeq, err := cliCtx.GetAccountSequence(from) + if err != nil { + return txCtx, err + } + + txCtx = txCtx.WithSequence(accSeq) + } + return txCtx, nil +} + func adjustGasEstimate(estimate int64, adjustment float64) int64 { if adjustment == 0 { return int64(DefaultGasAdjustment * float64(estimate)) diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index c6e280157b2f..92136da9c484 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -143,12 +143,12 @@ func StdSignBytes(chainID string, accnum int64, sequence int64, fee StdFee, msgs // a Msg with the other requirements for a StdSignDoc before // it is signed. For use in the CLI. type StdSignMsg struct { - ChainID string - AccountNumber int64 - Sequence int64 - Fee StdFee - Msgs []sdk.Msg - Memo string + ChainID string `json:"chain-id"` + AccountNumber int64 `json:"account-number"` + Sequence int64 `json:"sequence"` + Fee StdFee `json:"fee"` + Msgs []sdk.Msg `json:"msgs"` + Memo string `json:"memo"` } // get message bytes diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 92ac37c1e973..2a5323bf5375 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -1,6 +1,7 @@ package cli import ( + "fmt" "os" "github.com/cosmos/cosmos-sdk/client/context" @@ -68,6 +69,14 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint msg := client.BuildMsg(from, to, coins) + if cliCtx.GenerateOnly { + json, err := utils.MarshalJSON(txCtx, cliCtx, []sdk.Msg{msg}) + if err != nil { + return err + } + fmt.Printf("%s\n", json) + return nil + } return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 2a7e71aac8d5..c92774117cd0 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -13,11 +13,12 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" "encoding/json" + "io/ioutil" + "strings" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - "io/ioutil" - "strings" ) const ( @@ -99,6 +100,14 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome } msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount) + if cliCtx.GenerateOnly { + json, err := utils.MarshalJSON(txCtx, cliCtx, []sdk.Msg{msg}) + if err != nil { + return err + } + fmt.Printf("%s\n", json) + return nil + } err = msg.ValidateBasic() if err != nil { @@ -177,7 +186,14 @@ func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { } msg := gov.NewMsgDeposit(depositerAddr, proposalID, amount) - + if cliCtx.GenerateOnly { + json, err := utils.MarshalJSON(txCtx, cliCtx, []sdk.Msg{msg}) + if err != nil { + return err + } + fmt.Printf("%s\n", json) + return nil + } err = msg.ValidateBasic() if err != nil { return err @@ -221,7 +237,14 @@ func GetCmdVote(cdc *wire.Codec) *cobra.Command { } msg := gov.NewMsgVote(voterAddr, proposalID, byteVoteOption) - + if cliCtx.GenerateOnly { + json, err := utils.MarshalJSON(txCtx, cliCtx, []sdk.Msg{msg}) + if err != nil { + return err + } + fmt.Printf("%s\n", json) + return nil + } err = msg.ValidateBasic() if err != nil { return err diff --git a/x/gov/msgs.go b/x/gov/msgs.go index 5d85f689e58c..ef1c5e53d320 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -12,11 +12,11 @@ const MsgType = "gov" //----------------------------------------------------------- // MsgSubmitProposal type MsgSubmitProposal struct { - Title string // Title of the proposal - Description string // Description of the proposal - ProposalType ProposalKind // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Proposer sdk.AccAddress // Address of the proposer - InitialDeposit sdk.Coins // Initial deposit paid by sender. Must be strictly positive. + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType ProposalKind `json:"proposal-type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer + InitialDeposit sdk.Coins `json:"initial-deposit"` // Initial deposit paid by sender. Must be strictly positive. } func NewMsgSubmitProposal(title string, description string, proposalType ProposalKind, proposer sdk.AccAddress, initialDeposit sdk.Coins) MsgSubmitProposal { diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index f44c736d891c..4ff52662dad9 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -2,6 +2,7 @@ package cli import ( "encoding/hex" + "fmt" "os" "github.com/cosmos/cosmos-sdk/client" @@ -43,6 +44,14 @@ func IBCTransferCmd(cdc *wire.Codec) *cobra.Command { if err != nil { return err } + if cliCtx.GenerateOnly { + json, err := utils.MarshalJSON(txCtx, cliCtx, []sdk.Msg{msg}) + if err != nil { + return err + } + fmt.Printf("%s\n", json) + return nil + } return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, diff --git a/x/ibc/types.go b/x/ibc/types.go index 5f9ee611bb13..08ad02a35d0e 100644 --- a/x/ibc/types.go +++ b/x/ibc/types.go @@ -22,11 +22,11 @@ func init() { // IBCPacket defines a piece of data that can be send between two separate // blockchains. type IBCPacket struct { - SrcAddr sdk.AccAddress - DestAddr sdk.AccAddress - Coins sdk.Coins - SrcChain string - DestChain string + SrcAddr sdk.AccAddress `json:"src-addr"` + DestAddr sdk.AccAddress `json:"dest-addr"` + Coins sdk.Coins `json:"coins"` + SrcChain string `json:"src-chain"` + DestChain string `json:"dest-chain"` } func NewIBCPacket(srcAddr sdk.AccAddress, destAddr sdk.AccAddress, coins sdk.Coins, diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index 5831ef310035..f32259795fc9 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -1,6 +1,7 @@ package cli import ( + "fmt" "os" "github.com/cosmos/cosmos-sdk/client/context" @@ -33,7 +34,14 @@ func GetCmdUnjail(cdc *wire.Codec) *cobra.Command { } msg := slashing.NewMsgUnjail(validatorAddr) - + if cliCtx.GenerateOnly { + json, err := utils.MarshalJSON(txCtx, cliCtx, []sdk.Msg{msg}) + if err != nil { + return err + } + fmt.Printf("%s\n", json) + return nil + } return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 9f04f2ed2d38..f1128822564d 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -77,7 +77,14 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { } else { msg = stake.NewMsgCreateValidator(validatorAddr, pk, amount, description) } - + if cliCtx.GenerateOnly { + json, err := utils.MarshalJSON(txCtx, cliCtx, []sdk.Msg{msg}) + if err != nil { + return err + } + fmt.Printf("%s\n", json) + return nil + } // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) },