package rest import ( "fmt" "net/http" "github.com/cosmos/cosmos-sdk/client/context" "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/gov" govClientUtils "github.com/cosmos/cosmos-sdk/x/gov/client/utils" "github.com/gorilla/mux" "github.com/pkg/errors" ) // REST Variable names // nolint const ( RestParamsType = "type" RestProposalID = "proposal-id" RestDepositer = "depositer" RestVoter = "voter" RestProposalStatus = "status" RestNumLimit = "limit" storeName = "gov" ) // RegisterRoutes - Central function to define routes that get registered by the main application func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { r.HandleFunc("/gov/proposals", postProposalHandlerFn(cdc, cliCtx)).Methods("POST") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, cliCtx)).Methods("POST") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, cliCtx)).Methods("POST") r.HandleFunc( fmt.Sprintf("/gov/parameters/{%s}", RestParamsType), queryParamsHandlerFn(cdc, cliCtx), ).Methods("GET") r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc, cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc, cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cdc, cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc, cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cdc, cliCtx)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc, cliCtx)).Methods("GET") } type postProposalReq struct { BaseReq utils.BaseReq `json:"base_req"` Title string `json:"title"` // Title of the proposal Description string `json:"description"` // Description of the proposal ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer InitialDeposit sdk.Coins `json:"initial_deposit"` // Coins to add to the proposal's deposit } type depositReq struct { BaseReq utils.BaseReq `json:"base_req"` Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } type voteReq struct { BaseReq utils.BaseReq `json:"base_req"` Voter sdk.AccAddress `json:"voter"` // address of the voter Option string `json:"option"` // option from OptionSet chosen by the voter } func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req postProposalReq err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } baseReq := req.BaseReq.Sanitize() if !baseReq.ValidateBasic(w) { return } proposalType, err := gov.ProposalTypeFromString(govClientUtils.NormalizeProposalType(req.ProposalType)) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } // create the message msg := gov.NewMsgSubmitProposal(req.Title, req.Description, proposalType, req.Proposer, req.InitialDeposit) err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) 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") utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } var req depositReq err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } baseReq := req.BaseReq.Sanitize() if !baseReq.ValidateBasic(w) { return } // create the message msg := gov.NewMsgDeposit(req.Depositer, proposalID, req.Amount) err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) 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") utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } var req voteReq err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } baseReq := req.BaseReq.Sanitize() if !baseReq.ValidateBasic(w) { return } voteOption, err := gov.VoteOptionFromString(govClientUtils.NormalizeVoteOption(req.Option)) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } // create the message msg := gov.NewMsgVote(req.Voter, proposalID, voteOption) err = msg.ValidateBasic() if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } func queryParamsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) paramType := vars[RestParamsType] res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", gov.QueryParams, paramType), nil) if err != nil { utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } func queryProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) 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") utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } params := gov.QueryProposalParams{ ProposalID: proposalID, } bz, err := cdc.MarshalJSON(params) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/proposal", bz) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } func queryDepositsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } params := gov.QueryDepositsParams{ ProposalID: proposalID, } bz, err := cdc.MarshalJSON(params) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/deposits", bz) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] bechDepositerAddr := vars[RestDepositer] if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } if len(bechDepositerAddr) == 0 { err := errors.New("depositer address required but not specified") utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params := gov.QueryDepositParams{ ProposalID: proposalID, Depositer: depositerAddr, } bz, err := cdc.MarshalJSON(params) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/deposit", bz) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } var deposit gov.Deposit cdc.UnmarshalJSON(res, &deposit) if deposit.Empty() { res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinaryLengthPrefixed(gov.QueryProposalParams{params.ProposalID})) if err != nil || len(res) == 0 { err := errors.Errorf("proposalID [%d] does not exist", proposalID) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) 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") utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } if len(bechVoterAddr) == 0 { err := errors.New("voter address required but not specified") utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params := gov.QueryVoteParams{ Voter: voterAddr, ProposalID: proposalID, } bz, err := cdc.MarshalJSON(params) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/vote", bz) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } var vote gov.Vote cdc.UnmarshalJSON(res, &vote) if vote.Empty() { bz, err := cdc.MarshalJSON(gov.QueryProposalParams{params.ProposalID}) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/proposal", bz) if err != nil || len(res) == 0 { err := errors.Errorf("proposalID [%d] does not exist", proposalID) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } err = errors.Errorf("voter [%s] did not deposit on proposalID [%d]", bechVoterAddr, proposalID) utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) 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") utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } params := gov.QueryVotesParams{ ProposalID: proposalID, } bz, err := cdc.MarshalJSON(params) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/votes", bz) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { bechVoterAddr := r.URL.Query().Get(RestVoter) bechDepositerAddr := r.URL.Query().Get(RestDepositer) strProposalStatus := r.URL.Query().Get(RestProposalStatus) strNumLimit := r.URL.Query().Get(RestNumLimit) params := gov.QueryProposalsParams{} if len(bechVoterAddr) != 0 { voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.Voter = voterAddr } if len(bechDepositerAddr) != 0 { depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.Depositer = depositerAddr } if len(strProposalStatus) != 0 { proposalStatus, err := gov.ProposalStatusFromString(govClientUtils.NormalizeProposalStatus(strProposalStatus)) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.ProposalStatus = proposalStatus } if len(strNumLimit) != 0 { numLimit, ok := utils.ParseUint64OrReturnBadRequest(w, strNumLimit) if !ok { return } params.Limit = numLimit } bz, err := cdc.MarshalJSON(params) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/proposals", bz) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // todo: Split this functionality into helper functions to remove the above func queryTallyOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) strProposalID := vars[RestProposalID] if len(strProposalID) == 0 { w.WriteHeader(http.StatusBadRequest) err := errors.New("proposalId required but not specified") w.Write([]byte(err.Error())) return } proposalID, ok := utils.ParseUint64OrReturnBadRequest(w, strProposalID) if !ok { return } params := gov.QueryTallyParams{ ProposalID: proposalID, } bz, err := cdc.MarshalJSON(params) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } res, err := cliCtx.QueryWithData("custom/gov/tally", bz) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } }