limit /tx_search output

Refs #909
This commit is contained in:
Anton Kaliaev 2018-05-14 16:01:49 +04:00
parent b5c4098c53
commit a6b74b82d1
No known key found for this signature in database
GPG Key ID: 7B6881D965918214
9 changed files with 94 additions and 51 deletions

View File

@ -204,17 +204,19 @@ func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
return result, nil return result, nil
} }
func (c *HTTP) TxSearch(query string, prove bool) ([]*ctypes.ResultTx, error) { func (c *HTTP) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
results := new([]*ctypes.ResultTx) result := new(ctypes.ResultTxSearch)
params := map[string]interface{}{ params := map[string]interface{}{
"query": query, "query": query,
"prove": prove, "prove": prove,
"page": page,
"per_page": perPage,
} }
_, err := c.rpc.Call("tx_search", params, results) _, err := c.rpc.Call("tx_search", params, result)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "TxSearch") return nil, errors.Wrap(err, "TxSearch")
} }
return *results, nil return result, nil
} }
func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) { func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) {

View File

@ -50,7 +50,7 @@ type SignClient interface {
Commit(height *int64) (*ctypes.ResultCommit, error) Commit(height *int64) (*ctypes.ResultCommit, error)
Validators(height *int64) (*ctypes.ResultValidators, error) Validators(height *int64) (*ctypes.ResultValidators, error)
Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error)
TxSearch(query string, prove bool) ([]*ctypes.ResultTx, error) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error)
} }
// HistoryClient shows us data from genesis to now in large chunks. // HistoryClient shows us data from genesis to now in large chunks.

View File

@ -128,8 +128,8 @@ func (Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
return core.Tx(hash, prove) return core.Tx(hash, prove)
} }
func (Local) TxSearch(query string, prove bool) ([]*ctypes.ResultTx, error) { func (Local) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
return core.TxSearch(query, prove) return core.TxSearch(query, prove, page, perPage)
} }
func (c *Local) Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error { func (c *Local) Subscribe(ctx context.Context, subscriber string, query tmpubsub.Query, out chan<- interface{}) error {

View File

@ -334,11 +334,11 @@ func TestTxSearch(t *testing.T) {
// now we query for the tx. // now we query for the tx.
// since there's only one tx, we know index=0. // since there's only one tx, we know index=0.
results, err := c.TxSearch(fmt.Sprintf("tx.hash='%v'", txHash), true) result, err := c.TxSearch(fmt.Sprintf("tx.hash='%v'", txHash), true, 1, 30)
require.Nil(t, err, "%+v", err) require.Nil(t, err, "%+v", err)
require.Len(t, results, 1) require.Len(t, result.Txs, 1)
ptx := results[0] ptx := result.Txs[0]
assert.EqualValues(t, txHeight, ptx.Height) assert.EqualValues(t, txHeight, ptx.Height)
assert.EqualValues(t, tx, ptx.Tx) assert.EqualValues(t, tx, ptx.Tx)
assert.Zero(t, ptx.Index) assert.Zero(t, ptx.Index)
@ -352,14 +352,14 @@ func TestTxSearch(t *testing.T) {
} }
// we query for non existing tx // we query for non existing tx
results, err = c.TxSearch(fmt.Sprintf("tx.hash='%X'", anotherTxHash), false) result, err = c.TxSearch(fmt.Sprintf("tx.hash='%X'", anotherTxHash), false, 1, 30)
require.Nil(t, err, "%+v", err) require.Nil(t, err, "%+v", err)
require.Len(t, results, 0) require.Len(t, result.Txs, 0)
// we query using a tag (see kvstore application) // we query using a tag (see kvstore application)
results, err = c.TxSearch("app.creator='jae'", false) result, err = c.TxSearch("app.creator='jae'", false, 1, 30)
require.Nil(t, err, "%+v", err) require.Nil(t, err, "%+v", err)
if len(results) == 0 { if len(result.Txs) == 0 {
t.Fatal("expected a lot of transactions") t.Fatal("expected a lot of transactions")
} }
} }

View File

@ -13,3 +13,9 @@ go get github.com/melekes/godoc2md
godoc2md -template rpc/core/doc_template.txt github.com/tendermint/tendermint/rpc/core | grep -v -e "pipe.go" -e "routes.go" -e "dev.go" | sed 's$/src/target$https://github.com/tendermint/tendermint/tree/master/rpc/core$' godoc2md -template rpc/core/doc_template.txt github.com/tendermint/tendermint/rpc/core | grep -v -e "pipe.go" -e "routes.go" -e "dev.go" | sed 's$/src/target$https://github.com/tendermint/tendermint/tree/master/rpc/core$'
``` ```
## Pagination
Requests that return multiple items will be paginated to 30 items by default.
You can specify further pages with the ?page parameter. You can also set a
custom page size up to 100 with the ?per_page parameter.

View File

