From f3a12909ef48c3ae1bf96f3b9187edcdcb5fb854 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Thu, 19 Jul 2018 17:02:46 -0700 Subject: [PATCH] Merge PR #1773: Query the votes on a proposal * added lcd endpoint to query all votes on a proposal * added cli support * Gopkg.lock from new dep * Update PENDING.md --- Gopkg.lock | 1 + PENDING.md | 1 + client/lcd/lcd_test.go | 20 +++++++++++ cmd/gaia/cli_test/cli_test.go | 14 ++++++++ cmd/gaia/cmd/gaiacli/main.go | 1 + x/gov/client/cli/tx.go | 51 ++++++++++++++++++++++++++ x/gov/client/rest/rest.go | 67 +++++++++++++++++++++++++++++++++++ 7 files changed, 155 insertions(+) diff --git a/Gopkg.lock b/Gopkg.lock index cf5c97909..050569195 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -646,6 +646,7 @@ "github.com/tendermint/tendermint/rpc/lib/client", "github.com/tendermint/tendermint/rpc/lib/server", "github.com/tendermint/tendermint/types", + "github.com/tendermint/tendermint/version", "github.com/zondax/ledger-goclient", "golang.org/x/crypto/blowfish", "golang.org/x/crypto/ripemd160", diff --git a/PENDING.md b/PENDING.md index 968586ab1..42460bd14 100644 --- a/PENDING.md +++ b/PENDING.md @@ -31,6 +31,7 @@ IMPROVEMENTS * [cli] Improve error messages for all txs when the account doesn't exist * [tools] Remove `rm -rf vendor/` from `make get_vendor_deps` * [x/stake] Add revoked to human-readable validator +* [x/gov] Votes on a proposal can now be queried BUG FIXES * \#1666 Add intra-tx counter to the genesis validators diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 2de92575a..b9efbc143 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -611,6 +611,17 @@ func TestProposalsQuery(t *testing.T) { // Test query voted and deposited by addr1 proposals = getProposalsFilterVoterDepositer(t, port, addr, addr) require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) + + // Test query votes on Proposal 2 + votes := getVotes(t, port, proposalID2) + require.Len(t, votes, 1) + require.Equal(t, addr, votes[0].Voter) + + // Test query votes on Proposal 3 + votes = getVotes(t, port, proposalID3) + require.Len(t, votes, 2) + require.True(t, addr.String() == votes[0].Voter.String() || addr.String() == votes[1].Voter.String()) + require.True(t, addr2.String() == votes[0].Voter.String() || addr2.String() == votes[1].Voter.String()) } //_____________________________________________________________________________ @@ -875,6 +886,15 @@ func getVote(t *testing.T, port string, proposalID int64, voterAddr sdk.AccAddre return vote } +func getVotes(t *testing.T, port string, proposalID int64) []gov.Vote { + res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d/votes", proposalID), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var votes []gov.Vote + err := cdc.UnmarshalJSON([]byte(body), &votes) + require.Nil(t, err) + return votes +} + func getProposalsAll(t *testing.T, port string) []gov.Proposal { res, body := Request(t, port, "GET", "/gov/proposals", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index beac34097..31b03d0d0 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -217,6 +217,11 @@ func TestGaiaCLISubmitProposal(t *testing.T) { vote := executeGetVote(t, fmt.Sprintf("gaiacli gov query-vote --proposalID=1 --voter=%s --output=json %v", fooAddr, flags)) require.Equal(t, int64(1), vote.ProposalID) require.Equal(t, gov.OptionYes, vote.Option) + + votes := executeGetVotes(t, fmt.Sprintf("gaiacli gov query-votes --proposalID=1 --output=json %v", flags)) + require.Len(t, votes, 1) + require.Equal(t, int64(1), votes[0].ProposalID) + require.Equal(t, gov.OptionYes, votes[0].Option) } //___________________________________________________________________________________ @@ -321,3 +326,12 @@ func executeGetVote(t *testing.T, cmdStr string) gov.Vote { require.NoError(t, err, "out %v\n, err %v", out, err) return vote } + +func executeGetVotes(t *testing.T, cmdStr string) []gov.Vote { + out := tests.ExecuteT(t, cmdStr) + var votes []gov.Vote + cdc := app.MakeCodec() + err := cdc.UnmarshalJSON([]byte(out), &votes) + require.NoError(t, err, "out %v\n, err %v", out, err) + return votes +} diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 4ab5d02b9..7c66cb9ef 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -112,6 +112,7 @@ func main() { client.GetCommands( govcmd.GetCmdQueryProposal("gov", cdc), govcmd.GetCmdQueryVote("gov", cdc), + govcmd.GetCmdQueryVotes("gov", cdc), )...) govCmd.AddCommand( client.PostCommands( diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index c1bb62bc7..f534ff92c 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -239,3 +239,54 @@ func GetCmdQueryVote(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } + +// Command to Get a Proposal Information +func GetCmdQueryVotes(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "query-votes", + Short: "query votes on a proposal", + RunE: func(cmd *cobra.Command, args []string) error { + proposalID := viper.GetInt64(flagProposalID) + + ctx := context.NewCoreContextFromViper() + + res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + if len(res) == 0 || err != nil { + return errors.Errorf("proposalID [%d] does not exist", proposalID) + } + + var proposal gov.Proposal + cdc.MustUnmarshalBinary(res, &proposal) + + if proposal.GetStatus() != gov.StatusVotingPeriod { + fmt.Println("Proposal not in voting period.") + return nil + } + + res2, err := ctx.QuerySubspace(cdc, gov.KeyVotesSubspace(proposalID), storeName) + if err != nil { + return err + } + + var votes []gov.Vote + for i := 0; i < len(res2); i++ { + var vote gov.Vote + cdc.MustUnmarshalBinary(res2[i].Value, &vote) + votes = append(votes, vote) + } + + output, err := wire.MarshalJSONIndent(cdc, votes) + if err != nil { + return err + } + + fmt.Println(string(output)) + + return nil + }, + } + + cmd.Flags().String(flagProposalID, "", "proposalID of which proposal's votes are being queried") + + return cmd +} diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index ffaf42749..7efce9f0b 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -33,6 +33,8 @@ func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits/{%s}", RestProposalID, RestDepositer), queryDepositHandlerFn(cdc)).Methods("GET") r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes/{%s}", RestProposalID, RestVoter), queryVoteHandlerFn(cdc)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), queryVotesOnProposalHandlerFn(cdc)).Methods("GET") + r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc)).Methods("GET") } @@ -335,6 +337,71 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { } } +// nolint: gocyclo +// todo: Split this functionality into helper functions to remove the above +func queryVotesOnProposalHandlerFn(cdc *wire.Codec) 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, err := strconv.ParseInt(strProposalID, 10, 64) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("proposalID [%s] is not positive", proposalID) + w.Write([]byte(err.Error())) + return + } + + ctx := context.NewCoreContextFromViper() + + res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName) + if err != nil || len(res) == 0 { + err := errors.Errorf("proposalID [%d] does not exist", proposalID) + w.Write([]byte(err.Error())) + return + } + + var proposal gov.Proposal + cdc.MustUnmarshalBinary(res, &proposal) + + if proposal.GetStatus() != gov.StatusVotingPeriod { + err := errors.Errorf("proposal is not in Voting Period", proposalID) + w.Write([]byte(err.Error())) + return + } + + res2, err := ctx.QuerySubspace(cdc, gov.KeyVotesSubspace(proposalID), storeName) + if err != nil { + err = errors.New("ProposalID doesn't exist") + w.Write([]byte(err.Error())) + return + } + + var votes []gov.Vote + + for i := 0; i < len(res2); i++ { + var vote gov.Vote + cdc.MustUnmarshalBinary(res2[i].Value, &vote) + votes = append(votes, vote) + } + + output, err := wire.MarshalJSONIndent(cdc, votes) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + w.Write(output) + } +} + // nolint: gocyclo // todo: Split this functionality into helper functions to remove the above func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {