Skip to content

Commit

Permalink
x/distribution: gRPC query service (#6600)
Browse files Browse the repository at this point in the history
* WIP: adding grpc

* added grpc for query withdraw address

* Fix outstanding rewards query

* added inteerface registry

* added gRPC for delegation rewards

* added remaining commands

* lint issue

* added tests

* added test for community pool

* fixed error

* added test for delegator validators

* updated to use test suite

* updated tests

* more test checks added

* updated tests

* Add sdk wrap

* removed pagination for outstanding rewards

* fixed distr tests issue

* fixed slashes issue

* migrated tests to table driven tests

* docs updated

* review changes

* review changes

* review changes

* Update x/distribution/keeper/grpc_query.go

Co-authored-by: sahith-narahari <sahithnarahari@gmail.com>
Co-authored-by: anilCSE <anil@vitwit.com>
Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>
Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
  • Loading branch information
5 people authored Jul 13, 2020
1 parent 7678f8a commit be111ef
Show file tree
Hide file tree
Showing 9 changed files with 5,374 additions and 77 deletions.
14 changes: 14 additions & 0 deletions proto/cosmos/distribution/distribution.proto
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,17 @@ message DelegatorStartingInfo {
uint64 height = 3
[(gogoproto.moretags) = "yaml:\"creation_height\"", (gogoproto.jsontag) = "creation_height"];
}

// DelegationDelegatorReward defines the properties
// of a delegator's delegation reward.
message DelegationDelegatorReward {
bytes validator_address = 1 [
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ValAddress",
(gogoproto.moretags) = "yaml:\"validator_address\""
];

repeated cosmos.DecCoin reward = 2 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins",
(gogoproto.nullable) = false
];
}
141 changes: 141 additions & 0 deletions proto/cosmos/distribution/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
syntax = "proto3";
package cosmos.distribution;

import "cosmos/query/pagination.proto";
import "gogoproto/gogo.proto";
import "cosmos/cosmos.proto";
import "cosmos/distribution/distribution.proto";

option go_package = "github.com/cosmos/cosmos-sdk/x/distribution/types";

// Query defines the gRPC querier service for distribution module
service Query {
// Params queries params of distribution module
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {}

// ValidatorOutstandingRewards queries rewards of a validator address
rpc ValidatorOutstandingRewards(QueryValidatorOutstandingRewardsRequest) returns (QueryValidatorOutstandingRewardsResponse) {}

// ValidatorCommission queries accumulated commission for a validator
rpc ValidatorCommission (QueryValidatorCommissionRequest) returns (QueryValidatorCommissionResponse) {}

// ValidatorSlashes queries slash events of a validator
rpc ValidatorSlashes (QueryValidatorSlashesRequest) returns (QueryValidatorSlashesResponse) {}

// DelegationRewards the total rewards accrued by a delegation
rpc DelegationRewards (QueryDelegationRewardsRequest) returns (QueryDelegationRewardsResponse) {}

// DelegationTotalRewards the total rewards accrued by a each validator
rpc DelegationTotalRewards (QueryDelegationTotalRewardsRequest) returns (QueryDelegationTotalRewardsResponse) {}

// DelegatorValidators queries the validators of a delegator
rpc DelegatorValidators (QueryDelegatorValidatorsRequest) returns (QueryDelegatorValidatorsResponse) {}

// DelegatorWithdrawAddress queries withdraw address of a delegator
rpc DelegatorWithdrawAddress (QueryDelegatorWithdrawAddressRequest) returns (QueryDelegatorWithdrawAddressResponse) {}

// CommunityPool queries the community pool coins
rpc CommunityPool (QueryCommunityPoolRequest) returns (QueryCommunityPoolResponse) {}
}

// QueryParamsRequest is the request type for the Query/Params RPC method
message QueryParamsRequest { }

// QueryParamsResponse is the response type for the Query/Params RPC method
message QueryParamsResponse {
Params params = 1 [(gogoproto.nullable) = false];
}

// QueryValidatorOutstandingRewardsRequest is the request type for the Query/ValidatorOutstandingRewards RPC method
message QueryValidatorOutstandingRewardsRequest {
bytes validator_address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ValAddress"];
}

// QueryValidatorOutstandingRewardsResponse is the response type for the Query/ValidatorOutstandingRewards RPC method
message QueryValidatorOutstandingRewardsResponse {
ValidatorOutstandingRewards rewards = 1 [(gogoproto.nullable) = false];
}

// QueryValidatorCommissionRequest is the request type for the Query/ValidatorCommission RPC method
message QueryValidatorCommissionRequest {
bytes validator_address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ValAddress"];
}

// QueryValidatorCommissionResponse is the response type for the Query/ValidatorCommission RPC method
message QueryValidatorCommissionResponse {
ValidatorAccumulatedCommission commission = 1 [(gogoproto.nullable) = false];
}

