Merge PR #4555: Move client/{tx,rest,utils} into x/auth/client

This commit is contained in:
Alessio Treglia
2019-06-15 14:34:11 +02:00
committed by Alexander Bezobchuk
parent 941effc14d
commit 73700df8c3
51 changed files with 621 additions and 641 deletions
-28
View File
@@ -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
)
+38
View File
@@ -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()
}
+50
View File
@@ -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)
}
}
-5
View File
@@ -1,5 +0,0 @@
package rest
import "errors"
var errInvalidGasAdjustment = errors.New("invalid gas adjustment")
-72
View File
@@ -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
}
-1
View File
@@ -9,5 +9,4 @@ import (
// Register routes
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
RegisterRPCRoutes(cliCtx, r)
RegisterTxRoutes(cliCtx, r)
}
-95
View File
@@ -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]
}
-104
View File
@@ -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]
}
-213
View File
@@ -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)
}
}
-15
View File
@@ -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")
}
-176
View File
@@ -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
}
-5
View File
@@ -1,5 +0,0 @@
package utils
import "errors"
var errInvalidSigner = errors.New("tx intended signer does not match the given signer")
-382
View File
@@ -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()
}
-186
View File
@@ -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
}