From 1ddc43d0296b1260027451760fde9ae27500d1ef Mon Sep 17 00:00:00 2001 From: Marko Date: Fri, 15 Nov 2019 06:38:43 +0100 Subject: [PATCH] rpc: add pagination to /validators (#3993) * Add pagination to /validators - closes #3472 Signed-off-by: Marko Baricevic * add swagger params, default returns all * address pr comments * golint fix * swagger default change, change to default in comment * swagger.yaml: replace x-example with example https://swagger.io/docs/specification/adding-examples/ * Revert "swagger.yaml: replace x-example with example" This reverts commit 9df1b006dee0e0d78c54a808d103e30bdd502c10. * update changelog and remove extra body close --- CHANGELOG_PENDING.md | 2 ++ lite/client/provider.go | 2 +- lite/proxy/proxy.go | 2 +- p2p/switch_test.go | 5 +++-- rpc/client/httpclient.go | 8 ++++++-- rpc/client/interface.go | 2 +- rpc/client/localclient.go | 4 ++-- rpc/client/mock/client.go | 4 ++-- rpc/client/rpc_test.go | 2 +- rpc/core/consensus.go | 17 +++++++++++++++-- rpc/core/routes.go | 2 +- rpc/lib/server/handlers_test.go | 2 +- rpc/swagger/swagger.yaml | 14 ++++++++++++++ 13 files changed, 50 insertions(+), 16 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6ba9e716c..498104a8c 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -11,6 +11,7 @@ program](https://hackerone.com/tendermint). ### BREAKING CHANGES: - CLI/RPC/Config + - [rpc] \#3471 Paginate `/validators` response (default: 30 vals per page) - [rpc] \#3188 Remove `BlockMeta` in `ResultBlock` in favor of `BlockId` for `/block` - [rpc] `/block_results` response format updated (see RPC docs for details) ``` @@ -32,6 +33,7 @@ program](https://hackerone.com/tendermint). - Go API - [libs/pubsub] [\#4070](https://github.com/tendermint/tendermint/pull/4070) `Query#(Matches|Conditions)` returns an error. + - [rpc/client] \#3471 `Validators` now requires two more args: `page` and `perPage` - Blockchain Protocol - [abci] \#2521 Remove `TotalTxs` and `NumTxs` from `Header` diff --git a/lite/client/provider.go b/lite/client/provider.go index e0c0a331b..2bcb8f731 100644 --- a/lite/client/provider.go +++ b/lite/client/provider.go @@ -106,7 +106,7 @@ func (p *provider) getValidatorSet(chainID string, height int64) (valset *types. err = fmt.Errorf("expected height >= 1, got height %v", height) return } - res, err := p.client.Validators(&height) + res, err := p.client.Validators(&height, 0, 0) if err != nil { // TODO pass through other types of errors. return nil, lerr.ErrUnknownValidators(chainID, height) diff --git a/lite/proxy/proxy.go b/lite/proxy/proxy.go index 2c75ae06e..dbe897464 100644 --- a/lite/proxy/proxy.go +++ b/lite/proxy/proxy.go @@ -134,7 +134,7 @@ func makeValidatorsFunc(c rpcclient.Client) func( height *int64, ) (*ctypes.ResultValidators, error) { return func(ctx *rpctypes.Context, height *int64) (*ctypes.ResultValidators, error) { - return c.Validators(height) + return c.Validators(height, 0, 0) } } diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 57de94868..bbbf6461e 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -368,9 +368,10 @@ func TestSwitchStopPeerForError(t *testing.T) { defer s.Close() scrapeMetrics := func() string { - resp, _ := http.Get(s.URL) + resp, err := http.Get(s.URL) + assert.NoError(t, err) + defer resp.Body.Close() buf, _ := ioutil.ReadAll(resp.Body) - resp.Body.Close() return string(buf) } diff --git a/rpc/client/httpclient.go b/rpc/client/httpclient.go index 5c6d0e4b6..a982f582c 100644 --- a/rpc/client/httpclient.go +++ b/rpc/client/httpclient.go @@ -339,9 +339,13 @@ func (c *baseRPCClient) TxSearch(query string, prove bool, page, perPage int) (* return result, nil } -func (c *baseRPCClient) Validators(height *int64) (*ctypes.ResultValidators, error) { +func (c *baseRPCClient) Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) { result := new(ctypes.ResultValidators) - _, err := c.caller.Call("validators", map[string]interface{}{"height": height}, result) + _, err := c.caller.Call("validators", map[string]interface{}{ + "height": height, + "page": page, + "per_page": perPage, + }, result) if err != nil { return nil, errors.Wrap(err, "Validators") } diff --git a/rpc/client/interface.go b/rpc/client/interface.go index c4a7b023d..861aaf3ee 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -66,7 +66,7 @@ type SignClient interface { Block(height *int64) (*ctypes.ResultBlock, error) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) Commit(height *int64) (*ctypes.ResultCommit, error) - Validators(height *int64) (*ctypes.ResultValidators, error) + Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) } diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index 5fef9d564..cb7ee8c94 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -148,8 +148,8 @@ func (c *Local) Commit(height *int64) (*ctypes.ResultCommit, error) { return core.Commit(c.ctx, height) } -func (c *Local) Validators(height *int64) (*ctypes.ResultValidators, error) { - return core.Validators(c.ctx, height) +func (c *Local) Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) { + return core.Validators(c.ctx, height, page, perPage) } func (c *Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index 313de2b68..525bcc2d3 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -149,8 +149,8 @@ func (c Client) Commit(height *int64) (*ctypes.ResultCommit, error) { return core.Commit(&rpctypes.Context{}, height) } -func (c Client) Validators(height *int64) (*ctypes.ResultValidators, error) { - return core.Validators(&rpctypes.Context{}, height) +func (c Client) Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) { + return core.Validators(&rpctypes.Context{}, height, page, perPage) } func (c Client) BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) { diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 50d3f8684..f1718fec5 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -152,7 +152,7 @@ func TestGenesisAndValidators(t *testing.T) { gval := gen.Genesis.Validators[0] // get the current validators - vals, err := c.Validators(nil) + vals, err := c.Validators(nil, 0, 0) require.Nil(t, err, "%d: %+v", i, err) require.Equal(t, 1, len(vals.Validators)) val := vals.Validators[0] diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 4b764a72f..794fb113b 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -2,6 +2,7 @@ package core import ( cm "github.com/tendermint/tendermint/consensus" + cmn "github.com/tendermint/tendermint/libs/common" ctypes "github.com/tendermint/tendermint/rpc/core/types" rpctypes "github.com/tendermint/tendermint/rpc/lib/types" sm "github.com/tendermint/tendermint/state" @@ -13,7 +14,7 @@ import ( // Note the validators are sorted by their address - this is the canonical // order for the validators in the set as used in computing their Merkle root. // More: https://tendermint.com/rpc/#/Info/validators -func Validators(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultValidators, error) { +func Validators(ctx *rpctypes.Context, heightPtr *int64, page, perPage int) (*ctypes.ResultValidators, error) { // The latest validator that we know is the // NextValidator of the last block. height := consensusState.GetState().LastBlockHeight + 1 @@ -26,9 +27,21 @@ func Validators(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultValidato if err != nil { return nil, err } + + totalCount := len(validators.Validators) + perPage = validatePerPage(perPage) + page, err = validatePage(page, perPage, totalCount) + if err != nil { + return nil, err + } + + skipCount := validateSkipCount(page, perPage) + + v := validators.Validators[skipCount : skipCount+cmn.MinInt(perPage, totalCount-skipCount)] + return &ctypes.ResultValidators{ BlockHeight: height, - Validators: validators.Validators}, nil + Validators: v}, nil } // DumpConsensusState dumps consensus state. diff --git a/rpc/core/routes.go b/rpc/core/routes.go index dbd9c6059..8fb47e62a 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -23,7 +23,7 @@ var Routes = map[string]*rpc.RPCFunc{ "commit": rpc.NewRPCFunc(Commit, "height"), "tx": rpc.NewRPCFunc(Tx, "hash,prove"), "tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page"), - "validators": rpc.NewRPCFunc(Validators, "height"), + "validators": rpc.NewRPCFunc(Validators, "height,page,per_page"), "dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""), "consensus_state": rpc.NewRPCFunc(ConsensusState, ""), "consensus_params": rpc.NewRPCFunc(ConsensusParams, "height"), diff --git a/rpc/lib/server/handlers_test.go b/rpc/lib/server/handlers_test.go index fc9ad6cb6..a882d5939 100644 --- a/rpc/lib/server/handlers_test.go +++ b/rpc/lib/server/handlers_test.go @@ -71,6 +71,7 @@ func TestRPCParams(t *testing.T) { rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) res := rec.Result() + defer res.Body.Close() // Always expecting back a JSONRPCResponse assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i) blob, err := ioutil.ReadAll(res.Body) @@ -78,7 +79,6 @@ func TestRPCParams(t *testing.T) { t.Errorf("#%d: err reading body: %v", i, err) continue } - res.Body.Close() recv := new(types.RPCResponse) assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) diff --git a/rpc/swagger/swagger.yaml b/rpc/swagger/swagger.yaml index a87a37786..dc41ce47d 100644 --- a/rpc/swagger/swagger.yaml +++ b/rpc/swagger/swagger.yaml @@ -584,6 +584,20 @@ paths: description: height to return. If no height is provided, it will fetch validato set at the latest block. 0 means latest default: 0 x-example: 1 + - in: query + name: page + type: number + description: "Page number (1-based)" + required: false + x-example: 1 + default: 0 + - in: query + name: per_page + type: number + description: "Number of entries per page (max: 100)" + required: false + x-example: 30 + default: 30 tags: - Info description: |