cosmos-sdk/x/gov/client/cli/query.go
Callum Waters eb5b11be8a
feat: implement new gov msg & query servers (#10868)
## Description

Ref: #9438

This PR performs the major work of swapping out the v1beta1 msg server and query server for the new one which can process a proposal as an array of messages. This PR still retains the legacy servers which simply wrap around the new ones, providing the same interface as before.

In order to keep backwards compatibility, a new msg, `MsgExecLegacyContent` has been created which allows `Content` to become a `Msg` type and still be used as part of the new implementation. 


---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
2022-01-21 11:14:00 +00:00

672 lines
17 KiB
Go

package cli
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
"github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta2"
)
// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd() *cobra.Command {
// Group gov queries under a subcommand
govQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Querying commands for the governance module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
govQueryCmd.AddCommand(
GetCmdQueryProposal(),
GetCmdQueryProposals(),
GetCmdQueryVote(),
GetCmdQueryVotes(),
GetCmdQueryParam(),
GetCmdQueryParams(),
GetCmdQueryProposer(),
GetCmdQueryDeposit(),
GetCmdQueryDeposits(),
GetCmdQueryTally(),
)
return govQueryCmd
}
// GetCmdQueryProposal implements the query proposal command.
func GetCmdQueryProposal() *cobra.Command {
cmd := &cobra.Command{
Use: "proposal [proposal-id]",
Args: cobra.ExactArgs(1),
Short: "Query details of a single proposal",
Long: strings.TrimSpace(
fmt.Sprintf(`Query details for a proposal. You can find the
proposal-id by running "%s query gov proposals".
Example:
$ %s query gov proposal 1
`,
version.AppName, version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := v1beta2.NewQueryClient(clientCtx)
// validate that the proposal id is a uint
proposalID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0])
}
// Query the proposal
res, err := queryClient.Proposal(
cmd.Context(),
&v1beta2.QueryProposalRequest{ProposalId: proposalID},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res.Proposal)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryProposals implements a query proposals command. Command to Get a
// Proposal Information.
func GetCmdQueryProposals() *cobra.Command {
cmd := &cobra.Command{
Use: "proposals",
Short: "Query proposals with optional filters",
Long: strings.TrimSpace(
fmt.Sprintf(`Query for a all paginated proposals that match optional filters:
Example:
$ %s query gov proposals --depositor cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk
$ %s query gov proposals --voter cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk
$ %s query gov proposals --status (DepositPeriod|VotingPeriod|Passed|Rejected)
$ %s query gov proposals --page=2 --limit=100
`,
version.AppName, version.AppName, version.AppName, version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
bechDepositorAddr, _ := cmd.Flags().GetString(flagDepositor)
bechVoterAddr, _ := cmd.Flags().GetString(flagVoter)
strProposalStatus, _ := cmd.Flags().GetString(flagStatus)
var proposalStatus v1beta2.ProposalStatus
if len(bechDepositorAddr) != 0 {
_, err := sdk.AccAddressFromBech32(bechDepositorAddr)
if err != nil {
return err
}
}
if len(bechVoterAddr) != 0 {
_, err := sdk.AccAddressFromBech32(bechVoterAddr)
if err != nil {
return err
}
}
if len(strProposalStatus) != 0 {
proposalStatus1, err := v1beta2.ProposalStatusFromString(gcutils.NormalizeProposalStatus(strProposalStatus))
proposalStatus = proposalStatus1
if err != nil {
return err
}
}
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := v1beta2.NewQueryClient(clientCtx)
pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}
res, err := queryClient.Proposals(
cmd.Context(),
&v1beta2.QueryProposalsRequest{
ProposalStatus: proposalStatus,
Voter: bechVoterAddr,
Depositor: bechDepositorAddr,
Pagination: pageReq,
},
)
if err != nil {
return err
}
if len(res.GetProposals()) == 0 {
return fmt.Errorf("no proposals found")
}
return clientCtx.PrintProto(res)
},
}
cmd.Flags().String(flagDepositor, "", "(optional) filter by proposals deposited on by depositor")
cmd.Flags().String(flagVoter, "", "(optional) filter by proposals voted on by voted")
cmd.Flags().String(flagStatus, "", "(optional) filter proposals by proposal status, status: deposit_period/voting_period/passed/rejected")
flags.AddPaginationFlagsToCmd(cmd, "proposals")
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryVote implements the query proposal vote command. Command to Get a
// Proposal Information.
func GetCmdQueryVote() *cobra.Command {
cmd := &cobra.Command{
Use: "vote [proposal-id] [voter-addr]",
Args: cobra.ExactArgs(2),
Short: "Query details of a single vote",
Long: strings.TrimSpace(
fmt.Sprintf(`Query details for a single vote on a proposal given its identifier.
Example:
$ %s query gov vote 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk
`,
version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := v1beta2.NewQueryClient(clientCtx)
// validate that the proposal id is a uint
proposalID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0])
}
// check to see if the proposal is in the store
ctx := cmd.Context()
_, err = queryClient.Proposal(
ctx,
&v1beta2.QueryProposalRequest{ProposalId: proposalID},
)
if err != nil {
return fmt.Errorf("failed to fetch proposal-id %d: %s", proposalID, err)
}
voterAddr, err := sdk.AccAddressFromBech32(args[1])
if err != nil {
return err
}
res, err := queryClient.Vote(
ctx,
&v1beta2.QueryVoteRequest{ProposalId: proposalID, Voter: args[1]},
)
if err != nil {
return err
}
vote := res.GetVote()
if vote.Empty() {
params := v1beta2.NewQueryVoteParams(proposalID, voterAddr)
resByTxQuery, err := gcutils.QueryVoteByTxQuery(clientCtx, params)
if err != nil {
return err
}
if err := clientCtx.Codec.UnmarshalJSON(resByTxQuery, vote); err != nil {
return err
}
}
return clientCtx.PrintProto(res.Vote)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryVotes implements the command to query for proposal votes.
func GetCmdQueryVotes() *cobra.Command {
cmd := &cobra.Command{
Use: "votes [proposal-id]",
Args: cobra.ExactArgs(1),
Short: "Query votes on a proposal",
Long: strings.TrimSpace(
fmt.Sprintf(`Query vote details for a single proposal by its identifier.
Example:
$ %[1]s query gov votes 1
$ %[1]s query gov votes 1 --page=2 --limit=100
`,
version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := v1beta2.NewQueryClient(clientCtx)
// validate that the proposal id is a uint
proposalID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0])
}
// check to see if the proposal is in the store
ctx := cmd.Context()
proposalRes, err := queryClient.Proposal(
ctx,
&v1beta2.QueryProposalRequest{ProposalId: proposalID},
)
if err != nil {
return fmt.Errorf("failed to fetch proposal-id %d: %s", proposalID, err)
}
propStatus := proposalRes.GetProposal().Status
if !(propStatus == v1beta2.StatusVotingPeriod || propStatus == v1beta2.StatusDepositPeriod) {
page, _ := cmd.Flags().GetInt(flags.FlagPage)
limit, _ := cmd.Flags().GetInt(flags.FlagLimit)
params := v1beta2.NewQueryProposalVotesParams(proposalID, page, limit)
resByTxQuery, err := gcutils.QueryVotesByTxQuery(clientCtx, params)
if err != nil {
return err
}
var votes v1beta2.Votes
// TODO migrate to use JSONCodec (implement MarshalJSONArray
// or wrap lists of proto.Message in some other message)
clientCtx.LegacyAmino.MustUnmarshalJSON(resByTxQuery, &votes)
return clientCtx.PrintObjectLegacy(votes)
}
pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}
res, err := queryClient.Votes(
ctx,
&v1beta2.QueryVotesRequest{ProposalId: proposalID, Pagination: pageReq},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
flags.AddPaginationFlagsToCmd(cmd, "votes")
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryDeposit implements the query proposal deposit command. Command to
// get a specific Deposit Information
func GetCmdQueryDeposit() *cobra.Command {
cmd := &cobra.Command{
Use: "deposit [proposal-id] [depositer-addr]",
Args: cobra.ExactArgs(2),
Short: "Query details of a deposit",
Long: strings.TrimSpace(
fmt.Sprintf(`Query details for a single proposal deposit on a proposal by its identifier.
Example:
$ %s query gov deposit 1 cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk
`,
version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := v1beta2.NewQueryClient(clientCtx)
// validate that the proposal id is a uint
proposalID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0])
}
// check to see if the proposal is in the store
ctx := cmd.Context()
_, err = queryClient.Proposal(
ctx,
&v1beta2.QueryProposalRequest{ProposalId: proposalID},
)
if err != nil {
return fmt.Errorf("failed to fetch proposal-id %d: %s", proposalID, err)
}
res, err := queryClient.Deposit(
ctx,
&v1beta2.QueryDepositRequest{ProposalId: proposalID, Depositor: args[1]},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res.Deposit)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryDeposits implements the command to query for proposal deposits.
func GetCmdQueryDeposits() *cobra.Command {
cmd := &cobra.Command{
Use: "deposits [proposal-id]",
Args: cobra.ExactArgs(1),
Short: "Query deposits on a proposal",
Long: strings.TrimSpace(
fmt.Sprintf(`Query details for all deposits on a proposal.
You can find the proposal-id by running "%s query gov proposals".
Example:
$ %s query gov deposits 1
`,
version.AppName, version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := v1beta2.NewQueryClient(clientCtx)
// validate that the proposal id is a uint
proposalID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("proposal-id %s not a valid uint, please input a valid proposal-id", args[0])
}
// check to see if the proposal is in the store
ctx := cmd.Context()
_, err = queryClient.Proposal(
ctx,
&v1beta2.QueryProposalRequest{ProposalId: proposalID},
)
if err != nil {
return fmt.Errorf("failed to fetch proposal-id %d: %s", proposalID, err)
}
pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}
res, err := queryClient.Deposits(
ctx,
&v1beta2.QueryDepositsRequest{ProposalId: proposalID, Pagination: pageReq},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
flags.AddPaginationFlagsToCmd(cmd, "deposits")
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryTally implements the command to query for proposal tally result.
func GetCmdQueryTally() *cobra.Command {
cmd := &cobra.Command{
Use: "tally [proposal-id]",
Args: cobra.ExactArgs(1),
Short: "Get the tally of a proposal vote",
Long: strings.TrimSpace(
fmt.Sprintf(`Query tally of votes on a proposal. You can find
the proposal-id by running "%s query gov proposals".
Example:
$ %s query gov tally 1
`,
version.AppName, version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := v1beta2.NewQueryClient(clientCtx)
// validate that the proposal id is a uint
proposalID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("proposal-id %s not a valid int, please input a valid proposal-id", args[0])
}
// check to see if the proposal is in the store
ctx := cmd.Context()
_, err = queryClient.Proposal(
ctx,
&v1beta2.QueryProposalRequest{ProposalId: proposalID},
)
if err != nil {
return fmt.Errorf("failed to fetch proposal-id %d: %s", proposalID, err)
}
// Query store
res, err := queryClient.TallyResult(
ctx,
&v1beta2.QueryTallyResultRequest{ProposalId: proposalID},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res.Tally)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryParams implements the query params command.
func GetCmdQueryParams() *cobra.Command {
cmd := &cobra.Command{
Use: "params",
Short: "Query the parameters of the governance process",
Long: strings.TrimSpace(
fmt.Sprintf(`Query the all the parameters for the governance process.
Example:
$ %s query gov params
`,
version.AppName,
),
),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := v1beta2.NewQueryClient(clientCtx)
// Query store for all 3 params
ctx := cmd.Context()
votingRes, err := queryClient.Params(
ctx,
&v1beta2.QueryParamsRequest{ParamsType: "voting"},
)
if err != nil {
return err
}
tallyRes, err := queryClient.Params(
ctx,
&v1beta2.QueryParamsRequest{ParamsType: "tallying"},
)
if err != nil {
return err
}
depositRes, err := queryClient.Params(
ctx,
&v1beta2.QueryParamsRequest{ParamsType: "deposit"},
)
if err != nil {
return err
}
params := v1beta2.NewParams(
*votingRes.GetVotingParams(),
*tallyRes.GetTallyParams(),
*depositRes.GetDepositParams(),
)
return clientCtx.PrintObjectLegacy(params)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryParam implements the query param command.
func GetCmdQueryParam() *cobra.Command {
cmd := &cobra.Command{
Use: "param [param-type]",
Args: cobra.ExactArgs(1),
Short: "Query the parameters (voting|tallying|deposit) of the governance process",
Long: strings.TrimSpace(
fmt.Sprintf(`Query the all the parameters for the governance process.
Example:
$ %s query gov param voting
$ %s query gov param tallying
$ %s query gov param deposit
`,
version.AppName, version.AppName, version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := v1beta2.NewQueryClient(clientCtx)
// Query store
res, err := queryClient.Params(
cmd.Context(),
&v1beta2.QueryParamsRequest{ParamsType: args[0]},
)
if err != nil {
return err
}
var out fmt.Stringer
switch args[0] {
case "voting":
out = res.GetVotingParams()
case "tallying":
out = res.GetTallyParams()
case "deposit":
out = res.GetDepositParams()
default:
return fmt.Errorf("argument must be one of (voting|tallying|deposit), was %s", args[0])
}
return clientCtx.PrintObjectLegacy(out)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryProposer implements the query proposer command.
func GetCmdQueryProposer() *cobra.Command {
cmd := &cobra.Command{
Use: "proposer [proposal-id]",
Args: cobra.ExactArgs(1),
Short: "Query the proposer of a governance proposal",
Long: strings.TrimSpace(
fmt.Sprintf(`Query which address proposed a proposal with a given ID.
Example:
$ %s query gov proposer 1
`,
version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
// validate that the proposalID is a uint
proposalID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("proposal-id %s is not a valid uint", args[0])
}
prop, err := gcutils.QueryProposerByTxQuery(clientCtx, proposalID)
if err != nil {
return err
}
return clientCtx.PrintObjectLegacy(prop)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}