Merge PR #1918: Keeper custom queries & QueryRouter

This commit is contained in:
Christopher Goes 2018-08-22 12:50:59 +02:00 committed by GitHub
commit b23fe15e4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 572 additions and 193 deletions

View File

@ -34,6 +34,7 @@ FEATURES
* Gaia
* SDK
* [querier] added custom querier functionality, so ABCI query requests can be handled by keepers
* Tendermint

View File

@ -41,13 +41,14 @@ const (
// BaseApp reflects the ABCI application implementation.
type BaseApp struct {
// initialized on creation
Logger log.Logger
name string // application name from abci.Info
db dbm.DB // common DB backend
cms sdk.CommitMultiStore // Main (uncached) state
router Router // handle any kind of message
codespacer *sdk.Codespacer // handle module codespacing
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
Logger log.Logger
name string // application name from abci.Info
db dbm.DB // common DB backend
cms sdk.CommitMultiStore // Main (uncached) state
router Router // handle any kind of message
queryRouter QueryRouter // router for redirecting query calls
codespacer *sdk.Codespacer // handle module codespacing
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
anteHandler sdk.AnteHandler // ante handler for fee and auth
@ -84,13 +85,14 @@ var _ abci.Application = (*BaseApp)(nil)
// Accepts variable number of option functions, which act on the BaseApp to set configuration choices
func NewBaseApp(name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp)) *BaseApp {
app := &BaseApp{
Logger: logger,
name: name,
db: db,
cms: store.NewCommitMultiStore(db),
router: NewRouter(),
codespacer: sdk.NewCodespacer(),
txDecoder: txDecoder,
Logger: logger,
name: name,
db: db,
cms: store.NewCommitMultiStore(db),
router: NewRouter(),
queryRouter: NewQueryRouter(),
codespacer: sdk.NewCodespacer(),
txDecoder: txDecoder,
}
// Register the undefined & root codespaces, which should not be used by
@ -266,6 +268,7 @@ func (app *BaseApp) FilterPeerByPubKey(info string) abci.ResponseQuery {
return abci.ResponseQuery{}
}
// Splits a string path using the delimter '/'. i.e. "this/is/funny" becomes []string{"this", "is", "funny"}
func splitPath(requestPath string) (path []string) {
path = strings.Split(requestPath, "/")
// first element is empty string
@ -291,6 +294,8 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
return handleQueryStore(app, path, req)
case "p2p":
return handleQueryP2P(app, path, req)
case "custom":
return handleQueryCustom(app, path, req)
}
msg := "unknown query path"
@ -362,6 +367,33 @@ func handleQueryP2P(app *BaseApp, path []string, req abci.RequestQuery) (res abc
return sdk.ErrUnknownRequest(msg).QueryResult()
}
func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) {
// path[0] should be "custom" because "/custom" prefix is required for keeper queries.
// the queryRouter routes using path[1]. For example, in the path "custom/gov/proposal", queryRouter routes using "gov"
if path[1] == "" {
sdk.ErrUnknownRequest("No route for custom query specified").QueryResult()
}
querier := app.queryRouter.Route(path[1])
if querier == nil {
sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult()
}
ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger)
// Passes the rest of the path as an argument to the querier.
// For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path
resBytes, err := querier(ctx, path[2:], req)
if err != nil {
return abci.ResponseQuery{
Code: uint32(err.ABCICode()),
Log: err.ABCILog(),
}
}
return abci.ResponseQuery{
Code: uint32(sdk.ABCICodeOK),
Value: resBytes,
}
}
// BeginBlock implements the ABCI application interface.
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
if app.cms.TracingEnabled() {

41
baseapp/queryrouter.go Normal file
View File

@ -0,0 +1,41 @@
package baseapp
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// QueryRouter provides queryables for each query path.
type QueryRouter interface {
AddRoute(r string, h sdk.Querier) (rtr QueryRouter)
Route(path string) (h sdk.Querier)
}
type queryrouter struct {
routes map[string]sdk.Querier
}
// nolint
// NewRouter - create new router
// TODO either make Function unexported or make return type (router) Exported
func NewQueryRouter() *queryrouter {
return &queryrouter{
routes: map[string]sdk.Querier{},
}
}
// AddRoute - Adds an sdk.Querier to the route provided. Panics on duplicate
func (rtr *queryrouter) AddRoute(r string, q sdk.Querier) QueryRouter {
if !isAlphaNumeric(r) {
panic("route expressions can only contain alphanumeric characters")
}
if rtr.routes[r] != nil {
panic("route has already been initialized")
}
rtr.routes[r] = q
return rtr
}
// Returns the sdk.Querier for a certain route path
func (rtr *queryrouter) Route(path string) (h sdk.Querier) {
return rtr.routes[path]
}

View File

@ -74,6 +74,9 @@ func (app *BaseApp) Router() Router {
}
return app.router
}
func (app *BaseApp) QueryRouter() QueryRouter {
return app.queryRouter
}
func (app *BaseApp) Seal() { app.sealed = true }
func (app *BaseApp) IsSealed() bool { return app.sealed }
func (app *BaseApp) enforceSeal() {

View File

@ -31,6 +31,11 @@ func (ctx CLIContext) Query(path string) (res []byte, err error) {
return ctx.query(path, nil)
}
// Query information about the connected node with a data payload
func (ctx CLIContext) QueryWithData(path string, data []byte) (res []byte, err error) {
return ctx.query(path, data)
}
// QueryStore performs a query from a Tendermint node with the provided key and
// store name.
func (ctx CLIContext) QueryStore(key cmn.HexBytes, storeName string) (res []byte, err error) {

View File

@ -105,6 +105,9 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)).
AddRoute("gov", gov.NewHandler(app.govKeeper))
app.QueryRouter().
AddRoute("gov", gov.NewQuerier(app.govKeeper))
// initialize BaseApp
app.SetInitChainer(app.initChainer)
app.SetBeginBlocker(app.BeginBlocker)

