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 }