Merge PR #4177: Update Staking Validators Rest Query

* Support pagination and status query params for /staking/validators

* Rename BondStatusToString to String
This commit is contained in:
Alexander Bezobchuk 2019-04-24 11:48:39 -04:00 committed by frog power 4000
parent 29755e66f6
commit 0e54369850
10 changed files with 142 additions and 37 deletions

View File

@ -0,0 +1,2 @@
#4099 Update the /staking/validators endpoint to support
status and pagination query flags.

View File

@ -763,7 +763,23 @@ paths:
description: Internal Server Error
/staking/validators:
get:
summary: Get all validator candidates
summary: Get all validator candidates. By default it returns only the bonded validators.
parameters:
- in: query
name: status
type: string
description: The validator bond status. Must be either 'bonded', 'unbonded', or 'unbonding'.
x-example: bonded
- in: query
name: page
description: The gage number.
type: integer
x-example: 1
- in: query
name: limit
description: The maximum number of items per page.
type: integer
x-example: 1
tags:
- ICS21
produces:

View File

@ -162,7 +162,6 @@ func QueryTxsByTagsRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec)
}
tags, page, limit, err = rest.ParseHTTPArgs(r)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return

View File

@ -25,19 +25,23 @@ const (
// Constant as this should not change without a hard fork.
// TODO: Link to some Tendermint docs, this is very unobvious.
ValidatorUpdateDelay int64 = 1
BondStatusUnbonded = "Unbonded"
BondStatusUnbonding = "Unbonding"
BondStatusBonded = "Bonded"
)
//BondStatusToString for pretty prints of Bond Status
func BondStatusToString(b BondStatus) string {
// String implements the Stringer interface for BondStatus.
func (b BondStatus) String() string {
switch b {
case 0x00:
return "Unbonded"
return BondStatusUnbonded
case 0x01:
return "Unbonding"
return BondStatusUnbonding
case 0x02:
return "Bonded"
return BondStatusBonded
default:
panic("improper use of BondStatusToString")
panic("invalid bond status")
}
}

View File

@ -35,6 +35,7 @@ type (
QueryValidatorParams = querier.QueryValidatorParams
QueryBondsParams = querier.QueryBondsParams
QueryRedelegationParams = querier.QueryRedelegationParams
QueryValidatorsParams = querier.QueryValidatorsParams
)
var (
@ -96,10 +97,11 @@ var (
NewMsgUndelegate = types.NewMsgUndelegate
NewMsgBeginRedelegate = types.NewMsgBeginRedelegate
NewQuerier = querier.NewQuerier
NewQueryDelegatorParams = querier.NewQueryDelegatorParams
NewQueryValidatorParams = querier.NewQueryValidatorParams
NewQueryBondsParams = querier.NewQueryBondsParams
NewQuerier = querier.NewQuerier
NewQueryDelegatorParams = querier.NewQueryDelegatorParams
NewQueryValidatorParams = querier.NewQueryValidatorParams
NewQueryBondsParams = querier.NewQueryBondsParams
NewQueryValidatorsParams = querier.NewQueryValidatorsParams
)
const (

View File

@ -1,6 +1,7 @@
package rest
import (
"fmt"
"net/http"
"strings"
@ -14,7 +15,6 @@ import (
)
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) {
// Get all delegations from a delegator
r.HandleFunc(
"/staking/delegators/{delegatorAddr}/delegations",
@ -244,7 +244,31 @@ func delegatorValidatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) ht
// HTTP request handler to query list of validators
func validatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
res, err := cliCtx.QueryWithData("custom/staking/validators", nil)
_, page, limit, err := rest.ParseHTTPArgs(r)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
// override default limit if it wasn't provided
if l := r.FormValue("limit"); l == "" {
limit = 0
}
status := r.FormValue("status")
if status == "" {
status = sdk.BondStatusBonded
}
params := staking.NewQueryValidatorsParams(page, limit, status)
bz, err := cdc.MarshalJSON(params)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
route := fmt.Sprintf("custom/%s/%s", staking.QuerierRoute, staking.QueryValidators)
res, err := cliCtx.QueryWithData(route, bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return

View File

@ -162,9 +162,8 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) {
assert.Equal(
t, status, val.GetStatus(),
fmt.Sprintf("expected validator at index %v to have status: %s",
valIdx,
sdk.BondStatusToString(status)))
fmt.Sprintf("expected validator at index %v to have status: %s", valIdx, status),
)
}
}

View File

@ -2,6 +2,7 @@ package querier
import (
"fmt"
"strings"
abci "github.com/tendermint/tendermint/abci/types"
@ -35,7 +36,7 @@ func NewQuerier(k keep.Keeper, cdc *codec.Codec) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
switch path[0] {
case QueryValidators:
return queryValidators(ctx, cdc, k)
return queryValidators(ctx, cdc, req, k)
case QueryValidator:
return queryValidator(ctx, cdc, req, k)
case QueryValidatorDelegations:
@ -128,14 +129,47 @@ func NewQueryRedelegationParams(delegatorAddr sdk.AccAddress, srcValidatorAddr s
}
}
func queryValidators(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []byte, err sdk.Error) {
stakingParams := k.GetParams(ctx)
validators := k.GetValidators(ctx, stakingParams.MaxValidators)
func queryValidators(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) ([]byte, sdk.Error) {
var params QueryValidatorsParams
res, errRes := codec.MarshalJSONIndent(cdc, validators)
err := cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error()))
return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err))
}
stakingParams := k.GetParams(ctx)
if params.Limit == 0 {
params.Limit = int(stakingParams.MaxValidators)
}
validators := k.GetAllValidators(ctx)
filteredVals := make([]types.Validator, 0, len(validators))
for _, val := range validators {
if strings.ToLower(val.GetStatus().String()) == strings.ToLower(params.Status) {
filteredVals = append(filteredVals, val)
}
}
// get pagination bounds
start := (params.Page - 1) * params.Limit
end := params.Limit + start
if end >= len(filteredVals) {
end = len(filteredVals)
}
if start >= len(filteredVals) {
// page is out of bounds
filteredVals = []types.Validator{}
} else {
filteredVals = filteredVals[start:end]
}
res, err := codec.MarshalJSONIndent(cdc, filteredVals)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to JSON marshal result: %s", err.Error()))
}
return res, nil
}
@ -354,3 +388,14 @@ func queryParameters(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []by
}
return res, nil
}
// QueryValidatorsParams defines the params for the following queries:
// - 'custom/staking/validators'
type QueryValidatorsParams struct {
Page, Limit int
Status string
}
func NewQueryValidatorsParams(page, limit int, status string) QueryValidatorsParams {
return QueryValidatorsParams{page, limit, status}
}

