cosmos-sdk/x/gov/client/rest/query.go

469 lines
13 KiB
Go

package rest
import (
"errors"
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
"github.com/cosmos/cosmos-sdk/x/gov/types"
)
func registerQueryRoutes(clientCtx client.Context, r *mux.Router) {
r.HandleFunc(fmt.Sprintf("/gov/parameters/{%s}", RestParamsType), queryParamsHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(clientCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/proposer", RestProposalID), queryProposerHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositor), queryDepositHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/tally", RestProposalID), queryTallyOnProposalHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(clientCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(clientCtx)).Methods("GET")
}
func queryParamsHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
paramType := vars[RestParamsType]
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
if !ok {
return
}
res, height, err := clientCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", types.QueryParams, paramType), nil)
if rest.CheckNotFoundError(w, err) {
return
}
clientCtx = clientCtx.WithHeight(height)
rest.PostProcessResponse(w, clientCtx, res)
}
}
func queryProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
strProposalID := vars[RestProposalID]
if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified")
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
if !ok {
return
}
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
if !ok {
return
}
params := types.NewQueryProposalParams(proposalID)
bz, err := clientCtx.JSONMarshaler.MarshalJSON(params)
if rest.CheckBadRequestError(w, err) {
return
}
res, height, err := clientCtx.QueryWithData("custom/gov/proposal", bz)
if rest.CheckInternalServerError(w, err) {
return
}
clientCtx = clientCtx.WithHeight(height)
rest.PostProcessResponse(w, clientCtx, res)
}
}
func queryDepositsHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
strProposalID := vars[RestProposalID]
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
if !ok {
return
}
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
if !ok {
return
}
params := types.NewQueryProposalParams(proposalID)
bz, err := clientCtx.JSONMarshaler.MarshalJSON(params)
if rest.CheckBadRequestError(w, err) {
return
}
res, _, err := clientCtx.QueryWithData("custom/gov/proposal", bz)
if rest.CheckInternalServerError(w, err) {
return
}
var proposal types.Proposal
if rest.CheckInternalServerError(w, clientCtx.JSONMarshaler.UnmarshalJSON(res, &proposal)) {
return
}
// For inactive proposals we must query the txs directly to get the deposits
// as they're no longer in state.
propStatus := proposal.Status
if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) {
res, err = gcutils.QueryDepositsByTxQuery(clientCtx, params)
} else {
res, _, err = clientCtx.QueryWithData("custom/gov/deposits", bz)
}
if rest.CheckInternalServerError(w, err) {
return
}
rest.PostProcessResponse(w, clientCtx, res)
}
}
func queryProposerHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
strProposalID := vars[RestProposalID]
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
if !ok {
return
}
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
if !ok {
return
}
res, err := gcutils.QueryProposerByTxQuery(clientCtx, proposalID)
if rest.CheckInternalServerError(w, err) {
return
}
rest.PostProcessResponse(w, clientCtx, res)
}
}
func queryDepositHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
strProposalID := vars[RestProposalID]
bechDepositorAddr := vars[RestDepositor]
if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified")
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
if !ok {
return
}
if len(bechDepositorAddr) == 0 {
err := errors.New("depositor address required but not specified")
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr)
if rest.CheckBadRequestError(w, err) {
return
}
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
if !ok {
return
}
params := types.NewQueryDepositParams(proposalID, depositorAddr)
bz, err := clientCtx.JSONMarshaler.MarshalJSON(params)
if rest.CheckBadRequestError(w, err) {
return
}
res, _, err := clientCtx.QueryWithData("custom/gov/deposit", bz)
if rest.CheckInternalServerError(w, err) {
return
}
var deposit types.Deposit
if rest.CheckBadRequestError(w, clientCtx.JSONMarshaler.UnmarshalJSON(res, &deposit)) {
return
}
// For an empty deposit, either the proposal does not exist or is inactive in
// which case the deposit would be removed from state and should be queried
// for directly via a txs query.
if deposit.Empty() {
bz, err := clientCtx.JSONMarshaler.MarshalJSON(types.NewQueryProposalParams(proposalID))
if rest.CheckBadRequestError(w, err) {
return
}
res, _, err = clientCtx.QueryWithData("custom/gov/proposal", bz)
if err != nil || len(res) == 0 {
err := fmt.Errorf("proposalID %d does not exist", proposalID)
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return
}
res, err = gcutils.QueryDepositByTxQuery(clientCtx, params)
if rest.CheckInternalServerError(w, err) {
return
}
}
rest.PostProcessResponse(w, clientCtx, res)
}
}
func queryVoteHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
strProposalID := vars[RestProposalID]
bechVoterAddr := vars[RestVoter]
if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified")
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
if !ok {
return
}
if len(bechVoterAddr) == 0 {
err := errors.New("voter address required but not specified")
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
if !ok {
return
}
params := types.NewQueryVoteParams(proposalID, voterAddr)
bz, err := clientCtx.JSONMarshaler.MarshalJSON(params)
if rest.CheckBadRequestError(w, err) {
return
}
res, _, err := clientCtx.QueryWithData("custom/gov/vote", bz)
if rest.CheckInternalServerError(w, err) {
return
}
var vote types.Vote
if rest.CheckBadRequestError(w, clientCtx.JSONMarshaler.UnmarshalJSON(res, &vote)) {
return
}
// For an empty vote, either the proposal does not exist or is inactive in
// which case the vote would be removed from state and should be queried for
// directly via a txs query.
if vote.Empty() {
bz, err := clientCtx.JSONMarshaler.MarshalJSON(types.NewQueryProposalParams(proposalID))
if rest.CheckBadRequestError(w, err) {
return
}
res, _, err = clientCtx.QueryWithData("custom/gov/proposal", bz)
if err != nil || len(res) == 0 {
err := fmt.Errorf("proposalID %d does not exist", proposalID)
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return
}
res, err = gcutils.QueryVoteByTxQuery(clientCtx, params)
if rest.CheckInternalServerError(w, err) {
return
}
}
rest.PostProcessResponse(w, clientCtx, res)
}
}
// todo: Split this functionality into helper functions to remove the above
func queryVotesOnProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, page, limit, err := rest.ParseHTTPArgs(r)
if rest.CheckBadRequestError(w, err) {
return
}
vars := mux.Vars(r)
strProposalID := vars[RestProposalID]
if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified")
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
if !ok {
return
}
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
if !ok {
return
}
params := types.NewQueryProposalVotesParams(proposalID, page, limit)
bz, err := clientCtx.JSONMarshaler.MarshalJSON(params)
if rest.CheckBadRequestError(w, err) {
return
}
res, _, err := clientCtx.QueryWithData("custom/gov/proposal", bz)
if rest.CheckInternalServerError(w, err) {
return
}
var proposal types.Proposal
if rest.CheckInternalServerError(w, clientCtx.JSONMarshaler.UnmarshalJSON(res, &proposal)) {
return
}
// For inactive proposals we must query the txs directly to get the votes
// as they're no longer in state.
propStatus := proposal.Status
if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) {
res, err = gcutils.QueryVotesByTxQuery(clientCtx, params)
} else {
res, _, err = clientCtx.QueryWithData("custom/gov/votes", bz)
}
if rest.CheckInternalServerError(w, err) {
return
}
rest.PostProcessResponse(w, clientCtx, res)
}
}
// HTTP request handler to query list of governance proposals
func queryProposalsWithParameterFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0)
if rest.CheckBadRequestError(w, err) {
return
}
clientCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
if !ok {
return
}
var (
voterAddr sdk.AccAddress
depositorAddr sdk.AccAddress
proposalStatus types.ProposalStatus
)
if v := r.URL.Query().Get(RestVoter); len(v) != 0 {
voterAddr, err = sdk.AccAddressFromBech32(v)
if rest.CheckBadRequestError(w, err) {
return
}
}
if v := r.URL.Query().Get(RestDepositor); len(v) != 0 {
depositorAddr, err = sdk.AccAddressFromBech32(v)
if rest.CheckBadRequestError(w, err) {
return
}
}
if v := r.URL.Query().Get(RestProposalStatus); len(v) != 0 {
proposalStatus, err = types.ProposalStatusFromString(gcutils.NormalizeProposalStatus(v))
if rest.CheckBadRequestError(w, err) {
return
}
}
params := types.NewQueryProposalsParams(page, limit, proposalStatus, voterAddr, depositorAddr)
bz, err := clientCtx.JSONMarshaler.MarshalJSON(params)
if rest.CheckBadRequestError(w, err) {
return
}
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryProposals)
res, height, err := clientCtx.QueryWithData(route, bz)
if rest.CheckInternalServerError(w, err) {
return
}
clientCtx = clientCtx.WithHeight(height)
rest.PostProcessResponse(w, clientCtx, res)
}
}
// todo: Split this functionality into helper functions to remove the above
func queryTallyOnProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
strProposalID := vars[RestProposalID]
if len(strProposalID) == 0 {
err := errors.New("proposalId required but not specified")
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID)
if !ok {
return
}
clientCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, clientCtx, r)
if !ok {
return
}
params := types.NewQueryProposalParams(proposalID)
bz, err := clientCtx.JSONMarshaler.MarshalJSON(params)
if rest.CheckBadRequestError(w, err) {
return
}
res, height, err := clientCtx.QueryWithData("custom/gov/tally", bz)
if rest.CheckInternalServerError(w, err) {
return
}
clientCtx = clientCtx.WithHeight(height)
rest.PostProcessResponse(w, clientCtx, res)
}
}