From d6f8bff21887ad3a141f8ea07722e0a1139c34b6 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 29 Mar 2019 12:21:33 -0400 Subject: [PATCH 01/10] Consolidate tx quering logic into a single file --- client/tx/query.go | 178 ++++++++++++++++++++++++++------------- client/tx/root.go | 2 +- client/tx/search.go | 198 -------------------------------------------- client/tx/utils.go | 139 +++++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 256 deletions(-) delete mode 100644 client/tx/search.go create mode 100644 client/tx/utils.go diff --git a/client/tx/query.go b/client/tx/query.go index 74bb38495d12..182a9bf806d7 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -1,14 +1,13 @@ package tx import ( - "encoding/hex" "fmt" "net/http" "strings" "github.com/gorilla/mux" "github.com/spf13/cobra" - ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/types" "github.com/spf13/viper" @@ -17,14 +16,100 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/rest" - "github.com/cosmos/cosmos-sdk/x/auth" ) +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: +$ gaiacli query txs --tags ':&:' --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 :", tagsStr) + } else if strings.Count(tag, ":") > 1 { + return fmt.Errorf("%s should only contain one : 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, cdc, 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(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) + + cmd.Flags().String(flagTags, "", "tag:value list of tags that must match") + cmd.Flags().Int32(flagPage, rest.DefaultPage, "Query a specific page of paginated results") + cmd.Flags().Int32(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: "Matches this txhash over all committed blocks", + 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) @@ -46,76 +131,55 @@ func QueryTxCmd(cdc *codec.Codec) *cobra.Command { viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) + return cmd } -func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string) (out sdk.TxResponse, err error) { - hash, err := hex.DecodeString(hashHexStr) - if err != nil { - return out, err - } - - node, err := cliCtx.GetNode() - if err != nil { - return out, err - } +// ---------------------------------------------------------------------------- +// REST +// ---------------------------------------------------------------------------- - res, err := node.Tx(hash, !cliCtx.TrustNode) - if err != nil { - return out, err - } +// QueryTxsByTagsRequestHandlerFn implements a REST handler that searches for +// transactions by tags. +func QueryTxsByTagsRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var ( + tags []string + txs []sdk.TxResponse + page, limit int + ) - if !cliCtx.TrustNode { - if err = ValidateTxResult(cliCtx, res); err != nil { - return out, err + err := r.ParseForm() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, + sdk.AppendMsgToErr("could not parse query parameters", err.Error())) + return } - } - if out, err = formatTxResult(cdc, res); err != nil { - return out, err - } + if len(r.Form) == 0 { + rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent) + return + } - return out, nil -} + tags, page, limit, err = rest.ParseHTTPArgs(r) -// ValidateTxResult performs transaction verification -func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error { - if !cliCtx.TrustNode { - check, err := cliCtx.Verify(res.Height) if err != nil { - return err + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return } - err = res.Proof.Validate(check.Header.DataHash) + + txs, err = SearchTxs(cliCtx, cdc, tags, page, limit) if err != nil { - return err + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return } - } - return nil -} -func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (sdk.TxResponse, error) { - tx, err := parseTx(cdc, res.Tx) - if err != nil { - return sdk.TxResponse{}, err + rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent) } - - return sdk.NewResponseResultTx(res, tx), 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 -} - -// REST - -// QueryTxRequestHandlerFn transaction query REST handler +// QueryTxRequestHandlerFn implements a REST handler that queries a transaction +// by hash in a committed block. func QueryTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/client/tx/root.go b/client/tx/root.go index 104e0a57cbe2..cb2c6460917e 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -10,7 +10,7 @@ import ( // register REST routes func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, cliCtx)).Methods("GET") - r.HandleFunc("/txs", SearchTxRequestHandlerFn(cliCtx, cdc)).Methods("GET") + r.HandleFunc("/txs", QueryTxsByTagsRequestHandlerFn(cliCtx, cdc)).Methods("GET") r.HandleFunc("/txs", BroadcastTxRequest(cliCtx, cdc)).Methods("POST") r.HandleFunc("/txs/encode", EncodeTxRequestHandlerFn(cdc, cliCtx)).Methods("POST") } diff --git a/client/tx/search.go b/client/tx/search.go deleted file mode 100644 index 3ee020676842..000000000000 --- a/client/tx/search.go +++ /dev/null @@ -1,198 +0,0 @@ -package tx - -import ( - "errors" - "fmt" - "net/http" - "strings" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - rest "github.com/cosmos/cosmos-sdk/types/rest" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - - ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/types" -) - -const ( - flagTags = "tags" - flagAny = "any" - flagPage = "page" - flagLimit = "limit" -) - -// default client command to search through tagged transactions -func SearchTxCmd(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "txs", - Short: "Search for all transactions that match the given tags.", - Long: strings.TrimSpace(` -Search for transactions that match exactly the given tags. For example: - -$ gaiacli query txs --tags ':&:' --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 :", tagsStr) - } else if strings.Count(tag, ":") > 1 { - return fmt.Errorf("%s should only contain one : 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, cdc, 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(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) - cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") - viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) - cmd.Flags().String(flagTags, "", "tag:value list of tags that must match") - cmd.Flags().Int32(flagPage, rest.DefaultPage, "Query a specific page of paginated results") - cmd.Flags().Int32(flagLimit, rest.DefaultLimit, "Query number of transactions results per page returned") - cmd.MarkFlagRequired(flagTags) - return cmd -} - -// 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, cdc *codec.Codec, tags []string, page, limit int) ([]sdk.TxResponse, 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 ") - - // get the node - node, err := cliCtx.GetNode() - if err != nil { - return nil, err - } - - prove := !cliCtx.TrustNode - - res, err := node.TxSearch(query, prove, page, limit) - if err != nil { - return nil, err - } - - if prove { - for _, tx := range res.Txs { - err := ValidateTxResult(cliCtx, tx) - if err != nil { - return nil, err - } - } - } - - info, err := FormatTxResults(cdc, res.Txs) - if err != nil { - return nil, err - } - - return info, nil -} - -// parse the indexed txs into an array of Info -func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]sdk.TxResponse, error) { - var err error - out := make([]sdk.TxResponse, len(res)) - for i := range res { - out[i], err = formatTxResult(cdc, res[i]) - if err != nil { - return nil, err - } - } - return out, nil -} - -///////////////////////////////////////// -// REST - -// Search Tx REST Handler -func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var tags []string - var page, limit int - var txs []sdk.TxResponse - err := r.ParseForm() - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, - sdk.AppendMsgToErr("could not parse query parameters", err.Error())) - return - } - if len(r.Form) == 0 { - rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent) - return - } - - tags, page, limit, err = rest.ParseHTTPArgs(r) - - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - txs, err = SearchTxs(cliCtx, cdc, tags, page, limit) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent) - } -} diff --git a/client/tx/utils.go b/client/tx/utils.go new file mode 100644 index 000000000000..4958174e000c --- /dev/null +++ b/client/tx/utils.go @@ -0,0 +1,139 @@ +package tx + +import ( + "encoding/hex" + "errors" + "strings" + + "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" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// 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, cdc *codec.Codec, tags []string, page, limit int) ([]sdk.TxResponse, 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 + + res, err := node.TxSearch(query, prove, page, limit) + if err != nil { + return nil, err + } + + if prove { + for _, tx := range res.Txs { + err := ValidateTxResult(cliCtx, tx) + if err != nil { + return nil, err + } + } + } + + info, err := FormatTxResults(cdc, res.Txs) + if err != nil { + return nil, err + } + + return info, nil +} + +// FormatTxResults parses the indexed txs into a slice of TxResponse objects. +func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]sdk.TxResponse, error) { + var err error + out := make([]sdk.TxResponse, len(res)) + for i := range res { + out[i], err = formatTxResult(cdc, res[i]) + if err != nil { + return nil, err + } + } + + return out, nil +} + +// ValidateTxResult performs transaction verification. +func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error { + if !cliCtx.TrustNode { + check, err := cliCtx.Verify(res.Height) + if err != nil { + return err + } + err = res.Proof.Validate(check.Header.DataHash) + if err != nil { + return err + } + } + return nil +} + +func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (sdk.TxResponse, error) { + tx, err := parseTx(cdc, res.Tx) + if err != nil { + return sdk.TxResponse{}, err + } + + return sdk.NewResponseResultTx(res, tx), 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(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string) (out sdk.TxResponse, err error) { + hash, err := hex.DecodeString(hashHexStr) + if err != nil { + return out, err + } + + node, err := cliCtx.GetNode() + if err != nil { + return out, err + } + + res, err := node.Tx(hash, !cliCtx.TrustNode) + if err != nil { + return out, err + } + + if !cliCtx.TrustNode { + if err = ValidateTxResult(cliCtx, res); err != nil { + return out, err + } + } + + if out, err = formatTxResult(cdc, res); err != nil { + return out, err + } + + return out, nil +} From 3aafcdb572933c9b9f51b1de10efdf72cbea6eaf Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 29 Mar 2019 13:20:19 -0400 Subject: [PATCH 02/10] Cleanup queryTx --- client/tx/utils.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/client/tx/utils.go b/client/tx/utils.go index 4958174e000c..9e7feecdfbbe 100644 --- a/client/tx/utils.go +++ b/client/tx/utils.go @@ -52,7 +52,7 @@ func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, } } - info, err := FormatTxResults(cdc, res.Txs) + info, err := formatTxResults(cdc, res.Txs) if err != nil { return nil, err } @@ -60,8 +60,8 @@ func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, return info, nil } -// FormatTxResults parses the indexed txs into a slice of TxResponse objects. -func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]sdk.TxResponse, error) { +// formatTxResults parses the indexed txs into a slice of TxResponse objects. +func formatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]sdk.TxResponse, error) { var err error out := make([]sdk.TxResponse, len(res)) for i := range res { @@ -109,29 +109,30 @@ func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) { return tx, nil } -func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string) (out sdk.TxResponse, err error) { +func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string) (sdk.TxResponse, error) { hash, err := hex.DecodeString(hashHexStr) if err != nil { - return out, err + return sdk.TxResponse{}, err } node, err := cliCtx.GetNode() if err != nil { - return out, err + return sdk.TxResponse{}, err } res, err := node.Tx(hash, !cliCtx.TrustNode) if err != nil { - return out, err + return sdk.TxResponse{}, err } if !cliCtx.TrustNode { if err = ValidateTxResult(cliCtx, res); err != nil { - return out, err + return sdk.TxResponse{}, err } } - if out, err = formatTxResult(cdc, res); err != nil { + out, err := formatTxResult(cdc, res) + if err != nil { return out, err } From d3cf38d18dd4fd90cb103d6a54f101ad2d59d079 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 29 Mar 2019 13:33:46 -0400 Subject: [PATCH 03/10] Add timestamp to TxResponse --- types/result.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/types/result.go b/types/result.go index df88acdf3805..9b679b38604e 100644 --- a/types/result.go +++ b/types/result.go @@ -76,10 +76,11 @@ type TxResponse struct { Tags StringTags `json:"tags,omitempty"` Codespace string `json:"codespace,omitempty"` Tx Tx `json:"tx,omitempty"` + Timestamp string `json:"timestamp,omitempty"` } // NewResponseResultTx returns a TxResponse given a ResultTx from tendermint -func NewResponseResultTx(res *ctypes.ResultTx, tx Tx) TxResponse { +func NewResponseResultTx(res *ctypes.ResultTx, tx Tx, timestamp string) TxResponse { if res == nil { return TxResponse{} } @@ -98,6 +99,7 @@ func NewResponseResultTx(res *ctypes.ResultTx, tx Tx) TxResponse { GasUsed: res.TxResult.GasUsed, Tags: TagsToStringTags(res.TxResult.Tags), Tx: tx, + Timestamp: timestamp, } } @@ -230,6 +232,10 @@ func (r TxResponse) String() string { sb.WriteString(fmt.Sprintf(" Codespace: %s\n", r.Codespace)) } + if r.Timestamp != "" { + sb.WriteString(fmt.Sprintf(" Timestamp: %s\n", r.Timestamp)) + } + return strings.TrimSpace(sb.String()) } From ef3e5d1e152121aa9090fc79dc98629f5dd295dc Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 29 Mar 2019 13:34:00 -0400 Subject: [PATCH 04/10] Update tx querying to include timestamps --- client/tx/utils.go | 63 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/client/tx/utils.go b/client/tx/utils.go index 9e7feecdfbbe..0f7ea728ac69 100644 --- a/client/tx/utils.go +++ b/client/tx/utils.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "errors" "strings" + "time" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" @@ -52,20 +53,25 @@ func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, } } - info, err := formatTxResults(cdc, res.Txs) + resBlocks, err := getBlocksForTxResults(cliCtx, res.Txs) if err != nil { return nil, err } - return info, nil + txs, err := formatTxResults(cdc, res.Txs, resBlocks) + if err != nil { + return nil, err + } + + return txs, nil } // formatTxResults parses the indexed txs into a slice of TxResponse objects. -func formatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]sdk.TxResponse, error) { +func formatTxResults(cdc *codec.Codec, resTxs []*ctypes.ResultTx, resBlocks map[int64]*ctypes.ResultBlock) ([]sdk.TxResponse, error) { var err error - out := make([]sdk.TxResponse, len(res)) - for i := range res { - out[i], err = formatTxResult(cdc, res[i]) + 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 } @@ -75,13 +81,13 @@ func formatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]sdk.TxResponse } // ValidateTxResult performs transaction verification. -func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error { +func ValidateTxResult(cliCtx context.CLIContext, resTx *ctypes.ResultTx) error { if !cliCtx.TrustNode { - check, err := cliCtx.Verify(res.Height) + check, err := cliCtx.Verify(resTx.Height) if err != nil { return err } - err = res.Proof.Validate(check.Header.DataHash) + err = resTx.Proof.Validate(check.Header.DataHash) if err != nil { return err } @@ -89,13 +95,35 @@ func ValidateTxResult(cliCtx context.CLIContext, res *ctypes.ResultTx) error { return nil } -func formatTxResult(cdc *codec.Codec, res *ctypes.ResultTx) (sdk.TxResponse, error) { - tx, err := parseTx(cdc, res.Tx) +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(res, tx), nil + return sdk.NewResponseResultTx(resTx, tx, resBlock.Block.Time.Format(time.RFC3339)), nil } func parseTx(cdc *codec.Codec, txBytes []byte) (sdk.Tx, error) { @@ -120,18 +148,23 @@ func queryTx(cdc *codec.Codec, cliCtx context.CLIContext, hashHexStr string) (sd return sdk.TxResponse{}, err } - res, err := node.Tx(hash, !cliCtx.TrustNode) + resTx, err := node.Tx(hash, !cliCtx.TrustNode) if err != nil { return sdk.TxResponse{}, err } if !cliCtx.TrustNode { - if err = ValidateTxResult(cliCtx, res); err != nil { + if err = ValidateTxResult(cliCtx, resTx); err != nil { return sdk.TxResponse{}, err } } - out, err := formatTxResult(cdc, res) + resBlocks, err := getBlocksForTxResults(cliCtx, []*ctypes.ResultTx{resTx}) + if err != nil { + return sdk.TxResponse{}, err + } + + out, err := formatTxResult(cdc, resTx, resBlocks[resTx.Height]) if err != nil { return out, err } From 39b1557bf55bfaf6eacca61864cb1b1e85f2aba5 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 29 Mar 2019 13:41:19 -0400 Subject: [PATCH 05/10] Remove dup code --- x/staking/client/rest/query.go | 10 +--------- x/staking/client/rest/utils.go | 25 ++++++------------------- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/x/staking/client/rest/query.go b/x/staking/client/rest/query.go index 8a5a5be3ce9c..6b8c82cc298c 100644 --- a/x/staking/client/rest/query.go +++ b/x/staking/client/rest/query.go @@ -125,14 +125,6 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han return } - node, err := cliCtx.GetNode() - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - // Get values from query - typesQuery := r.URL.Query().Get("type") trimmedQuery := strings.TrimSpace(typesQuery) if len(trimmedQuery) != 0 { @@ -167,7 +159,7 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han } for _, action := range actions { - foundTxs, errQuery := queryTxs(node, cliCtx, cdc, action, delegatorAddr) + foundTxs, errQuery := queryTxs(cliCtx, cdc, action, delegatorAddr) if errQuery != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) } diff --git a/x/staking/client/rest/utils.go b/x/staking/client/rest/utils.go index dd34dd053ed2..041aeccbcb56 100644 --- a/x/staking/client/rest/utils.go +++ b/x/staking/client/rest/utils.go @@ -6,8 +6,6 @@ import ( "github.com/gorilla/mux" - rpcclient "github.com/tendermint/tendermint/rpc/client" - "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/codec" @@ -28,26 +26,15 @@ func contains(stringSlice []string, txType string) bool { } // queries staking txs -func queryTxs(node rpcclient.Client, cliCtx context.CLIContext, cdc *codec.Codec, tag string, delegatorAddr string) ([]sdk.TxResponse, error) { +func queryTxs(cliCtx context.CLIContext, cdc *codec.Codec, tag string, delegatorAddr string) ([]sdk.TxResponse, error) { page := 0 - perPage := 100 - prove := !cliCtx.TrustNode - query := fmt.Sprintf("%s='%s' AND %s='%s'", tags.Action, tag, tags.Delegator, delegatorAddr) - res, err := node.TxSearch(query, prove, page, perPage) - if err != nil { - return nil, err - } - - if prove { - for _, txData := range res.Txs { - err := tx.ValidateTxResult(cliCtx, txData) - if err != nil { - return nil, err - } - } + limit := 100 + tags := []string{ + fmt.Sprintf("%s='%s'", tags.Action, tag), + fmt.Sprintf("%s='%s'", tags.Delegator, delegatorAddr), } - return tx.FormatTxResults(cdc, res.Txs) + return tx.SearchTxs(cliCtx, cdc, tags, page, limit) } func queryRedelegations(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc { From 3aae95a6565fc2ed3f300e1ccdca1c4d89a6b9f0 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 29 Mar 2019 18:15:12 -0400 Subject: [PATCH 06/10] Update variable names --- client/tx/utils.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/tx/utils.go b/client/tx/utils.go index 0f7ea728ac69..41dfd72e883d 100644 --- a/client/tx/utils.go +++ b/client/tx/utils.go @@ -39,13 +39,13 @@ func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, prove := !cliCtx.TrustNode - res, err := node.TxSearch(query, prove, page, limit) + resTxs, err := node.TxSearch(query, prove, page, limit) if err != nil { return nil, err } if prove { - for _, tx := range res.Txs { + for _, tx := range resTxs.Txs { err := ValidateTxResult(cliCtx, tx) if err != nil { return nil, err @@ -53,12 +53,12 @@ func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, } } - resBlocks, err := getBlocksForTxResults(cliCtx, res.Txs) + resBlocks, err := getBlocksForTxResults(cliCtx, resTxs.Txs) if err != nil { return nil, err } - txs, err := formatTxResults(cdc, res.Txs, resBlocks) + txs, err := formatTxResults(cdc, resTxs.Txs, resBlocks) if err != nil { return nil, err } From ba8838fd523f0a086f0ed9fe62a3de8c3d0cb7c5 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 29 Mar 2019 20:33:09 -0400 Subject: [PATCH 07/10] Page fix, cleanup and remove dead code --- x/staking/client/rest/query.go | 2 +- x/staking/client/rest/utils.go | 46 +--------------------------------- 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/x/staking/client/rest/query.go b/x/staking/client/rest/query.go index 6b8c82cc298c..63fcc6d24b92 100644 --- a/x/staking/client/rest/query.go +++ b/x/staking/client/rest/query.go @@ -161,7 +161,7 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han for _, action := range actions { foundTxs, errQuery := queryTxs(cliCtx, cdc, action, delegatorAddr) if errQuery != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, errQuery.Error()) } txs = append(txs, foundTxs...) } diff --git a/x/staking/client/rest/utils.go b/x/staking/client/rest/utils.go index 041aeccbcb56..a34864e604f7 100644 --- a/x/staking/client/rest/utils.go +++ b/x/staking/client/rest/utils.go @@ -27,7 +27,7 @@ func contains(stringSlice []string, txType string) bool { // queries staking txs func queryTxs(cliCtx context.CLIContext, cdc *codec.Codec, tag string, delegatorAddr string) ([]sdk.TxResponse, error) { - page := 0 + page := 1 limit := 100 tags := []string{ fmt.Sprintf("%s='%s'", tags.Action, tag), @@ -37,50 +37,6 @@ func queryTxs(cliCtx context.CLIContext, cdc *codec.Codec, tag string, delegator return tx.SearchTxs(cliCtx, cdc, tags, page, limit) } -func queryRedelegations(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - bech32srcValidator := vars["srcValidatorAddr"] - bech32dstValidator := vars["dstValidatorAddr"] - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - srcValidatorAddr, err := sdk.ValAddressFromBech32(bech32srcValidator) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - dstValidatorAddr, err := sdk.ValAddressFromBech32(bech32dstValidator) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - params := staking.QueryRedelegationParams{ - DelegatorAddr: delegatorAddr, - SrcValidatorAddr: srcValidatorAddr, - DstValidatorAddr: dstValidatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, err := cliCtx.QueryWithData(endpoint, bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) - } -} - func queryBonds(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) From 418c0f883f319219a385f7d3ca4b72f14ed313d5 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Sat, 30 Mar 2019 11:13:08 -0400 Subject: [PATCH 08/10] Add pending log entry --- .../sdk/3238-Add-block-time-to-tx-responses-when-querying-for | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .pending/improvements/sdk/3238-Add-block-time-to-tx-responses-when-querying-for diff --git a/.pending/improvements/sdk/3238-Add-block-time-to-tx-responses-when-querying-for b/.pending/improvements/sdk/3238-Add-block-time-to-tx-responses-when-querying-for new file mode 100644 index 000000000000..d254b93e6298 --- /dev/null +++ b/.pending/improvements/sdk/3238-Add-block-time-to-tx-responses-when-querying-for @@ -0,0 +1,2 @@ +#3238 Add block time to tx responses when querying for +txs by tags or hash. From 2240df8baebb9b20a68f276bf7e1cf304211f88e Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Tue, 2 Apr 2019 20:47:06 -0400 Subject: [PATCH 09/10] Update client/tx/query.go Co-Authored-By: alexanderbez --- client/tx/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/tx/query.go b/client/tx/query.go index 182a9bf806d7..387c0b8b1031 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -98,7 +98,7 @@ $ gaiacli query txs --tags ':&:' --page 1 --limit 30 viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) cmd.Flags().String(flagTags, "", "tag:value list of tags that must match") - cmd.Flags().Int32(flagPage, rest.DefaultPage, "Query a specific page of paginated results") + cmd.Flags().Uint32(flagPage, rest.DefaultPage, "Query a specific page of paginated results") cmd.Flags().Int32(flagLimit, rest.DefaultLimit, "Query number of transactions results per page returned") cmd.MarkFlagRequired(flagTags) From 1af29fa12e2e5c72ee6ff3a7c6041b1183596d50 Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Tue, 2 Apr 2019 20:47:24 -0400 Subject: [PATCH 10/10] Update client/tx/query.go Co-Authored-By: alexanderbez --- client/tx/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/tx/query.go b/client/tx/query.go index 387c0b8b1031..8bb8e10cd705 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -99,7 +99,7 @@ $ gaiacli query txs --tags ':&:' --page 1 --limit 30 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().Int32(flagLimit, rest.DefaultLimit, "Query number of transactions results per page returned") + cmd.Flags().Uint32(flagLimit, rest.DefaultLimit, "Query number of transactions results per page returned") cmd.MarkFlagRequired(flagTags) return cmd