add querier

This commit is contained in:
rhuairahrighairigh 2020-03-13 15:11:31 +00:00
parent f773f7f278
commit a0e4ee7736
5 changed files with 554 additions and 0 deletions

View File

@ -66,6 +66,22 @@ func (k Keeper) DeleteCommittee(ctx sdk.Context, committeeID uint64) {
store.Delete(types.GetKeyFromID(committeeID)) store.Delete(types.GetKeyFromID(committeeID))
} }
// IterateCommittees provides an iterator over all stored committees.
// For each committee, cb will be called. If cb returns true, the iterator will close and stop.
func (k Keeper) IterateCommittees(ctx sdk.Context, cb func(committee types.Committee) (stop bool)) {
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.CommitteeKeyPrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var committee types.Committee
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &committee)
if cb(committee) {
break
}
}
}
// ---------- Proposals ---------- // ---------- Proposals ----------
// SetNextProposalID stores an ID to be used for the next created proposal // SetNextProposalID stores an ID to be used for the next created proposal

View File

@ -0,0 +1,220 @@
package keeper
import (
"fmt"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/kava-labs/kava/x/committee/types"
)
// NewQuerier creates a new gov Querier instance
func NewQuerier(keeper Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) {
switch path[0] {
case types.QueryCommittees:
return queryCommittees(ctx, path[1:], req, keeper)
case types.QueryCommittee:
return queryCommittee(ctx, path[1:], req, keeper)
case types.QueryProposals:
return queryProposals(ctx, path[1:], req, keeper)
case types.QueryProposal:
return queryProposal(ctx, path[1:], req, keeper)
case types.QueryVotes:
return queryVotes(ctx, path[1:], req, keeper)
case types.QueryVote:
return queryVote(ctx, path[1:], req, keeper)
case types.QueryTally:
return queryTally(ctx, path[1:], req, keeper)
// case types.QueryParams:
// return queryParams(ctx, path[1:], req, keeper)
default:
return nil, sdk.ErrUnknownRequest(fmt.Sprintf("unknown %s query endpoint", types.ModuleName))
}
}
}
// ---------- Committees ----------
func queryCommittees(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
committees := []types.Committee{}
keeper.IterateCommittees(ctx, func(com types.Committee) bool {
committees = append(committees, com)
return false
})
bz, err := codec.MarshalJSONIndent(keeper.cdc, committees)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return bz, nil
}
func queryCommittee(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
var params types.QueryCommitteeParams
err := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
}
committee, found := keeper.GetCommittee(ctx, params.CommitteeID)
if !found {
return nil, sdk.ErrInternal("not found") ///types.ErrUnknownProposal(types.DefaultCodespace, params.ProposalID)
}
bz, err := codec.MarshalJSONIndent(keeper.cdc, committee)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return bz, nil
}
// ---------- Proposals ----------
func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
var params types.QueryCommitteeParams
err := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
}
proposals := []types.Proposal{}
keeper.IterateProposals(ctx, func(p types.Proposal) bool {
if p.CommitteeID == params.CommitteeID {
proposals = append(proposals, p)
}
return false
})
bz, err := codec.MarshalJSONIndent(keeper.cdc, proposals)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return bz, nil
}
func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
var params types.QueryProposalParams
err := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
}
proposal, found := keeper.GetProposal(ctx, params.ProposalID)
if !found {
return nil, sdk.ErrInternal("not found") // TODO types.ErrUnknownProposal(types.DefaultCodespace, params.ProposalID)
}
bz, err := codec.MarshalJSONIndent(keeper.cdc, proposal)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return bz, nil
}
// ---------- Votes ----------
func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
var params types.QueryProposalParams
err := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
}
votes := []types.Vote{}
keeper.IterateVotes(ctx, params.ProposalID, func(v types.Vote) bool {
votes = append(votes, v)
return false
})
bz, err := codec.MarshalJSONIndent(keeper.cdc, votes)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return bz, nil
}
func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
var params types.QueryVoteParams
err := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
}
vote, found := keeper.GetVote(ctx, params.ProposalID, params.Voter)
if !found {
return nil, sdk.ErrInternal("not found")
}
bz, err := codec.MarshalJSONIndent(keeper.cdc, vote)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return bz, nil
}
// ---------- Tally ----------
func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
var params types.QueryProposalParams
err := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
}
// TODO split tally and process result logic so tally logic can be used here
pr, found := keeper.GetProposal(ctx, params.ProposalID)
if !found {
return nil, sdk.ErrInternal("proposal not found")
}
com, found := keeper.GetCommittee(ctx, pr.CommitteeID)
if !found {
return nil, sdk.ErrInternal("committee disbanded")
}
votes := []types.Vote{}
keeper.IterateVotes(ctx, params.ProposalID, func(vote types.Vote) bool {
votes = append(votes, vote)
return false
})
proposalPasses := sdk.NewDec(int64(len(votes))).GTE(types.VoteThreshold.MulInt64(int64(len(com.Members))))
// TODO return some kind of tally object, rather than just a bool
bz, err := codec.MarshalJSONIndent(keeper.cdc, proposalPasses)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return bz, nil
}
// ---------- Params ----------
// func queryParams(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
// switch path[0] {
// case types.ParamDeposit:
// bz, err := codec.MarshalJSONIndent(keeper.cdc, keeper.GetDepositParams(ctx))
// if err != nil {
// return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
// }
// return bz, nil
// case types.ParamVoting:
// bz, err := codec.MarshalJSONIndent(keeper.cdc, keeper.GetVotingParams(ctx))
// if err != nil {
// return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
// }
// return bz, nil
// case types.ParamTallying:
// bz, err := codec.MarshalJSONIndent(keeper.cdc, keeper.GetTallyParams(ctx))
// if err != nil {
// return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
// }
// return bz, nil
// default:
// return nil, sdk.ErrUnknownRequest(fmt.Sprintf("%s is not a valid query request path", req.Path))
// }
// }

