R4R: Query Gov Params (#2576)

* gov query params
This commit is contained in:
Sunny Aggarwal 2018-11-13 15:45:37 -08:00 committed by Jack Zampolin
parent d184121ea0
commit d1614ebb7e
18 changed files with 673 additions and 90 deletions

View File

@ -20,11 +20,17 @@ BREAKING CHANGES
FEATURES
* Gaia REST API (`gaiacli advanced rest-server`)
* [gov] [\#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Added governance parameter
query REST endpoints.
* Gaia CLI (`gaiacli`)
* [stake][cli] [\#2027] Add CLI query command for getting all delegations to a specific validator.
* [gov][cli] [\#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Added governance
parameter query commands.
* [stake][cli] [\#2027] Add CLI query command for getting all delegations to a specific validator.
* Gaia
* [x/gov] [#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Implemented querier
for getting governance parameters.
* SDK
* [simulator] \#2682 MsgEditValidator now looks at the validator's max rate, thus it now succeeds a significant portion of the time

View File

@ -735,26 +735,33 @@ func TestProposalsQuery(t *testing.T) {
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addrs[0], addrs[1]})
defer cleanup()
depositParam := getDepositParam(t, port)
halfMinDeposit := depositParam.MinDeposit.AmountOf(stakeTypes.DefaultBondDenom).Int64() / 2
getVotingParam(t, port)
getTallyingParam(t, port)
// Addr1 proposes (and deposits) proposals #1 and #2
resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5)
resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit)
var proposalID1 uint64
cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID1)
tests.WaitForHeight(resultTx.Height+1, port)
resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5)
resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], halfMinDeposit)
var proposalID2 uint64
cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID2)
tests.WaitForHeight(resultTx.Height+1, port)
// Addr2 proposes (and deposits) proposals #3
resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], 5)
resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], halfMinDeposit)
var proposalID3 uint64
cdc.MustUnmarshalBinaryLengthPrefixed(resultTx.DeliverTx.GetData(), &proposalID3)
tests.WaitForHeight(resultTx.Height+1, port)
// Addr2 deposits on proposals #2 & #3
resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID2, 5)
resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID2, halfMinDeposit)
tests.WaitForHeight(resultTx.Height+1, port)
resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, 5)
resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, halfMinDeposit)
tests.WaitForHeight(resultTx.Height+1, port)
// check deposits match proposal and individual deposits
@ -1246,6 +1253,36 @@ func getValidatorRedelegations(t *testing.T, port string, validatorAddr sdk.ValA
// ============= Governance Module ================
func getDepositParam(t *testing.T, port string) gov.DepositParams {
res, body := Request(t, port, "GET", "/gov/parameters/deposit", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var depositParams gov.DepositParams
err := cdc.UnmarshalJSON([]byte(body), &depositParams)
require.Nil(t, err)
return depositParams
}
func getVotingParam(t *testing.T, port string) gov.VotingParams {
res, body := Request(t, port, "GET", "/gov/parameters/voting", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var votingParams gov.VotingParams
err := cdc.UnmarshalJSON([]byte(body), &votingParams)
require.Nil(t, err)
return votingParams
}
func getTallyingParam(t *testing.T, port string) gov.TallyParams {
res, body := Request(t, port, "GET", "/gov/parameters/tallying", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var tallyParams gov.TallyParams
err := cdc.UnmarshalJSON([]byte(body), &tallyParams)
require.Nil(t, err)
return tallyParams
}
func getProposal(t *testing.T, port string, proposalID uint64) gov.Proposal {
res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d", proposalID), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)

View File

@ -1451,7 +1451,7 @@ paths:
/gov/proposals/{proposalId}/votes/{voter}:
get:
summary: Query vote
description: Query vote information by proposalId and voter address
description: Query vote information by proposal Id and voter address
produces:
- application/json
tags:
@ -1478,6 +1478,83 @@ paths:
description: Found no vote
500:
description: Internal Server Error
/gov/parameters/deposit:
get:
summary: Query governance deposit parameters
description: Query governance deposit parameters. The max_deposit_period units are in nanoseconds.
produces:
- application/json
tags:
- ICS22
responses:
200:
description: OK
schema:
type: object
properties:
min_deposit:
type: array
items:
$ref: "#/definitions/Coin"
max_deposit_period:
type: string
example: "86400000000000"
400:
description: <other_path> is not a valid query request path
404:
description: Found no deposit parameters
500:
description: Internal Server Error
/gov/parameters/tallying:
get:
summary: Query governance tally parameters
description: Query governance tally parameters
produces:
- application/json
tags:
- ICS22
responses:
200:
description: OK
schema:
properties:
threshold:
type: string
example: "0.5000000000"
veto:
type: string
example: "0.3340000000"
governance_penalty:
type: string
example: "0.0100000000"
400:
description: <other_path> is not a valid query request path
404:
description: Found no tally parameters
500:
description: Internal Server Error
/gov/parameters/voting:
get:
summary: Query governance voting parameters
description: Query governance voting parameters. The voting_period units are in nanoseconds.
produces:
- application/json
tags:
- ICS22
responses:
200:
description: OK
schema:
properties:
voting_period:
type: string
example: "86400000000000"
400:
description: <other_path> is not a valid query request path
404:
description: Found no voting parameters
500:
description: Internal Server Error
definitions:
CheckTxResult:

View File

@ -4,9 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/cosmos/cosmos-sdk/x/stake"
stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types"
"github.com/tendermint/tendermint/crypto/secp256k1"
"io/ioutil"
"net"
"net/http"
@ -16,6 +14,9 @@ import (
"strings"
"testing"
stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app"
@ -25,6 +26,7 @@ import (
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/stake"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"

View File

@ -317,6 +317,10 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
tests.WaitForTMStart(port)
tests.WaitForNextNBlocksTM(2, port)
executeGetDepositParam(t, fmt.Sprintf("gaiacli query gov param deposit %v", flags))
executeGetVotingParam(t, fmt.Sprintf("gaiacli query gov param voting %v", flags))
executeGetTallyingParam(t, fmt.Sprintf("gaiacli query gov param tallying %v", flags))
fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome))
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags))
@ -784,6 +788,33 @@ func executeGetParams(t *testing.T, cmdStr string) stake.Params {
//___________________________________________________________________________________
// gov
func executeGetDepositParam(t *testing.T, cmdStr string) gov.DepositParams {
out, _ := tests.ExecuteT(t, cmdStr, "")
var depositParam gov.DepositParams
cdc := app.MakeCodec()
err := cdc.UnmarshalJSON([]byte(out), &depositParam)
require.NoError(t, err, "out %v\n, err %v", out, err)
return depositParam
}
func executeGetVotingParam(t *testing.T, cmdStr string) gov.VotingParams {
out, _ := tests.ExecuteT(t, cmdStr, "")
var votingParam gov.VotingParams
cdc := app.MakeCodec()
err := cdc.UnmarshalJSON([]byte(out), &votingParam)
require.NoError(t, err, "out %v\n, err %v", out, err)
return votingParam
}
func executeGetTallyingParam(t *testing.T, cmdStr string) gov.TallyParams {
out, _ := tests.ExecuteT(t, cmdStr, "")
var tallyingParam gov.TallyParams
cdc := app.MakeCodec()
err := cdc.UnmarshalJSON([]byte(out), &tallyingParam)
require.NoError(t, err, "out %v\n, err %v", out, err)
return tallyingParam
}
func executeGetProposal(t *testing.T, cmdStr string) gov.Proposal {
out, _ := tests.ExecuteT(t, cmdStr, "")
var proposal gov.Proposal

View File

@ -53,6 +53,7 @@ func queryCmd(cdc *amino.Codec) *cobra.Command {
govcmd.GetCmdQueryProposals(storeGov, cdc),
govcmd.GetCmdQueryVote(storeGov, cdc),
govcmd.GetCmdQueryVotes(storeGov, cdc),
govcmd.GetCmdQueryParams(storeGov, cdc),
govcmd.GetCmdQueryDeposit(storeGov, cdc),
govcmd.GetCmdQueryDeposits(storeGov, cdc))...)

View File

@ -2,7 +2,7 @@
## Abstract
This paper specifies the Governance module of the Cosmos-SDK, which was first described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016.
This paper specifies the Governance module of the Cosmos-SDK, which was first described in the [Cosmos Whitepaper](https://cosmos.network/about/whitepaper) in June 2016.
The module enables Cosmos-SDK based blockchain to support an on-chain governance system. In this system, holders of the native staking token of the chain can vote on proposals on a 1 token 1 vote basis. Next is a list of features the module currently supports:
@ -24,7 +24,7 @@ The following specification uses *Atom* as the native staking token. The module
1. **[Design overview](overview.md)**
2. **Implementation**
1. **[State](state.md)**
1. Procedures
1. Parameters
2. Proposals
3. Proposal Processing Queue
2. **[Transactions](transactions.md)**

View File

@ -2,36 +2,35 @@
## State
### Procedures and base types
`Procedures` define the rule according to which votes are run. There can only
be one active procedure at any given time. If governance wants to change a
procedure, either to modify a value or add/remove a parameter, a new procedure
has to be created and the previous one rendered inactive.
### Parameters and base types
`Parameters` define the rules according to which votes are run. There can only
be one active parameter set at any given time. If governance wants to change a
parameter set, either to modify a value or add/remove a parameter field, a new
parameter set has to be created and the previous one rendered inactive.
```go
type DepositProcedure struct {
MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period.
MaxDepositPeriod time.Time // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
type DepositParams struct {
MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period.
MaxDepositPeriod time.Time // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
}
```
```go
type VotingProcedure struct {
VotingPeriod time.Time // Length of the voting period. Initial value: 2 weeks
type VotingParams struct {
VotingPeriod time.Time // Length of the voting period. Initial value: 2 weeks
}
```
```go
type TallyingProcedure struct {
Threshold sdk.Dec // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
Veto sdk.Dec // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
GovernancePenalty sdk.Dec // Penalty if validator does not vote
type TallyParams struct {
Threshold sdk.Dec // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5
Veto sdk.Dec // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
GovernancePenalty sdk.Dec // Penalty if validator does not vote
}
```
Procedures are stored in a global `GlobalParams` KVStore.
Parameters are stored in a global `GlobalParams` KVStore.
Additionally, we introduce some basic types:
@ -61,7 +60,7 @@ const (
ProposalStatusActive = 0x2 // MinDeposit is reachhed, participants can vote
ProposalStatusAccepted = 0x3 // Proposal has been accepted
ProposalStatusRejected = 0x4 // Proposal has been rejected
ProposalStatusClosed. = 0x5 // Proposal never reached MinDeposit
ProposalStatusClosed = 0x5 // Proposal never reached MinDeposit
)
```
@ -76,7 +75,7 @@ const (
### ValidatorGovInfo
This type is used in a temp map when tallying
This type is used in a temp map when tallying
```go
type ValidatorGovInfo struct {
@ -87,7 +86,7 @@ This type is used in a temp map when tallying
### Proposals
`Proposals` are an item to be voted on.
`Proposals` are an item to be voted on.
```go
type Proposal struct {
@ -99,7 +98,7 @@ type Proposal struct {
SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included
DepositEndTime time.Time // Time that the DepositPeriod of a proposal would expire
Submitter sdk.AccAddress // Address of the submitter
VotingStartTime time.Time // Time of the block where MinDeposit was reached. time.Time{} if MinDeposit is not reached
VotingEndTime time.Time // Time of the block that the VotingPeriod for a proposal will end.
CurrentStatus ProposalStatus // Current status of the proposal
@ -135,7 +134,7 @@ For pseudocode purposes, here are the two function we will use to read or write
### Proposal Processing Queue
**Store:**
* `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the
* `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the
`ProposalIDs` of proposals that reached `MinDeposit`. Each `EndBlock`, all the proposals
that have reached the end of their voting period are processed.
To process a finished proposal, the application tallies the votes, compute the votes of
@ -145,8 +144,8 @@ For pseudocode purposes, here are the two function we will use to read or write
And the pseudocode for the `ProposalProcessingQueue`:
```go
in EndBlock do
in EndBlock do
for finishedProposalID in GetAllFinishedProposalIDs(block.Time)
proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key
@ -171,7 +170,7 @@ And the pseudocode for the `ProposalProcessingQueue`:
if (isVal)
tmpValMap(voterAddress).Vote = vote
tallyingProcedure = load(GlobalParams, 'TallyingProcedure')
tallyingParam = load(GlobalParams, 'TallyingParam')
// Update tally if validator voted they voted
for each validator in validators
@ -182,14 +181,14 @@ And the pseudocode for the `ProposalProcessingQueue`:
// Check if proposal is accepted or rejected
totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes
if (proposal.Votes.YesVotes/totalNonAbstain > tallyingProcedure.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingProcedure.Veto)
if (proposal.Votes.YesVotes/totalNonAbstain > tallyingParam.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingParam.Veto)
// proposal was accepted at the end of the voting period
// refund deposits (non-voters already punished)
proposal.CurrentStatus = ProposalStatusAccepted
for each (amount, depositer) in proposal.Deposits
depositer.AtomBalance += amount
else
else
// proposal was rejected
proposal.CurrentStatus = ProposalStatusRejected

View File

@ -4,7 +4,7 @@
### Proposal Submission
Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal`
Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal`
transaction.
```go
@ -24,7 +24,7 @@ type TxGovSubmitProposal struct {
* If `MinDeposit` is reached:
* Push `proposalID` in `ProposalProcessingQueue`
A `TxGovSubmitProposal` transaction can be handled according to the following
A `TxGovSubmitProposal` transaction can be handled according to the following
pseudocode.
```go
@ -32,31 +32,31 @@ pseudocode.
// Check if TxGovSubmitProposal is valid. If it is, create proposal //
upon receiving txGovSubmitProposal from sender do
if !correctlyFormatted(txGovSubmitProposal)
if !correctlyFormatted(txGovSubmitProposal)
// check if proposal is correctly formatted. Includes fee payment.
throw
initialDeposit = txGovSubmitProposal.InitialDeposit
if (initialDeposit.Atoms <= 0) OR (sender.AtomBalance < initialDeposit.Atoms)
initialDeposit = txGovSubmitProposal.InitialDeposit
if (initialDeposit.Atoms <= 0) OR (sender.AtomBalance < initialDeposit.Atoms)
// InitialDeposit is negative or null OR sender has insufficient funds
throw
if (txGovSubmitProposal.Type != ProposalTypePlainText) OR (txGovSubmitProposal.Type != ProposalTypeSoftwareUpgrade)
sender.AtomBalance -= initialDeposit.Atoms
depositProcedure = load(GlobalParams, 'DepositProcedure')
depositParam = load(GlobalParams, 'DepositParam')
proposalID = generate new proposalID
proposal = NewProposal()
proposal.Title = txGovSubmitProposal.Title
proposal.Description = txGovSubmitProposal.Description
proposal.Type = txGovSubmitProposal.Type
proposal.TotalDeposit = initialDeposit
proposal.SubmitTime = <CurrentTime>
proposal.DepositEndTime = <CurrentTime>.Add(depositProcedure.MaxDepositPeriod)
proposal.DepositEndTime = <CurrentTime>.Add(depositParam.MaxDepositPeriod)
proposal.Deposits.append({initialDeposit, sender})
proposal.Submitter = sender
proposal.YesVotes = 0
@ -64,15 +64,15 @@ upon receiving txGovSubmitProposal from sender do
proposal.NoWithVetoVotes = 0
proposal.AbstainVotes = 0
proposal.CurrentStatus = ProposalStatusOpen
store(Proposals, <proposalID|'proposal'>, proposal) // Store proposal in Proposals mapping
return proposalID
```
### Deposit
Once a proposal is submitted, if
`Proposal.TotalDeposit < ActiveProcedure.MinDeposit`, Atom holders can send
Once a proposal is submitted, if
`Proposal.TotalDeposit < ActiveParam.MinDeposit`, Atom holders can send
`TxGovDeposit` transactions to increase the proposal's deposit.
```go
@ -89,7 +89,7 @@ type TxGovDeposit struct {
* If `MinDeposit` is reached:
* Push `proposalID` in `ProposalProcessingQueueEnd`
A `TxGovDeposit` transaction has to go through a number of checks to be valid.
A `TxGovDeposit` transaction has to go through a number of checks to be valid.
These checks are outlined in the following pseudocode.
```go
@ -98,27 +98,27 @@ These checks are outlined in the following pseudocode.
upon receiving txGovDeposit from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit)
if !correctlyFormatted(txGovDeposit)
throw
proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>) // proposal is a const key, proposalID is variable
if (proposal == nil)
if (proposal == nil)
// There is no proposal for this proposalID
throw
if (txGovDeposit.Deposit.Atoms <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit.Atoms) OR (proposal.CurrentStatus != ProposalStatusOpen)
// deposit is negative or null
// deposit is negative or null
// OR sender has insufficient funds
// OR proposal is not open for deposit anymore
throw
depositProcedure = load(GlobalParams, 'DepositProcedure')
depositParam = load(GlobalParams, 'DepositParam')
if (CurrentBlock >= proposal.SubmitBlock + depositProcedure.MaxDepositPeriod)
if (CurrentBlock >= proposal.SubmitBlock + depositParam.MaxDepositPeriod)
proposal.CurrentStatus = ProposalStatusClosed
else
@ -127,21 +127,21 @@ upon receiving txGovDeposit from sender do
proposal.Deposits.append({txGovVote.Deposit, sender})
proposal.TotalDeposit.Plus(txGovDeposit.Deposit)
if (proposal.TotalDeposit >= depositProcedure.MinDeposit)
if (proposal.TotalDeposit >= depositParam.MinDeposit)
// MinDeposit is reached, vote opens
proposal.VotingStartBlock = CurrentBlock
proposal.CurrentStatus = ProposalStatusActive
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
store(Proposals, <txGovVote.ProposalID|'proposal'>, proposal)
```
### Vote
Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there,
bonded Atom holders are able to send `TxGovVote` transactions to cast their
Once `ActiveParam.MinDeposit` is reached, voting period starts. From there,
bonded Atom holders are able to send `TxGovVote` transactions to cast their
vote on the proposal.
```go
@ -157,25 +157,25 @@ vote on the proposal.
*Note: Gas cost for this message has to take into account the future tallying of the vote in EndBlocker*
Next is a pseudocode proposal of the way `TxGovVote` transactions are
Next is a pseudocode proposal of the way `TxGovVote` transactions are
handled:
```go
// PSEUDOCODE //
// Check if TxGovVote is valid. If it is, count vote//
upon receiving txGovVote from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit)
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit)
throw
proposal = load(Proposals, <txGovDeposit.ProposalID|'proposal'>)
if (proposal == nil)
if (proposal == nil)
// There is no proposal for this proposalID
throw
if (proposal.CurrentStatus == ProposalStatusActive)

View File

@ -3,10 +3,11 @@ package clitest
import (
"encoding/json"
"fmt"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"os"
"testing"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/tests"
"github.com/stretchr/testify/require"

View File

@ -249,6 +249,30 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command {
return cmd
}
// GetCmdQueryProposal implements the query proposal command.
func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "param [param-type]",
Short: "Query the parameters (voting|tallying|deposit) of the governance process",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
paramType := args[0]
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/params/%s", queryRoute, paramType), nil)
if err != nil {
return err
}
fmt.Println(string(res))
return nil
},
}
return cmd
}
// GetCmdQueryProposal implements the query proposal command.
func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{

View File

@ -18,7 +18,8 @@ import (
// REST Variable names
// nolint
const (
RestProposalID = "proposalId"
RestParamsType = "type"
RestProposalID = "proposal-id"
RestDepositer = "depositer"
RestVoter = "voter"
RestProposalStatus = "status"
@ -32,6 +33,11 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec)
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, cliCtx)).Methods("POST")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, cliCtx)).Methods("POST")
r.HandleFunc(
fmt.Sprintf("/gov/parameters/{%s}", RestParamsType),
queryParamsHandlerFn(cdc, cliCtx),
).Methods("GET")
r.HandleFunc("/gov/proposals", queryProposalsWithParameterFn(cdc, cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", RestProposalID), queryProposalHandlerFn(cdc, cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), queryDepositsHandlerFn(cdc, cliCtx)).Methods("GET")
@ -177,6 +183,21 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc
}
}
func queryParamsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
paramType := vars[RestParamsType]
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/gov/%s/%s", gov.QueryParams, paramType), nil)
if err != nil {
utils.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return
}
utils.PostProcessResponse(w, cdc, res, cliCtx.Indent)
}
}
func queryProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)

