diff --git a/.pending/improvements/sdk/4501-Support-height- b/.pending/improvements/sdk/4501-Support-height- new file mode 100644 index 0000000000..c4db5df86d --- /dev/null +++ b/.pending/improvements/sdk/4501-Support-height- @@ -0,0 +1 @@ +#4501 Support height queriers in rest client \ No newline at end of file diff --git a/client/context/context.go b/client/context/context.go index 83122fa47e..4347cab73e 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -212,6 +212,12 @@ func (ctx CLIContext) WithNodeURI(nodeURI string) CLIContext { return ctx } +// WithHeight returns a copy of the context with an updated height. +func (ctx CLIContext) WithHeight(height int64) CLIContext { + ctx.Height = height + return ctx +} + // WithClient returns a copy of the context with an updated RPC client // instance. func (ctx CLIContext) WithClient(client rpcclient.Client) CLIContext { diff --git a/client/tx/query.go b/client/tx/query.go index e3c45850f0..931808f087 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -156,6 +156,11 @@ func QueryTxsByTagsRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + if len(r.Form) == 0 { rest.PostProcessResponse(w, cliCtx, txs) return @@ -184,6 +189,11 @@ func QueryTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { vars := mux.Vars(r) hashHexStr := vars["hash"] + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + output, err := queryTx(cliCtx, hashHexStr) if err != nil { if strings.Contains(err.Error(), hashHexStr) { diff --git a/types/rest/rest.go b/types/rest/rest.go index 238fa52601..ab72bcadbd 100644 --- a/types/rest/rest.go +++ b/types/rest/rest.go @@ -197,6 +197,25 @@ func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEm return n, true } +// ParseQueryHeightOrReturnBadRequest sets the height to execute a query if set by the http request. +// It returns false if there was an error parsing the height. +func ParseQueryHeightOrReturnBadRequest(w http.ResponseWriter, cliCtx context.CLIContext, r *http.Request) (context.CLIContext, bool) { + heightStr := r.FormValue("height") + if heightStr != "" { + height, err := strconv.ParseInt(heightStr, 10, 64) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return cliCtx, false + } + + if height > 0 { + cliCtx = cliCtx.WithHeight(height) + } + } + + return cliCtx, true +} + // PostProcessResponse performs post processing for a REST response. func PostProcessResponse(w http.ResponseWriter, cliCtx context.CLIContext, response interface{}) { var output []byte diff --git a/types/rest/rest_test.go b/types/rest/rest_test.go index e0acbd5b4b..472781c98a 100644 --- a/types/rest/rest_test.go +++ b/types/rest/rest_test.go @@ -8,6 +8,7 @@ import ( "net/http/httptest" "testing" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/types" @@ -101,6 +102,42 @@ func TestParseHTTPArgs(t *testing.T) { } } +func TestParseQueryHeight(t *testing.T) { + var emptyHeight int64 + height := int64(1256756) + + req0 := mustNewRequest(t, "", "/", nil) + req1 := mustNewRequest(t, "", "/?height=1256756", nil) + req2 := mustNewRequest(t, "", "/?height=456yui4567", nil) + req3 := mustNewRequest(t, "", "/?height=-1", nil) + + tests := []struct { + name string + req *http.Request + w http.ResponseWriter + cliCtx context.CLIContext + expectedHeight int64 + expectedOk bool + }{ + {"no height", req0, httptest.NewRecorder(), context.CLIContext{}, emptyHeight, true}, + {"height", req1, httptest.NewRecorder(), context.CLIContext{}, height, true}, + {"invalid height", req2, httptest.NewRecorder(), context.CLIContext{}, emptyHeight, false}, + {"negative height", req3, httptest.NewRecorder(), context.CLIContext{}, emptyHeight, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cliCtx, ok := ParseQueryHeightOrReturnBadRequest(tt.w, tt.cliCtx, tt.req) + if tt.expectedOk { + require.True(t, ok) + require.Equal(t, tt.expectedHeight, cliCtx.Height) + } else { + require.False(t, ok) + require.Empty(t, tt.expectedHeight, cliCtx.Height) + } + }) + } +} + func mustNewRequest(t *testing.T, method, url string, body io.Reader) *http.Request { req, err := http.NewRequest(method, url, body) require.NoError(t, err) diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index c797647afb..68f58aecba 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -40,6 +40,11 @@ func QueryAccountRequestHandlerFn( return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := cliCtx.QueryStore(types.AddressStoreKey(addr), storeName) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -79,6 +84,11 @@ func QueryBalancesRequestHandlerFn( return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := cliCtx.QueryStore(types.AddressStoreKey(addr), storeName) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) diff --git a/x/distribution/client/rest/query.go b/x/distribution/client/rest/query.go index a1a895a4e1..86ae87f87f 100644 --- a/x/distribution/client/rest/query.go +++ b/x/distribution/client/rest/query.go @@ -68,6 +68,11 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, queryRoute st // HTTP request handler to query the total rewards balance from all delegations func delegatorRewardsHandlerFn(cliCtx context.CLIContext, queryRoute string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + // query for rewards from a particular delegator res, ok := checkResponseQueryDelegatorTotalRewards(w, cliCtx, queryRoute, mux.Vars(r)["delegatorAddr"]) if !ok { @@ -81,6 +86,11 @@ func delegatorRewardsHandlerFn(cliCtx context.CLIContext, queryRoute string) htt // HTTP request handler to query a delegation rewards func delegationRewardsHandlerFn(cliCtx context.CLIContext, queryRoute string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + // query for rewards from a particular delegation res, ok := checkResponseQueryDelegationRewards(w, cliCtx, queryRoute, mux.Vars(r)["delegatorAddr"], mux.Vars(r)["validatorAddr"]) if !ok { @@ -99,6 +109,11 @@ func delegatorWithdrawalAddrHandlerFn(cliCtx context.CLIContext, queryRoute stri return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + bz := cliCtx.Codec.MustMarshalJSON(types.NewQueryDelegatorWithdrawAddrParams(delegatorAddr)) res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/withdraw_addr", queryRoute), bz) if err != nil { @@ -137,6 +152,11 @@ func validatorInfoHandlerFn(cliCtx context.CLIContext, queryRoute string) http.H return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + // query commission commissionRes, err := common.QueryValidatorCommission(cliCtx, queryRoute, validatorAddr) if err != nil { @@ -172,6 +192,11 @@ func validatorRewardsHandlerFn(cliCtx context.CLIContext, queryRoute string) htt return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + delAddr := sdk.AccAddress(validatorAddr).String() res, ok := checkResponseQueryDelegationRewards(w, cliCtx, queryRoute, delAddr, valAddr) if !ok { @@ -185,6 +210,11 @@ func validatorRewardsHandlerFn(cliCtx context.CLIContext, queryRoute string) htt // HTTP request handler to query the distribution params values func paramsHandlerFn(cliCtx context.CLIContext, queryRoute string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params, err := common.QueryParams(cliCtx, queryRoute) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -197,6 +227,11 @@ func paramsHandlerFn(cliCtx context.CLIContext, queryRoute string) http.HandlerF func communityPoolHandler(cliCtx context.CLIContext, queryRoute string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/community_pool", queryRoute), nil) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -221,6 +256,11 @@ func outstandingRewardsHandlerFn(cliCtx context.CLIContext, queryRoute string) h return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + bin := cliCtx.Codec.MustMarshalJSON(types.NewQueryValidatorOutstandingRewardsParams(validatorAddr)) res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/validator_outstanding_rewards", queryRoute), bin) if err != nil { diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 260e8dbf15..7c89731313 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -196,6 +196,11 @@ func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { vars := mux.Vars(r) paramType := vars[RestParamsType] + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", types.QueryParams, paramType), nil) if err != nil { rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) @@ -222,6 +227,11 @@ func queryProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQueryProposalParams(proposalID) bz, err := cliCtx.Codec.MarshalJSON(params) @@ -250,6 +260,11 @@ func queryDepositsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQueryProposalParams(proposalID) bz, err := cliCtx.Codec.MarshalJSON(params) @@ -298,6 +313,11 @@ func queryProposerHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := gcutils.QueryProposerByTxQuery(cliCtx, proposalID) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -337,6 +357,11 @@ func queryDepositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQueryDepositParams(proposalID, depositorAddr) bz, err := cliCtx.Codec.MarshalJSON(params) @@ -414,6 +439,11 @@ func queryVoteHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQueryVoteParams(proposalID, voterAddr) bz, err := cliCtx.Codec.MarshalJSON(params) @@ -479,6 +509,11 @@ func queryVotesOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQueryProposalParams(proposalID) bz, err := cliCtx.Codec.MarshalJSON(params) @@ -561,6 +596,11 @@ func queryProposalsWithParameterFn(cliCtx context.CLIContext) http.HandlerFunc { params.Limit = numLimit } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) @@ -594,6 +634,11 @@ func queryTallyOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQueryProposalParams(proposalID) bz, err := cliCtx.Codec.MarshalJSON(params) diff --git a/x/mint/client/rest/query.go b/x/mint/client/rest/query.go index 009b80210f..c9900ee698 100644 --- a/x/mint/client/rest/query.go +++ b/x/mint/client/rest/query.go @@ -32,6 +32,11 @@ func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParameters) + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := cliCtx.QueryWithData(route, nil) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -46,6 +51,11 @@ func queryInflationHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryInflation) + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := cliCtx.QueryWithData(route, nil) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -60,6 +70,11 @@ func queryAnnualProvisionsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc return func(w http.ResponseWriter, r *http.Request) { route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryAnnualProvisions) + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := cliCtx.QueryWithData(route, nil) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index d665ef5832..cb67893416 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -39,6 +39,11 @@ func signingInfoHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQuerySigningInfoParams(sdk.ConsAddress(pk.Address())) bz, err := cliCtx.Codec.MarshalJSON(params) @@ -67,6 +72,11 @@ func signingInfoHandlerListFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQuerySigningInfosParams(page, limit) bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { @@ -87,6 +97,11 @@ func signingInfoHandlerListFn(cliCtx context.CLIContext) http.HandlerFunc { func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + route := fmt.Sprintf("custom/%s/parameters", types.QuerierRoute) res, err := cliCtx.QueryWithData(route, nil) diff --git a/x/staking/client/rest/query.go b/x/staking/client/rest/query.go index 5ab29a855c..2d1e66da4a 100644 --- a/x/staking/client/rest/query.go +++ b/x/staking/client/rest/query.go @@ -123,6 +123,11 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + typesQuery := r.URL.Query().Get("type") trimmedQuery := strings.TrimSpace(typesQuery) if len(trimmedQuery) != 0 { @@ -180,6 +185,11 @@ func redelegationsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var params types.QueryRedelegationParams + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + bechDelegatorAddr := r.URL.Query().Get("delegator") bechSrcValidatorAddr := r.URL.Query().Get("validator_from") bechDstValidatorAddr := r.URL.Query().Get("validator_to") @@ -251,6 +261,11 @@ func validatorsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + status := r.FormValue("status") if status == "" { status = sdk.BondStatusBonded @@ -291,6 +306,11 @@ func validatorUnbondingDelegationsHandlerFn(cliCtx context.CLIContext) http.Hand // HTTP request handler to query the pool information func poolHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := cliCtx.QueryWithData("custom/staking/pool", nil) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) @@ -304,6 +324,11 @@ func poolHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { // HTTP request handler to query the staking params values func paramsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + res, err := cliCtx.QueryWithData("custom/staking/parameters", nil) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) diff --git a/x/staking/client/rest/utils.go b/x/staking/client/rest/utils.go index b8ed921c1d..488759c468 100644 --- a/x/staking/client/rest/utils.go +++ b/x/staking/client/rest/utils.go @@ -49,6 +49,11 @@ func queryBonds(cliCtx context.CLIContext, endpoint string) http.HandlerFunc { return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQueryBondsParams(delegatorAddr, validatorAddr) bz, err := cliCtx.Codec.MarshalJSON(params) @@ -77,6 +82,11 @@ func queryDelegator(cliCtx context.CLIContext, endpoint string) http.HandlerFunc return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQueryDelegatorParams(delegatorAddr) bz, err := cliCtx.Codec.MarshalJSON(params) @@ -105,6 +115,11 @@ func queryValidator(cliCtx context.CLIContext, endpoint string) http.HandlerFunc return } + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + params := types.NewQueryValidatorParams(validatorAddr) bz, err := cliCtx.Codec.MarshalJSON(params)