Merge PR #4101: Return per-validator rewards when querying delegator rewards

This commit is contained in:
Alessio Treglia 2019-04-16 14:43:08 +01:00 committed by Alexander Bezobchuk
parent 9cdd1d3e9e
commit 769370801d
11 changed files with 171 additions and 16 deletions

View File

@ -0,0 +1,2 @@
#3715 query distr rewards returns per-validator
rewards along with rewards total amount.

View File

@ -0,0 +1,3 @@
#3715 Update /distribution/delegators/{delegatorAddr}/rewards GET endpoint
as per new specs. For a given delegation, the endpoint now returns the
comprehensive list of validator-reward tuples along with the grand total.

View File

@ -3,6 +3,7 @@ package lcd
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
@ -27,6 +28,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/bank"
dclcommon "github.com/cosmos/cosmos-sdk/x/distribution/client/common"
distrrest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/staking"
@ -1003,9 +1005,10 @@ func TestDistributionFlow(t *testing.T) {
require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards))
// Query delegator's rewards total
var delRewards disttypes.QueryDelegatorTotalRewardsResponse
res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/delegators/%s/rewards", operAddr), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards))
require.NoError(t, json.Unmarshal([]byte(body), &delRewards))
// Query delegator's withdrawal address
var withdrawAddr string

View File

@ -1470,9 +1470,7 @@ paths:
200:
description: OK
schema:
type: array
items:
$ref: "#/definitions/Coin"
$ref: "#/definitions/DelegatorTotalRewards"
400:
description: Invalid delegator address
500:
@ -2096,6 +2094,26 @@ definitions:
$ref: "#/definitions/BlockID"
block:
$ref: "#/definitions/Block"
DelegationDelegatorReward:
type: object
properties:
validator_address:
$ref: "#/definitions/ValidatorAddress"
reward:
type: array
items:
$ref: "#/definitions/Coin"
DelegatorTotalRewards:
type: object
properties:
rewards:
type: array
items:
$ref: "#/definitions/DelegationDelegatorReward"
total:
type: array
items:
$ref: "#/definitions/Coin"
BaseReq:
type: object
properties:

View File

