github.com/cosmos/cosmos-sdk@v0.50.10/x/gov/client/utils/query.go (about)

     1  package utils
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/cosmos/cosmos-sdk/client"
     7  	sdk "github.com/cosmos/cosmos-sdk/types"
     8  	authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
     9  	"github.com/cosmos/cosmos-sdk/x/gov/types"
    10  	v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
    11  	"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
    12  )
    13  
    14  const (
    15  	defaultPage  = 1
    16  	defaultLimit = 30 // should be consistent with https://github.com/cometbft/cometbft/tree/v0.37.x/rpc/core#pagination
    17  )
    18  
    19  // Proposer contains metadata of a governance proposal used for querying a
    20  // proposer.
    21  type Proposer struct {
    22  	ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
    23  	Proposer   string `json:"proposer" yaml:"proposer"`
    24  }
    25  
    26  // NewProposer returns a new Proposer given id and proposer
    27  func NewProposer(proposalID uint64, proposer string) Proposer {
    28  	return Proposer{proposalID, proposer}
    29  }
    30  
    31  // String implements the fmt.Stringer interface.
    32  func (p Proposer) String() string {
    33  	return fmt.Sprintf("Proposal with ID %d was proposed by %s", p.ProposalID, p.Proposer)
    34  }
    35  
    36  // QueryVotesByTxQuery will query for votes via a direct txs tags query. It
    37  // will fetch and build votes directly from the returned txs and returns a JSON
    38  // marshaled result or any error that occurred.
    39  func QueryVotesByTxQuery(clientCtx client.Context, params v1.QueryProposalVotesParams) ([]byte, error) {
    40  	var (
    41  		votes      []*v1.Vote
    42  		nextTxPage = defaultPage
    43  		totalLimit = params.Limit * params.Page
    44  	)
    45  
    46  	// query interrupted either if we collected enough votes or tx indexer run out of relevant txs
    47  	for len(votes) < totalLimit {
    48  		// Search for both (legacy) votes and weighted votes.
    49  		q := fmt.Sprintf("%s.%s='%d'", types.EventTypeProposalVote, types.AttributeKeyProposalID, params.ProposalID)
    50  		searchResult, err := authtx.QueryTxsByEvents(clientCtx, nextTxPage, defaultLimit, q, "")
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  
    55  		for _, info := range searchResult.Txs {
    56  			for _, msg := range info.GetTx().GetMsgs() {
    57  				if voteMsg, ok := msg.(*v1beta1.MsgVote); ok {
    58  					votes = append(votes, &v1.Vote{
    59  						Voter:      voteMsg.Voter,
    60  						ProposalId: params.ProposalID,
    61  						Options:    v1.NewNonSplitVoteOption(v1.VoteOption(voteMsg.Option)),
    62  					})
    63  				}
    64  
    65  				if voteMsg, ok := msg.(*v1.MsgVote); ok {
    66  					votes = append(votes, &v1.Vote{
    67  						Voter:      voteMsg.Voter,
    68  						ProposalId: params.ProposalID,
    69  						Options:    v1.NewNonSplitVoteOption(voteMsg.Option),
    70  					})
    71  				}
    72  
    73  				if voteWeightedMsg, ok := msg.(*v1beta1.MsgVoteWeighted); ok {
    74  					votes = append(votes, convertVote(voteWeightedMsg))
    75  				}
    76  
    77  				if voteWeightedMsg, ok := msg.(*v1.MsgVoteWeighted); ok {
    78  					votes = append(votes, &v1.Vote{
    79  						Voter:      voteWeightedMsg.Voter,
    80  						ProposalId: params.ProposalID,
    81  						Options:    voteWeightedMsg.Options,
    82  					})
    83  				}
    84  			}
    85  		}
    86  		if len(searchResult.Txs) != defaultLimit {
    87  			break
    88  		}
    89  
    90  		nextTxPage++
    91  	}
    92  	start, end := client.Paginate(len(votes), params.Page, params.Limit, 100)
    93  	if start < 0 || end < 0 {
    94  		votes = []*v1.Vote{}
    95  	} else {
    96  		votes = votes[start:end]
    97  	}
    98  
    99  	bz, err := clientCtx.LegacyAmino.MarshalJSON(votes)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	return bz, nil
   105  }
   106  
   107  // QueryVoteByTxQuery will query for a single vote via a direct txs tags query.
   108  func QueryVoteByTxQuery(clientCtx client.Context, params v1.QueryVoteParams) ([]byte, error) {
   109  	q1 := fmt.Sprintf("%s.%s='%d'", types.EventTypeProposalVote, types.AttributeKeyProposalID, params.ProposalID)
   110  	q2 := fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, params.Voter.String())
   111  	q3 := fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, params.Voter)
   112  	searchResult, err := authtx.QueryTxsByEvents(clientCtx, defaultPage, defaultLimit, fmt.Sprintf("%s AND (%s OR %s)", q1, q2, q3), "")
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	for _, info := range searchResult.Txs {
   118  		for _, msg := range info.GetTx().GetMsgs() {
   119  			// there should only be a single vote under the given conditions
   120  			var vote *v1.Vote
   121  			if voteMsg, ok := msg.(*v1beta1.MsgVote); ok {
   122  				vote = &v1.Vote{
   123  					Voter:      voteMsg.Voter,
   124  					ProposalId: params.ProposalID,
   125  					Options:    v1.NewNonSplitVoteOption(v1.VoteOption(voteMsg.Option)),
   126  				}
   127  			}
   128  
   129  			if voteMsg, ok := msg.(*v1.MsgVote); ok {
   130  				vote = &v1.Vote{
   131  					Voter:      voteMsg.Voter,
   132  					ProposalId: params.ProposalID,
   133  					Options:    v1.NewNonSplitVoteOption(voteMsg.Option),
   134  				}
   135  			}
   136  
   137  			if voteWeightedMsg, ok := msg.(*v1beta1.MsgVoteWeighted); ok {
   138  				vote = convertVote(voteWeightedMsg)
   139  			}
   140  
   141  			if voteWeightedMsg, ok := msg.(*v1.MsgVoteWeighted); ok {
   142  				vote = &v1.Vote{
   143  					Voter:      voteWeightedMsg.Voter,
   144  					ProposalId: params.ProposalID,
   145  					Options:    voteWeightedMsg.Options,
   146  				}
   147  			}
   148  
   149  			if vote != nil {
   150  				bz, err := clientCtx.Codec.MarshalJSON(vote)
   151  				if err != nil {
   152  					return nil, err
   153  				}
   154  
   155  				return bz, nil
   156  			}
   157  		}
   158  	}
   159  
   160  	return nil, fmt.Errorf("address '%s' did not vote on proposalID %d", params.Voter, params.ProposalID)
   161  }
   162  
   163  // QueryProposerByTxQuery will query for a proposer of a governance proposal by ID.
   164  // Deprecated: Should not be used, as not always accurate. It will be removed in v0.51.
   165  func QueryProposerByTxQuery(clientCtx client.Context, proposalID uint64) (Proposer, error) {
   166  	q := fmt.Sprintf("%s.%s='%d'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, proposalID)
   167  	searchResult, err := authtx.QueryTxsByEvents(clientCtx, defaultPage, defaultLimit, q, "")
   168  	if err != nil {
   169  		return Proposer{}, err
   170  	}
   171  
   172  	for _, info := range searchResult.Txs {
   173  		for _, msg := range info.GetTx().GetMsgs() {
   174  			// there should only be a single proposal under the given conditions
   175  			if subMsg, ok := msg.(*v1beta1.MsgSubmitProposal); ok {
   176  				return NewProposer(proposalID, subMsg.Proposer), nil
   177  			}
   178  			if subMsg, ok := msg.(*v1.MsgSubmitProposal); ok {
   179  				return NewProposer(proposalID, subMsg.Proposer), nil
   180  			}
   181  		}
   182  	}
   183  
   184  	return Proposer{}, fmt.Errorf("failed to find the proposer for proposalID %d", proposalID)
   185  }
   186  
   187  // convertVote converts a MsgVoteWeighted into a *v1.Vote.
   188  func convertVote(v *v1beta1.MsgVoteWeighted) *v1.Vote {
   189  	opts := make([]*v1.WeightedVoteOption, len(v.Options))
   190  	for i, o := range v.Options {
   191  		opts[i] = &v1.WeightedVoteOption{
   192  			Option: v1.VoteOption(o.Option),
   193  			Weight: o.Weight.String(),
   194  		}
   195  	}
   196  	return &v1.Vote{
   197  		Voter:      v.Voter,
   198  		ProposalId: v.ProposalId,
   199  		Options:    opts,
   200  	}
   201  }