diff --git a/PENDING.md b/PENDING.md index 0c3aeea5f..f0d74501d 100644 --- a/PENDING.md +++ b/PENDING.md @@ -22,7 +22,8 @@ FEATURES * Gaia REST API (`gaiacli advanced rest-server`) * Gaia CLI (`gaiacli`) - + * [stake][cli] [\#2027] Add CLI query command for getting all delegations to a specific validator. + * Gaia * SDK diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 9b042832b..1f087ec88 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -539,7 +539,7 @@ func TestBonding(t *testing.T) { require.Equal(t, int64(40), coins.AmountOf(denom).Int64()) - // query validator + // query delegation bond := getDelegation(t, port, addr, operAddrs[0]) require.Equal(t, amt, bond.Shares) @@ -547,6 +547,10 @@ func TestBonding(t *testing.T) { require.Len(t, delegatorDels, 1) require.Equal(t, amt, delegatorDels[0].Shares) + // query all delegations to validator + bonds := getValidatorDelegations(t, port, operAddrs[0]) + require.Len(t, bonds, 2) + bondedValidators := getDelegatorValidators(t, port, addr) require.Len(t, bondedValidators, 1) require.Equal(t, operAddrs[0], bondedValidators[0].OperatorAddr) @@ -1207,6 +1211,17 @@ func getValidator(t *testing.T, port string, validatorAddr sdk.ValAddress) stake return validator } +func getValidatorDelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []stake.Delegation { + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s/delegations", validatorAddr.String()), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var delegations []stake.Delegation + err := cdc.UnmarshalJSON([]byte(body), &delegations) + require.Nil(t, err) + + return delegations +} + func getValidatorUnbondingDelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []stake.UnbondingDelegation { res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s/unbonding_delegations", validatorAddr.String()), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index 86b6ec681..b8494a722 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -950,6 +950,30 @@ paths: description: Invalid validator address 500: description: Internal Server Error + /stake/validators/{validatorAddr}/delegations: + parameters: + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Get all delegations from a validator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/Delegation" + 400: + description: Invalid validator address + 500: + description: Internal Server Error /stake/validators/{validatorAddr}/unbonding_delegations: parameters: - in: path diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index f6fb6381e..974541008 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -272,6 +272,10 @@ func TestGaiaCLICreateValidator(t *testing.T) { require.Equal(t, validator.OperatorAddr, sdk.ValAddress(barAddr)) require.True(sdk.DecEq(t, sdk.NewDec(2), validator.Tokens)) + validatorDelegations := executeGetValidatorDelegations(t, fmt.Sprintf("gaiacli query stake delegations-to %s --output=json %v", sdk.ValAddress(barAddr), flags)) + require.Len(t, validatorDelegations, 1) + require.NotZero(t, validatorDelegations[0].Shares) + // unbond a single share unbondStr := fmt.Sprintf("gaiacli tx stake unbond begin %v", flags) unbondStr += fmt.Sprintf(" --from=%s", "bar") @@ -750,6 +754,15 @@ func executeGetValidatorRedelegations(t *testing.T, cmdStr string) []stake.Redel return reds } +func executeGetValidatorDelegations(t *testing.T, cmdStr string) []stake.Delegation { + out, _ := tests.ExecuteT(t, cmdStr, "") + var delegations []stake.Delegation + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &delegations) + require.NoError(t, err, "out %v\n, err %v", out, err) + return delegations +} + func executeGetPool(t *testing.T, cmdStr string) stake.Pool { out, _ := tests.ExecuteT(t, cmdStr, "") var pool stake.Pool diff --git a/cmd/gaia/cmd/gaiacli/query.go b/cmd/gaia/cmd/gaiacli/query.go index 051d9427a..9ba3bca46 100644 --- a/cmd/gaia/cmd/gaiacli/query.go +++ b/cmd/gaia/cmd/gaiacli/query.go @@ -36,6 +36,7 @@ func queryCmd(cdc *amino.Codec) *cobra.Command { stakecmd.GetCmdQueryRedelegations(storeStake, cdc), stakecmd.GetCmdQueryValidator(storeStake, cdc), stakecmd.GetCmdQueryValidators(storeStake, cdc), + stakecmd.GetCmdQueryValidatorDelegations(storeStake, cdc), stakecmd.GetCmdQueryValidatorUnbondingDelegations(queryRouteStake, cdc), stakecmd.GetCmdQueryValidatorRedelegations(queryRouteStake, cdc), stakecmd.GetCmdQueryParams(storeStake, cdc), diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index 4f0669183..3c0ca5c50 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -341,6 +341,13 @@ Additionally, as you can get all the outgoing redelegations from a particular va To get previous redelegation(s) status on past blocks, try adding the `--height` flag. +##### Query Delegations To Validator + +You can also query all of the delegations to a particular validator: +```bash + gaiacli query delegations-to +``` + ### Governance Governance is the process from which users in the Cosmos Hub can come to consensus on software upgrades, parameters of the mainnet or on custom text proposals. This is done through voting on proposals, which will be submitted by `Atom` holders on the mainnet. diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 28d09df8e..24e449996 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -127,9 +127,7 @@ func GetCmdQueryValidatorUnbondingDelegations(queryRoute string, cdc *codec.Code } cliCtx := context.NewCLIContext().WithCodec(cdc) - params := stake.QueryValidatorParams{ - ValidatorAddr: valAddr, - } + params := stake.NewQueryValidatorParams(valAddr) bz, err := cdc.MarshalJSON(params) if err != nil { @@ -164,9 +162,7 @@ func GetCmdQueryValidatorRedelegations(queryRoute string, cdc *codec.Codec) *cob } cliCtx := context.NewCLIContext().WithCodec(cdc) - params := stake.QueryValidatorParams{ - ValidatorAddr: valAddr, - } + params := stake.NewQueryValidatorParams(valAddr) bz, err := cdc.MarshalJSON(params) if err != nil { @@ -290,6 +286,41 @@ func GetCmdQueryDelegations(storeName string, cdc *codec.Codec) *cobra.Command { return cmd } +// GetCmdQueryValidatorDelegations implements the command to query all the +// delegations to a specific validator. +func GetCmdQueryValidatorDelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "delegations-to [validator-addr]", + Short: "Query all delegations made to one validator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + validatorAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + params := stake.NewQueryValidatorParams(validatorAddr) + + bz, err := cdc.MarshalJSON(params) + if err != nil { + return err + } + + cliCtx := context.NewCLIContext().WithCodec(cdc) + + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/validatorDelegations", queryRoute), bz) + if err != nil { + return err + } + + fmt.Println(string(res)) + return nil + }, + } + + return cmd +} + // GetCmdQueryUnbondingDelegation implements the command to query a single // unbonding-delegation record. func GetCmdQueryUnbondingDelegation(storeName string, cdc *codec.Codec) *cobra.Command { diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 935c0229f..8c669ee66 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -78,6 +78,12 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co validatorHandlerFn(cliCtx, cdc), ).Methods("GET") + // Get all delegations to a validator + r.HandleFunc( + "/stake/validators/{validatorAddr}/delegations", + validatorDelegationsHandlerFn(cliCtx, cdc), + ).Methods("GET") + // Get all unbonding delegations from a validator r.HandleFunc( "/stake/validators/{validatorAddr}/unbonding_delegations", @@ -227,6 +233,11 @@ func validatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle return queryValidator(cliCtx, cdc, "custom/stake/validator") } +// HTTP request handler to query all unbonding delegations from a validator +func validatorDelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { + return queryValidator(cliCtx, cdc, "custom/stake/validatorDelegations") +} + // HTTP request handler to query all unbonding delegations from a validator func validatorUnbondingDelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return queryValidator(cliCtx, cdc, "custom/stake/validatorUnbondingDelegations") diff --git a/x/stake/client/rest/utils.go b/x/stake/client/rest/utils.go index bb986c314..7f6edc193 100644 --- a/x/stake/client/rest/utils.go +++ b/x/stake/client/rest/utils.go @@ -62,10 +62,7 @@ func queryBonds(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) ht return } - params := stake.QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } + params := stake.NewQueryBondsParams(delegatorAddr, validatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { @@ -93,9 +90,7 @@ func queryDelegator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string return } - params := stake.QueryDelegatorParams{ - DelegatorAddr: delegatorAddr, - } + params := stake.NewQueryDelegatorParams(delegatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { @@ -123,9 +118,7 @@ func queryValidator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string return } - params := stake.QueryValidatorParams{ - ValidatorAddr: validatorAddr, - } + params := stake.NewQueryValidatorParams(validatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index d535419e1..32f63f5ed 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -37,6 +37,21 @@ func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegati return delegations } +// return all delegations to a specific validator. Useful for querier. +func (k Keeper) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) (delegations []types.Delegation) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, DelegationKey) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + if delegation.GetValidatorAddr().Equals(valAddr) { + delegations = append(delegations, delegation) + } + } + return delegations +} + // return a given amount of all the delegations from a delegator func (k Keeper) GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []types.Delegation) { diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 23dcbc1a6..3fa641fd2 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -105,6 +105,9 @@ func TestDelegation(t *testing.T) { resVal, err = keeper.GetDelegatorValidator(ctx, addrDels[1], addrVals[i]) require.Nil(t, err) require.Equal(t, addrVals[i], resVal.GetOperator()) + + resDels := keeper.GetValidatorDelegations(ctx, addrVals[i]) + require.Len(t, resDels, 2) } // delete a record diff --git a/x/stake/querier/queryable.go b/x/stake/querier/queryable.go index bacc8d6ae..13ff97ef3 100644 --- a/x/stake/querier/queryable.go +++ b/x/stake/querier/queryable.go @@ -15,6 +15,7 @@ const ( QueryDelegatorDelegations = "delegatorDelegations" QueryDelegatorUnbondingDelegations = "delegatorUnbondingDelegations" QueryDelegatorRedelegations = "delegatorRedelegations" + QueryValidatorDelegations = "validatorDelegations" QueryValidatorUnbondingDelegations = "validatorUnbondingDelegations" QueryValidatorRedelegations = "validatorRedelegations" QueryDelegator = "delegator" @@ -34,6 +35,8 @@ func NewQuerier(k keep.Keeper, cdc *codec.Codec) sdk.Querier { return queryValidators(ctx, cdc, k) case QueryValidator: return queryValidator(ctx, cdc, req, k) + case QueryValidatorDelegations: + return queryValidatorDelegations(ctx, cdc, req, k) case QueryValidatorUnbondingDelegations: return queryValidatorUnbondingDelegations(ctx, cdc, req, k) case QueryValidatorRedelegations: @@ -73,6 +76,7 @@ type QueryDelegatorParams struct { // defines the params for the following queries: // - 'custom/stake/validator' +// - 'custom/stake/validatorDelegations' // - 'custom/stake/validatorUnbondingDelegations' // - 'custom/stake/validatorRedelegations' type QueryValidatorParams struct { @@ -88,6 +92,28 @@ type QueryBondsParams struct { ValidatorAddr sdk.ValAddress } +// creates a new QueryDelegatorParams +func NewQueryDelegatorParams(delegatorAddr sdk.AccAddress) QueryDelegatorParams { + return QueryDelegatorParams{ + DelegatorAddr: delegatorAddr, + } +} + +// creates a new QueryValidatorParams +func NewQueryValidatorParams(validatorAddr sdk.ValAddress) QueryValidatorParams { + return QueryValidatorParams{ + ValidatorAddr: validatorAddr, + } +} + +// creates a new QueryBondsParams +func NewQueryBondsParams(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryBondsParams { + return QueryBondsParams{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } +} + func queryValidators(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []byte, err sdk.Error) { stakeParams := k.GetParams(ctx) validators := k.GetValidators(ctx, stakeParams.MaxValidators) @@ -119,6 +145,23 @@ func queryValidator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k return res, nil } +func queryValidatorDelegations(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { + var params QueryValidatorParams + + errRes := cdc.UnmarshalJSON(req.Data, ¶ms) + if errRes != nil { + return []byte{}, sdk.ErrUnknownAddress("") + } + + delegations := k.GetValidatorDelegations(ctx, params.ValidatorAddr) + + res, errRes = codec.MarshalJSONIndent(cdc, delegations) + if errRes != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) + } + return res, nil +} + func queryValidatorUnbondingDelegations(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { var params QueryValidatorParams diff --git a/x/stake/querier/queryable_test.go b/x/stake/querier/queryable_test.go index 925eae63f..eda5c6952 100644 --- a/x/stake/querier/queryable_test.go +++ b/x/stake/querier/queryable_test.go @@ -17,25 +17,6 @@ var ( pk1, pk2 = keep.PKs[0], keep.PKs[1] ) -func newTestDelegatorQuery(delegatorAddr sdk.AccAddress) QueryDelegatorParams { - return QueryDelegatorParams{ - DelegatorAddr: delegatorAddr, - } -} - -func newTestValidatorQuery(validatorAddr sdk.ValAddress) QueryValidatorParams { - return QueryValidatorParams{ - ValidatorAddr: validatorAddr, - } -} - -func newTestBondQuery(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryBondsParams { - return QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } -} - func TestNewQuerier(t *testing.T) { cdc := codec.New() ctx, _, keeper := keep.CreateTestInput(t, false, 1000) @@ -72,7 +53,7 @@ func TestNewQuerier(t *testing.T) { _, err = querier(ctx, []string{"parameters"}, query) require.Nil(t, err) - queryValParams := newTestValidatorQuery(addrVal1) + queryValParams := NewQueryValidatorParams(addrVal1) bz, errRes := cdc.MarshalJSON(queryValParams) require.Nil(t, errRes) @@ -82,13 +63,16 @@ func TestNewQuerier(t *testing.T) { _, err = querier(ctx, []string{"validator"}, query) require.Nil(t, err) + _, err = querier(ctx, []string{"validatorDelegations"}, query) + require.Nil(t, err) + _, err = querier(ctx, []string{"validatorUnbondingDelegations"}, query) require.Nil(t, err) _, err = querier(ctx, []string{"validatorRedelegations"}, query) require.Nil(t, err) - queryDelParams := newTestDelegatorQuery(addrAcc2) + queryDelParams := NewQueryDelegatorParams(addrAcc2) bz, errRes = cdc.MarshalJSON(queryDelParams) require.Nil(t, errRes) @@ -160,7 +144,7 @@ func TestQueryValidators(t *testing.T) { require.ElementsMatch(t, queriedValidators, validatorsResp) // Query each validator - queryParams := newTestValidatorQuery(addrVal1) + queryParams := NewQueryValidatorParams(addrVal1) bz, errRes := cdc.MarshalJSON(queryParams) require.Nil(t, errRes) @@ -189,13 +173,18 @@ func TestQueryDelegation(t *testing.T) { pool := keeper.GetPool(ctx) keeper.SetValidatorByPowerIndex(ctx, val1, pool) + val2 := types.NewValidator(addrVal2, pk2, types.Description{}) + keeper.SetValidator(ctx, val2) + pool = keeper.GetPool(ctx) + keeper.SetValidatorByPowerIndex(ctx, val2, pool) + keeper.Delegate(ctx, addrAcc2, sdk.NewCoin(types.DefaultBondDenom, sdk.NewInt(20)), val1, true) // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) // Query Delegator bonded validators - queryParams := newTestDelegatorQuery(addrAcc2) + queryParams := NewQueryDelegatorParams(addrAcc2) bz, errRes := cdc.MarshalJSON(queryParams) require.Nil(t, errRes) @@ -223,7 +212,7 @@ func TestQueryDelegation(t *testing.T) { require.NotNil(t, err) // Query bonded validator - queryBondParams := newTestBondQuery(addrAcc2, addrVal1) + queryBondParams := NewQueryBondsParams(addrAcc2, addrVal1) bz, errRes = cdc.MarshalJSON(queryBondParams) require.Nil(t, errRes) @@ -288,9 +277,32 @@ func TestQueryDelegation(t *testing.T) { _, err = queryDelegation(ctx, cdc, query, keeper) require.NotNil(t, err) + // Query validator delegations + + bz, errRes = cdc.MarshalJSON(NewQueryValidatorParams(addrVal1)) + require.Nil(t, errRes) + + query = abci.RequestQuery{ + Path: "custom/stake/validatorDelegations", + Data: bz, + } + + res, err = queryValidatorDelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + var delegationsRes []types.Delegation + errRes = cdc.UnmarshalJSON(res, &delegationsRes) + require.Nil(t, errRes) + + require.Equal(t, delegationsRes[0], delegation) + // Query unbonging delegation keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(10)) + queryBondParams = NewQueryBondsParams(addrAcc2, addrVal1) + bz, errRes = cdc.MarshalJSON(queryBondParams) + require.Nil(t, errRes) + query = abci.RequestQuery{ Path: "/custom/stake/unbondingDelegation", Data: bz, @@ -356,7 +368,7 @@ func TestQueryRedelegations(t *testing.T) { require.True(t, found) // delegator redelegations - queryDelegatorParams := newTestDelegatorQuery(addrAcc2) + queryDelegatorParams := NewQueryDelegatorParams(addrAcc2) bz, errRes := cdc.MarshalJSON(queryDelegatorParams) require.Nil(t, errRes) @@ -375,7 +387,7 @@ func TestQueryRedelegations(t *testing.T) { require.Equal(t, redelegation, redsRes[0]) // validator redelegations - queryValidatorParams := newTestValidatorQuery(val1.GetOperator()) + queryValidatorParams := NewQueryValidatorParams(val1.GetOperator()) bz, errRes = cdc.MarshalJSON(queryValidatorParams) require.Nil(t, errRes) diff --git a/x/stake/stake.go b/x/stake/stake.go index 87087e59c..a922d9d72 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -84,7 +84,10 @@ var ( NewMsgBeginUnbonding = types.NewMsgBeginUnbonding NewMsgBeginRedelegate = types.NewMsgBeginRedelegate - NewQuerier = querier.NewQuerier + NewQuerier = querier.NewQuerier + NewQueryDelegatorParams = querier.NewQueryDelegatorParams + NewQueryValidatorParams = querier.NewQueryValidatorParams + NewQueryBondsParams = querier.NewQueryBondsParams ) const (