@ -427,6 +427,33 @@ func TestGaiaCLICreateValidator(t *testing.T) {
f.Cleanup()
}
func TestGaiaCLIQueryRewards(t *testing.T) {
t.Parallel()
f := InitFixtures(t)
genesisState := f.GenesisState()
inflationMin := sdk.MustNewDecFromStr("10000.0")
genesisState.MintData.Minter.Inflation = inflationMin
genesisState.MintData.Params.InflationMin = inflationMin
genesisState.MintData.Params.InflationMax = sdk.MustNewDecFromStr("15000.0")
genFile := filepath.Join(f.GaiadHome, "config", "genesis.json")
genDoc, err := tmtypes.GenesisDocFromFile(genFile)
require.NoError(t, err)
cdc := app.MakeCodec()
genDoc.AppState, err = cdc.MarshalJSON(genesisState)
require.NoError(t, genDoc.SaveAs(genFile))
// start gaiad server
proc := f.GDStart()
defer proc.Stop(false)
fooAddr := f.KeyAddress(keyFoo)
rewards := f.QueryRewards(fooAddr)
require.Equal(t, 1, len(rewards.Rewards))
f.Cleanup()
}
func TestGaiaCLISubmitProposal(t *testing.T) {
t.Parallel()
f := InitFixtures(t)

View File

@ -25,6 +25,7 @@ import (
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/staking"
@ -617,6 +618,21 @@ func (f *Fixtures) QuerySlashingParams() slashing.Params {
return params
}
//___________________________________________________________________________________
// query distribution
// QuerySigningInfo returns the signing info for a validator
func (f *Fixtures) QueryRewards(delAddr sdk.AccAddress, flags ...string) distribution.QueryDelegatorTotalRewardsResponse {
cmd := fmt.Sprintf("%s query distr rewards %s %s", f.GaiacliBinary, delAddr, f.Flags())
res, errStr := tests.ExecuteT(f.T, cmd, "")
require.Empty(f.T, errStr)
cdc := app.MakeCodec()
var rewards distribution.QueryDelegatorTotalRewardsResponse
err := cdc.UnmarshalJSON([]byte(res), &rewards)
require.NoError(f.T, err)
return rewards
}
//___________________________________________________________________________________
// executors

View File

@ -27,6 +27,10 @@ type (
QueryValidatorSlashesParams = keeper.QueryValidatorSlashesParams
QueryDelegationRewardsParams = keeper.QueryDelegationRewardsParams
QueryDelegatorWithdrawAddrParams = keeper.QueryDelegatorWithdrawAddrParams
// querier response types
QueryDelegatorTotalRewardsResponse = types.QueryDelegatorTotalRewardsResponse
DelegationDelegatorReward = types.DelegationDelegatorReward
)
const (

View File

@ -144,20 +144,25 @@ $ gaiacli query distr rewards cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p cosm
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
var resp []byte
var err error
if len(args) == 2 {
// query for rewards from a particular delegation
resp, err = common.QueryDelegationRewards(cliCtx, cdc, queryRoute, args[0], args[1])
} else {
// query for delegator total rewards
resp, err = common.QueryDelegatorTotalRewards(cliCtx, cdc, queryRoute, args[0])
resp, err := common.QueryDelegationRewards(cliCtx, cdc, queryRoute, args[0], args[1])
if err != nil {
return err
}
var result sdk.DecCoins
cdc.MustUnmarshalJSON(resp, &result)
return cliCtx.PrintOutput(result)
}
// query for delegator total rewards
resp, err := common.QueryDelegatorTotalRewards(cliCtx, cdc, queryRoute, args[0])
if err != nil {
return err
}
var result sdk.DecCoins
var result distr.QueryDelegatorTotalRewardsResponse
cdc.MustUnmarshalJSON(resp, &result)
return cliCtx.PrintOutput(result)
},

View File

@ -1,6 +1,7 @@
package keeper
import (
"encoding/json"
"fmt"
abci "github.com/tendermint/tendermint/abci/types"
@ -251,21 +252,25 @@ func queryDelegatorTotalRewards(ctx sdk.Context, _ []string, req abci.RequestQue
// cache-wrap context as to not persist state changes during querying
ctx, _ = ctx.CacheContext()
totalRewards := sdk.DecCoins{}
total := sdk.DecCoins{}
var delRewards []types.DelegationDelegatorReward
k.stakingKeeper.IterateDelegations(
ctx, params.DelegatorAddress,
func(_ int64, del sdk.Delegation) (stop bool) {
val := k.stakingKeeper.Validator(ctx, del.GetValidatorAddr())
valAddr := del.GetValidatorAddr()
val := k.stakingKeeper.Validator(ctx, valAddr)
endingPeriod := k.incrementValidatorPeriod(ctx, val)
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
delReward := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
totalRewards = totalRewards.Add(rewards)
delRewards = append(delRewards, types.NewDelegationDelegatorReward(valAddr, delReward))
total = total.Add(delReward)
return false
},
)
bz, err := codec.MarshalJSONIndent(k.cdc, totalRewards)
totalRewards := types.NewQueryDelegatorTotalRewardsResponse(delRewards, total)
bz, err := json.Marshal(totalRewards)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}

View File

@ -109,6 +109,19 @@ func getQueriedDelegationRewards(t *testing.T, ctx sdk.Context, cdc *codec.Codec
return
}
func getQueriedDelegatorTotalRewards(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, delegatorAddr sdk.AccAddress) (response types.QueryDelegatorTotalRewardsResponse) {
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, QueryDelegatorTotalRewards}, "/"),
Data: cdc.MustMarshalJSON(NewQueryDelegatorParams(delegatorAddr)),
}
bz, err := querier(ctx, []string{QueryDelegatorTotalRewards}, query)
require.Nil(t, err)
require.Nil(t, cdc.UnmarshalJSON(bz, &response))
return
}
func getQueriedCommunityPool(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier) (ptr []byte) {
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, QueryCommunityPool}, ""),
@ -124,6 +137,7 @@ func getQueriedCommunityPool(t *testing.T, ctx sdk.Context, cdc *codec.Codec, qu
func TestQueries(t *testing.T) {
cdc := codec.New()
types.RegisterCodec(cdc)
ctx, _, keeper, sk, _ := CreateTestInputDefault(t, false, 100)
querier := NewQuerier(keeper)
@ -154,6 +168,10 @@ func TestQueries(t *testing.T) {
retCommission := getQueriedValidatorCommission(t, ctx, cdc, querier, valOpAddr1)
require.Equal(t, commission, retCommission)
// test delegator's total rewards query
delRewards := getQueriedDelegatorTotalRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1))
require.Equal(t, types.QueryDelegatorTotalRewardsResponse{}, delRewards)
// test validator slashes query with height range
slashOne := types.NewValidatorSlashEvent(3, sdk.NewDecWithPrec(5, 1))
slashTwo := types.NewValidatorSlashEvent(7, sdk.NewDecWithPrec(6, 1))
@ -183,6 +201,14 @@ func TestQueries(t *testing.T) {
rewards = getQueriedDelegationRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1), valOpAddr1)
require.Equal(t, sdk.DecCoins{{sdk.DefaultBondDenom, sdk.NewDec(initial / 2)}}, rewards)
// test delegator's total rewards query
delRewards = getQueriedDelegatorTotalRewards(t, ctx, cdc, querier, sdk.AccAddress(valOpAddr1))
expectedDelReward := types.NewDelegationDelegatorReward(valOpAddr1,
sdk.DecCoins{sdk.NewInt64DecCoin("stake", 5)})
wantDelRewards := types.NewQueryDelegatorTotalRewardsResponse(
[]types.DelegationDelegatorReward{expectedDelReward}, expectedDelReward.Reward)
require.Equal(t, wantDelRewards, delRewards)
// currently community pool hold nothing so we should return null
communityPool := getQueriedCommunityPool(t, ctx, cdc, querier)
require.Nil(t, communityPool)

