Merge PR #2842: Fix tx search

This commit is contained in:
Christopher Goes 2018-11-28 00:09:14 +01:00 committed by GitHub
commit 1a18a428a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 221 additions and 90 deletions

View File

@ -3,6 +3,7 @@
BREAKING CHANGES
* Gaia REST API (`gaiacli advanced rest-server`)
* [gaia-lite] [\#2819](https://github.com/cosmos/cosmos-sdk/pull/2819) Txs query param format is now: `/txs?tag=value` (removed '' wrapping the query parameter `value`)
* Gaia CLI (`gaiacli`)
* [cli] [\#2728](https://github.com/cosmos/cosmos-sdk/pull/2728) Seperate `tx` and `query` subcommands by module
@ -56,10 +57,12 @@ FEATURES
IMPROVEMENTS
* Gaia REST API (`gaiacli advanced rest-server`)
* [gaia-lite] [\#2819](https://github.com/cosmos/cosmos-sdk/pull/2819) Tx search now supports multiple tags as query parameters
* [\#2836](https://github.com/cosmos/cosmos-sdk/pull/2836) Expose LCD router to allow users to register routes there.
* Gaia CLI (`gaiacli`)
* [\#2749](https://github.com/cosmos/cosmos-sdk/pull/2749) Add --chain-id flag to gaiad testnet
* [\#2819](https://github.com/cosmos/cosmos-sdk/pull/2819) Tx search now supports multiple tags as query parameters
* Gaia
- #2772 Update BaseApp to not persist state when the ante handler fails on DeliverTx.
@ -78,7 +81,7 @@ IMPROVEMENTS
- #2779 Introduce `ValidateBasic` to the `Tx` interface and call it in the ante
handler.
- #2825 More staking and distribution invariants
* #2912 Print commit ID in hex when commit is synced.
- #2912 Print commit ID in hex when commit is synced.
* Tendermint
- #2796 Update to go-amino 0.14.1

View File

@ -7,6 +7,7 @@ import (
"net/http"
"os"
"regexp"
"strings"
"testing"
"time"
@ -399,57 +400,39 @@ func TestTxs(t *testing.T) {
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr})
defer cleanup()
// query wrong
res, body := Request(t, port, "GET", "/txs", nil)
require.Equal(t, http.StatusBadRequest, res.StatusCode, body)
var emptyTxs []tx.Info
txs := getTransactions(t, port)
require.Equal(t, emptyTxs, txs)
// query empty
res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=sender_bech32='%s'", "cosmos1jawd35d9aq4u76sr3fjalmcqc8hqygs90d0g0v"), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
require.Equal(t, "[]", body)
txs = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String()))
require.Equal(t, emptyTxs, txs)
// create TX
// also tests url decoding
txs = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String()))
require.Equal(t, emptyTxs, txs)
txs = getTransactions(t, port, fmt.Sprintf("action=submit%%20proposal&proposer=%s", addr.String()))
require.Equal(t, emptyTxs, txs)
// create tx
receiveAddr, resultTx := doSend(t, port, seed, name, password, addr)
tests.WaitForHeight(resultTx.Height+1, port)
// check if tx is findable
res, body = Request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var indexedTxs []tx.Info
// check if tx is queryable
res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=tx.hash='%s'", resultTx.Hash), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
require.NotEqual(t, "[]", body)
err := cdc.UnmarshalJSON([]byte(body), &indexedTxs)
require.NoError(t, err)
require.Equal(t, 1, len(indexedTxs))
// XXX should this move into some other testfile for txs in general?
// test if created TX hash is the correct hash
require.Equal(t, resultTx.Hash, indexedTxs[0].Hash)
txs = getTransactions(t, port, fmt.Sprintf("tx.hash=%s", resultTx.Hash))
require.Len(t, txs, 1)
require.Equal(t, resultTx.Hash, txs[0].Hash)
// query sender
// also tests url decoding
res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=sender_bech32=%%27%s%%27", addr), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
err = cdc.UnmarshalJSON([]byte(body), &indexedTxs)
require.NoError(t, err)
require.Equal(t, 1, len(indexedTxs), "%v", indexedTxs) // there are 2 txs created with doSend
require.Equal(t, resultTx.Height, indexedTxs[0].Height)
txs = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String()))
require.Len(t, txs, 1)
require.Equal(t, resultTx.Height, txs[0].Height)
// query recipient
res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=recipient_bech32='%s'", receiveAddr), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
err = cdc.UnmarshalJSON([]byte(body), &indexedTxs)
require.NoError(t, err)
require.Equal(t, 1, len(indexedTxs))
require.Equal(t, resultTx.Height, indexedTxs[0].Height)
txs = getTransactions(t, port, fmt.Sprintf("recipient=%s", receiveAddr.String()))
require.Len(t, txs, 1)
require.Equal(t, resultTx.Height, txs[0].Height)
}
func TestPoolParamsQuery(t *testing.T) {
@ -534,6 +517,14 @@ func TestBonding(t *testing.T) {
require.Equal(t, uint32(0), resultTx.CheckTx.Code)
require.Equal(t, uint32(0), resultTx.DeliverTx.Code)
// query tx
txs := getTransactions(t, port,
fmt.Sprintf("action=delegate&delegator=%s", addr),
fmt.Sprintf("destination-validator=%s", operAddrs[0]),
)
require.Len(t, txs, 1)
require.Equal(t, resultTx.Height, txs[0].Height)
acc := getAccount(t, port, addr)
coins := acc.GetCoins()
@ -571,6 +562,14 @@ func TestBonding(t *testing.T) {
coins = acc.GetCoins()
require.Equal(t, int64(40), coins.AmountOf(stakeTypes.DefaultBondDenom).Int64())
// query tx
txs = getTransactions(t, port,
fmt.Sprintf("action=begin-unbonding&delegator=%s", addr),
fmt.Sprintf("source-validator=%s", operAddrs[0]),
)
require.Len(t, txs, 1)
require.Equal(t, resultTx.Height, txs[0].Height)
unbonding := getUndelegation(t, port, addr, operAddrs[0])
require.Equal(t, "30", unbonding.Balance.Amount.String())
@ -581,6 +580,15 @@ func TestBonding(t *testing.T) {
require.Equal(t, uint32(0), resultTx.CheckTx.Code)
require.Equal(t, uint32(0), resultTx.DeliverTx.Code)
// query tx
txs = getTransactions(t, port,
fmt.Sprintf("action=begin-redelegation&delegator=%s", addr),
fmt.Sprintf("source-validator=%s", operAddrs[0]),
fmt.Sprintf("destination-validator=%s", operAddrs[1]),
)
require.Len(t, txs, 1)
require.Equal(t, resultTx.Height, txs[0].Height)
// query delegations, unbondings and redelegations from validator and delegator
delegatorDels = getDelegatorDelegations(t, port, addr)
require.Len(t, delegatorDels, 1)
@ -606,7 +614,7 @@ func TestBonding(t *testing.T) {
// require.Equal(t, sdk.Unbonding, bondedValidators[0].Status)
// query txs
txs := getBondingTxs(t, port, addr, "")
txs = getBondingTxs(t, port, addr, "")
require.Len(t, txs, 3, "All Txs found")
txs = getBondingTxs(t, port, addr, "bond")
@ -639,6 +647,11 @@ func TestSubmitProposal(t *testing.T) {
// query proposal
proposal := getProposal(t, port, proposalID)
require.Equal(t, "Test", proposal.GetTitle())
// query tx
txs := getTransactions(t, port, fmt.Sprintf("action=submit-proposal&proposer=%s", addr))
require.Len(t, txs, 1)
require.Equal(t, resultTx.Height, txs[0].Height)
}
func TestDeposit(t *testing.T) {
@ -666,6 +679,11 @@ func TestDeposit(t *testing.T) {
resultTx = doDeposit(t, port, seed, name, password, addr, proposalID, 5)
tests.WaitForHeight(resultTx.Height+1, port)
// query tx
txs := getTransactions(t, port, fmt.Sprintf("action=deposit&depositor=%s", addr))
require.Len(t, txs, 1)
require.Equal(t, resultTx.Height, txs[0].Height)
// query proposal
proposal = getProposal(t, port, proposalID)
require.True(t, proposal.GetTotalDeposit().IsEqual(sdk.Coins{sdk.NewInt64Coin(stakeTypes.DefaultBondDenom, 10)}))
@ -708,6 +726,11 @@ func TestVote(t *testing.T) {
resultTx = doVote(t, port, seed, name, password, addr, proposalID)
tests.WaitForHeight(resultTx.Height+1, port)
// query tx
txs := getTransactions(t, port, fmt.Sprintf("action=vote&voter=%s", addr))
require.Len(t, txs, 1)
require.Equal(t, resultTx.Height, txs[0].Height)
vote := getVote(t, port, proposalID, addr)
require.Equal(t, proposalID, vote.ProposalID)
require.Equal(t, gov.OptionYes, vote.Option)
@ -866,7 +889,7 @@ func TestProposalsQuery(t *testing.T) {
//_____________________________________________________________________________
// get the account to get the sequence
func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr), nil)
res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr.String()), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var acc auth.Account
err := cdc.UnmarshalJSON([]byte(body), &acc)
@ -944,6 +967,22 @@ func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress
return receiveAddr, resultTx
}
func getTransactions(t *testing.T, port string, tags ...string) []tx.Info {
var txs []tx.Info
if len(tags) == 0 {
return txs
}
queryStr := strings.Join(tags, "&")
res, body := Request(t, port, "GET", fmt.Sprintf("/txs?%s", queryStr), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
err := cdc.UnmarshalJSON([]byte(body), &txs)
require.NoError(t, err)
return txs
}
// ============= IBC Module ================
func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) {
// create receive address
kb := client.MockKeyBase()
@ -984,6 +1023,8 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc
return resultTx
}
// ============= Slashing Module ================
func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing.ValidatorSigningInfo {
res, body := Request(t, port, "GET", fmt.Sprintf("/slashing/validators/%s/signing_info", validatorPubKey), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
@ -995,6 +1036,31 @@ func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing.
return signingInfo
}
func doUnjail(t *testing.T, port, seed, name, password string,
valAddr sdk.ValAddress) (resultTx ctypes.ResultBroadcastTxCommit) {
chainID := viper.GetString(client.FlagChainID)
jsonStr := []byte(fmt.Sprintf(`{
"base_req": {
"name": "%s",
"password": "%s",
"chain_id": "%s",
"account_number":"1",
"sequence":"1"
}
}`, name, password, chainID))
res, body := Request(t, port, "POST", fmt.Sprintf("/slashing/validators/%s/unjail", valAddr.String()), jsonStr)
// TODO : fails with "401 must use own validator address"
require.Equal(t, http.StatusOK, res.StatusCode, body)
var results []ctypes.ResultBroadcastTxCommit
err := cdc.UnmarshalJSON([]byte(body), &results)
require.Nil(t, err)
return results[0]
}
// ============= Stake Module ================
func getDelegation(t *testing.T, port string, delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) stake.Delegation {

View File

@ -209,14 +209,14 @@ paths:
tags:
- ICS0
summary: Search transactions
description: Search transactions by tag
description: Search transactions by tag(s).
produces:
- application/json
parameters:
- in: query
name: tag
type: string
description: "transaction tag, for instance: sender_bech32=`'cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc'`"
description: "transaction tags such as 'action=submit-proposal' and 'proposer=cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc' which results in the following endpoint: 'GET /txs?action=submit-proposal&proposer=cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc'"
required: true
- in: query
name: page
@ -228,7 +228,7 @@ paths:
type: integer
responses:
200:
description: All Tx matching the provided tags
description: All txs matching the provided tags
schema:
type: array
items:

View File

@ -9,18 +9,18 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client/utils"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
const (
flagTags = "tag"
flagTags = "tags"
flagAny = "any"
)
@ -30,24 +30,35 @@ func SearchTxCmd(cdc *codec.Codec) *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.
Search for transactions that match exactly the given tags. For example:
For example:
$ gaiacli tendermint txs --tag test1,test2
will match any transaction tagged with both test1,test2. To match a transaction tagged with either
test1 or test2, use:
$ gaiacli tendermint txs --tag test1,test2 --any
$ gaiacli query txs --tags '<tag1>:<value1>&<tag2>:<value2>'
`),
RunE: func(cmd *cobra.Command, args []string) error {
tags := viper.GetStringSlice(flagTags)
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, ":")
tag = fmt.Sprintf("%s='%s'", keyValue[0], keyValue[1])
tmTags = append(tmTags, tag)
}
cliCtx := context.NewCLIContext().WithCodec(cdc)
txs, err := searchTxs(cliCtx, cdc, tags)
txs, err := searchTxs(cliCtx, cdc, tmTags)
if err != nil {
return err
}
@ -74,8 +85,7 @@ $ gaiacli tendermint txs --tag test1,test2 --any
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")
cmd.Flags().String(flagTags, "", "tag:value list of tags that must match")
return cmd
}
@ -139,45 +149,35 @@ func FormatTxResults(cdc *codec.Codec, res []*ctypes.ResultTx) ([]Info, error) {
// Search Tx REST Handler
func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tag := r.FormValue("tag")
if tag == "" {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("You need to provide at least a tag as a key=value pair to search for. Postfix the key with _bech32 to search bech32-encoded addresses or public keys"))
return
}
keyValue := strings.Split(tag, "=")
key := keyValue[0]
value, err := url.QueryUnescape(keyValue[1])
var tags []string
var txs []Info
err := r.ParseForm()
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not decode address", err.Error()))
utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not parse query parameters", err.Error()))
return
}
if len(r.Form) == 0 {
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
return
}
if strings.HasSuffix(key, "_bech32") {
bech32address := strings.Trim(value, "'")
prefix := strings.Split(bech32address, "1")[0]
bz, err := sdk.GetFromBech32(bech32address, prefix)
for key, values := range r.Form {
value, err := url.QueryUnescape(values[0])
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not decode query value", err.Error()))
return
}
tag = strings.TrimRight(key, "_bech32") + "='" + sdk.AccAddress(bz).String() + "'"
tag := fmt.Sprintf("%s='%s'", key, value)
tags = append(tags, tag)
}
txs, err := searchTxs(cliCtx, cdc, []string{tag})
txs, err = searchTxs(cliCtx, cdc, tags)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
if len(txs) == 0 {
w.Write([]byte("[]"))
return
}
utils.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
}
}

View File

@ -21,6 +21,7 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server"
@ -277,7 +278,7 @@ func TestGaiaCLICreateValidator(t *testing.T) {
require.NotZero(t, validatorDelegations[0].Shares)
// unbond a single share
unbondStr := fmt.Sprintf("gaiacli tx stake unbond begin %v", flags)
unbondStr := fmt.Sprintf("gaiacli tx stake unbond %v", flags)
unbondStr += fmt.Sprintf(" --from=%s", "bar")
unbondStr += fmt.Sprintf(" --validator=%s", sdk.ValAddress(barAddr))
unbondStr += fmt.Sprintf(" --shares-amount=%v", "1")
@ -354,6 +355,9 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
executeWrite(t, spStr, app.DefaultKeyPass)
tests.WaitForNextNBlocksTM(2, port)
txs := executeGetTxs(t, fmt.Sprintf("gaiacli query txs --tags='action:submit-proposal&proposer:%s' %v", fooAddr, flags))
require.Len(t, txs, 1)
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64())
@ -398,6 +402,9 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
fooAddr, flags))
require.Equal(t, int64(15), deposit.Amount.AmountOf(stakeTypes.DefaultBondDenom).Int64())
txs = executeGetTxs(t, fmt.Sprintf("gaiacli query txs --tags=action:deposit&depositor:%s %v", fooAddr, flags))
require.Len(t, txs, 1)
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf(stakeTypes.DefaultBondDenom).Int64())
@ -432,6 +439,9 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
require.Equal(t, uint64(1), votes[0].ProposalID)
require.Equal(t, gov.OptionYes, votes[0].Option)
txs = executeGetTxs(t, fmt.Sprintf("gaiacli query txs --tags=action:vote&voter:%s %v", fooAddr, flags))
require.Len(t, txs, 1)
proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query gov proposals --status=DepositPeriod %v", flags), "")
require.Equal(t, "No matching proposals found", proposalsQuery)
@ -736,6 +746,18 @@ func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount {
return acc
}
//___________________________________________________________________________________
// txs
func executeGetTxs(t *testing.T, cmdStr string) []tx.Info {
out, _ := tests.ExecuteT(t, cmdStr, "")
var txs []tx.Info
cdc := app.MakeCodec()
err := cdc.UnmarshalJSON([]byte(out), &txs)
require.NoError(t, err, "out %v\n, err %v", out, err)
return txs
}
//___________________________________________________________________________________
// stake

View File

@ -183,10 +183,50 @@ gaiacli tx sign --validate-signatures signedSendTx.json
You can broadcast the signed transaction to a node by providing the JSON file to the following command:
```
```bash
gaiacli tx broadcast --node=<node> signedSendTx.json
```
### Query Transactions
#### Matching a set of tags
You can use the transaction search command to query for transactions that match a specific set of `tags`, which are added on every transaction.
Each tag is conformed by a key-value pair in the form of `<tag>:<value>`. Tags can also be combined to query for a more specific result using the `&` symbol.
The command for querying transactions using a `tag` is the following:
```bash
gaiacli query txs --tags='<tag>:<value>'
```
And for using multiple `tags`:
```bash
gaiacli query txs --tags='<tag1>:<value1>&<tag2>:<value2>'
```
::: tip Note
You can find a list of available `tags` on each of the SDK modules:
- [Common tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/types/tags.go#L57-L63)
- [Staking tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/x/stake/tags/tags.go#L8-L24)
- [Governance tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/x/gov/tags/tags.go#L8-L22)
- [Slashing tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/x/slashing/handler.go#L52)
- [Distribution tags](https://github.com/cosmos/cosmos-sdk/blob/develop/x/distribution/tags/tags.go#L8-L17)
- [Bank tags](https://github.com/cosmos/cosmos-sdk/blob/d1e76221d8e28824bb4791cb4ad8662d2ae9051e/x/bank/keeper.go#L193-L206)
:::
#### Matching a transaction's hash
You can also query a single transaction by its hash using the following command:
```bash
gaiacli query tx [hash]
```
### Staking
#### Set up a Validator