8.0 KiB
State
Proposals
Proposal objects are used to tally votes and generally track the proposal's state.
They contain an array of arbitrary sdk.Msg's which the governance module will attempt
to resolve and then execute if the proposal passes. Proposal's are identified by a
unique id and contains a series of timestamps: submit_time, deposit_end_time,
voting_start_time, voting_end_time which track the lifecycle of a proposal
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-rc1/proto/cosmos/gov/v1/gov.proto#L42-L59
A proposal will generally require more than just a set of messages to explain its
purpose but need some greater justification and allow a means for interested participants
to discuss and debate the proposal. In most cases, it is encouraged to have an off-chain
system that supports the on-chain governance process. To accommodate for this, a
proposal contains a special metadata field, an array of bytes, which can be used to
add context to the proposal. The metadata field allows custom use for networks, however,
it is expected that the field contains a URL or some form of CID using a system such as
IPFS. To support the case of
interoperability across networks, the SDK recommends that the metadata represents
the following JSON template:
{
"title": "...",
"description": "...",
"forum": "...", // a link to the discussion platform (i.e. Discord)
"other": "..." // any extra data that doesn't correspond to the other fields
}
This makes it far easier for clients to support multiple networks.
The metadata has a maximum length that is chosen by the app developer, and passed into the gov keeper as a config.
Writing a module that uses governance
There are many aspects of a chain, or of the individual modules that you may want to
use governance to perform such as changing various parameters. This is very simple
to do. First, write out your message types and MsgServer implementation. Add an
authority field to the keeper which will be populated in the constructor with the
governance module account: govKeeper.GetGovernanceAccount().GetAddress(). Then for
the methods in the msg_server.go, perform a check on the message that the signer
matches authority. This will prevent any user from executing that message.
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.
DepositParams
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-rc1/proto/cosmos/gov/v1/gov.proto#L102-L112
VotingParams
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-rc1/proto/cosmos/gov/v1/gov.proto#L114-L118
TallyParams
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-rc1/proto/cosmos/gov/v1/gov.proto#L120-L132
Parameters are stored in a global GlobalParams KVStore.
Additionally, we introduce some basic types:
type Vote byte
const (
VoteYes = 0x1
VoteNo = 0x2
VoteNoWithVeto = 0x3
VoteAbstain = 0x4
)
type ProposalType string
const (
ProposalTypePlainText = "Text"
ProposalTypeSoftwareUpgrade = "SoftwareUpgrade"
)
type ProposalStatus byte
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01 // Proposal is submitted. Participants can deposit on it but not vote
StatusVotingPeriod ProposalStatus = 0x02 // MinDeposit is reached, participants can vote
StatusPassed ProposalStatus = 0x03 // Proposal passed and successfully executed
StatusRejected ProposalStatus = 0x04 // Proposal has been rejected
StatusFailed ProposalStatus = 0x05 // Proposal passed but failed execution
)
Deposit
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.46.0-rc1/proto/cosmos/gov/v1/gov.proto#L34-L40
ValidatorGovInfo
This type is used in a temp map when tallying
type ValidatorGovInfo struct {
Minus sdk.Dec
Vote Vote
}
Stores
Stores are KVStores in the multi-store. The key to find the store is the first parameter in the list`
We will use one KVStore Governance to store two mappings:
- A mapping from
proposalID|'proposal'toProposal. - A mapping from
proposalID|'addresses'|addresstoVote. This mapping allows us to query all addresses that voted on the proposal along with their vote by doing a range query onproposalID:addresses.
For pseudocode purposes, here are the two function we will use to read or write in stores:
load(StoreKey, Key): Retrieve item stored at keyKeyin store found at keyStoreKeyin the multistorestore(StoreKey, Key, value): Write valueValueat keyKeyin store found at keyStoreKeyin the multistore
Proposal Processing Queue
Store:
ProposalProcessingQueue: A queuequeue[proposalID]containing all theProposalIDsof proposals that reachedMinDeposit. During eachEndBlock, all the proposals that have reached the end of their voting period are processed. To process a finished proposal, the application tallies the votes, computes the votes of each validator and checks if every validator in the validator set has voted. If the proposal is accepted, deposits are refunded. Finally, the proposal contentHandleris executed.
And the pseudocode for the ProposalProcessingQueue:
in EndBlock do
for finishedProposalID in GetAllFinishedProposalIDs(block.Time)
proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key
validators = Keeper.getAllValidators()
tmpValMap := map(sdk.AccAddress)ValidatorGovInfo
// Initiate mapping at 0. This is the amount of shares of the validator's vote that will be overridden by their delegator's votes
for each validator in validators
tmpValMap(validator.OperatorAddr).Minus = 0
// Tally
voterIterator = rangeQuery(Governance, <proposalID|'addresses'>) //return all the addresses that voted on the proposal
for each (voterAddress, vote) in voterIterator
delegations = stakingKeeper.getDelegations(voterAddress) // get all delegations for current voter
for each delegation in delegations
// make sure delegation.Shares does NOT include shares being unbonded
tmpValMap(delegation.ValidatorAddr).Minus += delegation.Shares
proposal.updateTally(vote, delegation.Shares)
_, isVal = stakingKeeper.getValidator(voterAddress)
if (isVal)
tmpValMap(voterAddress).Vote = vote
tallyingParam = load(GlobalParams, 'TallyingParam')
// Update tally if validator voted
for each validator in validators
if tmpValMap(validator).HasVoted
proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus))
// Check if proposal is accepted or rejected
totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes
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)
for each (amount, depositor) in proposal.Deposits
depositor.AtomBalance += amount
stateWriter, err := proposal.Handler()
if err != nil
// proposal passed but failed during state execution
proposal.CurrentStatus = ProposalStatusFailed
else
// proposal pass and state is persisted
proposal.CurrentStatus = ProposalStatusAccepted
stateWriter.save()
else
// proposal was rejected
proposal.CurrentStatus = ProposalStatusRejected
store(Governance, <proposalID|'proposal'>, proposal)