View File

@ -6,7 +6,7 @@
The Cosmos SDK has all the necessary pre-built modules to add functionality on top of a `BaseApp`, which is the template to build a blockchain dApp in Cosmos. In this context, a `module` is a fundamental unit in the Cosmos SDK.
Each module is an extension of the `BaseApp`'s functionalities that defines transactions, handles application state and manages the state transition logic. Each module also contains handlers for messages and transactions, as well as REST and CLI for secure user interactions.
Each module is an extension of the `BaseApp`'s functionalities that defines transactions, handles application state and manages the state transition logic. Each module also contains handlers for messages and transactions, queriers for handling query requests, as well as REST and CLI for secure user interactions.
Some of the most important modules in the SDK are:

View File

@ -1,6 +1,7 @@
package types
import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
@ -108,6 +109,23 @@ func (bz AccAddress) Format(s fmt.State, verb rune) {
}
}
// Returns boolean for whether two AccAddresses are Equal
func (bz AccAddress) Equals(bz2 AccAddress) bool {
if bz.Empty() && bz2.Empty() {
return true
}
return bytes.Compare(bz.Bytes(), bz2.Bytes()) == 0
}
// Returns boolean for whether an AccAddress is empty
func (bz AccAddress) Empty() bool {
if bz == nil {
return true
}
bz2 := AccAddress{}
return bytes.Compare(bz.Bytes(), bz2.Bytes()) == 0
}
//__________________________________________________________
// AccAddress a wrapper around bytes meant to represent a validator address
@ -192,6 +210,23 @@ func (bz ValAddress) Format(s fmt.State, verb rune) {
}
}
// Returns boolean for whether two ValAddresses are Equal
func (bz ValAddress) Equals(bz2 ValAddress) bool {
if bz.Empty() && bz2.Empty() {
return true
}
return bytes.Compare(bz.Bytes(), bz2.Bytes()) == 0
}
// Returns boolean for whether an AccAddress is empty
func (bz ValAddress) Empty() bool {
if bz == nil {
return true
}
bz2 := ValAddress{}
return bytes.Compare(bz.Bytes(), bz2.Bytes()) == 0
}
// Bech32ifyAccPub takes AccountPubKey and returns the bech32 encoded string
func Bech32ifyAccPub(pub crypto.PubKey) (string, error) {
return bech32.ConvertAndEncode(Bech32PrefixAccPub, pub.Bytes())

6
types/queryable.go Normal file
View File

@ -0,0 +1,6 @@
package types
import abci "github.com/tendermint/tendermint/abci/types"
// Type for querier functions on keepers to implement to handle custom queries
type Querier = func(ctx Context, path []string, req abci.RequestQuery) (res []byte, err Error)

View File

@ -3,7 +3,6 @@ package rest
import (
"fmt"
"net/http"
"strconv"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -21,6 +20,7 @@ const (
RestDepositer = "depositer"
RestVoter = "voter"
RestProposalStatus = "status"
RestNumLatest = "latest"
storeName = "gov"
)
@ -97,16 +97,13 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
err := errors.Errorf("proposalID [%d] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
var req depositReq
err = buildReq(w, r, cdc, &req)
err := buildReq(w, r, cdc, &req)
if err != nil {
return
}
@ -139,15 +136,13 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
err := errors.Errorf("proposalID [%d] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
var req voteReq
err = buildReq(w, r, cdc, &req)
err := buildReq(w, r, cdc, &req)
if err != nil {
return
}
@ -180,36 +175,33 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc {
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
err := errors.Errorf("proposalID [%d] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
return
params := gov.QueryProposalParams{
ProposalID: proposalID,
}
var proposal gov.Proposal
cdc.MustUnmarshalBinary(res, &proposal)
output, err := wire.MarshalJSONIndent(cdc, proposal)
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
res, err := cliCtx.QueryWithData("custom/gov/proposal", bz)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
w.Write(res)
}
}
@ -227,12 +219,8 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc {
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("proposalID [%d] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
@ -255,36 +243,43 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc {
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName)
if err != nil || len(res) == 0 {
res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
w.WriteHeader(http.StatusNotFound)
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
params := gov.QueryDepositParams{
ProposalID: proposalID,
Depositer: depositerAddr,
}
return
}
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(http.StatusNotFound)
err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID)
res, err := cliCtx.QueryWithData("custom/gov/deposit", bz)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
var deposit gov.Deposit
cdc.MustUnmarshalBinary(res, &deposit)
output, err := wire.MarshalJSONIndent(cdc, deposit)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
cdc.UnmarshalJSON(res, &deposit)
if deposit.Empty() {
res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinary(gov.QueryProposalParams{params.ProposalID}))
if err != nil || len(res) == 0 {
w.WriteHeader(http.StatusNotFound)
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(http.StatusNotFound)
err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
w.Write(res)
}
}
@ -301,12 +296,8 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc {
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("proposalID [%s] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
@ -328,36 +319,47 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc {
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName)
if err != nil || len(res) == 0 {
res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
w.WriteHeader(http.StatusNotFound)
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
params := gov.QueryVoteParams{
Voter: voterAddr,
ProposalID: proposalID,
}
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
return
}
w.WriteHeader(http.StatusNotFound)
err = errors.Errorf("voter [%s] did not vote on proposalID [%d]", bechVoterAddr, proposalID)
res, err := cliCtx.QueryWithData("custom/gov/vote", bz)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
var vote gov.Vote
cdc.MustUnmarshalBinary(res, &vote)
output, err := wire.MarshalJSONIndent(cdc, vote)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
cdc.UnmarshalJSON(res, &vote)
if vote.Empty() {
bz, err := cdc.MarshalJSON(gov.QueryProposalParams{params.ProposalID})
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
res, err := cliCtx.QueryWithData("custom/gov/proposal", bz)
if err != nil || len(res) == 0 {
w.WriteHeader(http.StatusNotFound)
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
return
}
w.WriteHeader(http.StatusNotFound)
err = errors.Errorf("voter [%s] did not deposit on proposalID [%d]", bechVoterAddr, proposalID)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
w.Write(res)
}
}
@ -376,59 +378,31 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc {
return
}
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("proposalID [%s] is not positive", proposalID)
w.Write([]byte(err.Error()))
proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w)
if !ok {
return
}
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
w.Write([]byte(err.Error()))
return
params := gov.QueryVotesParams{
ProposalID: proposalID,
}
var proposal gov.Proposal
cdc.MustUnmarshalBinary(res, &proposal)
if proposal.GetStatus() != gov.StatusVotingPeriod {
err := errors.Errorf("proposal is not in Voting Period", proposalID)
w.Write([]byte(err.Error()))
return
}
res2, err := cliCtx.QuerySubspace(gov.KeyVotesSubspace(proposalID), storeName)
if err != nil {
err = errors.New("ProposalID doesn't exist")
w.Write([]byte(err.Error()))
return
}
var votes []gov.Vote
for i := 0; i < len(res2); i++ {
var vote gov.Vote
cdc.MustUnmarshalBinary(res2[i].Value, &vote)
votes = append(votes, vote)
}
output, err := wire.MarshalJSONIndent(cdc, votes)
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
res, err := cliCtx.QueryWithData("custom/gov/votes", bz)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write(res)
}
}
@ -439,24 +413,23 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
bechVoterAddr := r.URL.Query().Get(RestVoter)
bechDepositerAddr := r.URL.Query().Get(RestDepositer)
strProposalStatus := r.URL.Query().Get(RestProposalStatus)
strNumLatest := r.URL.Query().Get(RestNumLatest)
var err error
var voterAddr sdk.AccAddress
var depositerAddr sdk.AccAddress
var proposalStatus gov.ProposalStatus
params := gov.QueryProposalsParams{}
if len(bechVoterAddr) != 0 {
voterAddr, err = sdk.AccAddressFromBech32(bechVoterAddr)
voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter)
w.Write([]byte(err.Error()))
return
}
params.Voter = voterAddr
}
if len(bechDepositerAddr) != 0 {
depositerAddr, err = sdk.AccAddressFromBech32(bechDepositerAddr)
depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer)
@ -464,10 +437,11 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
return
}
params.Depositer = depositerAddr
}
if len(strProposalStatus) != 0 {
proposalStatus, err = gov.ProposalStatusFromString(strProposalStatus)
proposalStatus, err := gov.ProposalStatusFromString(strProposalStatus)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := errors.Errorf("'%s' is not a valid Proposal Status", strProposalStatus)
@ -475,63 +449,33 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc {
return
}
params.ProposalStatus = proposalStatus
}
if len(strNumLatest) != 0 {
numLatest, ok := parseInt64OrReturnBadRequest(strNumLatest, w)
if !ok {
return
}
params.NumLatestProposals = numLatest
}
bz, err := cdc.MarshalJSON(params)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryStore(gov.KeyNextProposalID, storeName)
res, err := cliCtx.QueryWithData("custom/gov/proposals", bz)
if err != nil {
err = errors.New("no proposals exist yet and proposalID has not been set")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
var maxProposalID int64
cdc.MustUnmarshalBinary(res, &maxProposalID)
matchingProposals := []gov.Proposal{}
for proposalID := int64(0); proposalID < maxProposalID; proposalID++ {
if voterAddr != nil {
res, err = cliCtx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName)
if err != nil || len(res) == 0 {
continue
}
}
if depositerAddr != nil {
res, err = cliCtx.QueryStore(gov.KeyDeposit(proposalID, depositerAddr), storeName)
if err != nil || len(res) == 0 {
continue
}
}
res, err = cliCtx.QueryStore(gov.KeyProposal(proposalID), storeName)
if err != nil || len(res) == 0 {
continue
}
var proposal gov.Proposal
cdc.MustUnmarshalBinary(res, &proposal)
if len(strProposalStatus) != 0 {
if proposal.GetStatus() != proposalStatus {
continue
}
}
matchingProposals = append(matchingProposals, proposal)
}
output, err := wire.MarshalJSONIndent(cdc, matchingProposals)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
w.Write(res)
}
}

