Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: Redelegation Querier #2559

Merged
merged 17 commits into from
Dec 18, 2018
6 changes: 6 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ BREAKING CHANGES
FEATURES

* Gaia REST API (`gaiacli advanced rest-server`)
<<<<<<< HEAD
* [gaia-lite] [\#2182] Added LCD endpoint for querying redelegations
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have been in breaking changes cc: @sunnya97 @jackzampolin. I'll update it

=======
* [gov] [\#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Added governance parameter
query REST endpoints.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

>>>>>>> develop

* Gaia CLI (`gaiacli`)
* [gov][cli] [\#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Added governance
Expand All @@ -46,6 +50,8 @@ FEATURES
* [app] \#2791 Support export at a specific height, with `gaiad export --height=HEIGHT`.
* [app] \#2812 Support export alterations to prepare for restarting at zero-height

* [\#2182] [x/stake] Added querier for querying a single redelegation

* SDK
* [simulator] \#2682 MsgEditValidator now looks at the validator's max rate, thus it now succeeds a significant portion of the time
* [core] \#2775 Add deliverTx maximum block gas limit
Expand Down
46 changes: 27 additions & 19 deletions client/lcd/lcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,19 +586,23 @@ func TestBonding(t *testing.T) {
require.Len(t, delegatorDels, 1)
require.Equal(t, "30.0000000000", delegatorDels[0].GetShares().String())

redelegation := getRedelegations(t, port, addr, operAddrs[0], operAddrs[1])
require.Len(t, redelegation, 1)
require.Equal(t, "30", redelegation[0].Balance.Amount.String())

delegatorUbds := getDelegatorUnbondingDelegations(t, port, addr)
require.Len(t, delegatorUbds, 1)
require.Equal(t, "30", delegatorUbds[0].Balance.Amount.String())

delegatorReds := getDelegatorRedelegations(t, port, addr)
delegatorReds := getRedelegations(t, port, addr, nil, nil)
require.Len(t, delegatorReds, 1)
require.Equal(t, "30", delegatorReds[0].Balance.Amount.String())

validatorUbds := getValidatorUnbondingDelegations(t, port, operAddrs[0])
require.Len(t, validatorUbds, 1)
require.Equal(t, "30", validatorUbds[0].Balance.Amount.String())

validatorReds := getValidatorRedelegations(t, port, operAddrs[0])
validatorReds := getRedelegations(t, port, nil, operAddrs[0], nil)
require.Len(t, validatorReds, 1)
require.Equal(t, "30", validatorReds[0].Balance.Amount.String())

Expand Down Expand Up @@ -1043,16 +1047,31 @@ func getDelegatorUnbondingDelegations(t *testing.T, port string, delegatorAddr s
return ubds
}

func getDelegatorRedelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress) []stake.Redelegation {
res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/redelegations", delegatorAddr), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
func getRedelegations(t *testing.T, port string, delegatorAddr sdk.AccAddress, srcValidatorAddr sdk.ValAddress, dstValidatorAddr sdk.ValAddress) []stake.Redelegation {
var res *http.Response
var body string

endpoint := "/stake/redelegations?"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this is a terrible design, we should only add the query parameters to /stake/delegators/{delegatorAddr}/redelegations, not changing the current structure. cc: @faboweb

Copy link
Member Author

@sunnya97 sunnya97 Nov 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing I don't like about /stake/delegators/{delegatorAddr}/redelegations is that it's trying to put a fake structure on data that is not actually structured like in the state. Redelegations are not attributes of a delegator, but rather delegator, srcValidator, and dstValidator are attributes of a redelegation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would agree with @fedekunze here. I don't think how we have the data structured internally matters to users. Presenting the data in a way that is logically consistent (delegators have redelegations) is a great idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are /stake/delegators/<>/redelegations vs /stake/validators/<>/redelegations?

I think over-emphasis on the URL design of API endpoints often leads to premature design constraints that end up causing more headache down the line. It is simpler to model the API after what is modeled in the implementation.

REST isn't just about pretty URLs... there's nothing wrong with query parameters, esp given a context where we will want to provide filters etc. I favor Sunny's approach here, it's faster to execute and less brittle going into the future.

The end users here are developers who can deal with query parameters. We're not talking about website URLs, which is a different story.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is simpler to model the API after what is modeled in the implementation.

I think simple to implement is not what we want with an API. An API should be consistent for quiet some time so developers don't need to adjust to constant changes (this is frustrating from experience). We should come up with a design that reflects the expectation of the developers that want to use the API. Let's invite more of them to stakeholder meetings and discuss together with them the API design.


if !delegatorAddr.Empty() {
endpoint += fmt.Sprintf("delegator=%s&", delegatorAddr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm interesting - yeah seems like it would make the code a bit smaller - but I don't see builders often so might just be more confusing for new devs... but then again maybe we should all learn!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this situation, it is simple enough that we can just use the += for the test. But I agree, in more important places, where we want to reduce memory copying, a String builder is good to use.

}
if !srcValidatorAddr.Empty() {
endpoint += fmt.Sprintf("validator_from=%s&", srcValidatorAddr)
}
if !dstValidatorAddr.Empty() {
endpoint += fmt.Sprintf("validator_to=%s&", dstValidatorAddr)
}

var reds []stake.Redelegation
res, body = Request(t, port, "GET", endpoint, nil)

err := cdc.UnmarshalJSON([]byte(body), &reds)
require.Equal(t, http.StatusOK, res.StatusCode, body)

var redels []stake.Redelegation
err := cdc.UnmarshalJSON([]byte(body), &redels)
require.Nil(t, err)

return reds
return redels
}

func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, query string) []tx.Info {
Expand Down Expand Up @@ -1254,17 +1273,6 @@ func getValidatorUnbondingDelegations(t *testing.T, port string, validatorAddr s
return ubds
}

func getValidatorRedelegations(t *testing.T, port string, validatorAddr sdk.ValAddress) []stake.Redelegation {
res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s/redelegations", validatorAddr.String()), nil)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should stay the same, see comment above

require.Equal(t, http.StatusOK, res.StatusCode, body)

var reds []stake.Redelegation
err := cdc.UnmarshalJSON([]byte(body), &reds)
require.Nil(t, err)

return reds
}

// ============= Governance Module ================

func getDepositParam(t *testing.T, port string) gov.DepositParams {
Expand Down
50 changes: 17 additions & 33 deletions client/lcd/swagger-ui/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -753,15 +753,25 @@ paths:
description: Invalid delegator address
500:
description: Internal Server Error
/stake/delegators/{delegatorAddr}/redelegations:
/stake/redelegations:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would you want to get all the redelegations in the state ? there's no use case for that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not in Voyager? But that totally seems like something some block explorer might want.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but you can still go through the list of validators and do that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and through the /txs endpoint

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and through the /txs endpoint

Either through that or with the GET /stake/validators/{validatorAddr}/redelegations endpoint

parameters:
- in: path
name: delegatorAddr
description: Bech32 AccAddress of Delegator
required: true
type: string
- in: query
name: delegator
description: Bech32 AccAddress of Delegator
required: false
type: string
- in: query
name: validator_from
description: Bech32 ValAddress of SrcValidator
required: false
type: string
- in: query
name: validator_to
description: Bech32 ValAddress of DstValidator
required: false
type: string
get:
summary: Get all redelegations from a delegator
summary: Get all redelegations (filter by query params)
tags:
- ICS21
produces:
Expand All @@ -774,8 +784,6 @@ paths:
items:
type: object
"$ref": "#/definitions/Redelegation"
400:
description: Invalid delegator address
500:
description: Internal Server Error
/stake/delegators/{delegatorAddr}/validators:
Expand Down Expand Up @@ -998,30 +1006,6 @@ paths:
description: Invalid validator address
500:
description: Internal Server Error
/stake/validators/{validatorAddr}/redelegations:
parameters:
- in: path
name: validatorAddr
description: Bech32 OperatorAddress of validator
required: true
type: string
get:
summary: Get all outgoing redelegations from a validator
tags:
- ICS21
produces:
- application/json
responses:
200:
description: OK
schema:
type: array
items:
$ref: "#/definitions/Redelegation"
400:
description: Invalid validator address
500:
description: Internal Server Error
/stake/pool:
get:
summary: Get the current state of the staking pool
Expand Down
80 changes: 58 additions & 22 deletions x/stake/client/rest/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake"
"github.com/cosmos/cosmos-sdk/x/stake/tags"

"github.com/gorilla/mux"
Expand All @@ -30,12 +31,6 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co
delegatorUnbondingDelegationsHandlerFn(cliCtx, cdc),
).Methods("GET")

// Get all redelegations from a delegator
r.HandleFunc(
"/stake/delegators/{delegatorAddr}/redelegations",
delegatorRedelegationsHandlerFn(cliCtx, cdc),
).Methods("GET")

// Get all staking txs (i.e msgs) from a delegator
r.HandleFunc(
"/stake/delegators/{delegatorAddr}/txs",
Expand Down Expand Up @@ -66,6 +61,12 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co
unbondingDelegationHandlerFn(cliCtx, cdc),
).Methods("GET")

// Query redelegations (filters in query params)
sunnya97 marked this conversation as resolved.
Show resolved Hide resolved
r.HandleFunc(
"/stake/redelegations",
redelegationsHandlerFn(cliCtx, cdc),
).Methods("GET")

// Get all validators
r.HandleFunc(
"/stake/validators",
Expand All @@ -90,12 +91,6 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co
validatorUnbondingDelegationsHandlerFn(cliCtx, cdc),
).Methods("GET")

// Get all outgoing redelegations from a validator
r.HandleFunc(
"/stake/validators/{validatorAddr}/redelegations",
validatorRedelegationsHandlerFn(cliCtx, cdc),
).Methods("GET")

// Get the current state of the staking pool
r.HandleFunc(
"/stake/pool",
Expand All @@ -120,11 +115,6 @@ func delegatorUnbondingDelegationsHandlerFn(cliCtx context.CLIContext, cdc *code
return queryDelegator(cliCtx, cdc, "custom/stake/delegatorUnbondingDelegations")
}

// HTTP request handler to query a delegator redelegations
func delegatorRedelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return queryDelegator(cliCtx, cdc, "custom/stake/delegatorRedelegations")
}

// HTTP request handler to query all staking txs (msgs) from a delegator
func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -201,6 +191,57 @@ func unbondingDelegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) h
return queryBonds(cliCtx, cdc, "custom/stake/unbondingDelegation")
}

// HTTP request handler to query redelegations
func redelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var params stake.QueryRedelegationParams

bechDelegatorAddr := r.URL.Query().Get("delegator")
bechSrcValidatorAddr := r.URL.Query().Get("validator_from")
bechDstValidatorAddr := r.URL.Query().Get("validator_to")

if len(bechDelegatorAddr) != 0 {
delegatorAddr, err := sdk.AccAddressFromBech32(bechDelegatorAddr)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
params.DelegatorAddr = delegatorAddr
}

if len(bechSrcValidatorAddr) != 0 {
srcValidatorAddr, err := sdk.ValAddressFromBech32(bechSrcValidatorAddr)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
params.SrcValidatorAddr = srcValidatorAddr
}

if len(bechDstValidatorAddr) != 0 {
dstValidatorAddr, err := sdk.ValAddressFromBech32(bechDstValidatorAddr)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
params.DstValidatorAddr = dstValidatorAddr
}

bz, err := cdc.MarshalJSON(params)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

res, err := cliCtx.QueryWithData("custom/stake/redelegations", bz)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
utils.PostProcessResponse(w, cdc, res, cliCtx.Indent)
}
}

// HTTP request handler to query a delegation
func delegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return queryBonds(cliCtx, cdc, "custom/stake/delegation")
Expand Down Expand Up @@ -243,11 +284,6 @@ func validatorUnbondingDelegationsHandlerFn(cliCtx context.CLIContext, cdc *code
return queryValidator(cliCtx, cdc, "custom/stake/validatorUnbondingDelegations")
}

// HTTP request handler to query all redelegations from a source validator
func validatorRedelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return queryValidator(cliCtx, cdc, "custom/stake/validatorRedelegations")
}

// HTTP request handler to query the pool information
func poolHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
Expand Down
44 changes: 44 additions & 0 deletions x/stake/client/rest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,50 @@ func queryTxs(node rpcclient.Client, cliCtx context.CLIContext, cdc *codec.Codec
return tx.FormatTxResults(cdc, res.Txs)
}

func queryRedelegations(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bech32delegator := vars["delegatorAddr"]
bech32srcValidator := vars["srcValidatorAddr"]
bech32dstValidator := vars["dstValidatorAddr"]

delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator)
sunnya97 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
srcValidatorAddr, err := sdk.ValAddressFromBech32(bech32srcValidator)
sunnya97 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
dstValidatorAddr, err := sdk.ValAddressFromBech32(bech32dstValidator)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

params := stake.QueryRedelegationParams{
DelegatorAddr: delegatorAddr,
SrcValidatorAddr: srcValidatorAddr,
DstValidatorAddr: dstValidatorAddr,
}

bz, err := cdc.MarshalJSON(params)
if err != nil {
utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

res, err := cliCtx.QueryWithData(endpoint, bz)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
utils.PostProcessResponse(w, cdc, res, cliCtx.Indent)
}
}

func queryBonds(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
Expand Down
4 changes: 2 additions & 2 deletions x/stake/keeper/delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ func TestRedelegation(t *testing.T) {
require.Equal(t, 1, len(redelegations))
require.True(t, redelegations[0].Equal(resRed))

redelegations = keeper.GetAllRedelegations(ctx, addrDels[0])
redelegations = keeper.GetAllRedelegations(ctx, addrDels[0], nil, nil)
require.Equal(t, 1, len(redelegations))
require.True(t, redelegations[0].Equal(resRed))

Expand Down Expand Up @@ -580,7 +580,7 @@ func TestRedelegation(t *testing.T) {
redelegations = keeper.GetRedelegations(ctx, addrDels[0], 5)
require.Equal(t, 0, len(redelegations))

redelegations = keeper.GetAllRedelegations(ctx, addrDels[0])
redelegations = keeper.GetAllRedelegations(ctx, addrDels[0], nil, nil)
require.Equal(t, 0, len(redelegations))
}

Expand Down
Loading