// QueryValidatorSlashesRequest is the request type for the Query/ValidatorSlashes RPC method
message QueryValidatorSlashesRequest {
bytes validator_address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ValAddress"];
uint64 starting_height = 2;
uint64 ending_height = 3;
cosmos.query.PageRequest req = 4;
}

// QueryValidatorSlashesResponse is the response type for the Query/ValidatorSlashes RPC method
message QueryValidatorSlashesResponse {
repeated ValidatorSlashEvent slashes = 1 [(gogoproto.nullable) = false];

cosmos.query.PageResponse res = 2;
}

// QueryDelegationRewardsRequest is the request type for the Query/DelegationRewards RPC method
message QueryDelegationRewardsRequest {
bytes delegator_address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
bytes validator_address = 2 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ValAddress"];
}

// QueryDelegationRewardsResponse is the response type for the Query/DelegationRewards RPC method
message QueryDelegationRewardsResponse {
repeated cosmos.DecCoin rewards = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins"
];
}

// QueryDelegationTotalRewardsRequest is the request type for the Query/DelegationTotalRewards RPC method
message QueryDelegationTotalRewardsRequest {
bytes delegator_address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

// QueryDelegationTotalRewardsResponse is the response type for the Query/DelegationTotalRewards RPC method
message QueryDelegationTotalRewardsResponse {
repeated DelegationDelegatorReward rewards = 1 [(gogoproto.nullable) = false];
repeated cosmos.DecCoin total = 2 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins"
];
}

