From 39e62905266b014c93d809a99d559538b902533b Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 24 Apr 2019 10:21:40 -0400 Subject: [PATCH 1/2] Support pagination and status query params for /staking/validators --- ...the-staking-validators-endpoint-to-support | 2 + client/lcd/swagger-ui/swagger.yaml | 18 +++++- client/tx/query.go | 1 - types/staking.go | 12 ++-- x/staking/alias.go | 10 ++-- x/staking/client/rest/query.go | 28 +++++++++- x/staking/querier/querier.go | 55 +++++++++++++++++-- x/staking/querier/querier_test.go | 40 +++++++++----- 8 files changed, 136 insertions(+), 30 deletions(-) create mode 100644 .pending/features/sdk/4099-Update-the-staking-validators-endpoint-to-support diff --git a/.pending/features/sdk/4099-Update-the-staking-validators-endpoint-to-support b/.pending/features/sdk/4099-Update-the-staking-validators-endpoint-to-support new file mode 100644 index 000000000000..d59fdc9a48e8 --- /dev/null +++ b/.pending/features/sdk/4099-Update-the-staking-validators-endpoint-to-support @@ -0,0 +1,2 @@ +#4099 Update the /staking/validators endpoint to support +status and pagination query flags. diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index 8a0df1e318f8..c099c6f12279 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -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: diff --git a/client/tx/query.go b/client/tx/query.go index 8bb8e10cd705..3cf290044548 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -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 diff --git a/types/staking.go b/types/staking.go index 79832ac00ab0..50641485e295 100644 --- a/types/staking.go +++ b/types/staking.go @@ -25,17 +25,21 @@ 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 +// BondStatusToString for pretty prints of Bond Status. func BondStatusToString(b BondStatus) 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") } diff --git a/x/staking/alias.go b/x/staking/alias.go index cbc20d7dca06..3c8e19fd9f30 100644 --- a/x/staking/alias.go +++ b/x/staking/alias.go @@ -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 ( diff --git a/x/staking/client/rest/query.go b/x/staking/client/rest/query.go index 3f1e9bfd2997..e7ba6e83476b 100644 --- a/x/staking/client/rest/query.go +++ b/x/staking/client/rest/query.go @@ -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 diff --git a/x/staking/querier/querier.go b/x/staking/querier/querier.go index 4230dc6a43bc..9d16f9cc58a9 100644 --- a/x/staking/querier/querier.go +++ b/x/staking/querier/querier.go @@ -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) { +func queryValidators(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) ([]byte, sdk.Error) { + var params QueryValidatorsParams + + err := cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + } + stakingParams := k.GetParams(ctx) - validators := k.GetValidators(ctx, stakingParams.MaxValidators) + 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(sdk.BondStatusToString(val.GetStatus())) == 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) + } - res, errRes := codec.MarshalJSONIndent(cdc, validators) + 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("could not marshal result to JSON", errRes.Error())) + 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} +} diff --git a/x/staking/querier/querier_test.go b/x/staking/querier/querier_test.go index f4fe9592b33d..b2d855ec6100 100644 --- a/x/staking/querier/querier_test.go +++ b/x/staking/querier/querier_test.go @@ -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), sdk.BondStatusToString(s)) + 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, + } + + res, err := queryValidators(ctx, cdc, req, keeper) + require.Nil(t, err) - require.Equal(t, len(queriedValidators), len(validatorsResp)) - require.ElementsMatch(t, queriedValidators, validatorsResp) + 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 From 8ed867dc22185bfc110cc113b389df711307bbb4 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 24 Apr 2019 10:53:03 -0400 Subject: [PATCH 2/2] Rename BondStatusToString to String --- types/staking.go | 6 +++--- x/staking/keeper/validator_test.go | 5 ++--- x/staking/querier/querier.go | 2 +- x/staking/querier/querier_test.go | 2 +- x/staking/types/validator.go | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/types/staking.go b/types/staking.go index 50641485e295..54f28f97279d 100644 --- a/types/staking.go +++ b/types/staking.go @@ -31,8 +31,8 @@ const ( 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 BondStatusUnbonded @@ -41,7 +41,7 @@ func BondStatusToString(b BondStatus) string { case 0x02: return BondStatusBonded default: - panic("improper use of BondStatusToString") + panic("invalid bond status") } } diff --git a/x/staking/keeper/validator_test.go b/x/staking/keeper/validator_test.go index d2aa5b270bea..d359d637c682 100644 --- a/x/staking/keeper/validator_test.go +++ b/x/staking/keeper/validator_test.go @@ -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), + ) } } diff --git a/x/staking/querier/querier.go b/x/staking/querier/querier.go index 9d16f9cc58a9..8694a35b9ca2 100644 --- a/x/staking/querier/querier.go +++ b/x/staking/querier/querier.go @@ -146,7 +146,7 @@ func queryValidators(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k filteredVals := make([]types.Validator, 0, len(validators)) for _, val := range validators { - if strings.ToLower(sdk.BondStatusToString(val.GetStatus())) == strings.ToLower(params.Status) { + if strings.ToLower(val.GetStatus().String()) == strings.ToLower(params.Status) { filteredVals = append(filteredVals, val) } } diff --git a/x/staking/querier/querier_test.go b/x/staking/querier/querier_test.go index b2d855ec6100..f83c950706e9 100644 --- a/x/staking/querier/querier_test.go +++ b/x/staking/querier/querier_test.go @@ -137,7 +137,7 @@ func TestQueryValidators(t *testing.T) { queriedValidators := keeper.GetValidators(ctx, params.MaxValidators) for i, s := range status { - queryValsParams := NewQueryValidatorsParams(1, int(params.MaxValidators), sdk.BondStatusToString(s)) + queryValsParams := NewQueryValidatorsParams(1, int(params.MaxValidators), s.String()) bz, errRes := cdc.MarshalJSON(queryValsParams) require.Nil(t, errRes) diff --git a/x/staking/types/validator.go b/x/staking/types/validator.go index c048de744c0d..962345aac59f 100644 --- a/x/staking/types/validator.go +++ b/x/staking/types/validator.go @@ -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) }