Support query txs' TotalCount in GET /txs (#4214)

Closes: #3942
This commit is contained in:
Frank Yang 2019-05-04 19:09:03 +08:00 committed by Alessio Treglia
parent 67ab0b1e1d
commit 1cfc868d86
12 changed files with 127 additions and 78 deletions

View File

@ -0,0 +1 @@
#3942 Update pagination data in txs query.

View File

@ -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:

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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))

View File

@ -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

View File

@ -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
}
// ----------------------------------------------------------------------

View File

@ -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)

View File

@ -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) {

View File

@ -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 {

View File

@ -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)

View File

@ -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{