View File

@ -1,8 +1,10 @@
package rest
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -100,3 +102,15 @@ func signAndBuild(w http.ResponseWriter, cliCtx context.CLIContext, baseReq base
w.Write(output)
}
func parseInt64OrReturnBadRequest(s string, w http.ResponseWriter) (n int64, ok bool) {
var err error
n, err = strconv.ParseInt(s, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
err := fmt.Errorf("'%s' is not a valid int64", s)
w.Write([]byte(err.Error()))
return 0, false
}
return n, true
}

View File

@ -15,6 +15,17 @@ type Vote struct {
Option VoteOption `json:"option"` // option from OptionSet chosen by the voter
}
// Returns whether 2 votes are equal
func (voteA Vote) Equals(voteB Vote) bool {
return voteA.Voter.Equals(voteB.Voter) && voteA.ProposalID == voteB.ProposalID && voteA.Option == voteB.Option
}
// Returns whether a vote is empty
func (voteA Vote) Empty() bool {
voteB := Vote{}
return voteA.Equals(voteB)
}
// Deposit
type Deposit struct {
Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer
@ -22,6 +33,17 @@ type Deposit struct {
Amount sdk.Coins `json:"amount"` // Deposit amount
}
// Returns whether 2 deposits are equal
func (depositA Deposit) Equals(depositB Deposit) bool {
return depositA.Depositer.Equals(depositB.Depositer) && depositA.ProposalID == depositB.ProposalID && depositA.Amount.IsEqual(depositB.Amount)
}
// Returns whether a deposit is empty
func (depositA Deposit) Empty() bool {
depositB := Deposit{}
return depositA.Equals(depositB)
}
// Type that represents VoteOption as a byte
type VoteOption byte

View File

@ -108,6 +108,52 @@ func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) {
store.Delete(KeyProposal(proposal.GetProposalID()))
}
// nolint: gocyclo
// Get Proposal from store by ProposalID
func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositerAddr sdk.AccAddress, status ProposalStatus, numLatest int64) []Proposal {
maxProposalID, err := keeper.peekCurrentProposalID(ctx)
if err != nil {
return nil
}
matchingProposals := []Proposal{}
if numLatest <= 0 {
numLatest = maxProposalID
}
for proposalID := maxProposalID - numLatest; proposalID < maxProposalID; proposalID++ {
if voterAddr != nil && len(voterAddr) != 0 {
_, found := keeper.GetVote(ctx, proposalID, voterAddr)
if !found {
continue
}
}
if depositerAddr != nil && len(depositerAddr) != 0 {
_, found := keeper.GetDeposit(ctx, proposalID, depositerAddr)
if !found {
continue
}
}
proposal := keeper.GetProposal(ctx, proposalID)
if proposal == nil {
continue
}
if validProposalStatus(status) {
if proposal.GetStatus() != status {
continue
}
}
matchingProposals = append(matchingProposals, proposal)
}
return matchingProposals
}
func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) sdk.Error {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
@ -121,16 +167,15 @@ func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) sdk
// Get the last used proposal ID
func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID int64) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
proposalID, err := keeper.peekCurrentProposalID(ctx)
if err != nil {
return 0
}
keeper.cdc.MustUnmarshalBinary(bz, &proposalID)
proposalID--
return
}
// Gets the next available ProposalID and increments it
func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
@ -143,6 +188,17 @@ func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sd
return proposalID, nil
}
// Peeks the next available ProposalID without incrementing it
func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
}
keeper.cdc.MustUnmarshalBinary(bz, &proposalID)
return proposalID, nil
}
func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) {
proposal.SetVotingStartBlock(ctx.BlockHeight())
proposal.SetStatus(StatusVotingPeriod)

View File

@ -110,6 +110,7 @@ type ProposalKind byte
//nolint
const (
ProposalTypeNil ProposalKind = 0x00
ProposalTypeText ProposalKind = 0x01
ProposalTypeParameterChange ProposalKind = 0x02
ProposalTypeSoftwareUpgrade ProposalKind = 0x03
@ -203,6 +204,7 @@ type ProposalStatus byte
//nolint
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01
StatusVotingPeriod ProposalStatus = 0x02
StatusPassed ProposalStatus = 0x03
@ -220,6 +222,8 @@ func ProposalStatusFromString(str string) (ProposalStatus, error) {
return StatusPassed, nil
case "Rejected":
return StatusRejected, nil
case "":
return StatusNil, nil
default:
return ProposalStatus(0xff), errors.Errorf("'%s' is not a valid proposal status", str)
}

213
x/gov/queryable.go Normal file
View File

@ -0,0 +1,213 @@
package gov
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
abci "github.com/tendermint/tendermint/abci/types"
)
func NewQuerier(keeper Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
switch path[0] {
case "proposal":
return queryProposal(ctx, path[1:], req, keeper)
case "deposit":
return queryDeposit(ctx, path[1:], req, keeper)
case "vote":
return queryVote(ctx, path[1:], req, keeper)
case "deposits":
return queryDeposits(ctx, path[1:], req, keeper)
case "votes":
return queryVotes(ctx, path[1:], req, keeper)
case "proposals":
return queryProposals(ctx, path[1:], req, keeper)
case "tally":
return queryTally(ctx, path[1:], req, keeper)
default:
return nil, sdk.ErrUnknownRequest("unknown gov query endpoint")
}
}
}
// Params for query 'custom/gov/proposal'
type QueryProposalParams struct {
ProposalID int64
}
func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryProposalParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
proposal := keeper.GetProposal(ctx, params.ProposalID)
if proposal == nil {
return []byte{}, ErrUnknownProposal(DefaultCodespace, params.ProposalID)
}
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, proposal)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/deposit'
type QueryDepositParams struct {
ProposalID int64
Depositer sdk.AccAddress
}
func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryDepositParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
deposit, _ := keeper.GetDeposit(ctx, params.ProposalID, params.Depositer)
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, deposit)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/vote'
type QueryVoteParams struct {
ProposalID int64
Voter sdk.AccAddress
}
func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryVoteParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
vote, _ := keeper.GetVote(ctx, params.ProposalID, params.Voter)
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, vote)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/deposits'
type QueryDepositsParams struct {
ProposalID int64
}
func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryDepositParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
var deposits []Deposit
depositsIterator := keeper.GetDeposits(ctx, params.ProposalID)
for ; depositsIterator.Valid(); depositsIterator.Next() {
deposit := Deposit{}
keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit)
deposits = append(deposits, deposit)
}
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, deposits)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/votes'
type QueryVotesParams struct {
ProposalID int64
}
func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryVotesParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
var votes []Vote
votesIterator := keeper.GetVotes(ctx, params.ProposalID)
for ; votesIterator.Valid(); votesIterator.Next() {
vote := Vote{}
keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote)
votes = append(votes, vote)
}
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, votes)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/proposals'
type QueryProposalsParams struct {
Voter sdk.AccAddress
Depositer sdk.AccAddress
ProposalStatus ProposalStatus
NumLatestProposals int64
}
func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var params QueryProposalsParams
err2 := keeper.cdc.UnmarshalJSON(req.Data, &params)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositer, params.ProposalStatus, params.NumLatestProposals)
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, proposals)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}
// Params for query 'custom/gov/tally'
type QueryTallyParams struct {
ProposalID int64
}
func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
// TODO: Dependant on #1914
var proposalID int64
err2 := keeper.cdc.UnmarshalJSON(req.Data, proposalID)
if err2 != nil {
return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error()))
}
proposal := keeper.GetProposal(ctx, proposalID)
if proposal == nil {
return []byte{}, ErrUnknownProposal(DefaultCodespace, proposalID)
}
var tallyResult TallyResult
if proposal.GetStatus() == StatusDepositPeriod {
tallyResult = EmptyTallyResult()
} else if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected {
tallyResult = proposal.GetTallyResult()
} else {
_, tallyResult, _ = tally(ctx, keeper, proposal)
}
bz, err2 := wire.MarshalJSONIndent(keeper.cdc, tallyResult)
if err2 != nil {
panic("could not marshal result to JSON")
}
return bz, nil
}