diff --git a/.pending/breaking/gaiarest/3942-Support-query-t b/.pending/breaking/gaiarest/3942-Support-query-t new file mode 100644 index 000000000000..a5ab31147376 --- /dev/null +++ b/.pending/breaking/gaiarest/3942-Support-query-t @@ -0,0 +1 @@ +#3942 Update pagination data in txs query. diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index 8db0cb95309b..7d1afede22cf 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -253,9 +253,7 @@ paths: 200: description: All txs matching the provided tags schema: - type: array - items: - $ref: "#/definitions/TxQuery" + $ref: "#/definitions/PaginatedQueryTxs" 400: description: Invalid search tags 500: @@ -1963,8 +1961,10 @@ definitions: properties: hash: type: string + example: "D085138D913993919295FF4B0A9107F1F2CDE0D37A87CE0644E217CBF3B49656" height: type: number + example: 368 tx: $ref: "#/definitions/StdTx" result: @@ -1974,14 +1974,36 @@ definitions: type: string gas_wanted: type: string - example: "0" + example: "200000" gas_used: type: string - example: "0" + example: "26354" tags: type: array items: $ref: "#/definitions/KVPair" + PaginatedQueryTxs: + type: object + properties: + total_count: + type: number + example: 1 + count: + type: number + example: 1 + page_number: + type: number + example: 1 + page_total: + type: number + example: 1 + limit: + type: number + example: 30 + txs: + type: array + items: + $ref: "#/definitions/TxQuery" StdTx: type: object properties: diff --git a/client/tx/query.go b/client/tx/query.go index 3cf290044548..ea223c63b68a 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -167,13 +167,13 @@ func QueryTxsByTagsRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) return } - txs, err = SearchTxs(cliCtx, cdc, tags, page, limit) + searchResult, 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) + rest.PostProcessResponse(w, cdc, searchResult, cliCtx.Indent) } } diff --git a/client/tx/utils.go b/client/tx/utils.go index b15ac43f86a0..657300c9a911 100644 --- a/client/tx/utils.go +++ b/client/tx/utils.go @@ -17,7 +17,7 @@ import ( // 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) { +func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, limit int) (*sdk.SearchTxsResult, error) { if len(tags) == 0 { return nil, errors.New("must declare at least one tag to search") } @@ -64,7 +64,9 @@ func SearchTxs(cliCtx context.CLIContext, cdc *codec.Codec, tags []string, page, return nil, err } - return txs, nil + result := sdk.NewSearchTxsResult(resTxs.TotalCount, len(txs), page, limit, txs) + + return &result, nil } // formatTxResults parses the indexed txs into a slice of TxResponse objects. diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index f6adbfa6cd95..0e2ab0ff39bf 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -511,8 +511,8 @@ func TestGaiaCLISubmitProposal(t *testing.T) { tests.WaitForNextNBlocksTM(1, f.Port) // Ensure transaction tags can be queried - txs := f.QueryTxs(1, 50, "action:submit_proposal", fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txs, 1) + searchResult := f.QueryTxs(1, 50, "action:submit_proposal", fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, searchResult.Txs, 1) // Ensure deposit was deducted fooAcc = f.QueryAccount(fooAddr) @@ -555,8 +555,8 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, proposalTokens.Add(depositTokens), deposit.Amount.AmountOf(denom)) // Ensure tags are set on the transaction - txs = f.QueryTxs(1, 50, "action:deposit", fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txs, 1) + searchResult = f.QueryTxs(1, 50, "action:deposit", fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, searchResult.Txs, 1) // Ensure account has expected amount of funds fooAcc = f.QueryAccount(fooAddr) @@ -592,8 +592,8 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, gov.OptionYes, votes[0].Option) // Ensure tags are applied to voting transaction properly - txs = f.QueryTxs(1, 50, "action:vote", fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txs, 1) + searchResult = f.QueryTxs(1, 50, "action:vote", fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, searchResult.Txs, 1) // Ensure no proposals in deposit period proposalsQuery = f.QueryGovProposals("--status=DepositPeriod") @@ -654,8 +654,8 @@ func TestGaiaCLISubmitParamChangeProposal(t *testing.T) { tests.WaitForNextNBlocksTM(1, f.Port) // ensure transaction tags can be queried - txs := f.QueryTxs(1, 50, "action:submit_proposal", fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txs, 1) + txsPage := f.QueryTxs(1, 50, "action:submit_proposal", fmt.Sprintf("sender:%s", fooAddr)) + require.Len(t, txsPage.Txs, 1) // ensure deposit was deducted fooAcc = f.QueryAccount(fooAddr) @@ -700,25 +700,26 @@ func TestGaiaCLIQueryTxPagination(t *testing.T) { // perPage = 15, 2 pages txsPage1 := f.QueryTxs(1, 15, fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txsPage1, 15) + require.Len(t, txsPage1.Txs, 15) + require.Equal(t, txsPage1.Count, 15) txsPage2 := f.QueryTxs(2, 15, fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txsPage2, 15) - require.NotEqual(t, txsPage1, txsPage2) + require.Len(t, txsPage2.Txs, 15) + require.NotEqual(t, txsPage1.Txs, txsPage2.Txs) txsPage3 := f.QueryTxs(3, 15, fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txsPage3, 15) - require.Equal(t, txsPage2, txsPage3) + require.Len(t, txsPage3.Txs, 15) + require.Equal(t, txsPage2.Txs, txsPage3.Txs) // perPage = 16, 2 pages txsPage1 = f.QueryTxs(1, 16, fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txsPage1, 16) + require.Len(t, txsPage1.Txs, 16) txsPage2 = f.QueryTxs(2, 16, fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txsPage2, 14) - require.NotEqual(t, txsPage1, txsPage2) + require.Len(t, txsPage2.Txs, 14) + require.NotEqual(t, txsPage1.Txs, txsPage2.Txs) // perPage = 50 txsPageFull := f.QueryTxs(1, 50, fmt.Sprintf("sender:%s", fooAddr)) - require.Len(t, txsPageFull, 30) - require.Equal(t, txsPageFull, append(txsPage1, txsPage2...)) + require.Len(t, txsPageFull.Txs, 30) + require.Equal(t, txsPageFull.Txs, append(txsPage1.Txs, txsPage2.Txs...)) // perPage = 0 f.QueryTxsInvalid(errors.New("ERROR: page must greater than 0"), 0, 50, fmt.Sprintf("sender:%s", fooAddr)) diff --git a/cmd/gaia/cli_test/test_helpers.go b/cmd/gaia/cli_test/test_helpers.go index 3dec6d3e9300..d77dd626c23c 100644 --- a/cmd/gaia/cli_test/test_helpers.go +++ b/cmd/gaia/cli_test/test_helpers.go @@ -425,14 +425,14 @@ func (f *Fixtures) QueryAccount(address sdk.AccAddress, flags ...string) auth.Ba // gaiacli query txs // QueryTxs is gaiacli query txs -func (f *Fixtures) QueryTxs(page, limit int, tags ...string) []sdk.TxResponse { +func (f *Fixtures) QueryTxs(page, limit int, tags ...string) *sdk.SearchTxsResult { cmd := fmt.Sprintf("%s query txs --page=%d --limit=%d --tags='%s' %v", f.GaiacliBinary, page, limit, queryTags(tags), f.Flags()) out, _ := tests.ExecuteT(f.T, cmd, "") - var txs []sdk.TxResponse + var result sdk.SearchTxsResult cdc := app.MakeCodec() - err := cdc.UnmarshalJSON([]byte(out), &txs) + err := cdc.UnmarshalJSON([]byte(out), &result) require.NoError(f.T, err, "out %v\n, err %v", out, err) - return txs + return &result } // QueryTxsInvalid query txs with wrong parameters and compare expected error diff --git a/cmd/gaia/lcd_test/helpers_test.go b/cmd/gaia/lcd_test/helpers_test.go index a80e7b2c055e..463b0bd473bd 100644 --- a/cmd/gaia/lcd_test/helpers_test.go +++ b/cmd/gaia/lcd_test/helpers_test.go @@ -545,18 +545,19 @@ func getTransactionRequest(t *testing.T, port, hash string) (*http.Response, str // POST /txs broadcast txs // GET /txs search transactions -func getTransactions(t *testing.T, port string, tags ...string) []sdk.TxResponse { +func getTransactions(t *testing.T, port string, tags ...string) *sdk.SearchTxsResult { var txs []sdk.TxResponse + result := sdk.NewSearchTxsResult(0, 0, 1, 30, txs) if len(tags) == 0 { - return txs + return &result } 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) + err := cdc.UnmarshalJSON([]byte(body), &result) require.NoError(t, err) - return txs + return &result } // ---------------------------------------------------------------------- diff --git a/cmd/gaia/lcd_test/lcd_test.go b/cmd/gaia/lcd_test/lcd_test.go index 7c7da1e74957..8cf7d5d6eb4b 100644 --- a/cmd/gaia/lcd_test/lcd_test.go +++ b/cmd/gaia/lcd_test/lcd_test.go @@ -324,19 +324,19 @@ func TestTxs(t *testing.T) { defer cleanup() var emptyTxs []sdk.TxResponse - txs := getTransactions(t, port) - require.Equal(t, emptyTxs, txs) + txResult := getTransactions(t, port) + require.Equal(t, emptyTxs, txResult.Txs) // query empty - txs = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) - require.Equal(t, emptyTxs, txs) + txResult = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) + require.Equal(t, emptyTxs, txResult.Txs) // also tests url decoding - txs = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) - require.Equal(t, emptyTxs, txs) + txResult = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) + require.Equal(t, emptyTxs, txResult.Txs) - txs = getTransactions(t, port, fmt.Sprintf("action=submit%%20proposal&sender=%s", addr.String())) - require.Equal(t, emptyTxs, txs) + txResult = getTransactions(t, port, fmt.Sprintf("action=submit%%20proposal&sender=%s", addr.String())) + require.Equal(t, emptyTxs, txResult.Txs) // create tx receiveAddr, resultTx := doTransfer(t, port, seed, name1, memo, pw, addr, fees) @@ -347,14 +347,14 @@ func TestTxs(t *testing.T) { require.Equal(t, resultTx.TxHash, tx.TxHash) // query sender - txs = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) - require.Len(t, txs, 1) - require.Equal(t, resultTx.Height, txs[0].Height) + txResult = getTransactions(t, port, fmt.Sprintf("sender=%s", addr.String())) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) // query recipient - txs = getTransactions(t, port, fmt.Sprintf("recipient=%s", receiveAddr.String())) - require.Len(t, txs, 1) - require.Equal(t, resultTx.Height, txs[0].Height) + txResult = getTransactions(t, port, fmt.Sprintf("recipient=%s", receiveAddr.String())) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) // query transaction that doesn't exist validTxHash := "9ADBECAAD8DACBEC3F4F535704E7CF715C765BDCEDBEF086AFEAD31BA664FB0B" @@ -453,12 +453,12 @@ func TestBonding(t *testing.T) { require.Equal(t, uint32(0), resultTx.Code) // query tx - txs := getTransactions(t, port, + txResult := getTransactions(t, port, fmt.Sprintf("action=delegate&sender=%s", addr), fmt.Sprintf("destination-validator=%s", operAddrs[0]), ) - require.Len(t, txs, 1) - require.Equal(t, resultTx.Height, txs[0].Height) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) // verify balance acc = getAccount(t, port, addr) @@ -506,12 +506,12 @@ func TestBonding(t *testing.T) { expectedBalance = coins[0] // query tx - txs = getTransactions(t, port, + txResult = getTransactions(t, port, fmt.Sprintf("action=begin_unbonding&sender=%s", addr), fmt.Sprintf("source-validator=%s", operAddrs[0]), ) - require.Len(t, txs, 1) - require.Equal(t, resultTx.Height, txs[0].Height) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) ubd := getUnbondingDelegation(t, port, addr, operAddrs[0]) require.Len(t, ubd.Entries, 1) @@ -543,13 +543,13 @@ func TestBonding(t *testing.T) { ) // query tx - txs = getTransactions(t, port, + txResult = getTransactions(t, port, fmt.Sprintf("action=begin_redelegate&sender=%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) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) redelegation := getRedelegations(t, port, addr, operAddrs[0], operAddrs[1]) require.Len(t, redelegation, 1) @@ -577,7 +577,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") @@ -709,9 +709,9 @@ func TestDeposit(t *testing.T) { require.Equal(t, expectedBalance.Amount.Sub(depositTokens), acc.GetCoins().AmountOf(sdk.DefaultBondDenom)) // query tx - txs := getTransactions(t, port, fmt.Sprintf("action=deposit&sender=%s", addr)) - require.Len(t, txs, 1) - require.Equal(t, resultTx.Height, txs[0].Height) + txResult := getTransactions(t, port, fmt.Sprintf("action=deposit&sender=%s", addr)) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) // query proposal totalCoins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(10))} @@ -770,9 +770,9 @@ func TestVote(t *testing.T) { expectedBalance = coins[0] // query tx - txs := getTransactions(t, port, fmt.Sprintf("action=vote&sender=%s", addr)) - require.Len(t, txs, 1) - require.Equal(t, resultTx.Height, txs[0].Height) + txResult := getTransactions(t, port, fmt.Sprintf("action=vote&sender=%s", addr)) + require.Len(t, txResult.Txs, 1) + require.Equal(t, resultTx.Height, txResult.Txs[0].Height) vote := getVote(t, port, proposalID, addr) require.Equal(t, proposalID, vote.ProposalID) diff --git a/types/result.go b/types/result.go index 613cd141f55b..b5ebc38c13af 100644 --- a/types/result.go +++ b/types/result.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "math" "strings" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -249,6 +250,27 @@ func (r TxResponse) Empty() bool { return r.TxHash == "" && r.Logs == nil } +// SearchTxsResult defines a structure for querying txs pageable +type SearchTxsResult struct { + TotalCount int `json:"total_count"` // Count of all txs + Count int `json:"count"` // Count of txs in current page + PageNumber int `json:"page_number"` // Index of current page, start from 1 + PageTotal int `json:"page_total"` // Count of total pages + Limit int `json:"limit"` // Max count txs per page + Txs []TxResponse `json:"txs"` // List of txs in current page +} + +func NewSearchTxsResult(totalCount, count, page, limit int, txs []TxResponse) SearchTxsResult { + return SearchTxsResult{ + TotalCount: totalCount, + Count: count, + PageNumber: page, + PageTotal: int(math.Ceil(float64(totalCount) / float64(limit))), + Limit: limit, + Txs: txs, + } +} + // ParseABCILogs attempts to parse a stringified ABCI tx log into a slice of // ABCIMessageLog types. It returns an error upon JSON decoding failure. func ParseABCILogs(logs string) (res ABCIMessageLogs, err error) { diff --git a/x/gov/client/utils/query.go b/x/gov/client/utils/query.go index 351c8314350d..28e996981897 100644 --- a/x/gov/client/utils/query.go +++ b/x/gov/client/utils/query.go @@ -48,14 +48,14 @@ func QueryDepositsByTxQuery( // NOTE: SearchTxs is used to facilitate the txs query which does not currently // support configurable pagination. - infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) + searchResult, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) if err != nil { return nil, err } var deposits []gov.Deposit - for _, info := range infos { + for _, info := range searchResult.Txs { for _, msg := range info.Tx.GetMsgs() { if msg.Type() == gov.TypeMsgDeposit { depMsg := msg.(gov.MsgDeposit) @@ -93,14 +93,14 @@ func QueryVotesByTxQuery( // NOTE: SearchTxs is used to facilitate the txs query which does not currently // support configurable pagination. - infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) + searchResult, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) if err != nil { return nil, err } var votes []gov.Vote - for _, info := range infos { + for _, info := range searchResult.Txs { for _, msg := range info.Tx.GetMsgs() { if msg.Type() == gov.TypeMsgVote { voteMsg := msg.(gov.MsgVote) @@ -134,12 +134,12 @@ func QueryVoteByTxQuery( // NOTE: SearchTxs is used to facilitate the txs query which does not currently // support configurable pagination. - infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) + searchResult, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) if err != nil { return nil, err } - for _, info := range infos { + for _, info := range searchResult.Txs { for _, msg := range info.Tx.GetMsgs() { // there should only be a single vote under the given conditions if msg.Type() == gov.TypeMsgVote { @@ -177,12 +177,12 @@ func QueryDepositByTxQuery( // NOTE: SearchTxs is used to facilitate the txs query which does not currently // support configurable pagination. - infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) + searchResult, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) if err != nil { return nil, err } - for _, info := range infos { + for _, info := range searchResult.Txs { for _, msg := range info.Tx.GetMsgs() { // there should only be a single deposit under the given conditions if msg.Type() == gov.TypeMsgDeposit { @@ -219,12 +219,12 @@ func QueryProposerByTxQuery( // NOTE: SearchTxs is used to facilitate the txs query which does not currently // support configurable pagination. - infos, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) + searchResult, err := tx.SearchTxs(cliCtx, cdc, tags, defaultPage, defaultLimit) if err != nil { return Proposer{}, err } - for _, info := range infos { + for _, info := range searchResult.Txs { for _, msg := range info.Tx.GetMsgs() { // there should only be a single proposal under the given conditions if msg.Type() == gov.TypeMsgSubmitProposal { diff --git a/x/staking/client/rest/query.go b/x/staking/client/rest/query.go index e7ba6e83476b..8b74b92f169f 100644 --- a/x/staking/client/rest/query.go +++ b/x/staking/client/rest/query.go @@ -134,7 +134,7 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han isBondTx := contains(typesQuerySlice, "bond") isUnbondTx := contains(typesQuerySlice, "unbond") isRedTx := contains(typesQuerySlice, "redelegate") - var txs = []sdk.TxResponse{} + var txs []*sdk.SearchTxsResult var actions []string switch { @@ -158,7 +158,7 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han if errQuery != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, errQuery.Error()) } - txs = append(txs, foundTxs...) + txs = append(txs, foundTxs) } res, err := cdc.MarshalJSON(txs) diff --git a/x/staking/client/rest/utils.go b/x/staking/client/rest/utils.go index f6d4be4932ce..0bb363aa55a4 100644 --- a/x/staking/client/rest/utils.go +++ b/x/staking/client/rest/utils.go @@ -26,7 +26,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) { +func queryTxs(cliCtx context.CLIContext, cdc *codec.Codec, tag string, delegatorAddr string) (*sdk.SearchTxsResult, error) { page := 1 limit := 100 tags := []string{