cosmos-sdk/x/gov/keeper/grpc_query.go

512 lines
16 KiB
Go

package keeper
import (
"context"
stderrors "errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"cosmossdk.io/collections"
"cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
v3 "cosmossdk.io/x/gov/migrations/v3"
v1 "cosmossdk.io/x/gov/types/v1"
"cosmossdk.io/x/gov/types/v1beta1"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
)
var (
_ v1.QueryServer = queryServer{}
defaultVoteOptions = &v1.ProposalVoteOptions{
OptionOne: "yes",
OptionTwo: "abstain",
OptionThree: "no",
OptionFour: "no_with_veto",
OptionSpam: "spam",
}
)
type queryServer struct{ k *Keeper }
func NewQueryServer(k *Keeper) v1.QueryServer {
return queryServer{k: k}
}
func (q queryServer) Constitution(ctx context.Context, _ *v1.QueryConstitutionRequest) (*v1.QueryConstitutionResponse, error) {
constitution, err := q.k.Constitution.Get(ctx)
if err != nil {
return nil, err
}
return &v1.QueryConstitutionResponse{Constitution: constitution}, nil
}
// Proposal returns proposal details based on ProposalID
func (q queryServer) Proposal(ctx context.Context, req *v1.QueryProposalRequest) (*v1.QueryProposalResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
if req.ProposalId == 0 {
return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0")
}
proposal, err := q.k.Proposals.Get(ctx, req.ProposalId)
if err == nil {
return &v1.QueryProposalResponse{Proposal: &proposal}, nil
}
if errors.IsOf(err, collections.ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "proposal %d doesn't exist", req.ProposalId)
}
return nil, status.Error(codes.Internal, err.Error())
}
// ProposalVoteOptions returns the proposal votes options
// It returns the stringified vote options if the proposal is a multiple choice proposal
// Otherwise it returns the generic vote options
func (q queryServer) ProposalVoteOptions(ctx context.Context, req *v1.QueryProposalVoteOptionsRequest) (*v1.QueryProposalVoteOptionsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
if req.ProposalId == 0 {
return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0")
}
ok, err := q.k.Proposals.Has(ctx, req.ProposalId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
if !ok {
return nil, status.Errorf(codes.NotFound, "proposal %d doesn't exist", req.ProposalId)
}
voteOptions, err := q.k.ProposalVoteOptions.Get(ctx, req.ProposalId)
if err != nil {
if stderrors.Is(err, collections.ErrNotFound) { // fallback to generic vote options
return &v1.QueryProposalVoteOptionsResponse{
VoteOptions: defaultVoteOptions,
}, nil
}
return nil, status.Error(codes.Internal, err.Error())
}
return &v1.QueryProposalVoteOptionsResponse{
VoteOptions: &v1.ProposalVoteOptions{
OptionOne: voteOptions.OptionOne,
OptionTwo: voteOptions.OptionTwo,
OptionThree: voteOptions.OptionThree,
OptionFour: voteOptions.OptionFour,
OptionSpam: defaultVoteOptions.OptionSpam,
},
}, nil
}
// Proposals implements the Query/Proposals gRPC method
func (q queryServer) Proposals(ctx context.Context, req *v1.QueryProposalsRequest) (*v1.QueryProposalsResponse, error) {
filteredProposals, pageRes, err := query.CollectionFilteredPaginate(ctx, q.k.Proposals, req.Pagination, func(key uint64, p v1.Proposal) (include bool, err error) {
matchVoter, matchDepositor, matchStatus := true, true, true
// match status (if supplied/valid)
if v1.ValidProposalStatus(req.ProposalStatus) {
matchStatus = p.Status == req.ProposalStatus
}
// match voter address (if supplied)
if len(req.Voter) > 0 {
voter, err := q.k.authKeeper.AddressCodec().StringToBytes(req.Voter)
if err != nil {
return false, err
}
has, err := q.k.Votes.Has(ctx, collections.Join(p.Id, sdk.AccAddress(voter)))
// if no error, vote found, matchVoter = true
matchVoter = err == nil && has
}
// match depositor (if supplied)
if len(req.Depositor) > 0 {
depositor, err := q.k.authKeeper.AddressCodec().StringToBytes(req.Depositor)
if err != nil {
return false, err
}
has, err := q.k.Deposits.Has(ctx, collections.Join(p.Id, sdk.AccAddress(depositor)))
// if no error, deposit found, matchDepositor = true
matchDepositor = err == nil && has
}
// if all match, append to results
if matchVoter && matchDepositor && matchStatus {
return true, nil
}
// continue to next item, do not include because we're appending results above.
return false, nil
}, func(_ uint64, value v1.Proposal) (*v1.Proposal, error) {
return &value, nil
})
if err != nil && !errors.IsOf(err, collections.ErrInvalidIterator) {
return nil, status.Error(codes.Internal, err.Error())
}
return &v1.QueryProposalsResponse{Proposals: filteredProposals, Pagination: pageRes}, nil
}
// Vote returns Voted information based on proposalID, voterAddr
func (q queryServer) Vote(ctx context.Context, req *v1.QueryVoteRequest) (*v1.QueryVoteResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
if req.ProposalId == 0 {
return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0")
}
if req.Voter == "" {
return nil, status.Error(codes.InvalidArgument, "empty voter address")
}
voter, err := q.k.authKeeper.AddressCodec().StringToBytes(req.Voter)
if err != nil {
return nil, err
}
vote, err := q.k.Votes.Get(ctx, collections.Join(req.ProposalId, sdk.AccAddress(voter)))
if err == nil {
return &v1.QueryVoteResponse{Vote: &vote}, nil
}
if errors.IsOf(err, collections.ErrNotFound) {
return nil, status.Errorf(codes.InvalidArgument,
"voter: %v not found for proposal: %v", req.Voter, req.ProposalId)
}
return nil, status.Error(codes.Internal, err.Error())
}
// Votes returns single proposal's votes
func (q queryServer) Votes(ctx context.Context, req *v1.QueryVotesRequest) (*v1.QueryVotesResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
if req.ProposalId == 0 {
return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0")
}
votes, pageRes, err := query.CollectionPaginate(ctx, q.k.Votes, req.Pagination, func(_ collections.Pair[uint64, sdk.AccAddress], value v1.Vote) (vote *v1.Vote, err error) {
return &value, nil
}, query.WithCollectionPaginationPairPrefix[uint64, sdk.AccAddress](req.ProposalId))
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &v1.QueryVotesResponse{Votes: votes, Pagination: pageRes}, nil
}
// Params queries all params
func (q queryServer) Params(ctx context.Context, req *v1.QueryParamsRequest) (*v1.QueryParamsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
params, err := q.k.Params.Get(ctx)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &v1.QueryParamsResponse{Params: &params}, nil
}
// MessageBasedParams queries params for a specific message
func (q queryServer) MessageBasedParams(ctx context.Context, req *v1.QueryMessageBasedParamsRequest) (*v1.QueryMessageBasedParamsResponse, error) {
if req == nil || req.MsgUrl == "" {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
params, err := q.k.MessageBasedParams.Get(ctx, req.MsgUrl)
if err == nil {
return &v1.QueryMessageBasedParamsResponse{Params: &params}, nil
}
if errors.IsOf(err, collections.ErrNotFound) {
resp, err := q.Params(ctx, &v1.QueryParamsRequest{})
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &v1.QueryMessageBasedParamsResponse{Params: &v1.MessageBasedParams{
VotingPeriod: resp.Params.VotingPeriod,
Quorum: resp.Params.Quorum,
Threshold: resp.Params.Threshold,
VetoThreshold: resp.Params.VetoThreshold,
}}, nil
}
return nil, status.Error(codes.Internal, err.Error())
}
// Deposit queries single deposit information based on proposalID, depositAddr.
func (q queryServer) Deposit(ctx context.Context, req *v1.QueryDepositRequest) (*v1.QueryDepositResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
if req.ProposalId == 0 {
return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0")
}
if req.Depositor == "" {
return nil, status.Error(codes.InvalidArgument, "empty depositor address")
}
depositor, err := q.k.authKeeper.AddressCodec().StringToBytes(req.Depositor)
if err != nil {
return nil, err
}
deposit, err := q.k.Deposits.Get(ctx, collections.Join(req.ProposalId, sdk.AccAddress(depositor)))
if err != nil {
return nil, status.Error(codes.NotFound, err.Error())
}
return &v1.QueryDepositResponse{Deposit: &deposit}, nil
}
// Deposits returns single proposal's all deposits
func (q queryServer) Deposits(ctx context.Context, req *v1.QueryDepositsRequest) (*v1.QueryDepositsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
if req.ProposalId == 0 {
return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0")
}
var deposits []*v1.Deposit
deposits, pageRes, err := query.CollectionPaginate(ctx, q.k.Deposits, req.Pagination, func(_ collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (*v1.Deposit, error) {
return &deposit, nil
}, query.WithCollectionPaginationPairPrefix[uint64, sdk.AccAddress](req.ProposalId))
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &v1.QueryDepositsResponse{Deposits: deposits, Pagination: pageRes}, nil
}
// TallyResult queries the tally of a proposal vote
func (q queryServer) TallyResult(ctx context.Context, req *v1.QueryTallyResultRequest) (*v1.QueryTallyResultResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
if req.ProposalId == 0 {
return nil, status.Error(codes.InvalidArgument, "proposal id can not be 0")
}
proposal, err := q.k.Proposals.Get(ctx, req.ProposalId)
if err != nil {
if errors.IsOf(err, collections.ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "proposal %d doesn't exist", req.ProposalId)
}
return nil, status.Error(codes.Internal, err.Error())
}
var tallyResult v1.TallyResult
switch {
case proposal.Status == v1.StatusDepositPeriod:
tallyResult = v1.EmptyTallyResult()
case proposal.Status == v1.StatusPassed || proposal.Status == v1.StatusRejected || proposal.Status == v1.StatusFailed:
tallyResult = *proposal.FinalTallyResult
default:
// proposal is in voting period
var err error
_, _, tallyResult, err = q.k.Tally(ctx, proposal)
if err != nil {
return nil, err
}
}
return &v1.QueryTallyResultResponse{Tally: &tallyResult}, nil
}
var _ v1beta1.QueryServer = legacyQueryServer{}
type legacyQueryServer struct{ qs v1.QueryServer }
// NewLegacyQueryServer returns an implementation of the v1beta1 legacy QueryServer interface.
func NewLegacyQueryServer(k *Keeper) v1beta1.QueryServer {
return &legacyQueryServer{qs: NewQueryServer(k)}
}
func (q legacyQueryServer) Proposal(ctx context.Context, req *v1beta1.QueryProposalRequest) (*v1beta1.QueryProposalResponse, error) {
resp, err := q.qs.Proposal(ctx, &v1.QueryProposalRequest{
ProposalId: req.ProposalId,
})
if err != nil {
return nil, err
}
proposal, err := v3.ConvertToLegacyProposal(*resp.Proposal)
if err != nil {
return nil, err
}
return &v1beta1.QueryProposalResponse{Proposal: proposal}, nil
}
func (q legacyQueryServer) Proposals(ctx context.Context, req *v1beta1.QueryProposalsRequest) (*v1beta1.QueryProposalsResponse, error) {
resp, err := q.qs.Proposals(ctx, &v1.QueryProposalsRequest{
ProposalStatus: v1.ProposalStatus(req.ProposalStatus),
Voter: req.Voter,
Depositor: req.Depositor,
Pagination: req.Pagination,
})
if err != nil {
return nil, err
}
legacyProposals := make([]v1beta1.Proposal, len(resp.Proposals))
for idx, proposal := range resp.Proposals {
legacyProposals[idx], err = v3.ConvertToLegacyProposal(*proposal)
if err != nil {
return nil, err
}
}
return &v1beta1.QueryProposalsResponse{
Proposals: legacyProposals,
Pagination: resp.Pagination,
}, nil
}
func (q legacyQueryServer) Vote(ctx context.Context, req *v1beta1.QueryVoteRequest) (*v1beta1.QueryVoteResponse, error) {
resp, err := q.qs.Vote(ctx, &v1.QueryVoteRequest{
ProposalId: req.ProposalId,
Voter: req.Voter,
})
if err != nil {
return nil, err
}
vote, err := v3.ConvertToLegacyVote(*resp.Vote)
if err != nil {
return nil, err
}
return &v1beta1.QueryVoteResponse{Vote: vote}, nil
}
func (q legacyQueryServer) Votes(ctx context.Context, req *v1beta1.QueryVotesRequest) (*v1beta1.QueryVotesResponse, error) {
resp, err := q.qs.Votes(ctx, &v1.QueryVotesRequest{
ProposalId: req.ProposalId,
Pagination: req.Pagination,
})
if err != nil {
return nil, err
}
votes := make([]v1beta1.Vote, len(resp.Votes))
for i, v := range resp.Votes {
votes[i], err = v3.ConvertToLegacyVote(*v)
if err != nil {
return nil, err
}
}
return &v1beta1.QueryVotesResponse{
Votes: votes,
Pagination: resp.Pagination,
}, nil
}
func (q legacyQueryServer) Params(ctx context.Context, req *v1beta1.QueryParamsRequest) (*v1beta1.QueryParamsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
resp, err := q.qs.Params(ctx, &v1.QueryParamsRequest{
ParamsType: req.ParamsType,
})
if err != nil {
return nil, err
}
response := &v1beta1.QueryParamsResponse{}
switch req.ParamsType {
case v1beta1.ParamDeposit:
minDeposit := sdk.NewCoins(resp.Params.MinDeposit...)
response.DepositParams = v1beta1.NewDepositParams(minDeposit, *resp.Params.MaxDepositPeriod)
case v1beta1.ParamVoting:
response.VotingParams = v1beta1.NewVotingParams(*resp.Params.VotingPeriod)
case v1beta1.ParamTallying:
quorum, err := sdkmath.LegacyNewDecFromStr(resp.Params.Quorum)
if err != nil {
return nil, err
}
threshold, err := sdkmath.LegacyNewDecFromStr(resp.Params.Threshold)
if err != nil {
return nil, err
}
vetoThreshold, err := sdkmath.LegacyNewDecFromStr(resp.Params.VetoThreshold)
if err != nil {
return nil, err
}
response.TallyParams = v1beta1.NewTallyParams(quorum, threshold, vetoThreshold)
default:
return nil, status.Errorf(codes.InvalidArgument, "%s is not a valid parameter type", req.ParamsType)
}
return response, nil
}
func (q legacyQueryServer) Deposit(ctx context.Context, req *v1beta1.QueryDepositRequest) (*v1beta1.QueryDepositResponse, error) {
resp, err := q.qs.Deposit(ctx, &v1.QueryDepositRequest{
ProposalId: req.ProposalId,
Depositor: req.Depositor,
})
if err != nil {
return nil, err
}
deposit := v3.ConvertToLegacyDeposit(resp.Deposit)
return &v1beta1.QueryDepositResponse{Deposit: deposit}, nil
}
func (q legacyQueryServer) Deposits(ctx context.Context, req *v1beta1.QueryDepositsRequest) (*v1beta1.QueryDepositsResponse, error) {
resp, err := q.qs.Deposits(ctx, &v1.QueryDepositsRequest{
ProposalId: req.ProposalId,
Pagination: req.Pagination,
})
if err != nil {
return nil, err
}
deposits := make([]v1beta1.Deposit, len(resp.Deposits))
for idx, deposit := range resp.Deposits {
deposits[idx] = v3.ConvertToLegacyDeposit(deposit)
}
return &v1beta1.QueryDepositsResponse{Deposits: deposits, Pagination: resp.Pagination}, nil
}
func (q legacyQueryServer) TallyResult(ctx context.Context, req *v1beta1.QueryTallyResultRequest) (*v1beta1.QueryTallyResultResponse, error) {
resp, err := q.qs.TallyResult(ctx, &v1.QueryTallyResultRequest{
ProposalId: req.ProposalId,
})
if err != nil {
return nil, err
}
tally, err := v3.ConvertToLegacyTallyResult(resp.Tally)
if err != nil {
return nil, err
}
return &v1beta1.QueryTallyResultResponse{Tally: tally}, nil
}