From 769370801db9312df4faae71837e1649f266d390 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Tue, 16 Apr 2019 14:43:08 +0100 Subject: [PATCH] Merge PR #4101: Return per-validator rewards when querying delegator rewards --- .../breaking/gaiacli/3715-query-distr-rew | 2 + .../breaking/gaiarest/3715-Update-distribu | 3 ++ client/lcd/lcd_test.go | 5 +- client/lcd/swagger-ui/swagger.yaml | 24 ++++++++-- cmd/gaia/cli_test/cli_test.go | 27 +++++++++++ cmd/gaia/cli_test/test_helpers.go | 16 +++++++ x/distribution/alias.go | 4 ++ x/distribution/client/cli/query.go | 19 +++++--- x/distribution/keeper/querier.go | 15 ++++-- x/distribution/keeper/querier_test.go | 26 +++++++++++ x/distribution/types/query.go | 46 +++++++++++++++++++ 11 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 .pending/breaking/gaiacli/3715-query-distr-rew create mode 100644 .pending/breaking/gaiarest/3715-Update-distribu create mode 100644 x/distribution/types/query.go diff --git a/.pending/breaking/gaiacli/3715-query-distr-rew b/.pending/breaking/gaiacli/3715-query-distr-rew new file mode 100644 index 0000000000..5303400b68 --- /dev/null +++ b/.pending/breaking/gaiacli/3715-query-distr-rew @@ -0,0 +1,2 @@ +#3715 query distr rewards returns per-validator +rewards along with rewards total amount. diff --git a/.pending/breaking/gaiarest/3715-Update-distribu b/.pending/breaking/gaiarest/3715-Update-distribu new file mode 100644 index 0000000000..1c562efa45 --- /dev/null +++ b/.pending/breaking/gaiarest/3715-Update-distribu @@ -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. diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index e4d49229b4..69c5281b38 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -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 diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index e1c11a0062..8a0df1e318 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -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: diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 4b2e9adfa7..2cb0d22239 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -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) diff --git a/cmd/gaia/cli_test/test_helpers.go b/cmd/gaia/cli_test/test_helpers.go index 990b7716d6..1df5b93c9e 100644 --- a/cmd/gaia/cli_test/test_helpers.go +++ b/cmd/gaia/cli_test/test_helpers.go @@ -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 diff --git a/x/distribution/alias.go b/x/distribution/alias.go index b78f39909e..57038618bc 100644 --- a/x/distribution/alias.go +++ b/x/distribution/alias.go @@ -27,6 +27,10 @@ type ( QueryValidatorSlashesParams = keeper.QueryValidatorSlashesParams QueryDelegationRewardsParams = keeper.QueryDelegationRewardsParams QueryDelegatorWithdrawAddrParams = keeper.QueryDelegatorWithdrawAddrParams + + // querier response types + QueryDelegatorTotalRewardsResponse = types.QueryDelegatorTotalRewardsResponse + DelegationDelegatorReward = types.DelegationDelegatorReward ) const ( diff --git a/x/distribution/client/cli/query.go b/x/distribution/client/cli/query.go index 7c4c2bcbd7..bc5777ac07 100644 --- a/x/distribution/client/cli/query.go +++ b/x/distribution/client/cli/query.go @@ -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) }, diff --git a/x/distribution/keeper/querier.go b/x/distribution/keeper/querier.go index 6b79313395..6c172b8b79 100644 --- a/x/distribution/keeper/querier.go +++ b/x/distribution/keeper/querier.go @@ -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())) } diff --git a/x/distribution/keeper/querier_test.go b/x/distribution/keeper/querier_test.go index 8ba091bcf4..fb0b6c3d4a 100644 --- a/x/distribution/keeper/querier_test.go +++ b/x/distribution/keeper/querier_test.go @@ -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) diff --git a/x/distribution/types/query.go b/x/distribution/types/query.go new file mode 100644 index 0000000000..7eeadaa480 --- /dev/null +++ b/x/distribution/types/query.go @@ -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} +}