View File

@ -250,7 +250,7 @@ func (keeper Keeper) GetDepositParams(ctx sdk.Context) DepositParams {
return depositParams
}
// Returns the current Voting Procedure from the global param store
// Returns the current VotingParams from the global param store
// nolint: errcheck
func (keeper Keeper) GetVotingParams(ctx sdk.Context) VotingParams {
var votingParams VotingParams
@ -258,7 +258,7 @@ func (keeper Keeper) GetVotingParams(ctx sdk.Context) VotingParams {
return votingParams
}
// Returns the current Tallying Procedure from the global param store
// Returns the current TallyParam from the global param store
// nolint: errcheck
func (keeper Keeper) GetTallyParams(ctx sdk.Context) TallyParams {
var tallyParams TallyParams

View File

@ -6,20 +6,20 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Procedure around Deposits for governance
// Param around Deposits for governance
type DepositParams struct {
MinDeposit sdk.Coins `json:"min_deposit"` // Minimum deposit for a proposal to enter voting period.
MaxDepositPeriod time.Duration `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
}
// Procedure around Tallying votes in governance
// Param around Tallying votes in governance
type TallyParams struct {
Threshold sdk.Dec `json:"threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
Veto sdk.Dec `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
GovernancePenalty sdk.Dec `json:"governance_penalty"` // Penalty if validator does not vote
}
// Procedure around Voting in governance
// Param around Voting in governance
type VotingParams struct {
VotingPeriod time.Duration `json:"voting_period"` // Length of the voting period.
}

View File

@ -1,6 +1,8 @@
package gov
import (
"fmt"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
@ -8,6 +10,7 @@ import (
// query endpoints supported by the governance Querier
const (
QueryParams = "params"
QueryProposals = "proposals"
QueryProposal = "proposal"
QueryDeposits = "deposits"
@ -15,11 +18,17 @@ const (
QueryVotes = "votes"
QueryVote = "vote"
QueryTally = "tally"
ParamDeposit = "deposit"
ParamVoting = "voting"
ParamTallying = "tallying"
)
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 QueryParams:
return queryParams(ctx, path[1:], req, keeper)
case QueryProposals:
return queryProposals(ctx, path[1:], req, keeper)
case QueryProposal:
@ -40,6 +49,31 @@ func NewQuerier(keeper Keeper) sdk.Querier {
}
}
func queryParams(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
switch path[0] {
case ParamDeposit:
bz, err2 := codec.MarshalJSONIndent(keeper.cdc, keeper.GetDepositParams(ctx))
if err2 != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error()))
}
return bz, nil
case ParamVoting:
bz, err2 := codec.MarshalJSONIndent(keeper.cdc, keeper.GetVotingParams(ctx))
if err2 != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error()))
}
return bz, nil
case ParamTallying:
bz, err2 := codec.MarshalJSONIndent(keeper.cdc, keeper.GetTallyParams(ctx))
if err2 != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error()))
}
return bz, nil
default:
return res, sdk.ErrUnknownRequest(fmt.Sprintf("%s is not a valid query request path", req.Path))
}
}
// Params for query 'custom/gov/proposal'
type QueryProposalParams struct {
ProposalID uint64

306
x/gov/querier_test.go Normal file
View File

@ -0,0 +1,306 @@
package gov
import (
"strings"
"testing"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
)
func getQueriedParams(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier) (DepositParams, VotingParams, TallyParams) {
query := abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryParams, ParamDeposit}, "/"),
Data: []byte{},
}
bz, err := querier(ctx, []string{QueryParams, ParamDeposit}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var depositParams DepositParams
err2 := cdc.UnmarshalJSON(bz, &depositParams)
require.Nil(t, err2)
query = abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryParams, ParamVoting}, "/"),
Data: []byte{},
}
bz, err = querier(ctx, []string{QueryParams, ParamVoting}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var votingParams VotingParams
err2 = cdc.UnmarshalJSON(bz, &votingParams)
require.Nil(t, err2)
query = abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryParams, ParamTallying}, "/"),
Data: []byte{},
}
bz, err = querier(ctx, []string{QueryParams, ParamTallying}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var tallyParams TallyParams
err2 = cdc.UnmarshalJSON(bz, &tallyParams)
require.Nil(t, err2)
return depositParams, votingParams, tallyParams
}
func getQueriedProposal(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) Proposal {
query := abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryProposal}, "/"),
Data: cdc.MustMarshalJSON(QueryProposalParams{
ProposalID: proposalID,
}),
}
bz, err := querier(ctx, []string{QueryProposal}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var proposal Proposal
err2 := cdc.UnmarshalJSON(bz, proposal)
require.Nil(t, err2)
return proposal
}
func getQueriedProposals(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, depositer, voter sdk.AccAddress, status ProposalStatus, limit uint64) []Proposal {
query := abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryProposals}, "/"),
Data: cdc.MustMarshalJSON(QueryProposalsParams{
Voter: voter,
Depositer: depositer,
ProposalStatus: status,
Limit: limit,
}),
}
bz, err := querier(ctx, []string{QueryProposal}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var proposals []Proposal
err2 := cdc.UnmarshalJSON(bz, proposals)
require.Nil(t, err2)
return proposals
}
func getQueriedDeposit(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64, depositer sdk.AccAddress) Deposit {
query := abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryDeposit}, "/"),
Data: cdc.MustMarshalJSON(QueryDepositParams{
ProposalID: proposalID,
Depositer: depositer,
}),
}
bz, err := querier(ctx, []string{QueryDeposits}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var deposit Deposit
err2 := cdc.UnmarshalJSON(bz, deposit)
require.Nil(t, err2)
return deposit
}
func getQueriedDeposits(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) []Deposit {
query := abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryDeposits}, "/"),
Data: cdc.MustMarshalJSON(QueryDepositsParams{
ProposalID: proposalID,
}),
}
bz, err := querier(ctx, []string{QueryDeposits}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var deposits []Deposit
err2 := cdc.UnmarshalJSON(bz, &deposits)
require.Nil(t, err2)
return deposits
}
func getQueriedVote(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64, voter sdk.AccAddress) Vote {
query := abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryVote}, "/"),
Data: cdc.MustMarshalJSON(QueryVoteParams{
ProposalID: proposalID,
Voter: voter,
}),
}
bz, err := querier(ctx, []string{QueryVote}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var vote Vote
err2 := cdc.UnmarshalJSON(bz, &vote)
require.Nil(t, err2)
return vote
}
func getQueriedVotes(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) []Vote {
query := abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryVote}, "/"),
Data: cdc.MustMarshalJSON(QueryVotesParams{
ProposalID: proposalID,
}),
}
bz, err := querier(ctx, []string{QueryVotes}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var votes []Vote
err2 := cdc.UnmarshalJSON(bz, &votes)
require.Nil(t, err2)
return votes
}
func getQueriedTally(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sdk.Querier, proposalID uint64) TallyResult {
query := abci.RequestQuery{
Path: strings.Join([]string{"custom", "gov", QueryTally}, "/"),
Data: cdc.MustMarshalJSON(QueryTallyParams{
ProposalID: proposalID,
}),
}
bz, err := querier(ctx, []string{QueryTally}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var tally TallyResult
err2 := cdc.UnmarshalJSON(bz, &tally)
require.Nil(t, err2)
return tally
}
func testQueryParams(t *testing.T) {
cdc := codec.New()
mapp, keeper, _, _, _, _ := getMockApp(t, 1000)
querier := NewQuerier(keeper)
ctx := mapp.NewContext(false, abci.Header{})
getQueriedParams(t, ctx, cdc, querier)
}
func testQueries(t *testing.T) {
cdc := codec.New()
mapp, keeper, _, addrs, _, _ := getMockApp(t, 1000)
querier := NewQuerier(keeper)
handler := NewHandler(keeper)
ctx := mapp.NewContext(false, abci.Header{})
depositParams, _, _ := getQueriedParams(t, ctx, cdc, querier)
// addrs[0] proposes (and deposits) proposals #1 and #2
res := handler(ctx, NewMsgSubmitProposal("title", "description", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("dummycoin", 1)}))
var proposalID1 uint64
cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID1)
res = handler(ctx, NewMsgSubmitProposal("title", "description", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("dummycoin", 1)}))
var proposalID2 uint64
cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID2)
// addrs[1] proposes (and deposits) proposals #3
res = handler(ctx, NewMsgSubmitProposal("title", "description", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin("dummycoin", 1)}))
var proposalID3 uint64
cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID3)
// addrs[1] deposits on proposals #2 & #3
res = handler(ctx, NewMsgDeposit(addrs[1], proposalID2, depositParams.MinDeposit))
res = handler(ctx, NewMsgDeposit(addrs[1], proposalID3, depositParams.MinDeposit))
// check deposits on proposal1 match individual deposits
deposits := getQueriedDeposits(t, ctx, cdc, querier, proposalID1)
require.Len(t, deposits, 1)
deposit := getQueriedDeposit(t, ctx, cdc, querier, proposalID1, addrs[0])
require.Equal(t, deposit, deposits[0])
// check deposits on proposal2 match individual deposits
deposits = getQueriedDeposits(t, ctx, cdc, querier, proposalID2)
require.Len(t, deposits, 2)
deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, addrs[0])
require.True(t, deposit.Equals(deposits[0]))
deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, addrs[1])
require.True(t, deposit.Equals(deposits[1]))
// check deposits on proposal3 match individual deposits
deposits = getQueriedDeposits(t, ctx, cdc, querier, proposalID3)
require.Len(t, deposits, 1)
deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID3, addrs[1])
require.Equal(t, deposit, deposits[0])
// Only proposal #1 should be in Deposit Period
proposals := getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusDepositPeriod, 0)
require.Len(t, proposals, 1)
require.Equal(t, proposalID1, proposals[0].GetProposalID())
// Only proposals #2 and #3 should be in Voting Period
proposals = getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusVotingPeriod, 0)
require.Len(t, proposals, 2)
require.Equal(t, proposalID2, proposals[0].GetProposalID())
require.Equal(t, proposalID3, proposals[1].GetProposalID())
// Addrs[0] votes on proposals #2 & #3
handler(ctx, NewMsgVote(addrs[0], proposalID2, OptionYes))
handler(ctx, NewMsgVote(addrs[0], proposalID3, OptionYes))
// Addrs[1] votes on proposal #3
handler(ctx, NewMsgVote(addrs[1], proposalID3, OptionYes))
// Test query voted by addrs[0]
proposals = getQueriedProposals(t, ctx, cdc, querier, nil, addrs[0], StatusNil, 0)
require.Equal(t, proposalID2, (proposals[0]).GetProposalID())
require.Equal(t, proposalID3, (proposals[1]).GetProposalID())
// Test query votes on Proposal 2
votes := getQueriedVotes(t, ctx, cdc, querier, proposalID2)
require.Len(t, votes, 1)
require.Equal(t, addrs[0], votes[0].Voter)
vote := getQueriedVote(t, ctx, cdc, querier, proposalID2, addrs[0])
require.Equal(t, vote, votes[0])
// Test query votes on Proposal 3
votes = getQueriedVotes(t, ctx, cdc, querier, proposalID3)
require.Len(t, votes, 2)
require.True(t, addrs[0].String() == votes[0].Voter.String())
require.True(t, addrs[1].String() == votes[0].Voter.String())
// Test proposals queries with filters
// Test query all proposals
proposals = getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusNil, 0)
require.Equal(t, proposalID1, (proposals[0]).GetProposalID())
require.Equal(t, proposalID2, (proposals[1]).GetProposalID())
require.Equal(t, proposalID3, (proposals[2]).GetProposalID())
// Test query voted by addrs[1]
proposals = getQueriedProposals(t, ctx, cdc, querier, nil, addrs[1], StatusNil, 0)
require.Equal(t, proposalID3, (proposals[0]).GetProposalID())
// Test query deposited by addrs[0]
proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[0], nil, StatusNil, 0)
require.Equal(t, proposalID1, (proposals[0]).GetProposalID())
// Test query deposited by addr2
proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[1], nil, StatusNil, 0)
require.Equal(t, proposalID2, (proposals[0]).GetProposalID())
require.Equal(t, proposalID3, (proposals[1]).GetProposalID())
// Test query voted AND deposited by addr1
proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[0], addrs[0], StatusNil, 0)
require.Equal(t, proposalID2, (proposals[0]).GetProposalID())
// Test Tally Query
tally := getQueriedTally(t, ctx, cdc, querier, proposalID2)
require.True(t, !tally.Equals(EmptyTallyResult()))
}

View File

@ -39,6 +39,7 @@ func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper,
keeper := NewKeeper(mapp.Cdc, keyGov, pk, pk.Subspace("testgov"), ck, sk, DefaultCodespace)
mapp.Router().AddRoute("gov", NewHandler(keeper))
mapp.QueryRouter().AddRoute("gov", NewQuerier(keeper))
mapp.SetEndBlocker(getEndBlocker(keeper))
mapp.SetInitChainer(getInitChainer(mapp, keeper, sk))

View File

@ -1,9 +1,11 @@
package mock
import (
"bytes"
"fmt"
"math/rand"
"os"
"sort"
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
@ -109,23 +111,64 @@ func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.Respo
return abci.ResponseInitChain{}
}
// Type that combines an Address with the privKey and pubKey to that address
type AddrKeys struct {
Address sdk.AccAddress
PubKey crypto.PubKey
PrivKey crypto.PrivKey
}
// implement `Interface` in sort package.
type AddrKeysSlice []AddrKeys
func (b AddrKeysSlice) Len() int {
return len(b)
}
// Sorts lexographically by Address
func (b AddrKeysSlice) Less(i, j int) bool {
// bytes package already implements Comparable for []byte.
switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) {
case -1:
return true
case 0, 1:
return false
default:
panic("not fail-able with `bytes.Comparable` bounded [-1, 1].")
}
}
func (b AddrKeysSlice) Swap(i, j int) {
b[j], b[i] = b[i], b[j]
}
// CreateGenAccounts generates genesis accounts loaded with coins, and returns
// their addresses, pubkeys, and privkeys.
func CreateGenAccounts(numAccs int, genCoins sdk.Coins) (genAccs []auth.Account, addrs []sdk.AccAddress, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) {
addrKeysSlice := AddrKeysSlice{}
for i := 0; i < numAccs; i++ {
privKey := ed25519.GenPrivKey()
pubKey := privKey.PubKey()
addr := sdk.AccAddress(pubKey.Address())
genAcc := &auth.BaseAccount{
addrKeysSlice = append(addrKeysSlice, AddrKeys{
Address: addr,
Coins: genCoins,
}
PubKey: pubKey,
PrivKey: privKey,
})
}
genAccs = append(genAccs, genAcc)
privKeys = append(privKeys, privKey)
pubKeys = append(pubKeys, pubKey)
addrs = append(addrs, addr)
sort.Sort(addrKeysSlice)
for i := range addrKeysSlice {
addrs = append(addrs, addrKeysSlice[i].Address)
pubKeys = append(pubKeys, addrKeysSlice[i].PubKey)
privKeys = append(privKeys, addrKeysSlice[i].PrivKey)
genAccs = append(genAccs, &auth.BaseAccount{
Address: addrKeysSlice[i].Address,
Coins: genCoins,
})
}
return