From 86395809cbcbe99ba7da9a6f4b3103eee8c0cd05 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Sun, 2 Sep 2018 20:20:14 +0200 Subject: [PATCH] Implement generate-only option for commands that create txs The new CLI flag builds an unsigned transaction and writes it to STDOUT. Likewise, REST clients can now append generate_only=true to a request's query arguments list and expect a JSON response carrying the unsigned transaction. Closes: #966 --- PENDING.md | 1 + client/context/context.go | 5 +- client/flags.go | 2 + client/lcd/lcd_test.go | 15 ++++++ client/utils/rest.go | 33 +++++++++++-- client/utils/utils.go | 37 +++++++++++++- cmd/gaia/cli_test/cli_test.go | 91 ++++++++++++++++++++++++++++++++++- crypto/keys/keybase.go | 4 +- docs/sdk/clients.md | 11 +++++ x/auth/stdtx.go | 12 ++--- x/bank/client/cli/sendtx.go | 3 ++ x/bank/client/rest/sendtx.go | 5 ++ x/gov/client/cli/tx.go | 11 ++++- x/gov/client/rest/util.go | 6 +++ x/gov/msgs.go | 10 ++-- x/ibc/client/cli/ibctx.go | 3 ++ x/ibc/client/rest/transfer.go | 5 ++ x/ibc/types.go | 10 ++-- x/slashing/client/cli/tx.go | 4 +- x/slashing/client/rest/tx.go | 5 ++ x/stake/client/cli/tx.go | 22 ++++++++- x/stake/client/rest/tx.go | 5 ++ 22 files changed, 270 insertions(+), 30 deletions(-) diff --git a/PENDING.md b/PENDING.md index 17616ced1..7289cd23f 100644 --- a/PENDING.md +++ b/PENDING.md @@ -53,6 +53,7 @@ FEATURES * [cli] \#2047 Setting the --gas flag value to 0 triggers a simulation of the tx before the actual execution. The gas estimate obtained via the simulation will be used as gas limit in the actual execution. * [cli] \#2047 The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=0. * [cli] \#2110 Add --dry-run flag to perform a simulation of a transaction without broadcasting it. The --gas flag is ignored as gas would be automatically estimated. + * [cli] \#966 Add --generate-only flag to build an unsigned transaction and write it to STDOUT. * Gaia * [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address` diff --git a/client/context/context.go b/client/context/context.go index 8e56cfa1a..a4d4afd3b 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -3,10 +3,11 @@ package context import ( "bytes" "fmt" + "io" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" - "io" "github.com/spf13/viper" @@ -38,6 +39,7 @@ type CLIContext struct { PrintResponse bool Certifier tmlite.Certifier DryRun bool + GenerateOnly bool } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -65,6 +67,7 @@ func NewCLIContext() CLIContext { PrintResponse: viper.GetBool(client.FlagPrintResponse), Certifier: createCertifier(), DryRun: viper.GetBool(client.FlagDryRun), + GenerateOnly: viper.GetBool(client.FlagGenerateOnly), } } diff --git a/client/flags.go b/client/flags.go index a1d3c6e17..97fce42a5 100644 --- a/client/flags.go +++ b/client/flags.go @@ -27,6 +27,7 @@ const ( FlagJson = "json" FlagPrintResponse = "print-response" FlagDryRun = "dry-run" + FlagGenerateOnly = "generate-only" ) // LineBreak can be included in a command list to provide a blank line @@ -64,6 +65,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, "Don't verify proofs for query 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") } return cmds } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 707cc21f7..7eb592a70 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -313,6 +313,21 @@ func TestIBCTransfer(t *testing.T) { // TODO: query ibc egress packet state } +func TestCoinSendGenerateOnly(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + // create TX + res, body, _ := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?generate_only=true") + require.Equal(t, http.StatusOK, res.StatusCode, body) + var msg auth.StdTx + require.Nil(t, cdc.UnmarshalJSON([]byte(body), &msg)) + require.Equal(t, len(msg.Msgs), 1) + require.Equal(t, msg.Msgs[0].Type(), "bank") + require.Equal(t, msg.Msgs[0].GetSigners(), []sdk.AccAddress{addr}) +} + func TestTxs(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) diff --git a/client/utils/rest.go b/client/utils/rest.go index 25e1c23d8..b6cee6c1f 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -3,11 +3,17 @@ package utils import ( "fmt" "net/http" + "net/url" "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + auth "github.com/cosmos/cosmos-sdk/x/auth" + authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context" ) const ( - queryArgDryRun = "simulate" + queryArgDryRun = "simulate" + queryArgGenerateOnly = "generate_only" ) // WriteErrorResponse prepares and writes a HTTP error @@ -26,9 +32,10 @@ func WriteSimulationResponse(w http.ResponseWriter, gas int64) { // HasDryRunArg returns true if the request's URL query contains // the dry run argument and its value is set to "true". -func HasDryRunArg(r *http.Request) bool { - return r.URL.Query().Get(queryArgDryRun) == "true" -} +func HasDryRunArg(r *http.Request) bool { return urlQueryHasArg(r.URL, queryArgDryRun) } + +// HasGenerateOnlyArg returns whether a URL's query "generate-only" parameter is set to "true". +func HasGenerateOnlyArg(r *http.Request) bool { return urlQueryHasArg(r.URL, queryArgGenerateOnly) } // ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a default // value if the string is empty. Write @@ -43,3 +50,21 @@ func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEm } return n, true } + +// WriteGenerateStdTxResponse writes response for the generate_only mode. +func WriteGenerateStdTxResponse(w http.ResponseWriter, txCtx authctx.TxContext, msgs []sdk.Msg) { + stdMsg, err := txCtx.Build(msgs) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + output, err := txCtx.Codec.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo)) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + w.Write(output) + return +} + +func urlQueryHasArg(url *url.URL, arg string) bool { return url.Query().Get(arg) == "true" } diff --git a/client/utils/utils.go b/client/utils/utils.go index 20b1c6bc5..fa5bcf817 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" + auth "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" @@ -28,7 +29,7 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) if err != nil { return err } - fmt.Fprintf(os.Stdout, "estimated gas = %v\n", txCtx.Gas) + fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txCtx.Gas) } if cliCtx.DryRun { return nil @@ -85,6 +86,19 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc * return } +// PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout. +func PrintUnsignedStdTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) (err error) { + stdTx, err := buildUnsignedStdTx(txCtx, cliCtx, msgs) + if err != nil { + return + } + json, err := txCtx.Codec.MarshalJSON(stdTx) + if err == nil { + fmt.Printf("%s\n", json) + } + return +} + func adjustGasEstimate(estimate int64, adjustment float64) int64 { return int64(adjustment * float64(estimate)) } @@ -128,3 +142,24 @@ func prepareTxContext(txCtx authctx.TxContext, cliCtx context.CLIContext) (authc } return txCtx, 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(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { + txCtx, err = prepareTxContext(txCtx, cliCtx) + if err != nil { + return + } + if txCtx.Gas == 0 { + txCtx, err = EnrichCtxWithGas(txCtx, cliCtx, cliCtx.FromAddressName, msgs) + if err != nil { + return + } + fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txCtx.Gas) + } + stdSignMsg, err := txCtx.Build(msgs) + if err != nil { + return + } + 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 696c0fc6a..473a3bb3b 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -13,6 +13,7 @@ import ( "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/server" @@ -155,8 +156,17 @@ func TestGaiaCLICreateValidator(t *testing.T) { initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(1)) + // Test --generate-only + success, stdout, stderr := executeWriteRetStdStreams(t, cvStr+" --generate-only", app.DefaultKeyPass) + require.True(t, success) + require.True(t, success) + require.Empty(t, stderr) + msg := unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + // Test --dry-run - success := executeWrite(t, cvStr+" --dry-run", app.DefaultKeyPass) + success = executeWrite(t, cvStr+" --dry-run", app.DefaultKeyPass) require.True(t, success) executeWrite(t, cvStr, app.DefaultKeyPass) @@ -222,8 +232,17 @@ func TestGaiaCLISubmitProposal(t *testing.T) { spStr += fmt.Sprintf(" --title=%s", "Test") spStr += fmt.Sprintf(" --description=%s", "test") + // Test generate only + success, stdout, stderr := executeWriteRetStdStreams(t, spStr+" --generate-only", app.DefaultKeyPass) + require.True(t, success) + require.True(t, success) + require.Empty(t, stderr) + msg := unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + // Test --dry-run - success := executeWrite(t, spStr+" --dry-run", app.DefaultKeyPass) + success = executeWrite(t, spStr+" --dry-run", app.DefaultKeyPass) require.True(t, success) executeWrite(t, spStr, app.DefaultKeyPass) @@ -244,6 +263,15 @@ func TestGaiaCLISubmitProposal(t *testing.T) { depositStr += fmt.Sprintf(" --deposit=%s", "10steak") depositStr += fmt.Sprintf(" --proposal-id=%s", "1") + // Test generate only + success, stdout, stderr = executeWriteRetStdStreams(t, depositStr+" --generate-only", app.DefaultKeyPass) + require.True(t, success) + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + executeWrite(t, depositStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) @@ -258,6 +286,15 @@ func TestGaiaCLISubmitProposal(t *testing.T) { voteStr += fmt.Sprintf(" --proposal-id=%s", "1") voteStr += fmt.Sprintf(" --option=%s", "Yes") + // Test generate only + success, stdout, stderr = executeWriteRetStdStreams(t, voteStr+" --generate-only", app.DefaultKeyPass) + require.True(t, success) + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + executeWrite(t, voteStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) @@ -291,6 +328,50 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, " 2 - Apples", proposalsQuery) } +func TestGaiaCLISendGenerateOnly(t *testing.T) { + chainID, servAddr, port := initializeFixtures(t) + flags := fmt.Sprintf("--home=%s --node=%v --chain-id=%v", gaiacliHome, servAddr, chainID) + + // start gaiad server + proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v", gaiadHome, servAddr)) + + defer proc.Stop(false) + tests.WaitForTMStart(port) + tests.WaitForNextNBlocksTM(2, port) + + barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) + + // Test generate sendTx with default gas + success, stdout, stderr := executeWriteRetStdStreams(t, fmt.Sprintf( + "gaiacli send %v --amount=10steak --to=%s --from=foo --generate-only", + flags, barAddr), []string{}...) + require.True(t, success) + require.Empty(t, stderr) + msg := unmarshalStdTx(t, stdout) + require.Equal(t, msg.Fee.Gas, int64(client.DefaultGasLimit)) + require.Equal(t, len(msg.Msgs), 1) + + // Test generate sendTx, estimate gas + success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( + "gaiacli send %v --amount=10steak --to=%s --from=foo --gas=0 --generate-only", + flags, barAddr), []string{}...) + require.True(t, success) + require.NotEmpty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.NotZero(t, msg.Fee.Gas) + require.Equal(t, len(msg.Msgs), 1) + + // Test generate sendTx with --gas=$amount + success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( + "gaiacli send %v --amount=10steak --to=%s --from=foo --gas=100 --generate-only", + flags, barAddr), []string{}...) + require.True(t, success) + require.Empty(t, stderr) + msg = unmarshalStdTx(t, stdout) + require.Equal(t, msg.Fee.Gas, int64(100)) + require.Equal(t, len(msg.Msgs), 1) +} + //___________________________________________________________________________________ // helper methods @@ -315,6 +396,12 @@ func initializeFixtures(t *testing.T) (chainID, servAddr, port string) { return } +func unmarshalStdTx(t *testing.T, s string) (stdTx auth.StdTx) { + cdc := app.MakeCodec() + require.Nil(t, cdc.UnmarshalJSON([]byte(s), &stdTx)) + return +} + //___________________________________________________________________________________ // executors diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index ae036362e..ec5b7b0fd 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -224,9 +224,9 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t } case offlineInfo: linfo := info.(offlineInfo) - fmt.Printf("Bytes to sign:\n%s", msg) + fmt.Fprintf(os.Stderr, "Bytes to sign:\n%s", msg) buf := bufio.NewReader(os.Stdin) - fmt.Printf("\nEnter Amino-encoded signature:\n") + fmt.Fprintf(os.Stderr, "\nEnter Amino-encoded signature:\n") // Will block until user inputs the signature signed, err := buf.ReadString('\n') if err != nil { diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index 62d79bbac..fdfbca7bd 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -139,6 +139,17 @@ gaiacli send \ --dry-run ``` +Furthermore, you can build a transaction and print its JSON format to STDOUT by appending `--generate-only` to the list of the command line arguments: + +```bash +gaiacli send \ + --amount=10faucetToken \ + --chain-id= \ + --name= \ + --to= \ + --generate-only +``` + ### Staking #### Set up a Validator diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index c6e280157..69627b31a 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 92ac37c1e..08210569e 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -68,6 +68,9 @@ 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 { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 23506e0fe..1c09943d0 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -107,6 +107,11 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo txCtx = newCtx } + if utils.HasGenerateOnlyArg(r) { + utils.WriteGenerateStdTxResponse(w, txCtx, []sdk.Msg{msg}) + return + } + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 65c922c6c..b0fa4d6be 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -99,6 +99,9 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome } msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, fromAddr, amount) + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } err = msg.ValidateBasic() if err != nil { @@ -177,7 +180,9 @@ func GetCmdDeposit(cdc *wire.Codec) *cobra.Command { } msg := gov.NewMsgDeposit(depositerAddr, proposalID, amount) - + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } err = msg.ValidateBasic() if err != nil { return err @@ -221,7 +226,9 @@ func GetCmdVote(cdc *wire.Codec) *cobra.Command { } msg := gov.NewMsgVote(voterAddr, proposalID, byteVoteOption) - + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } err = msg.ValidateBasic() if err != nil { return err diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go index a5fae3c3f..7ba23befa 100644 --- a/x/gov/client/rest/util.go +++ b/x/gov/client/rest/util.go @@ -96,6 +96,12 @@ func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLICont } txCtx = newCtx } + + if utils.HasGenerateOnlyArg(r) { + utils.WriteGenerateStdTxResponse(w, txCtx, []sdk.Msg{msg}) + return + } + txBytes, err := txCtx.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) diff --git a/x/gov/msgs.go b/x/gov/msgs.go index 5d85f689e..dcd7112aa 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 f44c736d8..37cf7ce91 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -43,6 +43,9 @@ func IBCTransferCmd(cdc *wire.Codec) *cobra.Command { if err != nil { return err } + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 0580ff406..7f5595dd7 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -98,6 +98,11 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C txCtx = newCtx } + if utils.HasGenerateOnlyArg(r) { + utils.WriteGenerateStdTxResponse(w, txCtx, []sdk.Msg{msg}) + return + } + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) diff --git a/x/ibc/types.go b/x/ibc/types.go index 5f9ee611b..b3fe6fc39 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 4234e52ce..d69a9e158 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -33,7 +33,9 @@ func GetCmdUnjail(cdc *wire.Codec) *cobra.Command { } msg := slashing.NewMsgUnjail(sdk.ValAddress(valAddr)) - + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index faf0341ce..bbed6ed9a 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -99,6 +99,11 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI txCtx = newCtx } + if utils.HasGenerateOnlyArg(r) { + utils.WriteGenerateStdTxResponse(w, txCtx, []sdk.Msg{msg}) + return + } + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own validator address") diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index defc2b1e2..bd38e7b5b 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -77,7 +77,9 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command { } else { msg = stake.NewMsgCreateValidator(sdk.ValAddress(valAddr), pk, amount, description) } - + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, @@ -117,6 +119,9 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgEditValidator(sdk.ValAddress(valAddr), description) + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, @@ -156,6 +161,9 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDelegate(delAddr, valAddr, amount) + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, @@ -225,6 +233,9 @@ func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgBeginRedelegate(delAddr, valSrcAddr, valDstAddr, sharesAmount) + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, @@ -313,6 +324,9 @@ func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgCompleteRedelegate(delAddr, valSrcAddr, valDstAddr) + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, @@ -374,6 +388,9 @@ func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgBeginUnbonding(delAddr, valAddr, sharesAmount) + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, @@ -409,6 +426,9 @@ func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgCompleteUnbonding(delAddr, valAddr) + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txCtx, cliCtx, []sdk.Msg{msg}) + } // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txCtx, cliCtx, []sdk.Msg{msg}) }, diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 9925b6b1c..4bb6d0f7e 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -297,6 +297,11 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex txCtx = newCtx } + if utils.HasGenerateOnlyArg(r) { + utils.WriteGenerateStdTxResponse(w, txCtx, []sdk.Msg{msg}) + return + } + txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error())