diff --git a/Gopkg.lock b/Gopkg.lock index cf5c97909d08..050569195e12 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 2160dd80cd7a..d21d5ec68afc 100644 --- a/PENDING.md +++ b/PENDING.md @@ -27,6 +27,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 2de92575a510..b9efbc1433d0 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 beac340970fb..31b03d0d04ff 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 4ab5d02b98cf..7c66cb9ef7bf 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 c1bb62bc708a..f534ff92c901 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 ffaf4274923b..7efce9f0b48e 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 {