github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/gov/client/rest/rest.go (about) 1 package rest 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 8 "github.com/gorilla/mux" 9 10 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/context" 11 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 12 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/rest" 13 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/client/utils" 14 gcutils "github.com/fibonacci-chain/fbc/x/gov/client/utils" 15 "github.com/fibonacci-chain/fbc/x/gov/types" 16 ) 17 18 // REST Variable names 19 // nolint 20 const ( 21 RestParamsType = "type" 22 RestProposalID = "proposal-id" 23 RestDepositor = "depositor" 24 RestVoter = "voter" 25 RestProposalStatus = "status" 26 RestNumLimit = "limit" 27 ) 28 29 // ProposalRESTHandler defines a REST handler implemented in another module. The 30 // sub-route is mounted on the governance REST handler. 31 type ProposalRESTHandler struct { 32 SubRoute string 33 Handler func(http.ResponseWriter, *http.Request) 34 } 35 36 // RegisterRoutes - Central function to define routes that get registered by the main application 37 func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, phs []ProposalRESTHandler) { 38 propSubRtr := r.PathPrefix("/gov/proposals").Subrouter() 39 for _, ph := range phs { 40 propSubRtr.HandleFunc(fmt.Sprintf("/%s", ph.SubRoute), ph.Handler).Methods("POST") 41 } 42 43 r.HandleFunc("/gov/proposals", postProposalHandlerFn(cliCtx)).Methods("POST") 44 r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cliCtx)).Methods("POST") 45 r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cliCtx)).Methods("POST") 46 47 r.HandleFunc( 48 fmt.Sprintf("/gov/parameters/{%s}", RestParamsType), 49 queryParamsHandlerFn(cliCtx), 50 ).Methods("GET") 51 52 r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cliCtx)).Methods("GET") 53 r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cliCtx)).Methods("GET") 54 r.HandleFunc( 55 fmt.Sprintf("/gov/proposals/{%s}/proposer", RestProposalID), 56 queryProposerHandlerFn(cliCtx), 57 ).Methods("GET") 58 r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cliCtx)).Methods("GET") 59 r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositor), queryDepositHandlerFn(cliCtx)).Methods("GET") 60 r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/tally", RestProposalID), queryTallyOnProposalHandlerFn(cliCtx)).Methods("GET") 61 r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cliCtx)).Methods("GET") 62 r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cliCtx)).Methods("GET") 63 } 64 65 // PostProposalReq defines the properties of a proposal request's body. 66 type PostProposalReq struct { 67 BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` 68 Title string `json:"title" yaml:"title"` // Title of the proposal 69 Description string `json:"description" yaml:"description"` // Description of the proposal 70 ProposalType string `json:"proposal_type" yaml:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} 71 Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"` // Address of the proposer 72 InitialDeposit sdk.SysCoins `json:"initial_deposit" yaml:"initial_deposit"` // Coins to add to the proposal's deposit 73 } 74 75 // DepositReq defines the properties of a deposit request's body. 76 type DepositReq struct { 77 BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` 78 Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` // Address of the depositor 79 Amount sdk.SysCoins `json:"amount" yaml:"amount"` // Coins to add to the proposal's deposit 80 } 81 82 // VoteReq defines the properties of a vote request's body. 83 type VoteReq struct { 84 BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` 85 Voter sdk.AccAddress `json:"voter" yaml:"voter"` // address of the voter 86 Option string `json:"option" yaml:"option"` // option from OptionSet chosen by the voter 87 } 88 89 func postProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 90 return func(w http.ResponseWriter, r *http.Request) { 91 var req PostProposalReq 92 if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { 93 return 94 } 95 96 req.BaseReq = req.BaseReq.Sanitize() 97 if !req.BaseReq.ValidateBasic(w) { 98 return 99 } 100 101 proposalType := gcutils.NormalizeProposalType(req.ProposalType) 102 content := types.ContentFromProposalType(req.Title, req.Description, proposalType) 103 104 msg := types.NewMsgSubmitProposal(content, req.InitialDeposit, req.Proposer) 105 if err := msg.ValidateBasic(); err != nil { 106 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 107 return 108 } 109 110 utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) 111 } 112 } 113 114 func depositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 115 return func(w http.ResponseWriter, r *http.Request) { 116 vars := mux.Vars(r) 117 strProposalID := vars[RestProposalID] 118 119 if len(strProposalID) == 0 { 120 err := errors.New("proposalId required but not specified") 121 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 122 return 123 } 124 125 proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) 126 if !ok { 127 return 128 } 129 130 var req DepositReq 131 if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { 132 return 133 } 134 135 req.BaseReq = req.BaseReq.Sanitize() 136 if !req.BaseReq.ValidateBasic(w) { 137 return 138 } 139 140 // create the message 141 msg := types.NewMsgDeposit(req.Depositor, proposalID, req.Amount) 142 if err := msg.ValidateBasic(); err != nil { 143 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 144 return 145 } 146 147 utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) 148 } 149 } 150 151 func voteHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 152 return func(w http.ResponseWriter, r *http.Request) { 153 vars := mux.Vars(r) 154 strProposalID := vars[RestProposalID] 155 156 if len(strProposalID) == 0 { 157 err := errors.New("proposalId required but not specified") 158 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 159 return 160 } 161 162 proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) 163 if !ok { 164 return 165 } 166 167 var req VoteReq 168 if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { 169 return 170 } 171 172 req.BaseReq = req.BaseReq.Sanitize() 173 if !req.BaseReq.ValidateBasic(w) { 174 return 175 } 176 177 voteOption, err := types.VoteOptionFromString(gcutils.NormalizeVoteOption(req.Option)) 178 if err != nil { 179 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 180 return 181 } 182 183 // create the message 184 msg := types.NewMsgVote(req.Voter, proposalID, voteOption) 185 if err := msg.ValidateBasic(); err != nil { 186 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 187 return 188 } 189 190 utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) 191 } 192 } 193 194 func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 195 return func(w http.ResponseWriter, r *http.Request) { 196 vars := mux.Vars(r) 197 paramType := vars[RestParamsType] 198 199 cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) 200 if !ok { 201 return 202 } 203 204 res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", types.QueryParams, paramType), nil) 205 if err != nil { 206 rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) 207 return 208 } 209 210 cliCtx = cliCtx.WithHeight(height) 211 rest.PostProcessResponse(w, cliCtx, res) 212 } 213 } 214 215 func queryProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 216 return func(w http.ResponseWriter, r *http.Request) { 217 vars := mux.Vars(r) 218 strProposalID := vars[RestProposalID] 219 220 if len(strProposalID) == 0 { 221 err := errors.New("proposalId required but not specified") 222 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 223 return 224 } 225 226 proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) 227 if !ok { 228 return 229 } 230 231 cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) 232 if !ok { 233 return 234 } 235 236 params := types.NewQueryProposalParams(proposalID) 237 238 bz, err := cliCtx.Codec.MarshalJSON(params) 239 if err != nil { 240 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 241 return 242 } 243 244 res, height, err := cliCtx.QueryWithData("custom/gov/proposal", bz) 245 if err != nil { 246 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 247 return 248 } 249 250 cliCtx = cliCtx.WithHeight(height) 251 rest.PostProcessResponse(w, cliCtx, res) 252 } 253 } 254 255 func queryDepositsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 256 return func(w http.ResponseWriter, r *http.Request) { 257 vars := mux.Vars(r) 258 strProposalID := vars[RestProposalID] 259 260 proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) 261 if !ok { 262 return 263 } 264 265 cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) 266 if !ok { 267 return 268 } 269 270 params := types.NewQueryProposalParams(proposalID) 271 272 bz, err := cliCtx.Codec.MarshalJSON(params) 273 if err != nil { 274 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 275 return 276 } 277 278 res, _, err := cliCtx.QueryWithData("custom/gov/proposal", bz) 279 if err != nil { 280 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 281 return 282 } 283 284 var proposal types.Proposal 285 if err := cliCtx.Codec.UnmarshalJSON(res, &proposal); err != nil { 286 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 287 return 288 } 289 290 // For inactive proposals we must query the txs directly to get the deposits 291 // as they're no longer in state. 292 propStatus := proposal.Status 293 if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) { 294 res, err = gcutils.QueryDepositsByTxQuery(cliCtx, params) 295 } else { 296 res, _, err = cliCtx.QueryWithData("custom/gov/deposits", bz) 297 } 298 299 if err != nil { 300 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 301 return 302 } 303 304 rest.PostProcessResponse(w, cliCtx, res) 305 } 306 } 307 308 func queryProposerHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 309 return func(w http.ResponseWriter, r *http.Request) { 310 vars := mux.Vars(r) 311 strProposalID := vars[RestProposalID] 312 313 proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) 314 if !ok { 315 return 316 } 317 318 cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) 319 if !ok { 320 return 321 } 322 323 res, err := gcutils.QueryProposerByTxQuery(cliCtx, proposalID) 324 if err != nil { 325 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 326 return 327 } 328 329 rest.PostProcessResponse(w, cliCtx, res) 330 } 331 } 332 333 func queryDepositHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 334 return func(w http.ResponseWriter, r *http.Request) { 335 vars := mux.Vars(r) 336 strProposalID := vars[RestProposalID] 337 bechDepositorAddr := vars[RestDepositor] 338 339 if len(strProposalID) == 0 { 340 err := errors.New("proposalId required but not specified") 341 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 342 return 343 } 344 345 proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) 346 if !ok { 347 return 348 } 349 350 if len(bechDepositorAddr) == 0 { 351 err := errors.New("depositor address required but not specified") 352 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 353 return 354 } 355 356 depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr) 357 if err != nil { 358 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 359 return 360 } 361 362 cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) 363 if !ok { 364 return 365 } 366 367 params := types.NewQueryDepositParams(proposalID, depositorAddr) 368 369 bz, err := cliCtx.Codec.MarshalJSON(params) 370 if err != nil { 371 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 372 return 373 } 374 375 res, _, err := cliCtx.QueryWithData("custom/gov/deposit", bz) 376 if err != nil { 377 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 378 return 379 } 380 381 var deposit types.Deposit 382 if err := cliCtx.Codec.UnmarshalJSON(res, &deposit); err != nil { 383 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 384 return 385 } 386 387 // For an empty deposit, either the proposal does not exist or is inactive in 388 // which case the deposit would be removed from state and should be queried 389 // for directly via a txs query. 390 if deposit.Empty() { 391 bz, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposalParams(proposalID)) 392 if err != nil { 393 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 394 return 395 } 396 397 res, _, err = cliCtx.QueryWithData("custom/gov/proposal", bz) 398 if err != nil || len(res) == 0 { 399 err := fmt.Errorf("proposalID %d does not exist", proposalID) 400 rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) 401 return 402 } 403 404 res, err = gcutils.QueryDepositByTxQuery(cliCtx, params) 405 if err != nil { 406 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 407 return 408 } 409 } 410 411 rest.PostProcessResponse(w, cliCtx, res) 412 } 413 } 414 415 func queryVoteHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 416 return func(w http.ResponseWriter, r *http.Request) { 417 vars := mux.Vars(r) 418 strProposalID := vars[RestProposalID] 419 bechVoterAddr := vars[RestVoter] 420 421 if len(strProposalID) == 0 { 422 err := errors.New("proposalId required but not specified") 423 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 424 return 425 } 426 427 proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) 428 if !ok { 429 return 430 } 431 432 if len(bechVoterAddr) == 0 { 433 err := errors.New("voter address required but not specified") 434 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 435 return 436 } 437 438 voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) 439 if err != nil { 440 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 441 return 442 } 443 444 cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) 445 if !ok { 446 return 447 } 448 449 params := types.NewQueryVoteParams(proposalID, voterAddr) 450 451 bz, err := cliCtx.Codec.MarshalJSON(params) 452 if err != nil { 453 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 454 return 455 } 456 457 res, _, err := cliCtx.QueryWithData("custom/gov/vote", bz) 458 if err != nil { 459 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 460 return 461 } 462 463 var vote types.Vote 464 if err := cliCtx.Codec.UnmarshalJSON(res, &vote); err != nil { 465 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 466 return 467 } 468 469 // For an empty vote, either the proposal does not exist or is inactive in 470 // which case the vote would be removed from state and should be queried for 471 // directly via a txs query. 472 if vote.Empty() { 473 bz, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposalParams(proposalID)) 474 if err != nil { 475 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 476 return 477 } 478 479 res, _, err = cliCtx.QueryWithData("custom/gov/proposal", bz) 480 if err != nil || len(res) == 0 { 481 err := fmt.Errorf("proposalID %d does not exist", proposalID) 482 rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) 483 return 484 } 485 486 res, err = gcutils.QueryVoteByTxQuery(cliCtx, params) 487 if err != nil { 488 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 489 return 490 } 491 } 492 493 rest.PostProcessResponse(w, cliCtx, res) 494 } 495 } 496 497 // todo: Split this functionality into helper functions to remove the above 498 func queryVotesOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 499 return func(w http.ResponseWriter, r *http.Request) { 500 vars := mux.Vars(r) 501 strProposalID := vars[RestProposalID] 502 503 if len(strProposalID) == 0 { 504 err := errors.New("proposalId required but not specified") 505 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 506 return 507 } 508 509 proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) 510 if !ok { 511 return 512 } 513 514 cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) 515 if !ok { 516 return 517 } 518 519 params := types.NewQueryProposalParams(proposalID) 520 521 bz, err := cliCtx.Codec.MarshalJSON(params) 522 if err != nil { 523 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 524 return 525 } 526 527 res, _, err := cliCtx.QueryWithData("custom/gov/proposal", bz) 528 if err != nil { 529 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 530 return 531 } 532 533 var proposal types.Proposal 534 if err := cliCtx.Codec.UnmarshalJSON(res, &proposal); err != nil { 535 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 536 return 537 } 538 539 // For inactive proposals we must query the txs directly to get the votes 540 // as they're no longer in state. 541 propStatus := proposal.Status 542 if !(propStatus == types.StatusVotingPeriod || propStatus == types.StatusDepositPeriod) { 543 res, err = gcutils.QueryVotesByTxQuery(cliCtx, params) 544 } else { 545 res, _, err = cliCtx.QueryWithData("custom/gov/votes", bz) 546 } 547 548 if err != nil { 549 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 550 return 551 } 552 553 rest.PostProcessResponse(w, cliCtx, res) 554 } 555 } 556 557 // todo: Split this functionality into helper functions to remove the above 558 func queryProposalsWithParameterFn(cliCtx context.CLIContext) http.HandlerFunc { 559 return func(w http.ResponseWriter, r *http.Request) { 560 bechVoterAddr := r.URL.Query().Get(RestVoter) 561 bechDepositorAddr := r.URL.Query().Get(RestDepositor) 562 strProposalStatus := r.URL.Query().Get(RestProposalStatus) 563 strNumLimit := r.URL.Query().Get(RestNumLimit) 564 565 params := types.QueryProposalsParams{} 566 567 if len(bechVoterAddr) != 0 { 568 voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) 569 if err != nil { 570 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 571 return 572 } 573 params.Voter = voterAddr 574 } 575 576 if len(bechDepositorAddr) != 0 { 577 depositorAddr, err := sdk.AccAddressFromBech32(bechDepositorAddr) 578 if err != nil { 579 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 580 return 581 } 582 params.Depositor = depositorAddr 583 } 584 585 if len(strProposalStatus) != 0 { 586 proposalStatus, err := types.ProposalStatusFromString(gcutils.NormalizeProposalStatus(strProposalStatus)) 587 if err != nil { 588 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 589 return 590 } 591 params.ProposalStatus = proposalStatus 592 } 593 if len(strNumLimit) != 0 { 594 numLimit, ok := rest.ParseUint64OrReturnBadRequest(w, strNumLimit) 595 if !ok { 596 return 597 } 598 params.Limit = numLimit 599 } 600 601 cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) 602 if !ok { 603 return 604 } 605 606 bz, err := cliCtx.Codec.MarshalJSON(params) 607 if err != nil { 608 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 609 return 610 } 611 612 res, height, err := cliCtx.QueryWithData("custom/gov/proposals", bz) 613 if err != nil { 614 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 615 return 616 } 617 618 cliCtx = cliCtx.WithHeight(height) 619 rest.PostProcessResponse(w, cliCtx, res) 620 } 621 } 622 623 // todo: Split this functionality into helper functions to remove the above 624 func queryTallyOnProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { 625 return func(w http.ResponseWriter, r *http.Request) { 626 vars := mux.Vars(r) 627 strProposalID := vars[RestProposalID] 628 629 if len(strProposalID) == 0 { 630 err := errors.New("proposalId required but not specified") 631 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 632 return 633 } 634 635 proposalID, ok := rest.ParseUint64OrReturnBadRequest(w, strProposalID) 636 if !ok { 637 return 638 } 639 640 cliCtx, ok = rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) 641 if !ok { 642 return 643 } 644 645 params := types.NewQueryProposalParams(proposalID) 646 647 bz, err := cliCtx.Codec.MarshalJSON(params) 648 if err != nil { 649 rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) 650 return 651 } 652 653 res, height, err := cliCtx.QueryWithData("custom/gov/tally", bz) 654 if err != nil { 655 rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) 656 return 657 } 658 659 cliCtx = cliCtx.WithHeight(height) 660 rest.PostProcessResponse(w, cliCtx, res) 661 } 662 }