View File

@ -1,6 +1,7 @@
package querier
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
@ -44,9 +45,6 @@ func TestNewQuerier(t *testing.T) {
require.NotNil(t, err)
require.Nil(t, bz)
_, err = querier(ctx, []string{"validators"}, query)
require.Nil(t, err)
_, err = querier(ctx, []string{"pool"}, query)
require.Nil(t, err)
@ -121,28 +119,44 @@ func TestQueryValidators(t *testing.T) {
params := keeper.GetParams(ctx)
// Create Validators
amts := []sdk.Int{sdk.NewInt(9), sdk.NewInt(8)}
var validators [2]types.Validator
amts := []sdk.Int{sdk.NewInt(9), sdk.NewInt(8), sdk.NewInt(7)}
status := []sdk.BondStatus{sdk.Bonded, sdk.Unbonded, sdk.Unbonding}
var validators [3]types.Validator
for i, amt := range amts {
validators[i] = types.NewValidator(sdk.ValAddress(keep.Addrs[i]), keep.PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
validators[i], pool = validators[i].UpdateStatus(pool, status[i])
}
keeper.SetPool(ctx, pool)
keeper.SetValidator(ctx, validators[0])
keeper.SetValidator(ctx, validators[1])
keeper.SetValidator(ctx, validators[2])
// Query Validators
queriedValidators := keeper.GetValidators(ctx, params.MaxValidators)
res, err := queryValidators(ctx, cdc, keeper)
require.Nil(t, err)
for i, s := range status {
queryValsParams := NewQueryValidatorsParams(1, int(params.MaxValidators), s.String())
bz, errRes := cdc.MarshalJSON(queryValsParams)
require.Nil(t, errRes)
var validatorsResp []types.Validator
errRes := cdc.UnmarshalJSON(res, &validatorsResp)
require.Nil(t, errRes)
req := abci.RequestQuery{
Path: fmt.Sprintf("/custom/%s/%s", types.QuerierRoute, QueryValidators),
Data: bz,
}
require.Equal(t, len(queriedValidators), len(validatorsResp))
require.ElementsMatch(t, queriedValidators, validatorsResp)
res, err := queryValidators(ctx, cdc, req, keeper)
require.Nil(t, err)
var validatorsResp []types.Validator
errRes = cdc.UnmarshalJSON(res, &validatorsResp)
require.Nil(t, errRes)
require.Equal(t, 1, len(validatorsResp))
require.ElementsMatch(t, validators[i].OperatorAddress, validatorsResp[0].OperatorAddress)
}
// Query each validator
queryParams := NewQueryValidatorParams(addrVal1)
@ -153,7 +167,7 @@ func TestQueryValidators(t *testing.T) {
Path: "/custom/staking/validator",
Data: bz,
}
res, err = queryValidator(ctx, cdc, query, keeper)
res, err := queryValidator(ctx, cdc, query, keeper)
require.Nil(t, err)
var validator types.Validator

View File

@ -117,7 +117,7 @@ func (v Validator) String() string {
Unbonding Completion Time: %v
Minimum Self Delegation: %v
Commission: %s`, v.OperatorAddress, bechConsPubKey,
v.Jailed, sdk.BondStatusToString(v.Status), v.Tokens,
v.Jailed, v.Status, v.Tokens,
v.DelegatorShares, v.Description,
v.UnbondingHeight, v.UnbondingCompletionTime, v.MinSelfDelegation, v.Commission)
}