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
This commit is contained in:
Alessio Treglia
2018-09-04 02:32:05 +02:00
parent 7f1b06a724
commit 86395809cb
22 changed files with 270 additions and 30 deletions
+4 -1
View File
@@ -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),
}
}
+2
View File
@@ -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
}
+15
View File
@@ -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))
+29 -4
View File
@@ -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" }
+36 -1
View File
@@ -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
}