github.com/Finschia/finschia-sdk@v0.48.1/x/gov/client/utils/query.go (about)

     1  package utils
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/Finschia/finschia-sdk/client"
     7  	sdk "github.com/Finschia/finschia-sdk/types"
     8  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
     9  	authtx "github.com/Finschia/finschia-sdk/x/auth/tx"
    10  	"github.com/Finschia/finschia-sdk/x/gov/types"
    11  )
    12  
    13  const (
    14  	defaultPage  = 1
    15  	defaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
    16  )
    17  
    18  // Proposer contains metadata of a governance proposal used for querying a
    19  // proposer.
    20  type Proposer struct {
    21  	ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
    22  	Proposer   string `json:"proposer" yaml:"proposer"`
    23  }
    24  
    25  // NewProposer returns a new Proposer given id and proposer
    26  func NewProposer(proposalID uint64, proposer string) Proposer {
    27  	return Proposer{proposalID, proposer}
    28  }
    29  
    30  func (p Proposer) String() string {
    31  	return fmt.Sprintf("Proposal with ID %d was proposed by %s", p.ProposalID, p.Proposer)
    32  }
    33  
    34  // QueryDepositsByTxQuery will query for deposits via a direct txs tags query. It
    35  // will fetch and build deposits directly from the returned txs and return a
    36  // JSON marshalled result or any error that occurred.
    37  //
    38  // NOTE: SearchTxs is used to facilitate the txs query which does not currently
    39  // support configurable pagination.
    40  func QueryDepositsByTxQuery(clientCtx client.Context, params types.QueryProposalParams) ([]byte, error) {
    41  	var deposits []types.Deposit
    42  
    43  	// initial deposit was submitted with proposal, so must be queried separately
    44  	initialDeposit, err := queryInitialDepositByTxQuery(clientCtx, params.ProposalID)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	if !initialDeposit.Amount.IsZero() {
    50  		deposits = append(deposits, initialDeposit)
    51  	}
    52  
    53  	searchResult, err := combineEvents(
    54  		clientCtx, defaultPage,
    55  		// Query legacy Msgs event action
    56  		[]string{
    57  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit),
    58  			fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
    59  		},
    60  		// Query proto Msgs event action
    61  		[]string{
    62  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgDeposit{})),
    63  			fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
    64  		},
    65  	)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	for _, info := range searchResult.Txs {
    71  		for _, msg := range info.GetTx().GetMsgs() {
    72  			if depMsg, ok := msg.(*types.MsgDeposit); ok {
    73  				deposits = append(deposits, types.Deposit{
    74  					Depositor:  depMsg.Depositor,
    75  					ProposalId: params.ProposalID,
    76  					Amount:     depMsg.Amount,
    77  				})
    78  			}
    79  		}
    80  	}
    81  
    82  	bz, err := clientCtx.LegacyAmino.MarshalJSON(deposits)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	return bz, nil
    88  }
    89  
    90  // QueryVotesByTxQuery will query for votes via a direct txs tags query. It
    91  // will fetch and build votes directly from the returned txs and return a JSON
    92  // marshalled result or any error that occurred.
    93  func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVotesParams) ([]byte, error) {
    94  	var (
    95  		votes      []types.Vote
    96  		nextTxPage = defaultPage
    97  		totalLimit = params.Limit * params.Page
    98  	)
    99  
   100  	// query interrupted either if we collected enough votes or tx indexer run out of relevant txs
   101  	for len(votes) < totalLimit {
   102  		// Search for both (legacy) votes and weighted votes.
   103  		searchResult, err := combineEvents(
   104  			clientCtx, nextTxPage,
   105  			// Query legacy Vote Msgs
   106  			[]string{
   107  				fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote),
   108  				fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
   109  			},
   110  			// Query Vote proto Msgs
   111  			[]string{
   112  				fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVote{})),
   113  				fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
   114  			},
   115  			// Query legacy VoteWeighted Msgs
   116  			[]string{
   117  				fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVoteWeighted),
   118  				fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
   119  			},
   120  			// Query VoteWeighted proto Msgs
   121  			[]string{
   122  				fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVoteWeighted{})),
   123  				fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
   124  			},
   125  		)
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  
   130  		for _, info := range searchResult.Txs {
   131  			for _, msg := range info.GetTx().GetMsgs() {
   132  				if voteMsg, ok := msg.(*types.MsgVote); ok {
   133  					votes = append(votes, types.Vote{
   134  						Voter:      voteMsg.Voter,
   135  						ProposalId: params.ProposalID,
   136  						Options:    types.NewNonSplitVoteOption(voteMsg.Option),
   137  					})
   138  				}
   139  
   140  				if voteWeightedMsg, ok := msg.(*types.MsgVoteWeighted); ok {
   141  					votes = append(votes, types.Vote{
   142  						Voter:      voteWeightedMsg.Voter,
   143  						ProposalId: params.ProposalID,
   144  						Options:    voteWeightedMsg.Options,
   145  					})
   146  				}
   147  			}
   148  		}
   149  		if len(searchResult.Txs) != defaultLimit {
   150  			break
   151  		}
   152  
   153  		nextTxPage++
   154  	}
   155  	start, end := client.Paginate(len(votes), params.Page, params.Limit, 100)
   156  	if start < 0 || end < 0 {
   157  		votes = []types.Vote{}
   158  	} else {
   159  		votes = votes[start:end]
   160  	}
   161  
   162  	bz, err := clientCtx.LegacyAmino.MarshalJSON(votes)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	return bz, nil
   168  }
   169  
   170  // QueryVoteByTxQuery will query for a single vote via a direct txs tags query.
   171  func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) ([]byte, error) {
   172  	searchResult, err := combineEvents(
   173  		clientCtx, defaultPage,
   174  		// Query legacy Vote Msgs
   175  		[]string{
   176  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote),
   177  			fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
   178  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())),
   179  		},
   180  		// Query Vote proto Msgs
   181  		[]string{
   182  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVote{})),
   183  			fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
   184  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())),
   185  		},
   186  		// Query legacy VoteWeighted Msgs
   187  		[]string{
   188  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVoteWeighted),
   189  			fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
   190  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())),
   191  		},
   192  		// Query VoteWeighted proto Msgs
   193  		[]string{
   194  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVoteWeighted{})),
   195  			fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
   196  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())),
   197  		},
   198  	)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	for _, info := range searchResult.Txs {
   204  		for _, msg := range info.GetTx().GetMsgs() {
   205  			// there should only be a single vote under the given conditions
   206  			var vote *types.Vote
   207  			if voteMsg, ok := msg.(*types.MsgVote); ok {
   208  				vote = &types.Vote{
   209  					Voter:      voteMsg.Voter,
   210  					ProposalId: params.ProposalID,
   211  					Options:    types.NewNonSplitVoteOption(voteMsg.Option),
   212  				}
   213  			}
   214  
   215  			if voteWeightedMsg, ok := msg.(*types.MsgVoteWeighted); ok {
   216  				vote = &types.Vote{
   217  					Voter:      voteWeightedMsg.Voter,
   218  					ProposalId: params.ProposalID,
   219  					Options:    voteWeightedMsg.Options,
   220  				}
   221  			}
   222  
   223  			if vote != nil {
   224  				bz, err := clientCtx.Codec.MarshalJSON(vote)
   225  				if err != nil {
   226  					return nil, err
   227  				}
   228  
   229  				return bz, nil
   230  			}
   231  		}
   232  	}
   233  
   234  	return nil, fmt.Errorf("address '%s' did not vote on proposalID %d", params.Voter, params.ProposalID)
   235  }
   236  
   237  // QueryDepositByTxQuery will query for a single deposit via a direct txs tags
   238  // query.
   239  func QueryDepositByTxQuery(clientCtx client.Context, params types.QueryDepositParams) ([]byte, error) {
   240  	// initial deposit was submitted with proposal, so must be queried separately
   241  	initialDeposit, err := queryInitialDepositByTxQuery(clientCtx, params.ProposalID)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	if !initialDeposit.Amount.IsZero() {
   247  		bz, err := clientCtx.Codec.MarshalJSON(&initialDeposit)
   248  		if err != nil {
   249  			return nil, err
   250  		}
   251  
   252  		return bz, nil
   253  	}
   254  
   255  	searchResult, err := combineEvents(
   256  		clientCtx, defaultPage,
   257  		// Query legacy Msgs event action
   258  		[]string{
   259  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit),
   260  			fmt.Sprintf("%s.%s='%d'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, params.ProposalID),
   261  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, params.Depositor.String()),
   262  		},
   263  		// Query proto Msgs event action v1
   264  		[]string{
   265  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgDeposit{})),
   266  			fmt.Sprintf("%s.%s='%d'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, params.ProposalID),
   267  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, params.Depositor.String()),
   268  		},
   269  	)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  
   274  	for _, info := range searchResult.Txs {
   275  		for _, msg := range info.GetTx().GetMsgs() {
   276  			// there should only be a single deposit under the given conditions
   277  			if depMsg, ok := msg.(*types.MsgDeposit); ok {
   278  				deposit := types.Deposit{
   279  					Depositor:  depMsg.Depositor,
   280  					ProposalId: params.ProposalID,
   281  					Amount:     depMsg.Amount,
   282  				}
   283  
   284  				bz, err := clientCtx.Codec.MarshalJSON(&deposit)
   285  				if err != nil {
   286  					return nil, err
   287  				}
   288  
   289  				return bz, nil
   290  			}
   291  		}
   292  	}
   293  
   294  	return nil, fmt.Errorf("address '%s' did not deposit to proposalID %d", params.Depositor, params.ProposalID)
   295  }
   296  
   297  // QueryProposerByTxQuery will query for a proposer of a governance proposal by
   298  // ID.
   299  func QueryProposerByTxQuery(clientCtx client.Context, proposalID uint64) (Proposer, error) {
   300  	searchResult, err := combineEvents(
   301  		clientCtx,
   302  		defaultPage,
   303  		// Query legacy Msgs event action
   304  		[]string{
   305  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgSubmitProposal),
   306  			fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))),
   307  		},
   308  		// Query proto Msgs event action
   309  		[]string{
   310  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgSubmitProposal{})),
   311  			fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))),
   312  		},
   313  	)
   314  	if err != nil {
   315  		return Proposer{}, err
   316  	}
   317  
   318  	for _, info := range searchResult.Txs {
   319  		for _, msg := range info.GetTx().GetMsgs() {
   320  			// there should only be a single proposal under the given conditions
   321  			if subMsg, ok := msg.(*types.MsgSubmitProposal); ok {
   322  				return NewProposer(proposalID, subMsg.Proposer), nil
   323  			}
   324  		}
   325  	}
   326  
   327  	return Proposer{}, fmt.Errorf("failed to find the proposer for proposalID %d", proposalID)
   328  }
   329  
   330  // QueryProposalByID takes a proposalID and returns a proposal
   331  func QueryProposalByID(proposalID uint64, clientCtx client.Context, queryRoute string) ([]byte, error) {
   332  	params := types.NewQueryProposalParams(proposalID)
   333  	bz, err := clientCtx.LegacyAmino.MarshalJSON(params)
   334  	if err != nil {
   335  		return nil, err
   336  	}
   337  
   338  	res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/proposal", queryRoute), bz)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	return res, err
   344  }
   345  
   346  // queryInitialDepositByTxQuery will query for a initial deposit of a governance proposal by
   347  // ID.
   348  func queryInitialDepositByTxQuery(clientCtx client.Context, proposalID uint64) (types.Deposit, error) {
   349  	searchResult, err := combineEvents(
   350  		clientCtx, defaultPage,
   351  		// Query legacy Msgs event action
   352  		[]string{
   353  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgSubmitProposal),
   354  			fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))),
   355  		},
   356  		// Query proto Msgs event action
   357  		[]string{
   358  			fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgSubmitProposal{})),
   359  			fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))),
   360  		},
   361  	)
   362  	if err != nil {
   363  		return types.Deposit{}, err
   364  	}
   365  
   366  	for _, info := range searchResult.Txs {
   367  		for _, msg := range info.GetTx().GetMsgs() {
   368  			// there should only be a single proposal under the given conditions
   369  			if subMsg, ok := msg.(*types.MsgSubmitProposal); ok {
   370  				return types.Deposit{
   371  					ProposalId: proposalID,
   372  					Depositor:  subMsg.Proposer,
   373  					Amount:     subMsg.InitialDeposit,
   374  				}, nil
   375  			}
   376  		}
   377  	}
   378  
   379  	return types.Deposit{}, sdkerrors.ErrNotFound.Wrapf("failed to find the initial deposit for proposalID %d", proposalID)
   380  }
   381  
   382  // combineEvents queries txs by events with all events from each event group,
   383  // and combines all those events together.
   384  //
   385  // Tx are indexed in tendermint via their Msgs `Type()`, which can be:
   386  // - via legacy Msgs (amino or proto), their `Type()` is a custom string,
   387  // - via ADR-031 proto msgs, their `Type()` is the protobuf FQ method name.
   388  // In searching for events, we search for both `Type()`s, and we use the
   389  // `combineEvents` function here to merge events.
   390  func combineEvents(clientCtx client.Context, page int, eventGroups ...[]string) (*sdk.SearchTxsResult, error) {
   391  	// Only the Txs field will be populated in the final SearchTxsResult.
   392  	allTxs := []*sdk.TxResponse{}
   393  	for _, events := range eventGroups {
   394  		res, err := authtx.QueryTxsByEvents(clientCtx, events, page, defaultLimit, "")
   395  		if err != nil {
   396  			return nil, err
   397  		}
   398  		allTxs = append(allTxs, res.Txs...)
   399  	}
   400  
   401  	return &sdk.SearchTxsResult{Txs: allTxs}, nil
   402  }