Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: Support query txs' TotalCount in GET /txs #4214

Merged
merged 11 commits into from
May 4, 2019
1 change: 1 addition & 0 deletions .pending/breaking/gaiarest/3942-Support-query-t
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#3942 Update pagination data in txs query.
32 changes: 27 additions & 5 deletions client/lcd/swagger-ui/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -1963,8 +1961,10 @@ definitions:
properties:
hash:
type: string
example: "D085138D913993919295FF4B0A9107F1F2CDE0D37A87CE0644E217CBF3B49656"
height:
type: number
example: 368
tx:
$ref: "#/definitions/StdTx"
result:
Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions client/tx/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
6 changes: 4 additions & 2 deletions client/tx/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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.
Expand Down
37 changes: 19 additions & 18 deletions cmd/gaia/cli_test/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
8 changes: 4 additions & 4 deletions cmd/gaia/cli_test/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions cmd/gaia/lcd_test/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

// ----------------------------------------------------------------------
Expand Down
60 changes: 30 additions & 30 deletions cmd/gaia/lcd_test/lcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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))}
Expand Down Expand Up @@ -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)
Expand Down
22 changes: 22 additions & 0 deletions types/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"math"
"strings"

ctypes "github.com/tendermint/tendermint/rpc/core/types"
Expand Down Expand Up @@ -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 {
yangyanqing marked this conversation as resolved.
Show resolved Hide resolved
TotalCount int `json:"total_count"` // Count of all txs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to returned with every transaction search query? Should this be it's own query?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think any query which does not return all items one time should be pageable. This is friendly to the user. @jackzampolin

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) {
Expand Down
Loading