From 5735075b0526a833e46550221752614c66b2f0b2 Mon Sep 17 00:00:00 2001 From: gamarin Date: Mon, 4 Jun 2018 17:20:07 +0200 Subject: [PATCH] Revamp gov spec --- docs/spec/governance/state.md | 145 +++++++++--------- docs/spec/governance/transactions.md | 210 +++++++-------------------- 2 files changed, 122 insertions(+), 233 deletions(-) diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index e533ec6fe..d82dd92ac 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -11,32 +11,38 @@ has to be created and the previous one rendered inactive. ```go -type VoteType byte +type Vote byte const ( - VoteTypeYes = 0x1 - VoteTypeNo = 0x2 - VoteTypeNoWithVeto = 0x3 - VoteTypeAbstain = 0x4 + VoteYes = 0x1 + VoteNo = 0x2 + VoteNoWithVeto = 0x3 + VoteAbstain = 0x4 ) type ProposalType byte const ( - ProposalTypePlainText = 0x1 + ProposalTypePlainText = 0x1 ProposalTypeSoftwareUpgrade = 0x2 +) +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 ) type Procedure struct { VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks - MinDeposit int64 // Minimum deposit for a proposal to enter voting period. - VoteTypes []VoteType // Vote types available to voters. - ProposalTypes []ProposalType // Proposal types available to submitters. + MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period. Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months - GovernancePenalty int64 // Penalty if validator does not vote + GovernancePenalty sdk.Rat // Penalty if validator does not vote IsActive bool // If true, procedure is active. Only one procedure can have isActive true. } @@ -53,18 +59,17 @@ The current active procedure is stored in a global `params` KVStore. } ``` -### Votes +### ValidatorGovInfo + +This type is used in a temp map when tallying ```go - type Votes struct { - Yes int64 - No int64 - NoWithVeto int64 - Abstain int64 + type ValidatorGovInfo struct { + Minus sdk.Rat + Vote Vote } ``` - ### Proposals `Proposals` are an item to be voted on. @@ -77,37 +82,34 @@ type Proposal struct { 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 crypto.Address // Address of the submitter + Submitter sdk.Address // Address of the submitter VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached - InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0) InitProcedure Procedure // Active Procedure when proposal enters voting period + CurrentStatus ProposalStatus // Current status of the proposal - Votes Votes // Total votes for each option + YesVotes sdk.Rat + NoVotes sdk.Rat + NoWithVetoVotes sdk.Rat + AbstainVotes sdk.Rat } ``` -We also introduce a type `ValidatorGovInfo` +We also mention a method to update the tally for a given proposal: ```go -type ValidatorGovInfo struct { - InitVotingPower int64 // Voting power of validator when proposal enters voting period - Minus int64 // Minus of validator, used to compute validator's voting power -} + func (proposal Proposal) updateTally(vote byte, amount sdk.Rat) ``` ### Stores -*Stores are KVStores in the multistore. The key to find the store is the first parameter in the list* +*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` 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` -* `Proposals: int64 => Proposal` maps `proposalID` to the `Proposal` - `proposalID` -* `Options: => VoteType`: maps to the vote of the `voterAddress` for `proposalID` re its delegation to `validatorAddress`. - Returns 0x0 If `voterAddress` has not voted under this validator. -* `ValidatorGovInfos: => ValidatorGovInfo`: maps to the gov info for the `validatorAddress` and `proposalID`. - Returns `nil` if proposal has not entered voting period or if `address` was not the - address of a validator when proposal entered voting period. For pseudocode purposes, here are the two function we will use to read or write in stores: @@ -121,16 +123,14 @@ For pseudocode purposes, here are the two function we will use to read or write `ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if `CurrentBlock == VotingStartBlock + InitProcedure.VotingPeriod`. If it is, - then the application checks if validators in `InitVotingPowerList` have voted + 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. - Note that if a proposal is accepted under the special condition, - its `ProposalID` must be ejected from `ProposalProcessingQueue`. And the pseudocode for the `ProposalProcessingQueue`: ```go - in BeginBlock do + in EndBlock do checkProposal() // First call of the recursive function @@ -141,58 +141,55 @@ And the pseudocode for the `ProposalProcessingQueue`: if (proposalID == nil) return - proposal = load(Proposals, proposalID) + proposal = load(Governance, proposalID) - if (proposal.Votes.YesVotes/proposal.InitTotalVotingPower > 2/3) + if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod && proposal.CurrentStatus == ProposalStatusActive) - // proposal accepted early by super-majority - // no punishments; refund deposits + // End of voting period, tally ProposalProcessingQueue.pop() - var newDeposits []Deposits + validators = stakeKeeper.getAllValidators() + tmpValMap := map(sdk.Address)ValidatorGovInfo - // XXX: why do we need to reset deposits? cant we just clear it ? - for each (amount, depositer) in proposal.Deposits - newDeposits.append[{0, depositer}] - depositer.AtomBalance += amount + // 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 + + voterIterator = rangeQuery(Governance, ) //return all the addresses that voted on the proposal - proposal.Deposits = newDeposits - store(Proposals, proposalID, proposal) + // Tally + for each (voterAddress, vote) in voterIterator + delegations = stakeKeeper.getDelegations(voterAddress) // get all delegations for current voter - checkProposal() + for each delegation in delegations + tmpValMap(delegation.ValidatorAddr).Minus += delegation.Shares + proposal.updateTally(vote, delegation.Shares) - else if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod) + _, isVal = stakeKeeper.getValidator(voterAddress) + if (isVal) + tmpValMap(voterAddress).Vote = vote - ProposalProcessingQueue.pop() - activeProcedure = load(params, 'ActiveProcedure') - - for each validator in CurrentBondedValidators - validatorGovInfo = load(ValidatorGovInfos, ) - - if (validatorGovInfo.InitVotingPower != nil) - // validator was bonded when vote started - - validatorOption = load(Options, ) - if (validatorOption == nil) - // validator did not vote - slash validator by activeProcedure.GovernancePenalty - - - totalNonAbstain = proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes - if( proposal.Votes.YesVotes/totalNonAbstain > 0.5 AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < 1/3) + // Slash validators that did not vote, or update tally if they voted + for each validator in validators + if (!tmpValMap(validator).HasVoted) + slash validator by proposal.Procedure.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 > proposal.InitProcedure.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < proposal.InitProcedure.Veto) // proposal was accepted at the end of the voting period // refund deposits (non-voters already punished) - - var newDeposits []Deposits - + proposal.CurrentStatus = ProposalStatusAccepted for each (amount, depositer) in proposal.Deposits - newDeposits.append[{0, depositer}] depositer.AtomBalance += amount - proposal.Deposits = newDeposits - store(Proposals, proposalID, proposal) + else + // proposal was rejected + proposal.CurrentStatus = ProposalStatusRejected - checkProposal() + store(Governance, proposalID, proposal) + checkProposal() ``` diff --git a/docs/spec/governance/transactions.md b/docs/spec/governance/transactions.md index 5f5401de8..25beb6d1a 100644 --- a/docs/spec/governance/transactions.md +++ b/docs/spec/governance/transactions.md @@ -12,7 +12,7 @@ type TxGovSubmitProposal struct { Title string // Title of the proposal Description string // Description of the proposal Type ProposalType // Type of proposal - InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. + InitialDeposit sdk.Coins // Initial deposit paid by sender. Must be strictly positive. } ``` @@ -22,8 +22,7 @@ type TxGovSubmitProposal struct { * Initialise `Proposals` attributes * Decrease balance of sender by `InitialDeposit` * If `MinDeposit` is reached: - * Push `proposalID` in `ProposalProcessingQueueEnd` - * Store each validator's voting power in `ValidatorGovInfos` + * Push `proposalID` in `ProposalProcessingQueue` A `TxGovSubmitProposal` transaction can be handled according to the following pseudocode. @@ -34,16 +33,16 @@ pseudocode. upon receiving txGovSubmitProposal from sender do - if !correctlyFormatted(txGovSubmitProposal) then + if !correctlyFormatted(txGovSubmitProposal) // check if proposal is correctly formatted. Includes fee payment. throw initialDeposit = txGovSubmitProposal.InitialDeposit - if (initialDeposit <= 0) OR (sender.AtomBalance < initialDeposit) then + if (initialDeposit.Atoms <= 0) OR (sender.AtomBalance < initialDeposit.Atoms) // InitialDeposit is negative or null OR sender has insufficient funds throw - sender.AtomBalance -= initialDeposit + sender.AtomBalance -= initialDeposit.Atoms proposalID = generate new proposalID proposal = NewProposal() @@ -55,35 +54,25 @@ upon receiving txGovSubmitProposal from sender do proposal.SubmitBlock = CurrentBlock proposal.Deposits.append({initialDeposit, sender}) proposal.Submitter = sender - proposal.Votes.Yes = 0 - proposal.Votes.No = 0 - proposal.Votes.NoWithVeto = 0 - proposal.Votes.Abstain = 0 + proposal.YesVotes = 0 + proposal.NoVotes = 0 + proposal.NoWithVetoVotes = 0 + proposal.AbstainVotes = 0 activeProcedure = load(params, 'ActiveProcedure') - if (initialDeposit < activeProcedure.MinDeposit) then + if (initialDeposit < activeProcedure.MinDeposit) // MinDeposit is not reached + proposal.CurrentStatus = ProposalStatusOpen proposal.VotingStartBlock = -1 - proposal.InitTotalVotingPower = 0 else // MinDeposit is reached + proposal.CurrentStatus = ProposalStatusActive proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower proposal.InitProcedure = activeProcedure - - for each validator in CurrentBondedValidators - // Store voting power of each bonded validator - - validatorGovInfo = new ValidatorGovInfo - validatorGovInfo.InitVotingPower = validator.VotingPower - validatorGovInfo.Minus = 0 - - store(ValidatorGovInfos, , validatorGovInfo) - ProposalProcessingQueue.push(proposalID) store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping @@ -98,8 +87,8 @@ Once a proposal is submitted, if ```go type TxGovDeposit struct { - ProposalID int64 // ID of the proposal - Deposit int64 // Number of Atoms to add to the proposal's deposit + ProposalID int64 // ID of the proposal + Deposit sdk.Coins // Number of Atoms to add to the proposal's deposit } ``` @@ -109,7 +98,6 @@ type TxGovDeposit struct { * Increase `proposal.TotalDeposit` by sender's `deposit` * If `MinDeposit` is reached: * Push `proposalID` in `ProposalProcessingQueueEnd` - * Store each validator's voting power in `ValidatorGovInfos` A `TxGovDeposit` transaction has to go through a number of checks to be valid. These checks are outlined in the following pseudocode. @@ -121,57 +109,40 @@ 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) then + if !correctlyFormatted(txGovDeposit) throw proposal = load(Proposals, txGovDeposit.ProposalID) - if (proposal == nil) then + if (proposal == nil) // There is no proposal for this proposalID throw - - if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) - // deposit is negative or null OR sender has insufficient funds - throw - + activeProcedure = load(params, 'ActiveProcedure') + + if (txGovDeposit.Deposit.Atoms <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit.Atoms) OR (proposal.TotalDeposit >= activeProcedure.MinDeposit) OR (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) + // deposit is negative or null + // OR sender has insufficient funds + // OR minDeposit has already been reached + // OR Maximum deposit period reached - if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then - // MinDeposit was reached - // TODO: shouldnt we do something here ? throw - else - if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then - // Maximum deposit period reached - throw + // sender can deposit + sender.AtomBalance -= txGovDeposit.Deposit.Atoms + + proposal.Deposits.append({txGovVote.Deposit, sender}) + proposal.TotalDeposit.Plus(txGovDeposit.Deposit) + + if (proposal.TotalDeposit >= activeProcedure.MinDeposit) + // MinDeposit is reached, vote opens - // sender can deposit - - sender.AtomBalance -= txGovDeposit.Deposit + proposal.VotingStartBlock = CurrentBlock + proposal.CurrentStatus = ProposalStatusActive + proposal.InitProcedure = activeProcedure + ProposalProcessingQueue.push(txGovDeposit.ProposalID) - proposal.Deposits.append({txGovVote.Deposit, sender}) - proposal.TotalDeposit += txGovDeposit.Deposit - - if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then - // MinDeposit is reached, vote opens - - proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedure = activeProcedure - - for each validator in CurrentBondedValidators - // Store voting power of each bonded validator - - validatorGovInfo = NewValidatorGovInfo() - validatorGovInfo.InitVotingPower = validator.VotingPower - validatorGovInfo.Minus = 0 - - store(ValidatorGovInfos, , validatorGovInfo) - - ProposalProcessingQueue.push(txGovDeposit.ProposalID) - - store(Proposals, txGovVote.ProposalID, proposal) + store(Proposals, txGovVote.ProposalID, proposal) ``` ### Vote @@ -183,26 +154,14 @@ vote on the proposal. ```go type TxGovVote struct { ProposalID int64 // proposalID of the proposal - Option string // option from OptionSet chosen by the voter - ValidatorAddress crypto.address // Address of the validator voter wants to tie its vote to + Vote byte // option from OptionSet chosen by the voter } ``` **State modifications:** -* If sender is not a validator and validator has not voted, initialize or - increase minus of validator by sender's `voting power` -* If sender is not a validator and validator has voted, decrease - votes of `validatorOption` by sender's `voting power` -* If sender is not a validator, increase votes of `txGovVote.Option` - by sender's `voting power` -* If sender is a validator, increase votes of `txGovVote.Option` by - validator's `InitVotingPower - minus` (`minus` can be equal to 0) +* Record `Vote` of sender -Votes need to be tied to a validator in order to compute validator's voting -power. If a delegator is bonded to multiple validators, it will have to send -one transaction per validator (the UI should facilitate this so that multiple -transactions can be sent in one "vote flow"). If the sender is the validator -itself, then it will input its own address as `ValidatorAddress` +*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 handled: @@ -214,91 +173,24 @@ handled: upon receiving txGovVote from sender do // check if proposal is correctly formatted. Includes fee payment. - if !correctlyFormatted(txGovDeposit) then + if !correctlyFormatted(txGovDeposit) throw proposal = load(Proposals, txGovDeposit.ProposalID) - if (proposal == nil) then + if (proposal == nil) // There is no proposal for this proposalID throw - validator = load(CurrentValidators, txGovVote.ValidatorAddress) + + if (proposal.VotingStartBlock >= 0) AND + (CurrentBlock <= proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) + + // Sender can vote if + // Vote has started AND if + // Vote had notended + + store(Governance, , txGovVote.Vote) // Voters can vote multiple times. Re-voting overrides previous vote. This is ok because tallying is done once at the end. + - if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR - (validator == nil) then - - // Throws if - // Option is not in Option Set of procedure that was active when vote opened OR if - // ValidatorAddress is not the address of a current validator - - throw - - option = load(Options, ::) - - if (option != nil) - // sender has already voted with the Atoms bonded to ValidatorAddress - throw - - if (proposal.VotingStartBlock < 0) OR - (CurrentBlock > proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) OR - (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorAddress) OR - (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.Address) OR - (proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) then - - // Throws if - // Vote has not started OR if - // Vote had ended OR if - // sender bonded Atoms to ValidatorAddress after start of vote OR if - // sender unbonded Atoms from ValidatorAddress after start of vote OR if - // special condition is met, i.e. proposal is accepted and closed - - throw - - validatorGovInfo = load(ValidatorGovInfos, :) - - if (validatorGovInfo == nil) - // validator became validator after proposal entered voting period - throw - - // sender can vote, check if sender == validator and store sender's option in Options - - store(Options, ::, txGovVote.Option) - - if (sender != validator.address) - // Here, sender is not the Address of the validator whose Address is txGovVote.ValidatorAddress - - if sender does not have bonded Atoms to txGovVote.ValidatorAddress then - // check in Staking module - throw - - validatorOption = load(Options, ::) - - if (validatorOption == nil) - // Validator has not voted already - - validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorAddress) - store(ValidatorGovInfos, :, validatorGovInfo) - - else - // Validator has already voted - // Reduce votes of option chosen by validator by sender's bonded Amount - - proposal.Votes.validatorOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress) - - // increase votes of option chosen by sender by bonded Amount - - senderOption = txGovVote.Option - propoal.Votes.senderOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress) - - store(Proposals, txGovVote.ProposalID, proposal) - - - else - // sender is the address of the validator whose main Address is txGovVote.ValidatorAddress - // i.e. sender == validator - - proposal.Votes.validatorOption += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) - - store(Proposals, txGovVote.ProposalID, proposal) ```