8.0 KiB
Implementation (1/2)
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.
type DepositProcedure struct {
MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period.
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
}
type VotingProcedure struct {
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
}
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
GracePeriod int64 // If validator entered validator set in this period of blocks before vote ended, governance penalty does not apply
}
Procedures 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 byte
const (
ProposalTypePlainText = 0x1 // Plain text proposals
ProposalTypeSoftwareUpgrade = 0x2 // Text proposal inducing a software upgrade
)
type ProposalStatus byte
const (
ProposalStatusOpen = 0x1 // Proposal is submitted. Participants can deposit on it but not vote
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
)
Deposit
type Deposit struct {
Amount sdk.Coins // Amount of coins deposited by depositer
Depositer crypto.address // Address of depositer
}
ValidatorGovInfo
This type is used in a temp map when tallying
type ValidatorGovInfo struct {
Minus sdk.Dec
Vote Vote
}
Proposals
Proposals
are an item to be voted on.
type Proposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type ProposalType // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit
Deposits []Deposit // List of deposits on the proposal
SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included
Submitter sdk.Address // Address of the submitter
VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
CurrentStatus ProposalStatus // Current status of the proposal
YesVotes sdk.Dec
NoVotes sdk.Dec
NoWithVetoVotes sdk.Dec
AbstainVotes sdk.Dec
}
We also mention a method to update the tally for a given proposal:
func (proposal Proposal) updateTally(vote byte, amount sdk.Dec)
Stores
Stores are KVStores in the multistore. 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'|address
toVote
. 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 keyKey
in store found at keyStoreKey
in the multistorestore(StoreKey, Key, value)
: Write valueValue
at keyKey
in store found at keyStoreKey
in the multistore
Proposal Processing Queue
Store:
ProposalProcessingQueue
: A queuequeue[proposalID]
containing all theProposalIDs
of proposals that reachedMinDeposit
. Each round, the oldest element ofProposalProcessingQueue
is checked duringBeginBlock
to see ifCurrentBlock == VotingStartBlock + activeProcedure.VotingPeriod
. If it is, then the application tallies the votes, compute the votes of each validator and checks if every validator in the valdiator set have voted and, if not, appliesGovernancePenalty
. If the proposal is accepted, deposits are refunded. After that proposal is ejected fromProposalProcessingQueue
and the next element of the queue is evaluated.
And the pseudocode for the ProposalProcessingQueue
:
in EndBlock do
checkProposal() // First call of the recursive function
// Recursive function. First call in BeginBlock
func checkProposal()
proposalID = ProposalProcessingQueue.Peek()
if (proposalID == nil)
return
proposal = load(Governance, <proposalID|'proposal'>) // proposal is a const key
votingProcedure = load(GlobalParams, 'VotingProcedure')
if (CurrentBlock == proposal.VotingStartBlock + votingProcedure.VotingPeriod && proposal.CurrentStatus == ProposalStatusActive)
// End of voting period, tally
ProposalProcessingQueue.pop()
validators =
Keeper.getAllValidators()
tmpValMap := map(sdk.Address)ValidatorGovInfo
// Initiate mapping at 0. Validators that remain at 0 at the end of tally will be punished
for each validator in validators
tmpValMap(validator).Minus = 0
// Tally
voterIterator = rangeQuery(Governance, <proposalID|'addresses'>) //return all the addresses that voted on the proposal
for each (voterAddress, vote) in voterIterator
delegations = stakeKeeper.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 = stakeKeeper.getValidator(voterAddress)
if (isVal)
tmpValMap(voterAddress).Vote = vote
tallyingProcedure = load(GlobalParams, 'TallyingProcedure')
// Slash validators that did not vote, or update tally if they voted
for each validator in validators
if (validator.bondHeight < CurrentBlock - tallyingProcedure.GracePeriod)
// only slash if validator entered validator set before grace period
if (!tmpValMap(validator).HasVoted)
slash validator by tallyingProcedure.GovernancePenalty
else
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 > tallyingProcedure.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingProcedure.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
// proposal was rejected
proposal.CurrentStatus = ProposalStatusRejected
store(Governance, <proposalID|'proposal'>, proposal)
checkProposal()