Packages named utils, common, or misc provide clients with no sense of what the package contains. This makes it harder for clients to use the package and makes it harder for maintainers to keep the package focused. Over time, they accumulate dependencies that can make compilation significantly and unnecessarily slower, especially in large programs. And since such package names are generic, they are more likely to collide with other packages imported by client code, forcing clients to invent names to distinguish them. cit. https://blog.golang.org/package-names
246 lines
8.2 KiB
Go
246 lines
8.2 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
"github.com/cosmos/cosmos-sdk/client/context"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
|
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
|
)
|
|
|
|
const (
|
|
defaultPage = 1
|
|
defaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
|
|
)
|
|
|
|
// Proposer contains metadata of a governance proposal used for querying a
|
|
// proposer.
|
|
type Proposer struct {
|
|
ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
|
|
Proposer string `json:"proposer" yaml:"proposer"`
|
|
}
|
|
|
|
// NewProposer returns a new Proposer given id and proposer
|
|
func NewProposer(proposalID uint64, proposer string) Proposer {
|
|
return Proposer{proposalID, proposer}
|
|
}
|
|
|
|
func (p Proposer) String() string {
|
|
return fmt.Sprintf("Proposal with ID %d was proposed by %s", p.ProposalID, p.Proposer)
|
|
}
|
|
|
|
// QueryDepositsByTxQuery will query for deposits via a direct txs tags query. It
|
|
// will fetch and build deposits directly from the returned txs and return a
|
|
// JSON marshalled result or any error that occurred.
|
|
//
|
|
// NOTE: SearchTxs is used to facilitate the txs query which does not currently
|
|
// support configurable pagination.
|
|
func QueryDepositsByTxQuery(cliCtx context.CLIContext, params types.QueryProposalParams) ([]byte, error) {
|
|
events := []string{
|
|
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit),
|
|
fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
|
|
}
|
|
|
|
// NOTE: SearchTxs is used to facilitate the txs query which does not currently
|
|
// support configurable pagination.
|
|
searchResult, err := authclient.QueryTxsByEvents(cliCtx, events, defaultPage, defaultLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var deposits []types.Deposit
|
|
|
|
for _, info := range searchResult.Txs {
|
|
for _, msg := range info.Tx.GetMsgs() {
|
|
if msg.Type() == types.TypeMsgDeposit {
|
|
depMsg := msg.(types.MsgDeposit)
|
|
|
|
deposits = append(deposits, types.Deposit{
|
|
Depositor: depMsg.Depositor,
|
|
ProposalID: params.ProposalID,
|
|
Amount: depMsg.Amount,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if cliCtx.Indent {
|
|
return cliCtx.Codec.MarshalJSONIndent(deposits, "", " ")
|
|
}
|
|
|
|
return cliCtx.Codec.MarshalJSON(deposits)
|
|
}
|
|
|
|
// QueryVotesByTxQuery will query for votes via a direct txs tags query. It
|
|
// will fetch and build votes directly from the returned txs and return a JSON
|
|
// marshalled result or any error that occurred.
|
|
func QueryVotesByTxQuery(cliCtx context.CLIContext, params types.QueryProposalVotesParams) ([]byte, error) {
|
|
var (
|
|
events = []string{
|
|
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote),
|
|
fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
|
|
}
|
|
votes []types.Vote
|
|
nextTxPage = defaultPage
|
|
totalLimit = params.Limit * params.Page
|
|
)
|
|
// query interrupted either if we collected enough votes or tx indexer run out of relevant txs
|
|
for len(votes) < totalLimit {
|
|
searchResult, err := authclient.QueryTxsByEvents(cliCtx, events, nextTxPage, defaultLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nextTxPage++
|
|
for _, info := range searchResult.Txs {
|
|
for _, msg := range info.Tx.GetMsgs() {
|
|
if msg.Type() == types.TypeMsgVote {
|
|
voteMsg := msg.(types.MsgVote)
|
|
|
|
votes = append(votes, types.Vote{
|
|
Voter: voteMsg.Voter,
|
|
ProposalID: params.ProposalID,
|
|
Option: voteMsg.Option,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
if len(searchResult.Txs) != defaultLimit {
|
|
break
|
|
}
|
|
}
|
|
start, end := client.Paginate(len(votes), params.Page, params.Limit, 100)
|
|
if start < 0 || end < 0 {
|
|
votes = []types.Vote{}
|
|
} else {
|
|
votes = votes[start:end]
|
|
}
|
|
if cliCtx.Indent {
|
|
return cliCtx.Codec.MarshalJSONIndent(votes, "", " ")
|
|
}
|
|
return cliCtx.Codec.MarshalJSON(votes)
|
|
}
|
|
|
|
// QueryVoteByTxQuery will query for a single vote via a direct txs tags query.
|
|
func QueryVoteByTxQuery(cliCtx context.CLIContext, params types.QueryVoteParams) ([]byte, error) {
|
|
events := []string{
|
|
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote),
|
|
fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
|
|
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())),
|
|
}
|
|
|
|
// NOTE: SearchTxs is used to facilitate the txs query which does not currently
|
|
// support configurable pagination.
|
|
searchResult, err := authclient.QueryTxsByEvents(cliCtx, events, defaultPage, defaultLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, info := range searchResult.Txs {
|
|
for _, msg := range info.Tx.GetMsgs() {
|
|
// there should only be a single vote under the given conditions
|
|
if msg.Type() == types.TypeMsgVote {
|
|
voteMsg := msg.(types.MsgVote)
|
|
|
|
vote := types.Vote{
|
|
Voter: voteMsg.Voter,
|
|
ProposalID: params.ProposalID,
|
|
Option: voteMsg.Option,
|
|
}
|
|
|
|
if cliCtx.Indent {
|
|
return cliCtx.Codec.MarshalJSONIndent(vote, "", " ")
|
|
}
|
|
|
|
return cliCtx.Codec.MarshalJSON(vote)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("address '%s' did not vote on proposalID %d", params.Voter, params.ProposalID)
|
|
}
|
|
|
|
// QueryDepositByTxQuery will query for a single deposit via a direct txs tags
|
|
// query.
|
|
func QueryDepositByTxQuery(cliCtx context.CLIContext, params types.QueryDepositParams) ([]byte, error) {
|
|
events := []string{
|
|
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit),
|
|
fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))),
|
|
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Depositor.String())),
|
|
}
|
|
|
|
// NOTE: SearchTxs is used to facilitate the txs query which does not currently
|
|
// support configurable pagination.
|
|
searchResult, err := authclient.QueryTxsByEvents(cliCtx, events, defaultPage, defaultLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, info := range searchResult.Txs {
|
|
for _, msg := range info.Tx.GetMsgs() {
|
|
// there should only be a single deposit under the given conditions
|
|
if msg.Type() == types.TypeMsgDeposit {
|
|
depMsg := msg.(types.MsgDeposit)
|
|
|
|
deposit := types.Deposit{
|
|
Depositor: depMsg.Depositor,
|
|
ProposalID: params.ProposalID,
|
|
Amount: depMsg.Amount,
|
|
}
|
|
|
|
if cliCtx.Indent {
|
|
return cliCtx.Codec.MarshalJSONIndent(deposit, "", " ")
|
|
}
|
|
|
|
return cliCtx.Codec.MarshalJSON(deposit)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("address '%s' did not deposit to proposalID %d", params.Depositor, params.ProposalID)
|
|
}
|
|
|
|
// QueryProposerByTxQuery will query for a proposer of a governance proposal by
|
|
// ID.
|
|
func QueryProposerByTxQuery(cliCtx context.CLIContext, proposalID uint64) (Proposer, error) {
|
|
events := []string{
|
|
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgSubmitProposal),
|
|
fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))),
|
|
}
|
|
|
|
// NOTE: SearchTxs is used to facilitate the txs query which does not currently
|
|
// support configurable pagination.
|
|
searchResult, err := authclient.QueryTxsByEvents(cliCtx, events, defaultPage, defaultLimit)
|
|
if err != nil {
|
|
return Proposer{}, err
|
|
}
|
|
|
|
for _, info := range searchResult.Txs {
|
|
for _, msg := range info.Tx.GetMsgs() {
|
|
// there should only be a single proposal under the given conditions
|
|
if msg.Type() == types.TypeMsgSubmitProposal {
|
|
subMsg := msg.(types.MsgSubmitProposal)
|
|
return NewProposer(proposalID, subMsg.Proposer.String()), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return Proposer{}, fmt.Errorf("failed to find the proposer for proposalID %d", proposalID)
|
|
}
|
|
|
|
// QueryProposalByID takes a proposalID and returns a proposal
|
|
func QueryProposalByID(proposalID uint64, cliCtx context.CLIContext, queryRoute string) ([]byte, error) {
|
|
params := types.NewQueryProposalParams(proposalID)
|
|
bz, err := cliCtx.Codec.MarshalJSON(params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/proposal", queryRoute), bz)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res, err
|
|
}
|