Merge PR #4555: Move client/{tx,rest,utils} into x/auth/client
This commit is contained in:
committed by
Alexander Bezobchuk
parent
941effc14d
commit
73700df8c3
@@ -18,10 +18,7 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/client/input"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/client/lcd"
|
||||
"github.com/cosmos/cosmos-sdk/client/rest"
|
||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -94,7 +91,6 @@ var (
|
||||
NewInMemoryKeyBase = keys.NewInMemoryKeyBase
|
||||
NewRestServer = lcd.NewRestServer
|
||||
ServeCommand = lcd.ServeCommand
|
||||
WriteGenerateStdTxResponse = rest.WriteGenerateStdTxResponse
|
||||
BlockCommand = rpc.BlockCommand
|
||||
GetChainHeight = rpc.GetChainHeight
|
||||
BlockRequestHandlerFn = rpc.BlockRequestHandlerFn
|
||||
@@ -109,27 +105,6 @@ var (
|
||||
GetValidators = rpc.GetValidators
|
||||
ValidatorSetRequestHandlerFn = rpc.ValidatorSetRequestHandlerFn
|
||||
LatestValidatorSetRequestHandlerFn = rpc.LatestValidatorSetRequestHandlerFn
|
||||
BroadcastTxRequest = tx.BroadcastTxRequest
|
||||
GetBroadcastCommand = tx.GetBroadcastCommand
|
||||
EncodeTxRequestHandlerFn = tx.EncodeTxRequestHandlerFn
|
||||
GetEncodeCommand = tx.GetEncodeCommand
|
||||
SearchTxCmd = tx.SearchTxCmd
|
||||
QueryTxCmd = tx.QueryTxCmd
|
||||
QueryTxsByTagsRequestHandlerFn = tx.QueryTxsByTagsRequestHandlerFn
|
||||
QueryTxRequestHandlerFn = tx.QueryTxRequestHandlerFn
|
||||
RegisterTxRoutes = tx.RegisterTxRoutes
|
||||
SearchTxs = tx.SearchTxs
|
||||
ValidateTxResult = tx.ValidateTxResult
|
||||
GenerateOrBroadcastMsgs = utils.GenerateOrBroadcastMsgs
|
||||
CompleteAndBroadcastTxCLI = utils.CompleteAndBroadcastTxCLI
|
||||
EnrichWithGas = utils.EnrichWithGas
|
||||
CalculateGas = utils.CalculateGas
|
||||
PrintUnsignedStdTx = utils.PrintUnsignedStdTx
|
||||
SignStdTx = utils.SignStdTx
|
||||
SignStdTxWithSignerAddress = utils.SignStdTxWithSignerAddress
|
||||
ReadStdTxFromFile = utils.ReadStdTxFromFile
|
||||
GetTxEncoder = utils.GetTxEncoder
|
||||
PrepareTxBuilder = utils.PrepareTxBuilder
|
||||
BufferStdin = input.BufferStdin
|
||||
OverrideStdin = input.OverrideStdin
|
||||
GetPassword = input.GetPassword
|
||||
@@ -153,7 +128,4 @@ type (
|
||||
RestServer = lcd.RestServer
|
||||
ValidatorOutput = rpc.ValidatorOutput
|
||||
ResultValidatorsOutput = rpc.ResultValidatorsOutput
|
||||
BroadcastReq = tx.BroadcastReq
|
||||
EncodeResp = tx.EncodeResp
|
||||
GasEstimateResponse = utils.GasEstimateResponse
|
||||
)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ValidateCmd returns unknown command error or Help display if help flag set
|
||||
func ValidateCmd(cmd *cobra.Command, args []string) error {
|
||||
var cmds []string
|
||||
var help bool
|
||||
|
||||
// construct array of commands and search for help flag
|
||||
for _, arg := range args {
|
||||
if arg == "--help" || arg == "-h" {
|
||||
help = true
|
||||
} else if len(arg) > 0 && !(arg[0] == '-') {
|
||||
cmds = append(cmds, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if !help && len(cmds) > 0 {
|
||||
err := fmt.Sprintf("unknown command \"%s\" for \"%s\"", cmds[0], cmd.CalledAs())
|
||||
|
||||
// build suggestions for unknown argument
|
||||
if suggestions := cmd.SuggestionsFor(cmds[0]); 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()
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package client_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
)
|
||||
|
||||
func TestValidateCmd(t *testing.T) {
|
||||
// setup root and subcommands
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "root",
|
||||
}
|
||||
queryCmd := &cobra.Command{
|
||||
Use: "query",
|
||||
}
|
||||
rootCmd.AddCommand(queryCmd)
|
||||
|
||||
// command being tested
|
||||
distCmd := &cobra.Command{
|
||||
Use: "distr",
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
}
|
||||
queryCmd.AddCommand(distCmd)
|
||||
|
||||
commissionCmd := &cobra.Command{
|
||||
Use: "commission",
|
||||
}
|
||||
distCmd.AddCommand(commissionCmd)
|
||||
|
||||
tests := []struct {
|
||||
reason string
|
||||
args []string
|
||||
wantErr bool
|
||||
}{
|
||||
{"misspelled command", []string{"comission"}, true},
|
||||
{"no command provided", []string{}, false},
|
||||
{"help flag", []string{"comission", "--help"}, false},
|
||||
{"shorthand help flag", []string{"comission", "-h"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
err := client.ValidateCmd(distCmd, tt.args)
|
||||
require.Equal(t, tt.wantErr, err != nil, tt.reason)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package rest
|
||||
|
||||
import "errors"
|
||||
|
||||
var errInvalidGasAdjustment = errors.New("invalid gas adjustment")
|
||||
@@ -1,72 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Building / Sending utilities
|
||||
|
||||
// WriteGenerateStdTxResponse writes response for the generate only mode.
|
||||
func WriteGenerateStdTxResponse(w http.ResponseWriter, cliCtx context.CLIContext, br rest.BaseReq, msgs []sdk.Msg) {
|
||||
gasAdj, ok := rest.ParseFloat64OrReturnBadRequest(w, br.GasAdjustment, flags.DefaultGasAdjustment)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
simAndExec, gas, err := flags.ParseGas(br.Gas)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
txBldr := auth.NewTxBuilder(
|
||||
utils.GetTxEncoder(cliCtx.Codec), br.AccountNumber, br.Sequence, gas, gasAdj,
|
||||
br.Simulate, br.ChainID, br.Memo, br.Fees, br.GasPrices,
|
||||
)
|
||||
|
||||
if br.Simulate || simAndExec {
|
||||
if gasAdj < 0 {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, errInvalidGasAdjustment.Error())
|
||||
return
|
||||
}
|
||||
|
||||
txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if br.Simulate {
|
||||
rest.WriteSimulationResponse(w, cliCtx.Codec, txBldr.Gas())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
stdMsg, err := txBldr.BuildSignMsg(msgs)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
output, err := cliCtx.Codec.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo))
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if _, err := w.Write(output); err != nil {
|
||||
log.Printf("could not write response: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -9,5 +9,4 @@ import (
|
||||
// Register routes
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
RegisterRPCRoutes(cliCtx, r)
|
||||
RegisterTxRoutes(cliCtx, r)
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
// BroadcastReq defines a tx broadcasting request.
|
||||
type BroadcastReq struct {
|
||||
Tx auth.StdTx `json:"tx"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
// BroadcastTxRequest implements a tx broadcasting handler that is responsible
|
||||
// for broadcasting a valid and signed tx to a full node. The tx can be
|
||||
// broadcasted via a sync|async|block mechanism.
|
||||
func BroadcastTxRequest(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req BroadcastReq
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = cliCtx.Codec.UnmarshalJSON(body, &req)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(req.Tx)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cliCtx = cliCtx.WithBroadcastMode(req.Mode)
|
||||
|
||||
res, err := cliCtx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBroadcastCommand returns the tx broadcast command.
|
||||
func GetBroadcastCommand(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "broadcast [file_path]",
|
||||
Short: "Broadcast transactions generated offline",
|
||||
Long: strings.TrimSpace(`Broadcast transactions created with the --generate-only
|
||||
flag and signed with the sign command. Read a transaction from [file_path] and
|
||||
broadcast it to a node. If you supply a dash (-) argument in place of an input
|
||||
filename, the command reads from standard input.
|
||||
|
||||
$ <appcli> tx broadcast ./mytxn.json
|
||||
`),
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
stdTx, err := utils.ReadStdTxFromFile(cliCtx.Codec, args[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := cliCtx.BroadcastTx(txBytes)
|
||||
cliCtx.PrintOutput(res) // nolint:errcheck
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
return flags.PostCommands(cmd)[0]
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
||||
type (
|
||||
// EncodeReq defines a tx encoding request.
|
||||
// Use auth.StdTx directly
|
||||
|
||||
// EncodeResp defines a tx encoding response.
|
||||
EncodeResp struct {
|
||||
Tx string `json:"tx"`
|
||||
}
|
||||
)
|
||||
|
||||
// EncodeTxRequestHandlerFn returns the encode tx REST handler. In particular,
|
||||
// it takes a json-formatted transaction, encodes it to the Amino wire protocol,
|
||||
// and responds with base64-encoded bytes.
|
||||
func EncodeTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req auth.StdTx
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = cliCtx.Codec.UnmarshalJSON(body, &req)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// re-encode it via the Amino wire protocol
|
||||
txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(req)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// base64 encode the encoded tx bytes
|
||||
txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes)
|
||||
|
||||
response := EncodeResp{Tx: txBytesBase64}
|
||||
rest.PostProcessResponse(w, cliCtx, response)
|
||||
}
|
||||
}
|
||||
|
||||
// txEncodeRespStr implements a simple Stringer wrapper for a encoded tx.
|
||||
type txEncodeRespStr string
|
||||
|
||||
func (txr txEncodeRespStr) String() string {
|
||||
return string(txr)
|
||||
}
|
||||
|
||||
// GetEncodeCommand returns the encode command to take a JSONified transaction and turn it into
|
||||
// Amino-serialized bytes
|
||||
func GetEncodeCommand(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "encode [file]",
|
||||
Short: "Encode transactions generated offline",
|
||||
Long: `Encode transactions created with the --generate-only flag and signed with the sign command.
|
||||
Read a transaction from <file>, serialize it to the Amino wire protocol, and output it as base64.
|
||||
If you supply a dash (-) argument in place of an input filename, the command reads from standard input.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
stdTx, err := utils.ReadStdTxFromFile(cliCtx.Codec, args[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// re-encode it via the Amino wire protocol
|
||||
txBytes, err := cliCtx.Codec.MarshalBinaryLengthPrefixed(stdTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// base64 encode the encoded tx bytes
|
||||
txBytesBase64 := base64.StdEncoding.EncodeToString(txBytes)
|
||||
|
||||
response := txEncodeRespStr(txBytesBase64)
|
||||
cliCtx.PrintOutput(response) // nolint:errcheck
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return flags.PostCommands(cmd)[0]
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
flagTags = "tags"
|
||||
flagPage = "page"
|
||||
flagLimit = "limit"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CLI
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// SearchTxCmd returns a command to search through tagged transactions.
|
||||
func SearchTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "txs",
|
||||
Short: "Search for paginated transactions that match a set of tags",
|
||||
Long: strings.TrimSpace(`
|
||||
Search for transactions that match the exact given tags where results are paginated.
|
||||
|
||||
Example:
|
||||
$ <appcli> query txs --tags '<tag1>:<value1>&<tag2>:<value2>' --page 1 --limit 30
|
||||
`),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
tagsStr := viper.GetString(flagTags)
|
||||
tagsStr = strings.Trim(tagsStr, "'")
|
||||
|
||||
var tags []string
|
||||
if strings.Contains(tagsStr, "&") {
|
||||
tags = strings.Split(tagsStr, "&")
|
||||
} else {
|
||||
tags = append(tags, tagsStr)
|
||||
}
|
||||
|
||||
var tmTags []string
|
||||
for _, tag := range tags {
|
||||
if !strings.Contains(tag, ":") {
|
||||
return fmt.Errorf("%s should be of the format <key>:<value>", tagsStr)
|
||||
} else if strings.Count(tag, ":") > 1 {
|
||||
return fmt.Errorf("%s should only contain one <key>:<value> pair", tagsStr)
|
||||
}
|
||||
|
||||
keyValue := strings.Split(tag, ":")
|
||||
if keyValue[0] == types.TxHeightKey {
|
||||
tag = fmt.Sprintf("%s=%s", keyValue[0], keyValue[1])
|
||||
} else {
|
||||
tag = fmt.Sprintf("%s='%s'", keyValue[0], keyValue[1])
|
||||
}
|
||||
tmTags = append(tmTags, tag)
|
||||
}
|
||||
|
||||
page := viper.GetInt(flagPage)
|
||||
limit := viper.GetInt(flagLimit)
|
||||
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
txs, err := SearchTxs(cliCtx, tmTags, page, limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var output []byte
|
||||
if cliCtx.Indent {
|
||||
output, err = cdc.MarshalJSONIndent(txs, "", " ")
|
||||
} else {
|
||||
output, err = cdc.MarshalJSON(txs)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(output))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringP(flags.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||
viper.BindPFlag(flags.FlagNode, cmd.Flags().Lookup(flags.FlagNode))
|
||||
cmd.Flags().Bool(flags.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
|
||||
viper.BindPFlag(flags.FlagTrustNode, cmd.Flags().Lookup(flags.FlagTrustNode))
|
||||
|
||||
cmd.Flags().String(flagTags, "", "tag:value list of tags that must match")
|
||||
cmd.Flags().Uint32(flagPage, rest.DefaultPage, "Query a specific page of paginated results")
|
||||
cmd.Flags().Uint32(flagLimit, rest.DefaultLimit, "Query number of transactions results per page returned")
|
||||
cmd.MarkFlagRequired(flagTags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// QueryTxCmd implements the default command for a tx query.
|
||||
func QueryTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "tx [hash]",
|
||||
Short: "Find a transaction by hash in a committed block.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
output, err := queryTx(cliCtx, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if output.Empty() {
|
||||
return fmt.Errorf("No transaction found with hash %s", args[0])
|
||||
}
|
||||
|
||||
return cliCtx.PrintOutput(output)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringP(flags.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||
viper.BindPFlag(flags.FlagNode, cmd.Flags().Lookup(flags.FlagNode))
|
||||
cmd.Flags().Bool(flags.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
|
||||
viper.BindPFlag(flags.FlagTrustNode, cmd.Flags().Lookup(flags.FlagTrustNode))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// REST
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// QueryTxsByTagsRequestHandlerFn implements a REST handler that searches for
|
||||
// transactions by tags.
|
||||
func QueryTxsByTagsRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
tags []string
|
||||
txs []sdk.TxResponse
|
||||
page, limit int
|
||||
)
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest,
|
||||
sdk.AppendMsgToErr("could not parse query parameters", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if len(r.Form) == 0 {
|
||||
rest.PostProcessResponse(w, cliCtx, txs)
|
||||
return
|
||||
}
|
||||
|
||||
tags, page, limit, err = rest.ParseHTTPArgs(r)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
searchResult, err := SearchTxs(cliCtx, tags, page, limit)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, cliCtx, searchResult)
|
||||
}
|
||||
}
|
||||
|
||||
// QueryTxRequestHandlerFn implements a REST handler that queries a transaction
|
||||
// by hash in a committed block.
|
||||
func QueryTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
hashHexStr := vars["hash"]
|
||||
|
||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
output, err := queryTx(cliCtx, hashHexStr)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), hashHexStr) {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if output.Empty() {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, fmt.Sprintf("no transaction found with hash %s", hashHexStr))
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, cliCtx, output)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
)
|
||||
|
||||
// register REST routes
|
||||
func RegisterTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/txs", QueryTxsByTagsRequestHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/txs", BroadcastTxRequest(cliCtx)).Methods("POST")
|
||||
r.HandleFunc("/txs/encode", EncodeTxRequestHandlerFn(cliCtx)).Methods("POST")
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
package tx
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
||||
// SearchTxs performs a search for transactions for a given set of tags via
|
||||
// Tendermint RPC. It returns a slice of Info object containing txs and metadata.
|
||||
// An error is returned if the query fails.
|
||||
func SearchTxs(cliCtx context.CLIContext, tags []string, page, limit int) (*sdk.SearchTxsResult, error) {
|
||||
if len(tags) == 0 {
|
||||
return nil, errors.New("must declare at least one tag to search")
|
||||
}
|
||||
|
||||
if page <= 0 {
|
||||
return nil, errors.New("page must greater than 0")
|
||||
}
|
||||
|
||||
if limit <= 0 {
|
||||
return nil, errors.New("limit must greater than 0")
|
||||
}
|
||||
|
||||
// XXX: implement ANY
|
||||
query := strings.Join(tags, " AND ")
|
||||
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prove := !cliCtx.TrustNode
|
||||
|
||||
resTxs, err := node.TxSearch(query, prove, page, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if prove {
|
||||
for _, tx := range resTxs.Txs {
|
||||
err := ValidateTxResult(cliCtx, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resBlocks, err := getBlocksForTxResults(cliCtx, resTxs.Txs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txs, err := formatTxResults(cliCtx.Codec, resTxs.Txs, resBlocks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := sdk.NewSearchTxsResult(resTxs.TotalCount, len(txs), page, limit, txs)
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// formatTxResults parses the indexed txs into a slice of TxResponse objects.
|
||||
func formatTxResults(cdc *codec.Codec, resTxs []*ctypes.ResultTx, resBlocks map[int64]*ctypes.ResultBlock) ([]sdk.TxResponse, error) {
|
||||
var err error
|
||||
out := make([]sdk.TxResponse, len(resTxs))
|
||||
for i := range resTxs {
|
||||
out[i], err = formatTxResult(cdc, resTxs[i], resBlocks[resTxs[i].Height])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ValidateTxResult performs transaction verification.
|
||||
func ValidateTxResult(cliCtx context.CLIContext, resTx *ctypes.ResultTx) error {
|
||||
if !cliCtx.TrustNode {
|
||||
check, err := cliCtx.Verify(resTx.Height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = resTx.Proof.Validate(check.Header.DataHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBlocksForTxResults(cliCtx context.CLIContext, resTxs []*ctypes.ResultTx) (map[int64]*ctypes.ResultBlock, error) {
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resBlocks := make(map[int64]*ctypes.ResultBlock)
|
||||
|
||||
for _, resTx := range resTxs {
|
||||
if _, ok := resBlocks[resTx.Height]; !ok {
|
||||
resBlock, err := node.Block(&resTx.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resBlocks[resTx.Height] = resBlock
|
||||
}
|
||||
}
|
||||
|
||||
return resBlocks, nil
|
||||
}
|
||||
|
||||
func formatTxResult(cdc *codec.Codec, resTx *ctypes.ResultTx, resBlock *ctypes.ResultBlock) (sdk.TxResponse, error) {
|
||||
tx, err := parseTx(cdc, resTx.Tx)
|
||||
if err != nil {
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
|
||||
return sdk.NewResponseResultTx(resTx, tx, resBlock.Block.Time.Format(time.RFC3339)), nil
|
||||
}
|
||||
|
||||
func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) {
|
||||
var tx auth.StdTx
|
||||
|
||||
err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func queryTx(cliCtx context.CLIContext, hashHexStr string) (sdk.TxResponse, error) {
|
||||
hash, err := hex.DecodeString(hashHexStr)
|
||||
if err != nil {
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
|
||||
resTx, err := node.Tx(hash, !cliCtx.TrustNode)
|
||||
if err != nil {
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
|
||||
if !cliCtx.TrustNode {
|
||||
if err = ValidateTxResult(cliCtx, resTx); err != nil {
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
}
|
||||
|
||||
resBlocks, err := getBlocksForTxResults(cliCtx, []*ctypes.ResultTx{resTx})
|
||||
if err != nil {
|
||||
return sdk.TxResponse{}, err
|
||||
}
|
||||
|
||||
out, err := formatTxResult(cliCtx.Codec, resTx, resBlocks[resTx.Height])
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package utils
|
||||
|
||||
import "errors"
|
||||
|
||||
var errInvalidSigner = errors.New("tx intended signer does not match the given signer")
|
||||
@@ -1,382 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/input"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
)
|
||||
|
||||
// GasEstimateResponse defines a response definition for tx gas estimation.
|
||||
type GasEstimateResponse struct {
|
||||
GasEstimate uint64 `json:"gas_estimate"`
|
||||
}
|
||||
|
||||
func (gr GasEstimateResponse) String() string {
|
||||
return fmt.Sprintf("gas estimate: %d", gr.GasEstimate)
|
||||
}
|
||||
|
||||
// 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 authtypes.TxBuilder, msgs []sdk.Msg) error {
|
||||
if cliCtx.GenerateOnly {
|
||||
return PrintUnsignedStdTx(txBldr, cliCtx, msgs)
|
||||
}
|
||||
|
||||
return CompleteAndBroadcastTxCLI(txBldr, cliCtx, msgs)
|
||||
}
|
||||
|
||||
// CompleteAndBroadcastTxCLI implements a utility function that facilitates
|
||||
// sending a series of messages in a signed transaction given a TxBuilder and a
|
||||
// QueryContext. It ensures that the account exists, has a proper number and
|
||||
// sequence set. In addition, it builds and signs a transaction with the
|
||||
// supplied messages. Finally, it broadcasts the signed transaction to a node.
|
||||
func CompleteAndBroadcastTxCLI(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error {
|
||||
txBldr, err := PrepareTxBuilder(txBldr, cliCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromName := cliCtx.GetFromName()
|
||||
|
||||
if txBldr.SimulateAndExecute() || cliCtx.Simulate {
|
||||
txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasEst := GasEstimateResponse{GasEstimate: txBldr.Gas()}
|
||||
fmt.Fprintf(os.Stderr, "%s\n", gasEst.String())
|
||||
}
|
||||
|
||||
if cliCtx.Simulate {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !cliCtx.SkipConfirm {
|
||||
stdSignMsg, err := txBldr.BuildSignMsg(msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var json []byte
|
||||
if viper.GetBool(flags.FlagIndentResponse) {
|
||||
json, err = cliCtx.Codec.MarshalJSONIndent(stdSignMsg, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
json = cliCtx.Codec.MustMarshalJSON(stdSignMsg)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s\n\n", json)
|
||||
|
||||
buf := input.BufferStdin()
|
||||
ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf)
|
||||
if err != nil || !ok {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
passphrase, err := keys.GetPassphrase(fromName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build and sign the transaction
|
||||
txBytes, err := txBldr.BuildAndSign(fromName, passphrase, msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// broadcast to a Tendermint node
|
||||
res, err := cliCtx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cliCtx.PrintOutput(res)
|
||||
}
|
||||
|
||||
// EnrichWithGas calculates the gas estimate that would be consumed by the
|
||||
// transaction and set the transaction's respective value accordingly.
|
||||
func EnrichWithGas(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (authtypes.TxBuilder, error) {
|
||||
_, adjusted, err := simulateMsgs(txBldr, cliCtx, msgs)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
return txBldr.WithGas(adjusted), nil
|
||||
}
|
||||
|
||||
// CalculateGas simulates the execution of a transaction and returns
|
||||
// both the estimate obtained by the query and the adjusted amount.
|
||||
func CalculateGas(queryFunc func(string, []byte) ([]byte, int64, error),
|
||||
cdc *codec.Codec, txBytes []byte, adjustment float64) (estimate, adjusted uint64, err error) {
|
||||
|
||||
// run a simulation (via /app/simulate query) to
|
||||
// estimate gas and update TxBuilder accordingly
|
||||
rawRes, _, err := queryFunc("/app/simulate", txBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
estimate, err = parseQueryResponse(cdc, rawRes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
adjusted = adjustGasEstimate(estimate, adjustment)
|
||||
return
|
||||
}
|
||||
|
||||
// PrintUnsignedStdTx builds an unsigned StdTx and prints it to os.Stdout.
|
||||
func PrintUnsignedStdTx(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error {
|
||||
stdTx, err := buildUnsignedStdTxOffline(txBldr, cliCtx, msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
json, err := cliCtx.Codec.MarshalJSON(stdTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(cliCtx.Output, "%s\n", json)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignStdTx appends a signature to a StdTx and returns a copy of it. If appendSig
|
||||
// is false, it replaces the signatures already attached with the new signature.
|
||||
// Don't perform online validation or lookups if offline is true.
|
||||
func SignStdTx(
|
||||
txBldr authtypes.TxBuilder, cliCtx context.CLIContext, name string,
|
||||
stdTx authtypes.StdTx, appendSig bool, offline bool,
|
||||
) (authtypes.StdTx, error) {
|
||||
|
||||
var signedStdTx authtypes.StdTx
|
||||
|
||||
info, err := txBldr.Keybase().Get(name)
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
|
||||
addr := info.GetPubKey().Address()
|
||||
|
||||
// check whether the address is a signer
|
||||
if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) {
|
||||
return signedStdTx, fmt.Errorf("%s: %s", errInvalidSigner, name)
|
||||
}
|
||||
|
||||
if !offline {
|
||||
txBldr, err = populateAccountFromState(txBldr, cliCtx, sdk.AccAddress(addr))
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
}
|
||||
|
||||
passphrase, err := keys.GetPassphrase(name)
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
|
||||
return txBldr.SignStdTx(name, passphrase, stdTx, appendSig)
|
||||
}
|
||||
|
||||
// SignStdTxWithSignerAddress attaches a signature to a StdTx and returns a copy of a it.
|
||||
// Don't perform online validation or lookups if offline is true, else
|
||||
// populate account and sequence numbers from a foreign account.
|
||||
func SignStdTxWithSignerAddress(txBldr authtypes.TxBuilder, cliCtx context.CLIContext,
|
||||
addr sdk.AccAddress, name string, stdTx authtypes.StdTx,
|
||||
offline bool) (signedStdTx authtypes.StdTx, err error) {
|
||||
|
||||
// check whether the address is a signer
|
||||
if !isTxSigner(addr, stdTx.GetSigners()) {
|
||||
return signedStdTx, fmt.Errorf("%s: %s", errInvalidSigner, name)
|
||||
}
|
||||
|
||||
if !offline {
|
||||
txBldr, err = populateAccountFromState(txBldr, cliCtx, addr)
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
}
|
||||
|
||||
passphrase, err := keys.GetPassphrase(name)
|
||||
if err != nil {
|
||||
return signedStdTx, err
|
||||
}
|
||||
|
||||
return txBldr.SignStdTx(name, passphrase, stdTx, false)
|
||||
}
|
||||
|
||||
// Read and decode a StdTx from the given filename. Can pass "-" to read from stdin.
|
||||
func ReadStdTxFromFile(cdc *codec.Codec, filename string) (stdTx authtypes.StdTx, err error) {
|
||||
var bytes []byte
|
||||
if filename == "-" {
|
||||
bytes, err = ioutil.ReadAll(os.Stdin)
|
||||
} else {
|
||||
bytes, err = ioutil.ReadFile(filename)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = cdc.UnmarshalJSON(bytes, &stdTx); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func populateAccountFromState(
|
||||
txBldr authtypes.TxBuilder, cliCtx context.CLIContext, addr sdk.AccAddress,
|
||||
) (authtypes.TxBuilder, error) {
|
||||
|
||||
accNum, err := cliCtx.GetAccountNumber(addr)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
|
||||
accSeq, err := cliCtx.GetAccountSequence(addr)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
|
||||
return txBldr.WithAccountNumber(accNum).WithSequence(accSeq), nil
|
||||
}
|
||||
|
||||
// GetTxEncoder return tx encoder from global sdk configuration if ones is defined.
|
||||
// Otherwise returns encoder with default logic.
|
||||
func GetTxEncoder(cdc *codec.Codec) (encoder sdk.TxEncoder) {
|
||||
encoder = sdk.GetConfig().GetTxEncoder()
|
||||
if encoder == nil {
|
||||
encoder = authtypes.DefaultTxEncoder(cdc)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// nolint
|
||||
// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value.
|
||||
func simulateMsgs(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (estimated, adjusted uint64, err error) {
|
||||
txBytes, err := txBldr.BuildTxForSim(msgs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
estimated, adjusted, err = CalculateGas(cliCtx.QueryWithData, cliCtx.Codec, txBytes, txBldr.GasAdjustment())
|
||||
return
|
||||
}
|
||||
|
||||
func adjustGasEstimate(estimate uint64, adjustment float64) uint64 {
|
||||
return uint64(adjustment * float64(estimate))
|
||||
}
|
||||
|
||||
func parseQueryResponse(cdc *codec.Codec, rawRes []byte) (uint64, error) {
|
||||
var simulationResult sdk.Result
|
||||
if err := cdc.UnmarshalBinaryLengthPrefixed(rawRes, &simulationResult); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return simulationResult.GasUsed, nil
|
||||
}
|
||||
|
||||
// PrepareTxBuilder populates a TxBuilder in preparation for the build of a Tx.
|
||||
func PrepareTxBuilder(txBldr authtypes.TxBuilder, cliCtx context.CLIContext) (authtypes.TxBuilder, error) {
|
||||
if err := cliCtx.EnsureAccountExists(); err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
|
||||
from := cliCtx.GetFromAddress()
|
||||
|
||||
// TODO: (ref #1903) Allow for user supplied account number without
|
||||
// automatically doing a manual lookup.
|
||||
if txBldr.AccountNumber() == 0 {
|
||||
accNum, err := cliCtx.GetAccountNumber(from)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
txBldr = txBldr.WithAccountNumber(accNum)
|
||||
}
|
||||
|
||||
// TODO: (ref #1903) Allow for user supplied account sequence without
|
||||
// automatically doing a manual lookup.
|
||||
if txBldr.Sequence() == 0 {
|
||||
accSeq, err := cliCtx.GetAccountSequence(from)
|
||||
if err != nil {
|
||||
return txBldr, err
|
||||
}
|
||||
txBldr = txBldr.WithSequence(accSeq)
|
||||
}
|
||||
return txBldr, nil
|
||||
}
|
||||
|
||||
func buildUnsignedStdTxOffline(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx authtypes.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 stdTx, err
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "estimated gas = %v\n", txBldr.Gas())
|
||||
}
|
||||
|
||||
stdSignMsg, err := txBldr.BuildSignMsg(msgs)
|
||||
if err != nil {
|
||||
return stdTx, nil
|
||||
}
|
||||
|
||||
return authtypes.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil
|
||||
}
|
||||
|
||||
func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool {
|
||||
for _, s := range signers {
|
||||
if bytes.Equal(user.Bytes(), s.Bytes()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateCmd returns unknown command error or Help display if help flag set
|
||||
func ValidateCmd(cmd *cobra.Command, args []string) error {
|
||||
var cmds []string
|
||||
var help bool
|
||||
|
||||
// construct array of commands and search for help flag
|
||||
for _, arg := range args {
|
||||
if arg == "--help" || arg == "-h" {
|
||||
help = true
|
||||
} else if len(arg) > 0 && !(arg[0] == '-') {
|
||||
cmds = append(cmds, arg)
|
||||
}
|
||||
}
|
||||
|
||||
if !help && len(cmds) > 0 {
|
||||
err := fmt.Sprintf("unknown command \"%s\" for \"%s\"", cmds[0], cmd.CalledAs())
|
||||
|
||||
// build suggestions for unknown argument
|
||||
if suggestions := cmd.SuggestionsFor(cmds[0]); 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()
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
)
|
||||
|
||||
var (
|
||||
priv = ed25519.GenPrivKey()
|
||||
addr = sdk.AccAddress(priv.PubKey().Address())
|
||||
)
|
||||
|
||||
func TestParseQueryResponse(t *testing.T) {
|
||||
cdc := makeCodec()
|
||||
sdkResBytes := cdc.MustMarshalBinaryLengthPrefixed(sdk.Result{GasUsed: 10})
|
||||
gas, err := parseQueryResponse(cdc, sdkResBytes)
|
||||
assert.Equal(t, gas, uint64(10))
|
||||
assert.Nil(t, err)
|
||||
gas, err = parseQueryResponse(cdc, []byte("fuzzy"))
|
||||
assert.Equal(t, gas, uint64(0))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCalculateGas(t *testing.T) {
|
||||
cdc := makeCodec()
|
||||
makeQueryFunc := func(gasUsed uint64, wantErr bool) func(string, []byte) ([]byte, int64, error) {
|
||||
return func(string, []byte) ([]byte, int64, error) {
|
||||
if wantErr {
|
||||
return nil, 0, errors.New("")
|
||||
}
|
||||
return cdc.MustMarshalBinaryLengthPrefixed(sdk.Result{GasUsed: gasUsed}), 0, nil
|
||||
}
|
||||
}
|
||||
type args struct {
|
||||
queryFuncGasUsed uint64
|
||||
queryFuncWantErr bool
|
||||
adjustment float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantEstimate uint64
|
||||
wantAdjusted uint64
|
||||
wantErr bool
|
||||
}{
|
||||
{"error", args{0, true, 1.2}, 0, 0, true},
|
||||
{"adjusted gas", args{10, false, 1.2}, 10, 12, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
queryFunc := makeQueryFunc(tt.args.queryFuncGasUsed, tt.args.queryFuncWantErr)
|
||||
gotEstimate, gotAdjusted, err := CalculateGas(queryFunc, cdc, []byte(""), tt.args.adjustment)
|
||||
assert.Equal(t, err != nil, tt.wantErr)
|
||||
assert.Equal(t, gotEstimate, tt.wantEstimate)
|
||||
assert.Equal(t, gotAdjusted, tt.wantAdjusted)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultTxEncoder(t *testing.T) {
|
||||
cdc := makeCodec()
|
||||
|
||||
defaultEncoder := authtypes.DefaultTxEncoder(cdc)
|
||||
encoder := GetTxEncoder(cdc)
|
||||
|
||||
compareEncoders(t, defaultEncoder, encoder)
|
||||
}
|
||||
|
||||
func TestConfiguredTxEncoder(t *testing.T) {
|
||||
cdc := makeCodec()
|
||||
|
||||
customEncoder := func(tx sdk.Tx) ([]byte, error) {
|
||||
return json.Marshal(tx)
|
||||
}
|
||||
|
||||
config := sdk.GetConfig()
|
||||
config.SetTxEncoder(customEncoder)
|
||||
|
||||
encoder := GetTxEncoder(cdc)
|
||||
|
||||
compareEncoders(t, customEncoder, encoder)
|
||||
}
|
||||
|
||||
func TestReadStdTxFromFile(t *testing.T) {
|
||||
cdc := codec.New()
|
||||
sdk.RegisterCodec(cdc)
|
||||
|
||||
// Build a test transaction
|
||||
fee := authtypes.NewStdFee(50000, sdk.Coins{sdk.NewInt64Coin("atom", 150)})
|
||||
stdTx := authtypes.NewStdTx([]sdk.Msg{}, fee, []authtypes.StdSignature{}, "foomemo")
|
||||
|
||||
// Write it to the file
|
||||
encodedTx, _ := cdc.MarshalJSON(stdTx)
|
||||
jsonTxFile := writeToNewTempFile(t, string(encodedTx))
|
||||
defer os.Remove(jsonTxFile.Name())
|
||||
|
||||
// Read it back
|
||||
decodedTx, err := ReadStdTxFromFile(cdc, jsonTxFile.Name())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, decodedTx.Memo, "foomemo")
|
||||
}
|
||||
|
||||
func TestValidateCmd(t *testing.T) {
|
||||
// Setup root and subcommands
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "root",
|
||||
}
|
||||
queryCmd := &cobra.Command{
|
||||
Use: "query",
|
||||
}
|
||||
rootCmd.AddCommand(queryCmd)
|
||||
|
||||
// Command being tested
|
||||
distCmd := &cobra.Command{
|
||||
Use: "distr",
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
}
|
||||
queryCmd.AddCommand(distCmd)
|
||||
|
||||
commissionCmd := &cobra.Command{
|
||||
Use: "commission",
|
||||
}
|
||||
distCmd.AddCommand(commissionCmd)
|
||||
|
||||
tests := []struct {
|
||||
reason string
|
||||
args []string
|
||||
wantErr bool
|
||||
}{
|
||||
{"misspelled command", []string{"comission"}, true},
|
||||
{"no command provided", []string{}, false},
|
||||
{"help flag", []string{"comission", "--help"}, false},
|
||||
{"shorthand help flag", []string{"comission", "-h"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
err := ValidateCmd(distCmd, tt.args)
|
||||
assert.Equal(t, tt.wantErr, err != nil, tt.reason)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// aux functions
|
||||
|
||||
func compareEncoders(t *testing.T, expected sdk.TxEncoder, actual sdk.TxEncoder) {
|
||||
msgs := []sdk.Msg{sdk.NewTestMsg(addr)}
|
||||
tx := authtypes.NewStdTx(msgs, authtypes.StdFee{}, []authtypes.StdSignature{}, "")
|
||||
|
||||
defaultEncoderBytes, err := expected(tx)
|
||||
require.NoError(t, err)
|
||||
encoderBytes, err := actual(tx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, defaultEncoderBytes, encoderBytes)
|
||||
}
|
||||
|
||||
func writeToNewTempFile(t *testing.T, data string) *os.File {
|
||||
fp, err := ioutil.TempFile(os.TempDir(), "client_tx_test")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = fp.WriteString(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
return fp
|
||||
}
|
||||
|
||||
func makeCodec() *codec.Codec {
|
||||
var cdc = codec.New()
|
||||
sdk.RegisterCodec(cdc)
|
||||
codec.RegisterCrypto(cdc)
|
||||
authtypes.RegisterCodec(cdc)
|
||||
cdc.RegisterConcrete(sdk.TestMsg{}, "cosmos-sdk/Test", nil)
|
||||
return cdc
|
||||
}
|
||||
Reference in New Issue
Block a user