@ -14,6 +14,12 @@ import (
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
) )
const (
// see README
defaultPerPage = 30
maxPerPage = 100
)
var subscribeTimeout = 5 * time.Second var subscribeTimeout = 5 * time.Second
//---------------------------------------------- //----------------------------------------------
@ -117,3 +123,17 @@ func SetLogger(l log.Logger) {
func SetEventBus(b *types.EventBus) { func SetEventBus(b *types.EventBus) {
eventBus = b eventBus = b
} }
func validatePage(page int) int {
if page < 1 {
return 1
}
return page
}
func validatePerPage(perPage int) int {
if perPage < 1 || perPage > maxPerPage {
return defaultPerPage
}
return perPage
}

View File

@ -22,7 +22,7 @@ var Routes = map[string]*rpc.RPCFunc{
"block_results": rpc.NewRPCFunc(BlockResults, "height"), "block_results": rpc.NewRPCFunc(BlockResults, "height"),
"commit": rpc.NewRPCFunc(Commit, "height"), "commit": rpc.NewRPCFunc(Commit, "height"),
"tx": rpc.NewRPCFunc(Tx, "hash,prove"), "tx": rpc.NewRPCFunc(Tx, "hash,prove"),
"tx_search": rpc.NewRPCFunc(TxSearch, "query,prove"), "tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page"),
"validators": rpc.NewRPCFunc(Validators, "height"), "validators": rpc.NewRPCFunc(Validators, "height"),
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""), "dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""),
"consensus_state": rpc.NewRPCFunc(ConsensusState, ""), "consensus_state": rpc.NewRPCFunc(ConsensusState, ""),

View File

@ -6,6 +6,7 @@ import (
ctypes "github.com/tendermint/tendermint/rpc/core/types" ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/state/txindex/null" "github.com/tendermint/tendermint/state/txindex/null"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common"
tmquery "github.com/tendermint/tmlibs/pubsub/query" tmquery "github.com/tendermint/tmlibs/pubsub/query"
) )
@ -104,7 +105,8 @@ func Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
}, nil }, nil
} }
// TxSearch allows you to query for multiple transactions results. // TxSearch allows you to query for multiple transactions results. It returns a
// list of transactions (maximum ?per_page entries) and the total count.
// //
// ```shell // ```shell
// curl "localhost:46657/tx_search?query=\"account.owner='Ivan'\"&prove=true" // curl "localhost:46657/tx_search?query=\"account.owner='Ivan'\"&prove=true"
@ -120,7 +122,10 @@ func Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
// //
// ```json // ```json
// { // {
// "result": [ // "jsonrpc": "2.0",
// "id": "",
// "result": {
// "txs": [
// { // {
// "proof": { // "proof": {
// "Proof": { // "Proof": {
@ -144,19 +149,19 @@ func Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
// "hash": "2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF" // "hash": "2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF"
// } // }
// ], // ],
// "id": "", // "total_count": 1
// "jsonrpc": "2.0" // }
// } // }
// ``` // ```
// //
// Returns transactions matching the given query.
//
// ### Query Parameters // ### Query Parameters
// //
// | Parameter | Type | Default | Required | Description | // | Parameter | Type | Default | Required | Description |
// |-----------+--------+---------+----------+-----------------------------------------------------------| // |-----------+--------+---------+----------+-----------------------------------------------------------|
// | query | string | "" | true | Query | // | query | string | "" | true | Query |
// | prove | bool | false | false | Include proofs of the transactions inclusion in the block | // | prove | bool | false | false | Include proofs of the transactions inclusion in the block |
// | page | int | 1 | false | Page number (1-based) |
// | per_page | int | 30 | false | Number of entries per page (max: 100) |
// //
// ### Returns // ### Returns
// //
@ -166,7 +171,7 @@ func Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
// - `index`: `int` - index of the transaction // - `index`: `int` - index of the transaction
// - `height`: `int` - height of the block where this transaction was in // - `height`: `int` - height of the block where this transaction was in
// - `hash`: `[]byte` - hash of the transaction // - `hash`: `[]byte` - hash of the transaction
func TxSearch(query string, prove bool) ([]*ctypes.ResultTx, error) { func TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
// if index is disabled, return error // if index is disabled, return error
if _, ok := txIndexer.(*null.TxIndex); ok { if _, ok := txIndexer.(*null.TxIndex); ok {
return nil, fmt.Errorf("Transaction indexing is disabled") return nil, fmt.Errorf("Transaction indexing is disabled")
@ -182,11 +187,15 @@ func TxSearch(query string, prove bool) ([]*ctypes.ResultTx, error) {
return nil, err return nil, err
} }
// TODO: we may want to consider putting a maximum on this length and somehow totalCount := len(results)
// informing the user that things were truncated. page = validatePage(page)
apiResults := make([]*ctypes.ResultTx, len(results)) perPage = validatePerPage(perPage)
skipCount := (page - 1) * perPage
apiResults := make([]*ctypes.ResultTx, cmn.MinInt(perPage, totalCount-skipCount))
var proof types.TxProof var proof types.TxProof
for i, r := range results { for i := 0; i < len(apiResults); i++ {
r := results[skipCount+i]
height := r.Height height := r.Height
index := r.Index index := r.Index
@ -205,5 +214,5 @@ func TxSearch(query string, prove bool) ([]*ctypes.ResultTx, error) {
} }
} }
return apiResults, nil return &ctypes.ResultTxSearch{Txs: apiResults, TotalCount: totalCount}, nil
} }

View File

@ -172,6 +172,12 @@ type ResultTx struct {
Proof types.TxProof `json:"proof,omitempty"` Proof types.TxProof `json:"proof,omitempty"`
} }
// Result of searching for txs
type ResultTxSearch struct {
Txs []*ResultTx `json:"txs"`
TotalCount int `json:"total_count"`
}
// List of mempool txs // List of mempool txs
type ResultUnconfirmedTxs struct { type ResultUnconfirmedTxs struct {
N int `json:"n_txs"` N int `json:"n_txs"`