View File

@ -0,0 +1,46 @@
package types
import (
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// QueryDelegatorTotalRewardsResponse defines the properties of
// QueryDelegatorTotalRewards query's response.
type QueryDelegatorTotalRewardsResponse struct {
Rewards []DelegationDelegatorReward `json:"rewards"`
Total sdk.DecCoins `json:"total"`
}
// NewQueryDelegatorTotalRewardsResponse constructs a QueryDelegatorTotalRewardsResponse
func NewQueryDelegatorTotalRewardsResponse(rewards []DelegationDelegatorReward,
total sdk.DecCoins) QueryDelegatorTotalRewardsResponse {
return QueryDelegatorTotalRewardsResponse{Rewards: rewards, Total: total}
}
func (res QueryDelegatorTotalRewardsResponse) String() string {
out := "Delegator Total Rewards:\n"
out += " Rewards:"
for _, reward := range res.Rewards {
out += fmt.Sprintf(`
ValidatorAddress: %s
Reward: %s`, reward.ValidatorAddress, reward.Reward)
}
out += fmt.Sprintf("\n Total: %s\n", res.Total)
return strings.TrimSpace(out)
}
// DelegationDelegatorReward defines the properties
// of a delegator's delegation reward.
type DelegationDelegatorReward struct {
ValidatorAddress sdk.ValAddress `json:"validator_address"`
Reward sdk.DecCoins `json:"reward"`
}
// NewDelegationDelegatorReward constructs a DelegationDelegatorReward.
func NewDelegationDelegatorReward(valAddr sdk.ValAddress,
reward sdk.DecCoins) DelegationDelegatorReward {
return DelegationDelegatorReward{ValidatorAddress: valAddr, Reward: reward}
}