222 lines
8.0 KiB
Markdown
222 lines
8.0 KiB
Markdown
# 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.
|
|
|
|
|
|
```go
|
|
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
|
|
}
|
|
```
|
|
|
|
```go
|
|
type VotingProcedure struct {
|
|
VotingPeriod int64 // 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
|
|
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:
|
|
|
|
```go
|
|
|
|
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
|
|
|
|
```go
|
|
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
|
|
|
|
```go
|
|
type ValidatorGovInfo struct {
|
|
Minus sdk.Dec
|
|
Vote Vote
|
|
}
|
|
```
|
|
|
|
### Proposals
|
|
|
|
`Proposals` are an item to be voted on.
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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'` to `Proposal`
|
|
* A mapping from `proposalID|'addresses'|address` to `Vote`. This mapping allows us to query all addresses that voted on the proposal along with their vote by doing a range query on `proposalID: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 key `Key` in store found at key `StoreKey` in the multistore
|
|
* `store(StoreKey, Key, value)`: Write value `Value` at key `Key` in store found at key `StoreKey` in the multistore
|
|
|
|
### Proposal Processing Queue
|
|
|
|
**Store:**
|
|
* `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the
|
|
`ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest
|
|
element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if
|
|
`CurrentBlock == 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, applies `GovernancePenalty`. If the proposal is accepted, deposits are refunded.
|
|
After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated.
|
|
|
|
And the pseudocode for the `ProposalProcessingQueue`:
|
|
|
|
```go
|
|
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()
|
|
```
|