cosmos-sdk/client/tx/search.go

194 lines
4.9 KiB
Go
Raw Normal View History

package tx
import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
2018-03-02 01:24:07 -08:00
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
2018-11-16 03:50:58 -08:00
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
2018-08-06 11:11:30 -07:00
"github.com/spf13/cobra"
"github.com/spf13/viper"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
const (
flagTags = "tag"
flagAny = "any"
)
2018-02-28 17:57:38 -08:00
// 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 the given tags. By default, transactions must match ALL tags
passed to the --tags option. To match any transaction, use the --any option.
For example:
2018-11-20 05:13:05 -08:00
$ gaiacli query txs --tag test1,test2
will match any transaction tagged with both test1,test2. To match a transaction tagged with either
test1 or test2, use:
2018-11-20 05:13:05 -08:00
$ gaiacli query txs --tag test1,test2 --any
`),
2018-04-18 21:49:24 -07:00
RunE: func(cmd *cobra.Command, args []string) error {
tags := viper.GetStringSlice(flagTags)
2018-08-06 11:11:30 -07:00
cliCtx := context.NewCLIContext().WithCodec(cdc)
txs, err := searchTxs(cliCtx, cdc, tags)
if err != nil {
return err
}
2018-08-06 11:11:30 -07:00
var output []byte
if cliCtx.Indent {
output, err = cdc.MarshalJSONIndent(txs, "", " ")
} else {
output, err = cdc.MarshalJSON(txs)
}
2018-04-18 21:49:24 -07:00
if err != nil {
return err
}
2018-08-06 11:11:30 -07:00
2018-04-18 21:49:24 -07:00
fmt.Println(string(output))
return nil
},
}
2018-04-18 21:49:24 -07:00
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode))
cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node")
viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID))
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().StringSlice(flagTags, nil, "Comma-separated list of tags that must match")
cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL")
return cmd
}
func searchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string) ([]Info, error) {
if len(tags) == 0 {
return nil, errors.New("must declare at least one tag to search")
}
2018-08-06 11:11:30 -07:00
// XXX: implement ANY
query := strings.Join(tags, " AND ")
2018-08-06 11:11:30 -07:00
// get the node
2018-08-06 11:11:30 -07:00
node, err := cliCtx.GetNode()
2018-02-28 15:26:39 -08:00
if err != nil {
return nil, err
}
prove := !cliCtx.TrustNode
2018-08-06 11:11:30 -07:00
2018-05-20 07:35:19 -07:00
// TODO: take these as args
page := 0
perPage := 100
res, err := node.TxSearch(query, prove, page, perPage)
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) ([]Info, error) {
var err error
out := make([]Info, len(res))
for i := range res {
2018-02-28 17:57:38 -08:00
out[i], err = formatTxResult(cdc, res[i])
if err != nil {
return nil, err
}
}
return out, nil
}
2018-04-18 21:49:24 -07:00
/////////////////////////////////////////
// REST
2018-04-18 21:49:24 -07:00
// Search Tx REST Handler
func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
2018-11-16 03:50:58 -08:00
var tags []string
2018-11-20 05:13:05 -08:00
var txs []Info
2018-11-16 03:50:58 -08:00
err := r.ParseForm()
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not parse query parameters", err.Error()))
2018-03-14 05:01:55 -07:00
return
}
2018-11-16 03:50:58 -08:00
if len(r.Form) == 0 {
2018-11-20 05:13:05 -08:00
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
return
}
2018-08-06 11:11:30 -07:00
2018-11-16 03:50:58 -08:00
for key, values := range r.Form {
value, err := url.QueryUnescape(values[0])
if err != nil {
2018-11-16 03:50:58 -08:00
utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not decode query value", err.Error()))
return
}
2018-11-16 03:50:58 -08:00
if strings.HasSuffix(key, "_bech32") {
prefix := strings.Split(value, "1")[0]
bz, err := sdk.GetFromBech32(value, prefix)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
key = strings.TrimRight(key, "_bech32")
2018-11-20 05:13:05 -08:00
if prefix == sdk.Bech32PrefixAccAddr {
value = sdk.AccAddress(bz).String()
} else if prefix == sdk.Bech32PrefixValAddr {
value = sdk.ValAddress(bz).String()
} else {
utils.WriteErrorResponse(w, http.StatusBadRequest,
sdk.ErrInvalidAddress(fmt.Sprintf("invalid bech32 prefix '%s'", prefix)).Error(),
)
return
}
2018-11-16 03:50:58 -08:00
}
tag := fmt.Sprintf("%s='%s'", key, value)
tags = append(tags, tag)
}
2018-03-14 05:01:55 -07:00
2018-11-20 05:13:05 -08:00
txs, err = searchTxs(cliCtx, cdc, tags)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
}
}