View File

@ -0,0 +1,252 @@
package keeper_test
import (
"strings"
"testing"
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/committee/keeper"
"github.com/kava-labs/kava/x/committee/types"
)
const (
custom = "custom"
)
type QuerierTestSuite struct {
suite.Suite
keeper keeper.Keeper
app app.TestApp
ctx sdk.Context
cdc *codec.Codec
querier sdk.Querier
addresses []sdk.AccAddress
committees []types.Committee
proposals []types.Proposal
votes map[uint64]([]types.Vote)
expectedTallyForTheFirstProposal bool // TODO replace once tallying has been refactored
}
func (suite *QuerierTestSuite) SetupTest() {
// SetupTest function runs before every test, but a new suite is not created every time.
// So be careful about modifying data on suite as data from previous tests will still be there.
// For example, don't append proposal to suite.proposals, initialize a new slice value.
suite.app = app.NewTestApp()
suite.keeper = suite.app.GetCommitteeKeeper()
suite.ctx = suite.app.NewContext(true, abci.Header{})
suite.cdc = suite.app.Codec()
suite.querier = keeper.NewQuerier(suite.keeper)
_, suite.addresses = app.GeneratePrivKeyAddressPairs(5)
suite.app.InitializeFromGenesisStates()
// TODO replace below with genesis state
normalCom := types.Committee{
ID: 12,
Members: suite.addresses[:2],
Permissions: []types.Permission{types.GodPermission{}},
}
suite.keeper.SetCommittee(suite.ctx, normalCom)
pprop1 := gov.NewTextProposal("1A Title", "A description of this proposal.")
id1, err := suite.keeper.SubmitProposal(suite.ctx, normalCom.Members[0], normalCom.ID, pprop1)
suite.NoError(err)
pprop2 := gov.NewTextProposal("2A Title", "A description of this proposal.")
id2, err := suite.keeper.SubmitProposal(suite.ctx, normalCom.Members[0], normalCom.ID, pprop2)
suite.NoError(err)
err = suite.keeper.AddVote(suite.ctx, id1, normalCom.Members[0])
suite.NoError(err)
err = suite.keeper.AddVote(suite.ctx, id1, normalCom.Members[1])
suite.NoError(err)
err = suite.keeper.AddVote(suite.ctx, id2, normalCom.Members[1])
suite.NoError(err)
suite.committees = []types.Committee{}
suite.committees = []types.Committee{normalCom} // TODO
suite.proposals = []types.Proposal{}
suite.keeper.IterateProposals(suite.ctx, func(p types.Proposal) bool {
suite.proposals = append(suite.proposals, p)
return false
})
suite.votes = map[uint64]([]types.Vote){}
suite.keeper.IterateProposals(suite.ctx, func(p types.Proposal) bool {
suite.keeper.IterateVotes(suite.ctx, p.ID, func(v types.Vote) bool {
suite.votes[p.ID] = append(suite.votes[p.ID], v)
return false
})
return false
})
suite.expectedTallyForTheFirstProposal = true // TODO replace once tallying has been refactored
}
func (suite *QuerierTestSuite) TestQueryCommittees() {
ctx := suite.ctx.WithIsCheckTx(false)
// Set up request query
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryCommittees}, "/"),
}
// Execute query and check the []byte result
bz, err := suite.querier(ctx, []string{types.QueryCommittees}, query)
suite.NoError(err)
suite.NotNil(bz)
// Unmarshal the bytes
var committees []types.Committee
suite.NoError(suite.cdc.UnmarshalJSON(bz, &committees))
// Check
suite.Equal(suite.committees, committees)
}
func (suite *QuerierTestSuite) TestQueryCommittee() {
ctx := suite.ctx.WithIsCheckTx(false) // ?
// Set up request query
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryCommittee}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryCommitteeParams(suite.committees[0].ID)),
}
// Execute query and check the []byte result
bz, err := suite.querier(ctx, []string{types.QueryCommittee}, query)
suite.NoError(err)
suite.NotNil(bz)
// Unmarshal the bytes
var committee types.Committee
suite.NoError(suite.cdc.UnmarshalJSON(bz, &committee))
// Check
suite.Equal(suite.committees[0], committee)
}
func (suite *QuerierTestSuite) TestQueryProposals() {
ctx := suite.ctx.WithIsCheckTx(false)
// Set up request query
comID := suite.proposals[0].CommitteeID
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryProposals}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryCommitteeParams(comID)),
}
// Execute query and check the []byte result
bz, err := suite.querier(ctx, []string{types.QueryProposals}, query)
suite.NoError(err)
suite.NotNil(bz)
// Unmarshal the bytes
var proposals []types.Proposal
suite.NoError(suite.cdc.UnmarshalJSON(bz, &proposals))
// Check
expectedProposals := []types.Proposal{}
for _, p := range suite.proposals {
if p.CommitteeID == comID {
expectedProposals = append(expectedProposals, p)
}
}
suite.Equal(expectedProposals, proposals)
}
func (suite *QuerierTestSuite) TestQueryProposal() {
ctx := suite.ctx.WithIsCheckTx(false) // ?
// Set up request query
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryProposal}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryProposalParams(suite.proposals[0].ID)),
}
// Execute query and check the []byte result
bz, err := suite.querier(ctx, []string{types.QueryProposal}, query)
suite.NoError(err)
suite.NotNil(bz)
// Unmarshal the bytes
var proposal types.Proposal
suite.NoError(suite.cdc.UnmarshalJSON(bz, &proposal))
// Check
suite.Equal(suite.proposals[0], proposal)
}
func (suite *QuerierTestSuite) TestQueryVotes() {
ctx := suite.ctx.WithIsCheckTx(false)
// Set up request query
propID := suite.proposals[0].ID
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryVotes}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryProposalParams(propID)),
}
// Execute query and check the []byte result
bz, err := suite.querier(ctx, []string{types.QueryVotes}, query)
suite.NoError(err)
suite.NotNil(bz)
// Unmarshal the bytes
var votes []types.Vote
suite.NoError(suite.cdc.UnmarshalJSON(bz, &votes))
// Check
suite.Equal(suite.votes[propID], votes)
}
func (suite *QuerierTestSuite) TestQueryVote() {
ctx := suite.ctx.WithIsCheckTx(false) // ?
// Set up request query
propID := suite.proposals[0].ID
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryVote}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryVoteParams(propID, suite.votes[propID][0].Voter)),
}
// Execute query and check the []byte result
bz, err := suite.querier(ctx, []string{types.QueryVote}, query)
suite.NoError(err)
suite.NotNil(bz)
// Unmarshal the bytes
var vote types.Vote
suite.NoError(suite.cdc.UnmarshalJSON(bz, &vote))
// Check
suite.Equal(suite.votes[propID][0], vote)
}
func (suite *QuerierTestSuite) TestQueryTally() {
ctx := suite.ctx.WithIsCheckTx(false) // ?
// Set up request query
propID := suite.proposals[0].ID
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryTally}, "/"),
Data: suite.cdc.MustMarshalJSON(types.NewQueryProposalParams(propID)),
}
// Execute query and check the []byte result
bz, err := suite.querier(ctx, []string{types.QueryTally}, query)
suite.NoError(err)
suite.NotNil(bz)
// Unmarshal the bytes
var tally bool
suite.NoError(suite.cdc.UnmarshalJSON(bz, &tally))
// Check
expectedTally := suite.expectedTallyForTheFirstProposal
suite.Equal(expectedTally, tally)
}
func TestQuerierTestSuite(t *testing.T) {
suite.Run(t, new(QuerierTestSuite))
}