// QueryDelegatorValidatorsRequest is the request type for the Query/DelegatorValidators RPC method
message QueryDelegatorValidatorsRequest {
bytes delegator_address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

// QueryDelegatorValidatorsResponse is the response type for the Query/DelegatorValidators RPC method
message QueryDelegatorValidatorsResponse {
repeated bytes validators = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.ValAddress"];
}

// QueryDelegatorWithdrawAddressRequest is the request type for the Query/DelegatorWithdrawAddress RPC method
message QueryDelegatorWithdrawAddressRequest {
bytes delegator_address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

// QueryDelegatorWithdrawAddressResponse is the response type for the Query/DelegatorWithdrawAddress RPC method
message QueryDelegatorWithdrawAddressResponse {
bytes withdraw_address = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"];
}

// QueryCommunityPoolRequest is the request type for the Query/CommunityPool RPC method
message QueryCommunityPoolRequest {}

// QueryCommunityPoolResponse is the response type for the Query/CommunityPool RPC method
message QueryCommunityPoolResponse {
repeated cosmos.DecCoin pool = 1 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins",
(gogoproto.nullable) = false
];
}
214 changes: 214 additions & 0 deletions x/distribution/keeper/grpc_query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package keeper

import (
"context"

"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/staking/exported"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

var _ types.QueryServer = Keeper{}

// Params queries params of distribution module
func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
var params types.Params
k.paramSpace.GetParamSet(ctx, &params)

return &types.QueryParamsResponse{Params: params}, nil
}

// ValidatorOutstandingRewards queries rewards of a validator address
func (k Keeper) ValidatorOutstandingRewards(c context.Context, req *types.QueryValidatorOutstandingRewardsRequest) (*types.QueryValidatorOutstandingRewardsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

if req.ValidatorAddress.Empty() {
return nil, status.Error(codes.InvalidArgument, "empty validator address")
}

ctx := sdk.UnwrapSDKContext(c)
rewards := k.GetValidatorOutstandingRewards(ctx, req.ValidatorAddress)

return &types.QueryValidatorOutstandingRewardsResponse{Rewards: rewards}, nil
}

// ValidatorCommission queries accumulated commission for a validator
func (k Keeper) ValidatorCommission(c context.Context, req *types.QueryValidatorCommissionRequest) (*types.QueryValidatorCommissionResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

if req.ValidatorAddress.Empty() {
return nil, status.Error(codes.InvalidArgument, "empty validator address")
}

ctx := sdk.UnwrapSDKContext(c)
commission := k.GetValidatorAccumulatedCommission(ctx, req.ValidatorAddress)

return &types.QueryValidatorCommissionResponse{Commission: commission}, nil
}

// ValidatorSlashes queries slash events of a validator
func (k Keeper) ValidatorSlashes(c context.Context, req *types.QueryValidatorSlashesRequest) (*types.QueryValidatorSlashesResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

if req.ValidatorAddress.Empty() {
return nil, status.Error(codes.InvalidArgument, "empty validator address")
}

if req.EndingHeight < req.StartingHeight {
return nil, status.Errorf(codes.InvalidArgument, "starting height greater than ending height (%d > %d)", req.StartingHeight, req.EndingHeight)
}

ctx := sdk.UnwrapSDKContext(c)
events := make([]types.ValidatorSlashEvent, 0)
store := ctx.KVStore(k.storeKey)
slashesStore := prefix.NewStore(store, types.GetValidatorSlashEventPrefix(req.ValidatorAddress))

res, err := query.FilteredPaginate(slashesStore, req.Req, func(key []byte, value []byte, accumulate bool) (bool, error) {
var result types.ValidatorSlashEvent
err := k.cdc.UnmarshalBinaryBare(value, &result)

if err != nil {
return false, err
}

if result.ValidatorPeriod < req.StartingHeight || result.ValidatorPeriod > req.EndingHeight {
return false, nil
}

if accumulate {
events = append(events, result)
}
return true, nil
})

if err != nil {
return &types.QueryValidatorSlashesResponse{}, err
}

return &types.QueryValidatorSlashesResponse{Slashes: events, Res: res}, nil
}

// DelegationRewards the total rewards accrued by a delegation
func (k Keeper) DelegationRewards(c context.Context, req *types.QueryDelegationRewardsRequest) (*types.QueryDelegationRewardsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

if req.DelegatorAddress.Empty() {
return nil, status.Error(codes.InvalidArgument, "empty delegator address")
}

if req.ValidatorAddress.Empty() {
return nil, status.Error(codes.InvalidArgument, "empty validator address")
}

ctx := sdk.UnwrapSDKContext(c)

val := k.stakingKeeper.Validator(ctx, req.ValidatorAddress)
if val == nil {
return nil, sdkerrors.Wrap(types.ErrNoValidatorExists, req.ValidatorAddress.String())
}

del := k.stakingKeeper.Delegation(ctx, req.DelegatorAddress, req.ValidatorAddress)
if del == nil {
return nil, types.ErrNoDelegationExists
}

endingPeriod := k.IncrementValidatorPeriod(ctx, val)
rewards := k.CalculateDelegationRewards(ctx, val, del, endingPeriod)

return &types.QueryDelegationRewardsResponse{Rewards: rewards}, nil
}

// DelegationTotalRewards the total rewards accrued by a each validator
func (k Keeper) DelegationTotalRewards(c context.Context, req *types.QueryDelegationTotalRewardsRequest) (*types.QueryDelegationTotalRewardsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

if req.DelegatorAddress.Empty() {
return nil, status.Error(codes.InvalidArgument, "empty delegator address")
}

ctx := sdk.UnwrapSDKContext(c)

total := sdk.DecCoins{}
var delRewards []types.DelegationDelegatorReward

k.stakingKeeper.IterateDelegations(
ctx, req.DelegatorAddress,
func(_ int64, del exported.DelegationI) (stop bool) {
valAddr := del.GetValidatorAddr()
val := k.stakingKeeper.Validator(ctx, valAddr)
endingPeriod := k.IncrementValidatorPeriod(ctx, val)
delReward := k.CalculateDelegationRewards(ctx, val, del, endingPeriod)

delRewards = append(delRewards, types.NewDelegationDelegatorReward(valAddr, delReward))
total = total.Add(delReward...)
return false
},
)

return &types.QueryDelegationTotalRewardsResponse{Rewards: delRewards, Total: total}, nil
}

// DelegatorValidators queries the validators list of a delegator
func (k Keeper) DelegatorValidators(c context.Context, req *types.QueryDelegatorValidatorsRequest) (*types.QueryDelegatorValidatorsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

if req.DelegatorAddress.Empty() {
return nil, status.Error(codes.InvalidArgument, "empty delegator address")
}

ctx := sdk.UnwrapSDKContext(c)

var validators []sdk.ValAddress

k.stakingKeeper.IterateDelegations(
ctx, req.DelegatorAddress,
func(_ int64, del exported.DelegationI) (stop bool) {
validators = append(validators, del.GetValidatorAddr())
return false
},
)

return &types.QueryDelegatorValidatorsResponse{Validators: validators}, nil
}

// DelegatorWithdrawAddress queries Query/delegatorWithdrawAddress
func (k Keeper) DelegatorWithdrawAddress(c context.Context, req *types.QueryDelegatorWithdrawAddressRequest) (*types.QueryDelegatorWithdrawAddressResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

if req.DelegatorAddress.Empty() {
return nil, status.Error(codes.InvalidArgument, "empty delegator address")
}

ctx := sdk.UnwrapSDKContext(c)
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, req.DelegatorAddress)

return &types.QueryDelegatorWithdrawAddressResponse{WithdrawAddress: withdrawAddr}, nil
}

// CommunityPool queries the community pool coins
func (k Keeper) CommunityPool(c context.Context, req *types.QueryCommunityPoolRequest) (*types.QueryCommunityPoolResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
pool := k.GetFeePoolCommunityCoins(ctx)

return &types.QueryCommunityPoolResponse{Pool: pool}, nil
}
Loading

0 comments on commit be111ef

Please sign in to comment.