View File

@ -0,0 +1,49 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Query endpoints supported by the Querier
const (
//QueryParams = "params"
QueryCommittees = "committees"
QueryCommittee = "committee"
QueryProposals = "proposals"
QueryProposal = "proposal"
QueryVotes = "votes"
QueryVote = "vote"
QueryTally = "tally"
)
type QueryCommitteeParams struct {
CommitteeID uint64
}
func NewQueryCommitteeParams(committeeID uint64) QueryCommitteeParams {
return QueryCommitteeParams{
CommitteeID: committeeID,
}
}
type QueryProposalParams struct {
ProposalID uint64
}
func NewQueryProposalParams(proposalID uint64) QueryProposalParams {
return QueryProposalParams{
ProposalID: proposalID,
}
}
type QueryVoteParams struct {
ProposalID uint64
Voter sdk.AccAddress
}
func NewQueryVoteParams(proposalID uint64, voter sdk.AccAddress) QueryVoteParams {
return QueryVoteParams{
ProposalID: proposalID,
Voter: voter,
}
}

View File

@ -1,6 +1,8 @@
package types package types
import ( import (
"fmt"
"strings"
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
@ -65,6 +67,21 @@ func (p Proposal) HasExpiredBy(time time.Time) bool {
return !time.Before(p.Deadline) return !time.Before(p.Deadline)
} }
// String implements the fmt.Stringer interface, and importantly overrides the String methods inherited from the embedded PubProposal type.
func (p Proposal) String() string {
return strings.TrimSpace(fmt.Sprintf(`Proposal:
PubProposal:
%s
ID: %d
Committee ID: %d
Deadline: %s`,
p.PubProposal,
p.ID,
p.CommitteeID,
p.Deadline,
))
}
type Vote struct { type Vote struct {
ProposalID uint64 ProposalID uint64
Voter sdk.AccAddress Voter sdk.AccAddress