From d0e458f3bc5e5a4b9c63ffab86380361bcf22877 Mon Sep 17 00:00:00 2001 From: gamarin Date: Tue, 20 Feb 2018 18:40:01 +0100 Subject: [PATCH 001/182] Massive consolidation: queue, data storage struct, store, logic, ... --- docs/spec/governance/governance.md | 452 ++++++++++++++++------------- 1 file changed, 258 insertions(+), 194 deletions(-) diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md index c8560cee6..833fdb4d2 100644 --- a/docs/spec/governance/governance.md +++ b/docs/spec/governance/governance.md @@ -22,15 +22,15 @@ Any Atom holder, whether bonded or unbonded, can submit proposals by sending a ` ### Proposal filter (minimum deposit) -To prevent spam, proposals must be submitted with a `deposit` in Atoms such that `0 < deposit < MinDeposit`. -Other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. -Once the proposals's deposit reaches `MinDeposit`, it enters voting period. +To prevent spam, proposals must be submitted with a deposit in Atoms. Voting period will not start as long as the proposal's deposit is smaller than the minimum deposit parameter `MinDeposit`. + +When a proposal is submitted, it has to be accompagnied by a deposit that must be strictly positive but that can be inferior to `MinDeposit`. Indeed, the submitter need not pay for the entire deposit on its own. If a proposal's deposit is strictly inferior to `MinDeposit`, other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposals's deposit reaches `minDeposit`, it enters voting period. ### Deposit refund There are two instances where Atom holders that deposited can claim back their deposit: - If the proposal is accepted -- If the proposal's deposit does not reach `MinDeposit` for a period longer than `mMxDepositPeriod` (initial value: 2 months). Then the proposal is considered closed and nobody can deposit on it anymore. +- If the proposal's deposit does not reach `MinDeposit` for a period longer than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is considered closed and nobody can deposit on it anymore. In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` transaction to retrieve their share of the deposit. @@ -48,11 +48,16 @@ There are two categories of proposal: These two categories are strictly identical except that `Urgent` proposals can be accepted faster if a certain condition is met. For more information, see [Threshold](#threshold) section. + ## Vote ### Participants -*Participants* are users that have the right to vote. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals. +*Participants* are users that have the right to vote on proposals. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals. + +Note that some *participants* can be forbidden to vote on a proposal under a certain validator if: +- *participant* bonded or unbonded Atoms to said validator after proposal entered voting period +- *participant* became validator after proposal entered voting period ### Voting period @@ -68,7 +73,7 @@ The initial option set includes the following options: - `NoWithVeto` - `Abstain` -`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote. +`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote. *Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ option that casts a `NoWithVeto` vote.* @@ -80,11 +85,11 @@ In the initial version of the governance module, there will be no quorum enforce ### Threshold -Threshold is defined as the minimum ratio of `Yes` votes to `No` votes for the proposal to be accepted. +Threshold is defined as the minimum proportion of `Yes` votes (excluding `Abstain` votes) for the proposal to be accepted. -Initially, the threshold is set at 50% with a possibility to veto if more than 1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means that proposals are accepted is the ratio of `Yes` votes to `No` votes at the end of the voting period is superior to 50% and if the number of `NoWithVeto` votes is inferior to 1/3rd of total votes (excluding `Abstain`). +Initially, the threshold is set at 50% with a possibility to veto if more than 1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means that proposals are accepted if the proportion of `Yes` votes (excluding `Abstain` votes) at the end of the voting period is superior to 50% and if the proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` votes). -`Urgent` proposals also work with the aforementioned threshold, except there is another condition that can accelerate the acceptance of the proposal. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2/3, `UrgentProposal` will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens. +`Urgent` proposals also work with the aforementioned threshold, except there is another condition that can accelerate the acceptance of the proposal. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, `UrgentProposal` will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens. ### Inheritance @@ -101,11 +106,11 @@ If a validator’s address is not in the list of addresses that voted on a propo *Note: Need to define values for `GovernancePenalty`* -**Exception:** If a proposal is a `Urgent` proposal and is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2/3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2/3, making it mechanically impossible for some validators to vote on it. +**Exception:** If a proposal is a `Urgent` proposal and is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2:3, making it mechanically impossible for some validators to vote on it. ### Governance key and governance address -Validators can make use of an additional slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PubKey`. From there, they will be able to sign vote using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment. +Validators can make use of an additional slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PrivKey`. From there, they will be able to sign vote using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment. ## Software Upgrade @@ -129,29 +134,125 @@ Once a block contains more than 2/3rd *precommits* where a common `SoftwareUpgra *Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic and implementation might widely differ* -### Procedures +### State + +#### Procedures `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 Procedure struct { - VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks - MinDeposit int64 // Minimum deposit for a proposal to enter voting period. + VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks + MinDeposit int64 // Minimum deposit for a proposal to enter voting period. OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} - Threshold int64 // Minimum value of Yes votes to No votes ratio for proposal to pass. Initial value: 0.5 + 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 + MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months + GovernancePenalty int64 // Penalty if validator does not vote - ProcedureNumber int16 // Incremented each time a new procedure is created + ProcedureNumber int16 // Incremented each time a new procedure is created IsActive bool // If true, procedure is active. Only one procedure can have isActive true. } ``` -### Proposals +**Store**: +- `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their `ProcedureNumber` -`Proposals` are item to be voted on. They can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction. +#### Proposals + +`Proposals` are item to be voted on. + +```Go +type Proposal struct { + Title string // Title of the proposal + Description string // Description of the proposal + Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Category bool // false=regular, true=urgent + Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit + SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included + + 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) + InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1) + Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain) +} +``` + +We also introduce a type `ValidatorGovInfo` + +```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 +} +``` + +**Store:** + +- `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID` +- `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by `:` as `[]byte`. Given a `proposalID` and a `PubKey`, returns deposit (`nil` if `PubKey` has not deposited) +- `Options`: A mapping `map[[]byte]string` of options indexed by `::` as `[]byte`. Given a `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by this `PubKey` for this validator (`nil` if `PubKey` has not voted under this validator) +- `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's governance infos indexed by `:`. Returns `nil` if proposal has not entered voting period or if `PubKey` was not the governance public key of a validator when proposal entered voting period. + + +#### 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 + InitProcedure.VotingPeriod`. If it is, then the application checks if validators in `InitVotingPowerList` have voted and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`. + +*Note: Actual data structure may differ* + +And the pseudocode for the `ProposalProcessingQueue`: + +``` + in BeginBlock do + + checkProposal() // First call of the recursive function + + + // Recursive function. First call in BeginBlock + func checkProposal() + if (ProposalProcessingQueue.Peek() == nil) + return + + else + proposalID = ProposalProcessingQueue.Peek() + proposal = load(store, Proposals, proposalID) + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) + + if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) + + // proposal was urgent and accepted under the special condition + // no punishment + + ProposalProcessingQueue.pop() + checkProposal() + + else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) + + for each validator in CurrentBondedValidators + validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey) + + if (validatorGovInfo.InitVotingPower != nil) + // validator was bonded when vote started + + validatorOption = load(store, Options, validator.GovPubKey) + if (validatorOption == nil) + // validator did not vote + slash validator by ActiveProcedure.GovernancePenalty + + ProposalProcessingQueue.pop() + checkProposal() +``` + + +### Transactions + +#### Proposal Submission + + +Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction. ```Go type TxGovSubmitProposal struct { @@ -161,30 +262,17 @@ type TxGovSubmitProposal struct { Category bool // false=regular, true=urgent InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. } - -type Proposal struct { - Title string // Title of the proposal - Description string // Description of the proposal - Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Category bool // false=regular, true=urgent - Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit - SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included - VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached. - Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain) -} ``` -Each `Proposal` is identified by its unique `proposalID`. - -Additionaly, four lists will be linked to each proposal: -- `DepositorList`: List of addresses that deposited on the proposal with their associated deposit -- `VotersList`: List of addresses that voted **under each validator** with their associated option -- `InitVotingPowerList`: Snapshot of validators' voting power **when proposal enters voting period** (only saves validators whose voting power is >0). -- `MinusesList`: List of minuses for each validator. Used to compute validators' voting power when they cast a vote. - -Two final parameters, `InitTotalVotingPower` and `InitProcedureNumber` associated with `proposalID` will be saved when proposal enters voting period. - -We also introduce `ProposalProcessingQueue` which lists all the `ProposalIDs` of proposals that reached `MinDeposit` from oldest to newest. 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 and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the new first element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`. +**State modifications:** +- Generate new `proposalID` +- Create new `Proposal` +- Initialise `Proposals` attributes +- Store sender's deposit in `Deposits` +- Decrease balance of sender by `InitialDeposit` +- If `MinDeposit` is reached: + - Push `proposalID` in `ProposalProcessingQueueEnd` + - Store each validator's voting power in `ValidatorGovInfos` A `TxGovSubmitProposal` transaction can be handled according to the following pseudocode @@ -193,9 +281,10 @@ A `TxGovSubmitProposal` transaction can be handled according to the following ps // Check if TxGovSubmitProposal is valid. If it is, create proposal // upon receiving txGovSubmitProposal from sender do - // check if proposal is correctly formatted. Includes fee payment. if !correctlyFormatted(txGovSubmitProposal) then + // check if proposal is correctly formatted. Includes fee payment. + throw else @@ -205,10 +294,10 @@ upon receiving txGovSubmitProposal from sender do throw else - sender.AtomBalance -= InitialDeposit + sender.AtomBalance -= txGovSubmitProposal.InitialDeposit proposalID = generate new proposalID - proposal = create new Proposal from proposalID + proposal = NewProposal() proposal.Title = txGovSubmitProposal.Title proposal.Description = txGovSubmitProposal.Description @@ -217,79 +306,58 @@ upon receiving txGovSubmitProposal from sender do proposal.Deposit = txGovSubmitProposal.InitialDeposit proposal.SubmitBlock = CurrentBlock - create depositorsList from proposalID - initiate deposit of sender in depositorsList at txGovSubmitProposal.InitialDeposit + store(Deposits, :, txGovSubmitProposal.InitialDeposit) if (txGovSubmitProposal.InitialDeposit < ActiveProcedure.MinDeposit) then // MinDeposit is not reached proposal.VotingStartBlock = -1 + proposal.InitTotalVotingPower = 0 + proposal.InitProcedureNumber = -1 else // MinDeposit is reached proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedureNumber = ActiveProcedure.ProcedureNumber - create votersList, - initVotingPowerList, - minusesList, - initProcedureNumber, - initTotalVotingPower from proposalID - - snapshot(ActiveProcedure.ProcedureNumber) // Save current procedure number in initProcedureNumber - snapshot(TotalVotingPower) // Save total voting power in initTotalVotingPower - snapshot(ValidatorVotingPower) // Save validators' voting power in initVotingPowerList + for each validator in CurrentBondedValidators + // Store voting power of each bonded validator + + validatorGovInfo = NewValidatorGovInfo() + validatorGovInfo.InitVotingPower = validator.VotingPower + validatorGovInfo.Minus = 0 + + store(ValidatorGovInfos, :, validatorGovInfo) - ProposalProcessingQueueEnd++ - ProposalProcessingQueue[ProposalProcessingQueueEnd] = proposalID + ProposalProcessingQueue.push(proposalID) + store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping return proposalID ``` -And the pseudocode for the `ProposalProcessingQueue`: -``` - in BeginBlock do - - checkProposal() - - - - func checkProposal() - if (ProposalProcessingQueueBeginning == ProposalProcessingQueueEnd) - return - else - retrieve proposalID from ProposalProcessingQueue[ProposalProcessingQueueBeginning] - retrieve proposal from proposalID - retrieve initProcedureNumber from proposalID - retrieve initProcedure from initProcedureNumber - - if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) - retrieve initVotingPowerList from proposalID - retrieve votersList from proposalID - retrieve validators from initVotingPowerList - - for each validator in validators - if validator is not in votersList - slash validator by ActiveProcedure.GovernancePenalty - - ProposalProcessingQueueBeginning++ // ProposalProcessingQueue will have a new element - checkProposal() - - else - return -``` +#### Deposit Once a proposal is submitted, if `Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send `TxGovDeposit` transactions to increase the proposal's deposit. ```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 int64 // Number of Atoms to add to the proposal's deposit } ``` +**State modifications:** +- Decrease balance of sender by `deposit` +- Initialize or increase `deposit` of sender in `Deposits` +- Increase `proposal.Deposit` 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. ``` @@ -303,7 +371,9 @@ upon receiving txGovDeposit from sender do throw else - if !exist(txGovDeposit.proposalID) then + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then // There is no proposal for this proposalID throw @@ -314,9 +384,7 @@ upon receiving txGovDeposit from sender do throw - else - retrieve proposal from txGovDeposit.ProposalID // retrieve throws if it fails - + else if (proposal.Deposit >= ActiveProcedure.MinDeposit) then // MinDeposit was reached @@ -331,36 +399,41 @@ upon receiving txGovDeposit from sender do else // sender can deposit - retrieve depositorsList from txGovDeposit.ProposalID sender.AtomBalance -= txGovDeposit.Deposit + deposit = load(store, Deposits, :) - if sender is in depositorsList - increase deposit of sender in depositorsList by txGovDeposit.Deposit + if (deposit == nil) + // sender has never deposited on this proposal + store(Deposits, :, deposit) else - initialise deposit of sender in depositorsList at txGovDeposit.Deposit - + // sender has already deposited on this proposal + newDeposit = deposit + txGovDeposit.Deposit + store(Deposits, :, newDeposit) + proposal.Deposit += txGovDeposit.Deposit if (proposal.Deposit >= ActiveProcedure.MinDeposit) then // MinDeposit is reached, vote opens proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedureNumber = ActiveProcedure.ProcedureNumber - create votersList, - initVotingPowerList, - minusesList, - initProcedureNumber, - initTotalVotingPower from proposalID + for each validator in CurrentBondedValidators + // Store voting power of each bonded validator + + validatorGovInfo = NewValidatorGovInfo() + validatorGovInfo.InitVotingPower = validator.VotingPower + validatorGovInfo.Minus = 0 + + store(ValidatorGovInfos, :, validatorGovInfo) - snapshot(ActiveProcedure.ProcedureNumber) // Save current procedure number in InitProcedureNumber - snapshot(TotalVotingPower) // Save total voting power in InitTotalVotingPower - snapshot(ValidatorVotingPower) // Save validators' voting power in InitVotingPowerList - - ProposalProcessingQueueEnd++ // ProposalProcessingQueue will have a new element - ProposalProcessingQueue[ProposalProcessingQueueEnd] = txGovDeposit.ProposalID + ProposalProcessingQueue.push(txGovDeposit.ProposalID) ``` +#### Claiming deposit + Finally, if the proposal is accepted or `MinDeposit` was not reached before the end of the `MaximumDepositPeriod`, then Atom holders can send `TxGovClaimDeposit` transaction to claim their deposits. ```Go @@ -369,6 +442,11 @@ Finally, if the proposal is accepted or `MinDeposit` was not reached before the } ``` +**State modifications:** +If conditions are met, reimburse the deposit, i.e. +- Increase `AtomBalance` of sender by `deposit` +- Set `deposit` of sender in `DepositorsList` to 0 + And the associated pseudocode ``` @@ -382,29 +460,28 @@ And the associated pseudocode throw else - if !exists(txGovClaimDeposit.ProposalID) then + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then // There is no proposal for this proposalID throw else - retrieve depositorsList from txGovClaimDeposit.ProposalID + deposit = load(store, Deposits, :) - - if sender is not in depositorsList then + if (deposit == nil) + // sender has not deposited on this proposal + throw - else - retrieve deposit from sender in depositorsList - + else if deposit <= 0 // deposit has already been claimed throw - else - retrieve proposal from txGovClaimDeposit.ProposalID - + else if proposal.VotingStartBlock <= 0 // Vote never started @@ -415,33 +492,30 @@ And the associated pseudocode else // MaxDepositPeriod is reached - - set deposit of sender in depositorsList to 0 + // Set sender's deposit to 0 and refund + + store(Deposits, :, 0) sender.AtomBalance += deposit else // Vote started + + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) - retrieve initTotalVotingPower from txGovClaimDeposit.ProposalID - retrieve initProcedureNumber from txGovClaimDeposit.ProposalID - retrieve initProcedure from initProcedureNumber // get procedure that was active when vote opened - - if (proposal.Category AND proposal.Votes['Yes']/initTotalVotingPower >= 2/3) OR - ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then + if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR + ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then // Proposal was accepted either because // Proposal was urgent and special condition was met // Voting period ended and vote satisfies threshold - set deposit of sender in depositorsList to 0 + store(Deposits, :, 0) sender.AtomBalance += deposit - - else - throw + ``` -### Vote +#### Vote Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there, bonded Atom holders are able to send `TxGovVote` transactions to cast their vote on the proposal. @@ -453,9 +527,17 @@ Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there, } ``` +**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 `proposal.Votes['validatorOption']` by sender's `voting power` +- If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` by sender's `voting power` +- If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by validator's `InitialVotingPower - minus` (`minus` can be equal to 0) + 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 GovernancePubKey as `ValidatorPubKey` + + Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled: ``` @@ -469,19 +551,19 @@ Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled throw else - if !exists(txGovVote.proposalID) OR - - // Throws if - // proposalID does not exist - + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + throw else - retrieve initProcedureNumber from txGovVote.ProposalID - retrieve initProcedure from initProcedureNumber // get procedure that was active when vote opened + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened + validator = load(store, Validators, txGovVote.ValidatorPubKey) if !initProcedure.OptionSet.includes(txGovVote.Option) OR - !isValid(txGovVote.ValidatorPubKey) then + validator == nil) then // Throws if // Option is not in Option Set of procedure that was active when vote opened OR if @@ -490,22 +572,19 @@ Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled throw else - retrieve votersList from txGovVote.ProposalID + option = load(store, Options, ::) - if sender is in votersList under txGovVote.ValidatorPubKey then + if (option != nil) // sender has already voted with the Atoms bonded to ValidatorPubKey throw else - retrieve proposal from txGovVote.ProposalID - retrieve InitTotalVotingPower from txGovVote.ProposalID - if (proposal.VotingStartBlock < 0) OR (CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR - (proposal.Category AND proposal.Votes['Yes']/InitTotalVotingPower >= 2/3) then + (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then // Throws if // Vote has not started OR if @@ -517,66 +596,51 @@ Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled throw else - // sender can vote, check if sender == validator and add sender to voter list - - add sender to votersList under txGovVote.ValidatorPubKey + validatorGovInfo = load(store, ValidatorGovInfos, :) - if (sender is not equal to GovPubKey that corresponds to txGovVote.ValidatorPubKey) - // Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey + if (validatorGovInfo == nil) + // validator became validator after proposal entered voting period - if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then - throw + throw - else - if txGovVote.ValidatorPubKey is not in votersList under txGovVote.ValidatorPubKey then - // Validator has not voted already + else + // sender can vote, check if sender == validator and store sender's option in Options + + store(Options, ::, txGovVote.Option) - if exists(MinusesList[txGovVote.ValidatorPubKey]) then - // a minus already exists for this validator's PubKey, increase minus - // by the amount of Atoms sender has bonded to ValidatorPubKey + if (sender != validator.GovPubKey) + // Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey - MinusesList[txGovVote.ValidatorPubKey] += sender.bondedAmountTo(txGovVote.ValidatorPubKey) + if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then + // check in Staking module - else - // a minus does not already exist for this validator's PubKey, initialise minus - // at the amount of Atoms sender has bonded to ValidatorPubKey - - MinusesList[txGovVote.ValidatorPubKey] = sender.bondedAmountTo(txGovVote.ValidatorPubKey) + throw else - // Validator has already voted - // Reduce option count chosen by validator by sender's bonded Amount + validatorOption = load(store, Options, :::, validatorGovInfo) - else - // sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey - // i.e. sender == validator + else + // Validator has already voted + // Reduce votes of option chosen by validator by sender's bonded Amount - retrieve initialVotingPower from InitVotingPowerList using txGovVote.ValidatorPubKey - - - if exists(MinusesList[txGovVote.ValidatorPubKey]) then - // a minus exists for this validator's PubKey, decrease vote of validator by minus + proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey) - proposal.Votes['txGovVote.Option'] += (initialVotingPower - MinusesList[txGovVote.ValidatorPubKey]) + // increase votes of option chosen by sender by bonded Amount + proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey) - else - // a minus does not exist for this validator's PubKey, validator votes with full voting power + else + // sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey + // i.e. sender == validator + + proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) - proposal.Votes['txGovVote.Option'] += initialVotingPower - - if (proposal.Category AND proposal.Votes['Yes']/InitTotalVotingPower >= 2/3) - // after vote is counted, if proposal is urgent and special condition is met - // remove proposalID from ProposalProcessingQueue - - remove txGovVote.ProposalID from ProposalProcessingQueue - Rearrange ProposalProcessingQueue - ProposalProcessingQueueEnd-- + ``` From dd0695afa011475a85064bcf4cb0ce800d146c57 Mon Sep 17 00:00:00 2001 From: gamarin Date: Wed, 21 Feb 2018 17:45:23 +0100 Subject: [PATCH 002/182] Small fixes --- docs/spec/governance/governance.md | 63 ++++++++++++++++-------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md index 833fdb4d2..2e40e92dd 100644 --- a/docs/spec/governance/governance.md +++ b/docs/spec/governance/governance.md @@ -10,21 +10,21 @@ The governance process is divided in a few steps that are outlined below: - **Proposal submission:** Proposal is submitted to the blockchain with a deposit - **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` transactions to vote on the proposal -- If the proposal involves a software upgrade - - **Signal:** Validator start signaling that they are ready to switch to the new version - - **Switch:** Once more than 2/3rd validators have signaled their readiness to switch, their software automatically flips to the new version +- If the proposal involves a software upgrade: + - **Signal:** Validators start signaling that they are ready to switch to the new version + - **Switch:** Once more than 75% of validators have signaled that they are ready to switch, their software automatically flips to the new version ## Proposal submission ### Right to submit a proposal -Any Atom holder, whether bonded or unbonded, can submit proposals by sending a `TxProposal` transaction. Once a proposal is submitted, it is identified by its unique `proposalID`. +Any Atom holder, whether bonded or unbonded, can submit proposals by sending a `TxGovProposal` transaction. Once a proposal is submitted, it is identified by its unique `proposalID`. ### Proposal filter (minimum deposit) -To prevent spam, proposals must be submitted with a deposit in Atoms. Voting period will not start as long as the proposal's deposit is smaller than the minimum deposit parameter `MinDeposit`. +To prevent spam, proposals must be submitted with a deposit in Atoms. Voting period will not start as long as the proposal's deposit is smaller than the minimum deposit `MinDeposit`. -When a proposal is submitted, it has to be accompagnied by a deposit that must be strictly positive but that can be inferior to `MinDeposit`. Indeed, the submitter need not pay for the entire deposit on its own. If a proposal's deposit is strictly inferior to `MinDeposit`, other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposals's deposit reaches `minDeposit`, it enters voting period. +When a proposal is submitted, it has to be accompagnied by a deposit that must be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter need not pay for the entire deposit on its own. If a proposal's deposit is strictly inferior to `MinDeposit`, other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposals's deposit reaches `MinDeposit`, it enters voting period. ### Deposit refund @@ -48,7 +48,6 @@ There are two categories of proposal: These two categories are strictly identical except that `Urgent` proposals can be accepted faster if a certain condition is met. For more information, see [Threshold](#threshold) section. - ## Vote ### Participants @@ -59,6 +58,8 @@ Note that some *participants* can be forbidden to vote on a proposal under a cer - *participant* bonded or unbonded Atoms to said validator after proposal entered voting period - *participant* became validator after proposal entered voting period +This does not prevent *participant* to vote with Atoms bonded to other validators. For example, if a *participant* bonded some Atoms to validator A before a proposal entered voting period and other Atoms to validator B after proposal entered voting period, only the vote under validator B will be forbidden. + ### Voting period Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We define `Voting period` as the interval between the moment the vote opens and the moment the vote closes. `Voting period` should always be shorter than `Unbonding period` to prevent double voting. The initial value of `Voting period` is 2 weeks. @@ -96,13 +97,13 @@ Initially, the threshold is set at 50% with a possibility to veto if more than 1 If a delegator does not vote, it will inherit its validator vote. - If the delegator votes before its validator, it will not inherit from the validator's vote. -- If the delegator votes after its validaotor, it will override its validator vote with its own vote. If the proposal is a `Urgent` proposal, it is possible that the vote will close before delegators have a chance to react and override their validator's vote. This is not a problem, as `Urgent` proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway. +- If the delegator votes after its validator, it will override its validator vote with its own. If the proposal is a `Urgent` proposal, it is possible that the vote will close before delegators have a chance to react and override their validator's vote. This is not a problem, as `Urgent` proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway. ### Validator’s punishment for non-voting Validators are required to vote on all proposals to ensure that results have legitimacy. Voting is part of validators' directives and failure to do it will result in a penalty. -If a validator’s address is not in the list of addresses that voted on a proposal and if the vote is closed (i.e. `MinDeposit` was reached and `Voting period` is over), then this validator will automatically be partially slashed of `GovernancePenalty`. +If a validator’s address is not in the list of addresses that voted on a proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting period` is over), then the validator will automatically be partially slashed of `GovernancePenalty`. *Note: Need to define values for `GovernancePenalty`* @@ -110,7 +111,7 @@ If a validator’s address is not in the list of addresses that voted on a propo ### Governance key and governance address -Validators can make use of an additional slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PrivKey`. From there, they will be able to sign vote using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment. +Validators can make use of a slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PrivKey`. From there, they will be able to sign votes using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment. ## Software Upgrade @@ -151,13 +152,13 @@ type Procedure struct { MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months GovernancePenalty int64 // Penalty if validator does not vote - ProcedureNumber int16 // Incremented each time a new procedure is created IsActive bool // If true, procedure is active. Only one procedure can have isActive true. } ``` **Store**: - `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their `ProcedureNumber` +- `ActiveProcedureNumber`: returns current procedure number #### Proposals @@ -191,7 +192,7 @@ type ValidatorGovInfo struct { **Store:** - `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID` -- `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by `:` as `[]byte`. Given a `proposalID` and a `PubKey`, returns deposit (`nil` if `PubKey` has not deposited) +- `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by `:` as `[]byte`. Given a `proposalID` and a `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the proposal) - `Options`: A mapping `map[[]byte]string` of options indexed by `::` as `[]byte`. Given a `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by this `PubKey` for this validator (`nil` if `PubKey` has not voted under this validator) - `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's governance infos indexed by `:`. Returns `nil` if proposal has not entered voting period or if `PubKey` was not the governance public key of a validator when proposal entered voting period. @@ -201,8 +202,6 @@ type ValidatorGovInfo struct { **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 + InitProcedure.VotingPeriod`. If it is, then the application checks if validators in `InitVotingPowerList` have voted and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`. -*Note: Actual data structure may differ* - And the pseudocode for the `ProposalProcessingQueue`: ``` @@ -229,7 +228,9 @@ And the pseudocode for the `ProposalProcessingQueue`: ProposalProcessingQueue.pop() checkProposal() - else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) + else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) + + activeProcedure = load(store, Procedures, ActiveProcedureNumber) for each validator in CurrentBondedValidators validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey) @@ -240,7 +241,7 @@ And the pseudocode for the `ProposalProcessingQueue`: validatorOption = load(store, Options, validator.GovPubKey) if (validatorOption == nil) // validator did not vote - slash validator by ActiveProcedure.GovernancePenalty + slash validator by activeProcedure.GovernancePenalty ProposalProcessingQueue.pop() checkProposal() @@ -251,7 +252,6 @@ And the pseudocode for the `ProposalProcessingQueue`: #### Proposal Submission - Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction. ```Go @@ -307,8 +307,9 @@ upon receiving txGovSubmitProposal from sender do proposal.SubmitBlock = CurrentBlock store(Deposits, :, txGovSubmitProposal.InitialDeposit) + activeProcedure = load(store, Procedures, ActiveProcedureNumber) - if (txGovSubmitProposal.InitialDeposit < ActiveProcedure.MinDeposit) then + if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then // MinDeposit is not reached proposal.VotingStartBlock = -1 @@ -320,7 +321,7 @@ upon receiving txGovSubmitProposal from sender do proposal.VotingStartBlock = CurrentBlock proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedureNumber = ActiveProcedure.ProcedureNumber + proposal.InitProcedureNumber = ActiveProcedureNumber for each validator in CurrentBondedValidators // Store voting power of each bonded validator @@ -379,19 +380,20 @@ upon receiving txGovDeposit from sender do throw else - if (txGovDeposit.Deposit <= 0 OR sender.AtomBalance < txGovDeposit.Deposit) + if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) // deposit is negative or null OR sender has insufficient funds throw else - if (proposal.Deposit >= ActiveProcedure.MinDeposit) then + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + if (proposal.Deposit >= activeProcedure.MinDeposit) then // MinDeposit was reached throw else - if (CurrentBlock >= proposal.SubmitBlock + ActiveProcedure.MaxDepositPeriod) then + if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then // Maximum deposit period reached throw @@ -404,21 +406,23 @@ upon receiving txGovDeposit from sender do if (deposit == nil) // sender has never deposited on this proposal + store(Deposits, :, deposit) else // sender has already deposited on this proposal + newDeposit = deposit + txGovDeposit.Deposit store(Deposits, :, newDeposit) proposal.Deposit += txGovDeposit.Deposit - if (proposal.Deposit >= ActiveProcedure.MinDeposit) then + if (proposal.Deposit >= activeProcedure.MinDeposit) then // MinDeposit is reached, vote opens proposal.VotingStartBlock = CurrentBlock proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedureNumber = ActiveProcedure.ProcedureNumber + proposal.InitProcedureNumber = ActiveProcedureNumber for each validator in CurrentBondedValidators // Store voting power of each bonded validator @@ -476,16 +480,17 @@ And the associated pseudocode throw else - if deposit <= 0 + if (deposit <= 0) // deposit has already been claimed throw else - if proposal.VotingStartBlock <= 0 + if (proposal.VotingStartBlock <= 0) // Vote never started - - if (CurrentBlock <= proposal.SubmitBlock + ActiveProcedure.MaxDepositPeriod) + + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) // MaxDepositPeriod is not reached throw @@ -563,7 +568,7 @@ Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled validator = load(store, Validators, txGovVote.ValidatorPubKey) if !initProcedure.OptionSet.includes(txGovVote.Option) OR - validator == nil) then + (validator == nil) then // Throws if // Option is not in Option Set of procedure that was active when vote opened OR if From d0539d5e9dbcd376a7209634053939774deb52ef Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Mon, 26 Feb 2018 12:27:26 +0100 Subject: [PATCH 003/182] Review Governance specification --- docs/spec/governance/governance.md | 373 ++++++++++++++++++++--------- 1 file changed, 260 insertions(+), 113 deletions(-) diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md index 2e40e92dd..145244127 100644 --- a/docs/spec/governance/governance.md +++ b/docs/spec/governance/governance.md @@ -2,71 +2,115 @@ *Disclaimer: This is work in progress. Mechanisms are susceptible to change.* -This document describes the high-level architecture of the governance module. The governance module allows bonded Atom holders to vote on proposals on a 1 bonded Atom 1 vote basis. +This document describes the high-level architecture of the governance module. +The governance module allows bonded Atom holders to vote on proposals on a 1 +bonded Atom 1 vote basis. ## Design overview The governance process is divided in a few steps that are outlined below: -- **Proposal submission:** Proposal is submitted to the blockchain with a deposit -- **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` transactions to vote on the proposal -- If the proposal involves a software upgrade: - - **Signal:** Validators start signaling that they are ready to switch to the new version - - **Switch:** Once more than 75% of validators have signaled that they are ready to switch, their software automatically flips to the new version - +* **Proposal submission:** Proposal is submitted to the blockchain with a + deposit. +* **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is + confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` + transactions to vote on the proposal. +* If the proposal involves a software upgrade: + * **Signal:** Validators start signaling that they are ready to switch to the + new version. + * **Switch:** Once more than 75% of validators have signaled that they are + ready to switch, their software automatically flips to the new version. + ## Proposal submission ### Right to submit a proposal -Any Atom holder, whether bonded or unbonded, can submit proposals by sending a `TxGovProposal` transaction. Once a proposal is submitted, it is identified by its unique `proposalID`. +Any Atom holder, whether bonded or unbonded, can submit proposals by sending a +`TxGovProposal` transaction. Once a proposal is submitted, it is identified by +its unique `proposalID`. ### Proposal filter (minimum deposit) -To prevent spam, proposals must be submitted with a deposit in Atoms. Voting period will not start as long as the proposal's deposit is smaller than the minimum deposit `MinDeposit`. +To prevent spam, proposals must be submitted with a deposit in Atoms. Voting +period will not start as long as the proposal's deposit is smaller than the +minimum deposit `MinDeposit`. -When a proposal is submitted, it has to be accompagnied by a deposit that must be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter need not pay for the entire deposit on its own. If a proposal's deposit is strictly inferior to `MinDeposit`, other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposals's deposit reaches `MinDeposit`, it enters voting period. +When a proposal is submitted, it has to be accompanied by a deposit that must +be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter +need not pay for the entire deposit on its own. If a proposal's deposit is +strictly inferior to `MinDeposit`, other Atom holders can increase the +proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposal's +deposit reaches `MinDeposit`, it enters voting period. ### Deposit refund -There are two instances where Atom holders that deposited can claim back their deposit: -- If the proposal is accepted -- If the proposal's deposit does not reach `MinDeposit` for a period longer than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is considered closed and nobody can deposit on it anymore. +There are two instances where Atom holders that deposited can claim back their +deposit: +* If the proposal is accepted. +* If the proposal's deposit does not reach `MinDeposit` for a period longer + than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is + considered closed and nobody can deposit on it anymore. -In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` transaction to retrieve their share of the deposit. +In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` +transaction to retrieve their share of the deposit. ### Proposal types -In the initial version of the governance module, there are two types of proposal: -- `PlainTextProposal`. All the proposals that do not involve a modification of the source code go under this type. For example, an opinion poll would use a proposal of type `PlainTextProposal` -- `SoftwareUpgradeProposal`. If accepted, validators are expected to update their software in accordance with the proposal. They must do so by following a 2-steps process described in the [Software Upgrade](#software-upgrade) section below. Software upgrade roadmap may be discussed and agreed on via `PlainTextProposals`, but actual software upgrades must be performed via `SoftwareUpgradeProposals`. +In the initial version of the governance module, there are two types of +proposal: +* `PlainTextProposal` All the proposals that do not involve a modification of + the source code go under this type. For example, an opinion poll would use a + proposal of type `PlainTextProposal`. +* `SoftwareUpgradeProposal`. If accepted, validators are expected to update + their software in accordance with the proposal. They must do so by following + a 2-steps process described in the [Software Upgrade](#software-upgrade) + section below. Software upgrade roadmap may be discussed and agreed on via + `PlainTextProposals`, but actual software upgrades must be performed via + `SoftwareUpgradeProposals`. ### Proposal categories There are two categories of proposal: -- `Regular` -- `Urgent` +* `Regular` +* `Urgent` -These two categories are strictly identical except that `Urgent` proposals can be accepted faster if a certain condition is met. For more information, see [Threshold](#threshold) section. +These two categories are strictly identical except that `Urgent` proposals can +be accepted faster if a certain condition is met. For more information, see +[Threshold](#threshold) section. ## Vote ### Participants -*Participants* are users that have the right to vote on proposals. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals. +*Participants* are users that have the right to vote on proposals. On the +Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and +other users do not get the right to participate in governance. However, they +can submit and deposit on proposals. -Note that some *participants* can be forbidden to vote on a proposal under a certain validator if: -- *participant* bonded or unbonded Atoms to said validator after proposal entered voting period -- *participant* became validator after proposal entered voting period +Note that some *participants* can be forbidden to vote on a proposal under a +certain validator if: +* *participant* bonded or unbonded Atoms to said validator after proposal + entered voting period. +* *participant* became validator after proposal entered voting period. -This does not prevent *participant* to vote with Atoms bonded to other validators. For example, if a *participant* bonded some Atoms to validator A before a proposal entered voting period and other Atoms to validator B after proposal entered voting period, only the vote under validator B will be forbidden. +This does not prevent *participant* to vote with Atoms bonded to other +validators. For example, if a *participant* bonded some Atoms to validator A +before a proposal entered voting period and other Atoms to validator B after +proposal entered voting period, only the vote under validator B will be +forbidden. ### Voting period -Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We define `Voting period` as the interval between the moment the vote opens and the moment the vote closes. `Voting period` should always be shorter than `Unbonding period` to prevent double voting. The initial value of `Voting period` is 2 weeks. +Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We +define `Voting period` as the interval between the moment the vote opens and +the moment the vote closes. `Voting period` should always be shorter than +`Unbonding period` to prevent double voting. The initial value of +`Voting period` is 2 weeks. ### Option set -The option set of a proposal refers to the set of choices a participant can choose from when casting its vote. +The option set of a proposal refers to the set of choices a participant can +choose from when casting its vote. The initial option set includes the following options: - `Yes` @@ -74,74 +118,128 @@ The initial option set includes the following options: - `NoWithVeto` - `Abstain` -`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote. +`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option +allows voters to signal that they do not intend to vote in favor or against the +proposal but accept the result of the vote. -*Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ option that casts a `NoWithVeto` vote.* +*Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ +option that casts a `NoWithVeto` vote.* ### Quorum -Quorum is defined as the minimum percentage of voting power that needs to be casted on a proposal for the result to be valid. +Quorum is defined as the minimum percentage of voting power that needs to be +casted on a proposal for the result to be valid. -In the initial version of the governance module, there will be no quorum enforced by the protocol. Participation is ensured via the combination of inheritance and validator's punishment for non-voting. +In the initial version of the governance module, there will be no quorum +enforced by the protocol. Participation is ensured via the combination of +inheritance and validator's punishment for non-voting. ### Threshold -Threshold is defined as the minimum proportion of `Yes` votes (excluding `Abstain` votes) for the proposal to be accepted. +Threshold is defined as the minimum proportion of `Yes` votes (excluding +`Abstain` votes) for the proposal to be accepted. -Initially, the threshold is set at 50% with a possibility to veto if more than 1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means that proposals are accepted if the proportion of `Yes` votes (excluding `Abstain` votes) at the end of the voting period is superior to 50% and if the proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` votes). +Initially, the threshold is set at 50% with a possibility to veto if more than +1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means +that proposals are accepted if the proportion of `Yes` votes (excluding +`Abstain` votes) at the end of the voting period is superior to 50% and if the +proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` +votes). -`Urgent` proposals also work with the aforementioned threshold, except there is another condition that can accelerate the acceptance of the proposal. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, `UrgentProposal` will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens. +`Urgent` proposals also work with the aforementioned threshold, except there is +another condition that can accelerate the acceptance of the proposal. Namely, +if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, +`UrgentProposal` will be immediately accepted, even if the `Voting period` is +not finished. `InitTotalVotingPower` is the total voting power of all bonded +Atom holders at the moment when the vote opens. ### Inheritance If a delegator does not vote, it will inherit its validator vote. -- If the delegator votes before its validator, it will not inherit from the validator's vote. -- If the delegator votes after its validator, it will override its validator vote with its own. If the proposal is a `Urgent` proposal, it is possible that the vote will close before delegators have a chance to react and override their validator's vote. This is not a problem, as `Urgent` proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway. +* If the delegator votes before its validator, it will not inherit from the + validator's vote. +* If the delegator votes after its validator, it will override its validator + vote with its own. If the proposal is a `Urgent` proposal, it is possible + that the vote will close before delegators have a chance to react and + override their validator's vote. This is not a problem, as `Urgent` proposals + require more than 2/3rd of the total voting power to pass before the end of + the voting period. If more than 2/3rd of validators collude, they can censor + the votes of delegators anyway. ### Validator’s punishment for non-voting -Validators are required to vote on all proposals to ensure that results have legitimacy. Voting is part of validators' directives and failure to do it will result in a penalty. +Validators are required to vote on all proposals to ensure that results have +legitimacy. Voting is part of validators' directives and failure to do it will +result in a penalty. -If a validator’s address is not in the list of addresses that voted on a proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting period` is over), then the validator will automatically be partially slashed of `GovernancePenalty`. +If a validator’s address is not in the list of addresses that voted on a +proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting +period` is over), then the validator will automatically be partially slashed by +`GovernancePenalty`. *Note: Need to define values for `GovernancePenalty`* -**Exception:** If a proposal is a `Urgent` proposal and is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2:3, making it mechanically impossible for some validators to vote on it. +**Exception:** If a proposal is an `Urgent` proposal and is accepted via the +special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` +that exceeds 2:3, validators cannot be punished for not having voted on it. +That is because the proposal will close as soon as the ratio exceeds 2:3, +making it mechanically impossible for some validators to vote on it. ### Governance key and governance address -Validators can make use of a slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PrivKey`. From there, they will be able to sign votes using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment. - +Validators can make use of a slot where they can designate a +`Governance PubKey`. By default, a validator's `Governance PubKey` will be the +same as its main PubKey. Validators can change this `Governance PubKey` by +sending a `Change Governance PubKey` transaction signed by their main +`Consensus PrivKey`. From there, they will be able to sign votes using the +`Governance PrivKey` associated with their `Governance PubKey`. The +`Governance PubKey` can be changed at any moment. ## Software Upgrade -If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade their software to the new version that was voted. This process is divided in two steps. +If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade +their software to the new version that was voted. This process is divided in +two steps. ### Signal -After a `SoftwareUpgradeProposal` is accepted, validators are expected to download and install the new version of the software while continuing to run the previous version. Once a validator has downloaded and installed the upgrade, it will start signaling to the network that it is ready to switch by including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation that we want it in the precommit?*) +After a `SoftwareUpgradeProposal` is accepted, validators are expected to +download and install the new version of the software while continuing to run +the previous version. Once a validator has downloaded and installed the +upgrade, it will start signaling to the network that it is ready to switch by +including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation +that we want it in the precommit?*) -Note: There is only one signal slot per *precommit*. If several `SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will form and they will be implemented one after the other in the order that they were accepted. +Note: There is only one signal slot per *precommit*. If several +`SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will +form and they will be implemented one after the other in the order that they +were accepted. ### Switch -Once a block contains more than 2/3rd *precommits* where a common `SoftwareUpgradeProposal` is signaled, all the nodes (including validator nodes, non-validating full nodes and light-nodes) are expected to switch to the new version of the software. +Once a block contains more than 2/3rd *precommits* where a common +`SoftwareUpgradeProposal` is signaled, all the nodes (including validator +nodes, non-validating full nodes and light-nodes) are expected to switch to the +new version of the software. *Note: Not clear how the flip is handled programatically* - ## Implementation -*Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic and implementation might widely differ* +*Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic +and implementation might widely differ* ### State #### Procedures -`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. +`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 +```go 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. @@ -157,14 +255,15 @@ type Procedure struct { ``` **Store**: -- `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their `ProcedureNumber` -- `ActiveProcedureNumber`: returns current procedure number +* `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their + `ProcedureNumber` +* `ActiveProcedureNumber`: returns current procedure number #### Proposals `Proposals` are item to be voted on. -```Go +```go type Proposal struct { Title string // Title of the proposal Description string // Description of the proposal @@ -182,7 +281,7 @@ type Proposal struct { We also introduce a type `ValidatorGovInfo` -```Go +```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 @@ -191,20 +290,39 @@ type ValidatorGovInfo struct { **Store:** -- `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID` -- `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by `:` as `[]byte`. Given a `proposalID` and a `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the proposal) -- `Options`: A mapping `map[[]byte]string` of options indexed by `::` as `[]byte`. Given a `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by this `PubKey` for this validator (`nil` if `PubKey` has not voted under this validator) -- `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's governance infos indexed by `:`. Returns `nil` if proposal has not entered voting period or if `PubKey` was not the governance public key of a validator when proposal entered voting period. +* `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their + `proposalID` +* `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by + `:` as `[]byte`. Given a `proposalID` and a + `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the + proposal) +* `Options`: A mapping `map[[]byte]string` of options indexed by + `::` as `[]byte`. Given a + `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by + this `PubKey` for this validator (`nil` if `PubKey` has not voted under this + validator) +* `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's + governance infos indexed by `:`. Returns + `nil` if proposal has not entered voting period or if `PubKey` was not the + governance public key of a validator when proposal entered voting period. #### 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 + InitProcedure.VotingPeriod`. If it is, then the application checks if validators in `InitVotingPowerList` have voted and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`. +* `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 + InitProcedure.VotingPeriod`. If it is, + then the application checks if validators in `InitVotingPowerList` have voted + and, if not, applies `GovernancePenalty`. After that proposal is ejected from + `ProposalProcessingQueue` and the next element of the queue is evaluated. + Note that if a proposal is urgent and accepted under the special condition, + its `ProposalID` must be ejected from `ProposalProcessingQueue`. And the pseudocode for the `ProposalProcessingQueue`: -``` +```go in BeginBlock do checkProposal() // First call of the recursive function @@ -252,9 +370,10 @@ And the pseudocode for the `ProposalProcessingQueue`: #### Proposal Submission -Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction. +Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` +transaction. -```Go +```go type TxGovSubmitProposal struct { Title string // Title of the proposal Description string // Description of the proposal @@ -265,18 +384,19 @@ type TxGovSubmitProposal struct { ``` **State modifications:** -- Generate new `proposalID` -- Create new `Proposal` -- Initialise `Proposals` attributes -- Store sender's deposit in `Deposits` -- Decrease balance of sender by `InitialDeposit` -- If `MinDeposit` is reached: - - Push `proposalID` in `ProposalProcessingQueueEnd` - - Store each validator's voting power in `ValidatorGovInfos` +* Generate new `proposalID` +* Create new `Proposal` +* Initialise `Proposals` attributes +* Store sender's deposit in `Deposits` +* Decrease balance of sender by `InitialDeposit` +* If `MinDeposit` is reached: + * Push `proposalID` in `ProposalProcessingQueueEnd` + * Store each validator's voting power in `ValidatorGovInfos` -A `TxGovSubmitProposal` transaction can be handled according to the following pseudocode +A `TxGovSubmitProposal` transaction can be handled according to the following +pseudocode. -``` +```go // PSEUDOCODE // // Check if TxGovSubmitProposal is valid. If it is, create proposal // @@ -338,13 +458,13 @@ upon receiving txGovSubmitProposal from sender do return proposalID ``` - - #### Deposit -Once a proposal is submitted, if `Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send `TxGovDeposit` transactions to increase the proposal's deposit. +Once a proposal is submitted, if +`Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send +`TxGovDeposit` transactions to increase the proposal's deposit. -```Go +```go type TxGovDeposit struct { ProposalID int64 // ID of the proposal Deposit int64 // Number of Atoms to add to the proposal's deposit @@ -352,16 +472,17 @@ type TxGovDeposit struct { ``` **State modifications:** -- Decrease balance of sender by `deposit` -- Initialize or increase `deposit` of sender in `Deposits` -- Increase `proposal.Deposit` by sender's `deposit` -- If `MinDeposit` is reached: - - Push `proposalID` in `ProposalProcessingQueueEnd` - - Store each validator's voting power in `ValidatorGovInfos` +* Decrease balance of sender by `deposit` +* Initialize or increase `deposit` of sender in `Deposits` +* Increase `proposal.Deposit` 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. +A `TxGovDeposit` transaction has to go through a number of checks to be valid. +These checks are outlined in the following pseudocode. -``` +```go // PSEUDOCODE // // Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached @@ -438,9 +559,11 @@ upon receiving txGovDeposit from sender do #### Claiming deposit -Finally, if the proposal is accepted or `MinDeposit` was not reached before the end of the `MaximumDepositPeriod`, then Atom holders can send `TxGovClaimDeposit` transaction to claim their deposits. +Finally, if the proposal is accepted or `MinDeposit` was not reached before the +end of the `MaximumDepositPeriod`, then Atom holders can send +`TxGovClaimDeposit` transaction to claim their deposits. -```Go +```go type TxGovClaimDeposit struct { ProposalID int64 } @@ -448,12 +571,12 @@ Finally, if the proposal is accepted or `MinDeposit` was not reached before the **State modifications:** If conditions are met, reimburse the deposit, i.e. -- Increase `AtomBalance` of sender by `deposit` -- Set `deposit` of sender in `DepositorsList` to 0 +* Increase `AtomBalance` of sender by `deposit` +* Set `deposit` of sender in `DepositorsList` to 0 -And the associated pseudocode +And the associated pseudocode. -``` +```go // PSEUDOCODE // /* Check if TxGovClaimDeposit is valid. If vote never started and MaxDepositPeriod is reached or if vote started and proposal was accepted, return deposit */ @@ -507,8 +630,7 @@ And the associated pseudocode initProcedure = load(store, Procedures, proposal.InitProcedureNumber) - if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR - ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then + if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then // Proposal was accepted either because // Proposal was urgent and special condition was met @@ -516,15 +638,15 @@ And the associated pseudocode store(Deposits, :, 0) sender.AtomBalance += deposit - ``` - #### Vote -Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there, bonded Atom holders are able to send `TxGovVote` transactions to cast their vote on the proposal. +Once `ActiveProcedure.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 +```go type TxGovVote struct { ProposalID int64 // proposalID of the proposal Option string // option from OptionSet chosen by the voter @@ -533,19 +655,25 @@ Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there, ``` **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 `proposal.Votes['validatorOption']` by sender's `voting power` -- If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` by sender's `voting power` -- If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by validator's `InitialVotingPower - minus` (`minus` can be equal to 0) +* 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 + `proposal.Votes['validatorOption']` by sender's `voting power` +* If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` + by sender's `voting power` +* If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by + validator's `InitialVotingPower - minus` (`minus` can be equal to 0) -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 GovernancePubKey as `ValidatorPubKey` +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 GovernancePubKey as `ValidatorPubKey` +Next is a pseudocode proposal of the way `TxGovVote` transactions can be +handled: - -Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled: - -``` +```go // PSEUDOCODE // // Check if TxGovVote is valid. If it is, count vote// @@ -644,16 +772,35 @@ Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled // i.e. sender == validator proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) - - ``` - ## Future improvements (not in scope for MVP) -The current documentation only describes the minimum viable product for the governance module. Future improvements may include: +The current documentation only describes the minimum viable product for the +governance module. Future improvements may include: -- **`BountyProposals`:** If accepted, a `BountyProposal` creates an open bounty. The `BountyProposal` specifies how many Atoms will be given upon completion. These Atoms will be taken from the `reserve pool`. After a `BountyProposal` is accepted by governance, anybody can submit a `SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a `BountyProposal` is accepted, the corresponding funds in the `reserve pool` are locked so that payment can always be honored. In order to link a `SoftwareUpgradeProposal` to an open bounty, the submitter of the `SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute. If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by governance, the funds that were reserved are automatically transferred to the submitter. -- **Complex delegation:** Delegators could choose other representatives than their validators. Ultimately, the chain of representatives would always end up to a validator, but delegators could inherit the vote of their chosen representative before they inherit the vote of their validator. In other words, they would only inherit the vote of their validator if their other appointed representative did not vote. -- **`ParameterProposals` and `WhitelistProposals`:** These proposals would automatically change pre-defined parameters and whitelists. Upon acceptance, these proposals would not require validators to do the signal and switch process. -- **Better process for proposal review:** There would be two parts to `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to reward third party auditors. +* **`BountyProposals`:** If accepted, a `BountyProposal` creates an open + bounty. The `BountyProposal` specifies how many Atoms will be given upon + completion. These Atoms will be taken from the `reserve pool`. After a + `BountyProposal` is accepted by governance, anybody can submit a + `SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a + `BountyProposal` is accepted, the corresponding funds in the `reserve pool` + are locked so that payment can always be honored. In order to link a + `SoftwareUpgradeProposal` to an open bounty, the submitter of the + `SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute. + If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by + governance, the funds that were reserved are automatically transferred to the + submitter. +* **Complex delegation:** Delegators could choose other representatives than + their validators. Ultimately, the chain of representatives would always end + up to a validator, but delegators could inherit the vote of their chosen + representative before they inherit the vote of their validator. In other + words, they would only inherit the vote of their validator if their other + appointed representative did not vote. +* **`ParameterProposals` and `WhitelistProposals`:** These proposals would + automatically change pre-defined parameters and whitelists. Upon acceptance, + these proposals would not require validators to do the signal and switch + process. +* **Better process for proposal review:** There would be two parts to + `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to + reward third party auditors. From 798b2b6ea37612b1f3fabb955c19f120345e276a Mon Sep 17 00:00:00 2001 From: gamarin Date: Mon, 26 Feb 2018 16:07:17 +0100 Subject: [PATCH 004/182] Add Readme --- docs/spec/governance/README.md | 28 ++++++++++++++++++++++++++++ docs/spec/governance/governance.md | 4 ++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 docs/spec/governance/README.md diff --git a/docs/spec/governance/README.md b/docs/spec/governance/README.md new file mode 100644 index 000000000..e337eca12 --- /dev/null +++ b/docs/spec/governance/README.md @@ -0,0 +1,28 @@ +# Governance module specification + +## 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. + +The module enables Cosmos-SDK based blockchain to support an on-chain governance system. In this system, token holders can vote on proposals on a 1 token 1 vote basis. Next is a list of features the module currently supports: + +- **Proposal submission:** Users can submit proposals with a deposit. Once the minimum deposit is reached, proposal enters voting period +- **Vote:** Participants can vote on proposals that reached MinDeposit +- **Inheritance and penalties:** Delegators inherit their validator's vote if they don't vote themselves. If validators do not vote, they get partially slashed. +- **Signal and switch:** If a proposal of type `SoftwareUpgradeProposal` is accepted, validators can signal it and switch once enough validators have signalled. +- **Claiming deposit:** Users that deposited on proposals can recover their deposits if the proposal was accepted OR if the proposal never entered voting period. + +Features that may be added in the future are described in [Future improvements](future_improvements.md) + + +## Contents + +1. **[Design overview](overview.md)** +2. **Implementation** + 1. **[State](state.md)** + 2. **Transactions** + 1. [Proposal Submission](proposal_submission.md) + 2. [Deposit](deposit.md) + 3. [Claim Deposit](claim_deposit.md) + 4. [Vote](vote.md) +3. **[Future improvements](future_improvements.md)** diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md index 145244127..155a20ac2 100644 --- a/docs/spec/governance/governance.md +++ b/docs/spec/governance/governance.md @@ -102,7 +102,7 @@ forbidden. ### Voting period Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We -define `Voting period` as the interval between the moment the vote opens and +define `Voting period` as the interval between the moment the vote opens and the moment the vote closes. `Voting period` should always be shorter than `Unbonding period` to prevent double voting. The initial value of `Voting period` is 2 weeks. @@ -557,7 +557,7 @@ upon receiving txGovDeposit from sender do ProposalProcessingQueue.push(txGovDeposit.ProposalID) ``` -#### Claiming deposit +#### Claim deposit Finally, if the proposal is accepted or `MinDeposit` was not reached before the end of the `MaximumDepositPeriod`, then Atom holders can send From 48bd59eee32b8ea12ff2d7423b4bb576292199f4 Mon Sep 17 00:00:00 2001 From: gamarin Date: Mon, 26 Feb 2018 16:35:09 +0100 Subject: [PATCH 005/182] Split in multiple files --- docs/spec/governance/.DS_Store | Bin 0 -> 6148 bytes docs/spec/governance/README.md | 19 +- docs/spec/governance/future_improvements.md | 30 + docs/spec/governance/governance.md | 806 -------------------- docs/spec/governance/overview.md | 220 ++++++ docs/spec/governance/state.md | 136 ++++ docs/spec/governance/transactions.md | 409 ++++++++++ 7 files changed, 808 insertions(+), 812 deletions(-) create mode 100644 docs/spec/governance/.DS_Store create mode 100644 docs/spec/governance/future_improvements.md delete mode 100644 docs/spec/governance/governance.md create mode 100644 docs/spec/governance/overview.md create mode 100644 docs/spec/governance/state.md create mode 100644 docs/spec/governance/transactions.md diff --git a/docs/spec/governance/.DS_Store b/docs/spec/governance/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..245302bad866a4420ac4f260272976d306216901 GIT binary patch literal 6148 zcmeHKyH3ME5S%qZN|b_BKzSt<5;cv9oPvS|J^=DUij)U33AAqbVm<(7?^Y3UOp8#o zEA7qQ-j2_nv%Ee4(%($3fF6K0T`}vhX)v8v-?Pjj$3%(OC^5w&W?0~5FWUqEQ32h% zQ#@mXHT~!Qm0018`?;VmIs1xB_9wUV)p#^tjmXoN=9+%-6^%WhYpjyo8JX`*EIF&O zI=IIGHz;sNY*:` as `[]byte`. Given a `proposalID` and a - `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the - proposal) -* `Options`: A mapping `map[[]byte]string` of options indexed by - `::` as `[]byte`. Given a - `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by - this `PubKey` for this validator (`nil` if `PubKey` has not voted under this - validator) -* `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's - governance infos indexed by `:`. Returns - `nil` if proposal has not entered voting period or if `PubKey` was not the - governance public key of a validator when proposal entered voting period. - - -#### 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 + InitProcedure.VotingPeriod`. If it is, - then the application checks if validators in `InitVotingPowerList` have voted - and, if not, applies `GovernancePenalty`. After that proposal is ejected from - `ProposalProcessingQueue` and the next element of the queue is evaluated. - Note that if a proposal is urgent and accepted under the special condition, - its `ProposalID` must be ejected from `ProposalProcessingQueue`. - -And the pseudocode for the `ProposalProcessingQueue`: - -```go - in BeginBlock do - - checkProposal() // First call of the recursive function - - - // Recursive function. First call in BeginBlock - func checkProposal() - if (ProposalProcessingQueue.Peek() == nil) - return - - else - proposalID = ProposalProcessingQueue.Peek() - proposal = load(store, Proposals, proposalID) - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) - - if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) - - // proposal was urgent and accepted under the special condition - // no punishment - - ProposalProcessingQueue.pop() - checkProposal() - - else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) - - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - - for each validator in CurrentBondedValidators - validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey) - - if (validatorGovInfo.InitVotingPower != nil) - // validator was bonded when vote started - - validatorOption = load(store, Options, validator.GovPubKey) - if (validatorOption == nil) - // validator did not vote - slash validator by activeProcedure.GovernancePenalty - - ProposalProcessingQueue.pop() - checkProposal() -``` - - -### Transactions - -#### Proposal Submission - -Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` -transaction. - -```go -type TxGovSubmitProposal struct { - Title string // Title of the proposal - Description string // Description of the proposal - Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Category bool // false=regular, true=urgent - InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. -} -``` - -**State modifications:** -* Generate new `proposalID` -* Create new `Proposal` -* Initialise `Proposals` attributes -* Store sender's deposit in `Deposits` -* Decrease balance of sender by `InitialDeposit` -* If `MinDeposit` is reached: - * Push `proposalID` in `ProposalProcessingQueueEnd` - * Store each validator's voting power in `ValidatorGovInfos` - -A `TxGovSubmitProposal` transaction can be handled according to the following -pseudocode. - -```go -// PSEUDOCODE // -// Check if TxGovSubmitProposal is valid. If it is, create proposal // - -upon receiving txGovSubmitProposal from sender do - - if !correctlyFormatted(txGovSubmitProposal) then - // check if proposal is correctly formatted. Includes fee payment. - - throw - - else - if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then - // InitialDeposit is negative or null OR sender has insufficient funds - - throw - - else - sender.AtomBalance -= txGovSubmitProposal.InitialDeposit - - proposalID = generate new proposalID - proposal = NewProposal() - - proposal.Title = txGovSubmitProposal.Title - proposal.Description = txGovSubmitProposal.Description - proposal.Type = txGovSubmitProposal.Type - proposal.Category = txGovSubmitProposal.Category - proposal.Deposit = txGovSubmitProposal.InitialDeposit - proposal.SubmitBlock = CurrentBlock - - store(Deposits, :, txGovSubmitProposal.InitialDeposit) - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - - if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then - // MinDeposit is not reached - - proposal.VotingStartBlock = -1 - proposal.InitTotalVotingPower = 0 - proposal.InitProcedureNumber = -1 - - else - // MinDeposit is reached - - proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedureNumber = ActiveProcedureNumber - - 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(proposalID) - - store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping - return proposalID -``` - -#### Deposit - -Once a proposal is submitted, if -`Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send -`TxGovDeposit` transactions to increase the proposal's deposit. - -```go -type TxGovDeposit struct { - ProposalID int64 // ID of the proposal - Deposit int64 // Number of Atoms to add to the proposal's deposit -} -``` - -**State modifications:** -* Decrease balance of sender by `deposit` -* Initialize or increase `deposit` of sender in `Deposits` -* Increase `proposal.Deposit` 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. - -```go -// PSEUDOCODE // -// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached - -upon receiving txGovDeposit from sender do - // check if proposal is correctly formatted. Includes fee payment. - - if !correctlyFormatted(txGovDeposit) then - throw - - else - proposal = load(store, Proposals, txGovDeposit.ProposalID) - - if (proposal == nil) then - // There is no proposal for this proposalID - - throw - - else - if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) - // deposit is negative or null OR sender has insufficient funds - - throw - - else - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - if (proposal.Deposit >= activeProcedure.MinDeposit) then - // MinDeposit was reached - - throw - - else - if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then - // Maximum deposit period reached - - throw - - else - // sender can deposit - - sender.AtomBalance -= txGovDeposit.Deposit - deposit = load(store, Deposits, :) - - if (deposit == nil) - // sender has never deposited on this proposal - - store(Deposits, :, deposit) - - else - // sender has already deposited on this proposal - - newDeposit = deposit + txGovDeposit.Deposit - store(Deposits, :, newDeposit) - - proposal.Deposit += txGovDeposit.Deposit - - if (proposal.Deposit >= activeProcedure.MinDeposit) then - // MinDeposit is reached, vote opens - - proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedureNumber = ActiveProcedureNumber - - 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) -``` - -#### Claim deposit - -Finally, if the proposal is accepted or `MinDeposit` was not reached before the -end of the `MaximumDepositPeriod`, then Atom holders can send -`TxGovClaimDeposit` transaction to claim their deposits. - -```go - type TxGovClaimDeposit struct { - ProposalID int64 - } -``` - -**State modifications:** -If conditions are met, reimburse the deposit, i.e. -* Increase `AtomBalance` of sender by `deposit` -* Set `deposit` of sender in `DepositorsList` to 0 - -And the associated pseudocode. - -```go - // PSEUDOCODE // - /* Check if TxGovClaimDeposit is valid. If vote never started and MaxDepositPeriod is reached or if vote started and proposal was accepted, return deposit */ - - upon receiving txGovClaimDeposit from sender do - // check if proposal is correctly formatted. Includes fee payment. - - if !correctlyFormatted(txGovClaimDeposit) then - throw - - else - proposal = load(store, Proposals, txGovDeposit.ProposalID) - - if (proposal == nil) then - // There is no proposal for this proposalID - - throw - - else - deposit = load(store, Deposits, :) - - if (deposit == nil) - // sender has not deposited on this proposal - - throw - - else - if (deposit <= 0) - // deposit has already been claimed - - throw - - else - if (proposal.VotingStartBlock <= 0) - // Vote never started - - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) - // MaxDepositPeriod is not reached - - throw - - else - // MaxDepositPeriod is reached - // Set sender's deposit to 0 and refund - - store(Deposits, :, 0) - sender.AtomBalance += deposit - - else - // Vote started - - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) - - if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then - - // Proposal was accepted either because - // Proposal was urgent and special condition was met - // Voting period ended and vote satisfies threshold - - store(Deposits, :, 0) - sender.AtomBalance += deposit -``` - -#### Vote - -Once `ActiveProcedure.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 - type TxGovVote struct { - ProposalID int64 // proposalID of the proposal - Option string // option from OptionSet chosen by the voter - ValidatorPubKey crypto.PubKey // PubKey of the validator voter wants to tie its vote to - } -``` - -**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 - `proposal.Votes['validatorOption']` by sender's `voting power` -* If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` - by sender's `voting power` -* If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by - validator's `InitialVotingPower - minus` (`minus` can be equal to 0) - -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 GovernancePubKey as `ValidatorPubKey` - -Next is a pseudocode proposal of the way `TxGovVote` transactions can be -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) then - throw - - else - proposal = load(store, Proposals, txGovDeposit.ProposalID) - - if (proposal == nil) then - // There is no proposal for this proposalID - - throw - - else - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened - validator = load(store, Validators, txGovVote.ValidatorPubKey) - - if !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 - // ValidatorPubKey is not the GovPubKey of a current validator - - throw - - else - option = load(store, Options, ::) - - if (option != nil) - // sender has already voted with the Atoms bonded to ValidatorPubKey - - throw - - else - if (proposal.VotingStartBlock < 0) OR - (CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR - (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR - (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR - (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then - - // Throws if - // Vote has not started OR if - // Vote had ended OR if - // sender bonded Atoms to ValidatorPubKey after start of vote OR if - // sender unbonded Atoms from ValidatorPubKey after start of vote OR if - // proposal is urgent and special condition is met, i.e. proposal is accepted and closed - - throw - - else - validatorGovInfo = load(store, ValidatorGovInfos, :) - - if (validatorGovInfo == nil) - // validator became validator after proposal entered voting period - - throw - - else - // sender can vote, check if sender == validator and store sender's option in Options - - store(Options, ::, txGovVote.Option) - - if (sender != validator.GovPubKey) - // Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey - - if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then - // check in Staking module - - throw - - else - validatorOption = load(store, Options, :::, validatorGovInfo) - - else - // Validator has already voted - // Reduce votes of option chosen by validator by sender's bonded Amount - - proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey) - - // increase votes of option chosen by sender by bonded Amount - proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey) - - else - // sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey - // i.e. sender == validator - - proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) -``` - -## Future improvements (not in scope for MVP) - -The current documentation only describes the minimum viable product for the -governance module. Future improvements may include: - -* **`BountyProposals`:** If accepted, a `BountyProposal` creates an open - bounty. The `BountyProposal` specifies how many Atoms will be given upon - completion. These Atoms will be taken from the `reserve pool`. After a - `BountyProposal` is accepted by governance, anybody can submit a - `SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a - `BountyProposal` is accepted, the corresponding funds in the `reserve pool` - are locked so that payment can always be honored. In order to link a - `SoftwareUpgradeProposal` to an open bounty, the submitter of the - `SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute. - If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by - governance, the funds that were reserved are automatically transferred to the - submitter. -* **Complex delegation:** Delegators could choose other representatives than - their validators. Ultimately, the chain of representatives would always end - up to a validator, but delegators could inherit the vote of their chosen - representative before they inherit the vote of their validator. In other - words, they would only inherit the vote of their validator if their other - appointed representative did not vote. -* **`ParameterProposals` and `WhitelistProposals`:** These proposals would - automatically change pre-defined parameters and whitelists. Upon acceptance, - these proposals would not require validators to do the signal and switch - process. -* **Better process for proposal review:** There would be two parts to - `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to - reward third party auditors. diff --git a/docs/spec/governance/overview.md b/docs/spec/governance/overview.md new file mode 100644 index 000000000..153b19740 --- /dev/null +++ b/docs/spec/governance/overview.md @@ -0,0 +1,220 @@ +# Design Overview + +*Disclaimer: This is work in progress. Mechanisms are susceptible to change.* + +The governance process is divided in a few steps that are outlined below: + +* **Proposal submission:** Proposal is submitted to the blockchain with a + deposit. +* **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is + confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` + transactions to vote on the proposal. +* If the proposal involves a software upgrade: + * **Signal:** Validators start signaling that they are ready to switch to the + new version. + * **Switch:** Once more than 75% of validators have signaled that they are + ready to switch, their software automatically flips to the new version. + +## Proposal submission + +### Right to submit a proposal + +Any Atom holder, whether bonded or unbonded, can submit proposals by sending a +`TxGovProposal` transaction. Once a proposal is submitted, it is identified by +its unique `proposalID`. + +### Proposal filter (minimum deposit) + +To prevent spam, proposals must be submitted with a deposit in Atoms. Voting +period will not start as long as the proposal's deposit is smaller than the +minimum deposit `MinDeposit`. + +When a proposal is submitted, it has to be accompanied by a deposit that must +be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter +need not pay for the entire deposit on its own. If a proposal's deposit is +strictly inferior to `MinDeposit`, other Atom holders can increase the +proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposal's +deposit reaches `MinDeposit`, it enters voting period. + +### Deposit refund + +There are two instances where Atom holders that deposited can claim back their +deposit: +* If the proposal is accepted. +* If the proposal's deposit does not reach `MinDeposit` for a period longer + than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is + considered closed and nobody can deposit on it anymore. + +In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` +transaction to retrieve their share of the deposit. + +### Proposal types + +In the initial version of the governance module, there are two types of +proposal: +* `PlainTextProposal` All the proposals that do not involve a modification of + the source code go under this type. For example, an opinion poll would use a + proposal of type `PlainTextProposal`. +* `SoftwareUpgradeProposal`. If accepted, validators are expected to update + their software in accordance with the proposal. They must do so by following + a 2-steps process described in the [Software Upgrade](#software-upgrade) + section below. Software upgrade roadmap may be discussed and agreed on via + `PlainTextProposals`, but actual software upgrades must be performed via + `SoftwareUpgradeProposals`. + +### Proposal categories + +There are two categories of proposal: +* `Regular` +* `Urgent` + +These two categories are strictly identical except that `Urgent` proposals can +be accepted faster if a certain condition is met. For more information, see +[Threshold](#threshold) section. + +## Vote + +### Participants + +*Participants* are users that have the right to vote on proposals. On the +Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and +other users do not get the right to participate in governance. However, they +can submit and deposit on proposals. + +Note that some *participants* can be forbidden to vote on a proposal under a +certain validator if: +* *participant* bonded or unbonded Atoms to said validator after proposal + entered voting period. +* *participant* became validator after proposal entered voting period. + +This does not prevent *participant* to vote with Atoms bonded to other +validators. For example, if a *participant* bonded some Atoms to validator A +before a proposal entered voting period and other Atoms to validator B after +proposal entered voting period, only the vote under validator B will be +forbidden. + +### Voting period + +Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We +define `Voting period` as the interval between the moment the vote opens and +the moment the vote closes. `Voting period` should always be shorter than +`Unbonding period` to prevent double voting. The initial value of +`Voting period` is 2 weeks. + +### Option set + +The option set of a proposal refers to the set of choices a participant can +choose from when casting its vote. + +The initial option set includes the following options: +- `Yes` +- `No` +- `NoWithVeto` +- `Abstain` + +`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option +allows voters to signal that they do not intend to vote in favor or against the +proposal but accept the result of the vote. + +*Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ +option that casts a `NoWithVeto` vote.* + +### Quorum + +Quorum is defined as the minimum percentage of voting power that needs to be +casted on a proposal for the result to be valid. + +In the initial version of the governance module, there will be no quorum +enforced by the protocol. Participation is ensured via the combination of +inheritance and validator's punishment for non-voting. + +### Threshold + +Threshold is defined as the minimum proportion of `Yes` votes (excluding +`Abstain` votes) for the proposal to be accepted. + +Initially, the threshold is set at 50% with a possibility to veto if more than +1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means +that proposals are accepted if the proportion of `Yes` votes (excluding +`Abstain` votes) at the end of the voting period is superior to 50% and if the +proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` +votes). + +`Urgent` proposals also work with the aforementioned threshold, except there is +another condition that can accelerate the acceptance of the proposal. Namely, +if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, +`UrgentProposal` will be immediately accepted, even if the `Voting period` is +not finished. `InitTotalVotingPower` is the total voting power of all bonded +Atom holders at the moment when the vote opens. + +### Inheritance + +If a delegator does not vote, it will inherit its validator vote. + +* If the delegator votes before its validator, it will not inherit from the + validator's vote. +* If the delegator votes after its validator, it will override its validator + vote with its own. If the proposal is a `Urgent` proposal, it is possible + that the vote will close before delegators have a chance to react and + override their validator's vote. This is not a problem, as `Urgent` proposals + require more than 2/3rd of the total voting power to pass before the end of + the voting period. If more than 2/3rd of validators collude, they can censor + the votes of delegators anyway. + +### Validator’s punishment for non-voting + +Validators are required to vote on all proposals to ensure that results have +legitimacy. Voting is part of validators' directives and failure to do it will +result in a penalty. + +If a validator’s address is not in the list of addresses that voted on a +proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting +period` is over), then the validator will automatically be partially slashed by +`GovernancePenalty`. + +*Note: Need to define values for `GovernancePenalty`* + +**Exception:** If a proposal is an `Urgent` proposal and is accepted via the +special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` +that exceeds 2:3, validators cannot be punished for not having voted on it. +That is because the proposal will close as soon as the ratio exceeds 2:3, +making it mechanically impossible for some validators to vote on it. + +### Governance key and governance address + +Validators can make use of a slot where they can designate a +`Governance PubKey`. By default, a validator's `Governance PubKey` will be the +same as its main PubKey. Validators can change this `Governance PubKey` by +sending a `Change Governance PubKey` transaction signed by their main +`Consensus PrivKey`. From there, they will be able to sign votes using the +`Governance PrivKey` associated with their `Governance PubKey`. The +`Governance PubKey` can be changed at any moment. + +## Software Upgrade + +If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade +their software to the new version that was voted. This process is divided in +two steps. + +### Signal + +After a `SoftwareUpgradeProposal` is accepted, validators are expected to +download and install the new version of the software while continuing to run +the previous version. Once a validator has downloaded and installed the +upgrade, it will start signaling to the network that it is ready to switch by +including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation +that we want it in the precommit?*) + +Note: There is only one signal slot per *precommit*. If several +`SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will +form and they will be implemented one after the other in the order that they +were accepted. + +### Switch + +Once a block contains more than 2/3rd *precommits* where a common +`SoftwareUpgradeProposal` is signaled, all the nodes (including validator +nodes, non-validating full nodes and light-nodes) are expected to switch to the +new version of the software. + +*Note: Not clear how the flip is handled programatically* \ No newline at end of file diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md new file mode 100644 index 000000000..97622645f --- /dev/null +++ b/docs/spec/governance/state.md @@ -0,0 +1,136 @@ +# Implementation (1/2) + +## State + +### Procedures + +`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 Procedure struct { + VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks + MinDeposit int64 // Minimum deposit for a proposal to enter voting period. + OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} + ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} + 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 + + IsActive bool // If true, procedure is active. Only one procedure can have isActive true. +} +``` + +**Store**: +* `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their + `ProcedureNumber` +* `ActiveProcedureNumber`: returns current procedure number + +### Proposals + +`Proposals` are item to be voted on. + +```go +type Proposal struct { + Title string // Title of the proposal + Description string // Description of the proposal + Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Category bool // false=regular, true=urgent + Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit + SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included + + 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) + InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1) + Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain) +} +``` + +We also introduce a type `ValidatorGovInfo` + +```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 +} +``` + +**Store:** + +* `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their + `proposalID` +* `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by + `:` as `[]byte`. Given a `proposalID` and a + `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the + proposal) +* `Options`: A mapping `map[[]byte]string` of options indexed by + `::` as `[]byte`. Given a + `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by + this `PubKey` for this validator (`nil` if `PubKey` has not voted under this + validator) +* `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's + governance infos indexed by `:`. Returns + `nil` if proposal has not entered voting period or if `PubKey` was not the + governance public key of a validator when proposal entered voting period. + + +### 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 + InitProcedure.VotingPeriod`. If it is, + then the application checks if validators in `InitVotingPowerList` have voted + and, if not, applies `GovernancePenalty`. After that proposal is ejected from + `ProposalProcessingQueue` and the next element of the queue is evaluated. + Note that if a proposal is urgent and accepted under the special condition, + its `ProposalID` must be ejected from `ProposalProcessingQueue`. + +And the pseudocode for the `ProposalProcessingQueue`: + +```go + in BeginBlock do + + checkProposal() // First call of the recursive function + + + // Recursive function. First call in BeginBlock + func checkProposal() + if (ProposalProcessingQueue.Peek() == nil) + return + + else + proposalID = ProposalProcessingQueue.Peek() + proposal = load(store, Proposals, proposalID) + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) + + if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) + + // proposal was urgent and accepted under the special condition + // no punishment + + ProposalProcessingQueue.pop() + checkProposal() + + else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) + + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + + for each validator in CurrentBondedValidators + validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey) + + if (validatorGovInfo.InitVotingPower != nil) + // validator was bonded when vote started + + validatorOption = load(store, Options, validator.GovPubKey) + if (validatorOption == nil) + // validator did not vote + slash validator by activeProcedure.GovernancePenalty + + ProposalProcessingQueue.pop() + checkProposal() +``` \ No newline at end of file diff --git a/docs/spec/governance/transactions.md b/docs/spec/governance/transactions.md new file mode 100644 index 000000000..4c58b0eb6 --- /dev/null +++ b/docs/spec/governance/transactions.md @@ -0,0 +1,409 @@ +# Implementation (2/2) + +## Transactions + +### Proposal Submission + +Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` +transaction. + +```go +type TxGovSubmitProposal struct { + Title string // Title of the proposal + Description string // Description of the proposal + Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Category bool // false=regular, true=urgent + InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. +} +``` + +**State modifications:** +* Generate new `proposalID` +* Create new `Proposal` +* Initialise `Proposals` attributes +* Store sender's deposit in `Deposits` +* Decrease balance of sender by `InitialDeposit` +* If `MinDeposit` is reached: + * Push `proposalID` in `ProposalProcessingQueueEnd` + * Store each validator's voting power in `ValidatorGovInfos` + +A `TxGovSubmitProposal` transaction can be handled according to the following +pseudocode. + +```go +// PSEUDOCODE // +// Check if TxGovSubmitProposal is valid. If it is, create proposal // + +upon receiving txGovSubmitProposal from sender do + + if !correctlyFormatted(txGovSubmitProposal) then + // check if proposal is correctly formatted. Includes fee payment. + + throw + + else + if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then + // InitialDeposit is negative or null OR sender has insufficient funds + + throw + + else + sender.AtomBalance -= txGovSubmitProposal.InitialDeposit + + proposalID = generate new proposalID + proposal = NewProposal() + + proposal.Title = txGovSubmitProposal.Title + proposal.Description = txGovSubmitProposal.Description + proposal.Type = txGovSubmitProposal.Type + proposal.Category = txGovSubmitProposal.Category + proposal.Deposit = txGovSubmitProposal.InitialDeposit + proposal.SubmitBlock = CurrentBlock + + store(Deposits, :, txGovSubmitProposal.InitialDeposit) + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + + if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then + // MinDeposit is not reached + + proposal.VotingStartBlock = -1 + proposal.InitTotalVotingPower = 0 + proposal.InitProcedureNumber = -1 + + else + // MinDeposit is reached + + proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedureNumber = ActiveProcedureNumber + + 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(proposalID) + + store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping + return proposalID +``` + +### Deposit + +Once a proposal is submitted, if +`Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send +`TxGovDeposit` transactions to increase the proposal's deposit. + +```go +type TxGovDeposit struct { + ProposalID int64 // ID of the proposal + Deposit int64 // Number of Atoms to add to the proposal's deposit +} +``` + +**State modifications:** +* Decrease balance of sender by `deposit` +* Initialize or increase `deposit` of sender in `Deposits` +* Increase `proposal.Deposit` 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. + +```go +// PSEUDOCODE // +// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached + +upon receiving txGovDeposit from sender do + // check if proposal is correctly formatted. Includes fee payment. + + if !correctlyFormatted(txGovDeposit) then + throw + + else + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + + throw + + else + if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) + // deposit is negative or null OR sender has insufficient funds + + throw + + else + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + if (proposal.Deposit >= activeProcedure.MinDeposit) then + // MinDeposit was reached + + throw + + else + if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then + // Maximum deposit period reached + + throw + + else + // sender can deposit + + sender.AtomBalance -= txGovDeposit.Deposit + deposit = load(store, Deposits, :) + + if (deposit == nil) + // sender has never deposited on this proposal + + store(Deposits, :, deposit) + + else + // sender has already deposited on this proposal + + newDeposit = deposit + txGovDeposit.Deposit + store(Deposits, :, newDeposit) + + proposal.Deposit += txGovDeposit.Deposit + + if (proposal.Deposit >= activeProcedure.MinDeposit) then + // MinDeposit is reached, vote opens + + proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedureNumber = ActiveProcedureNumber + + 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) +``` + +### Claim deposit + +Finally, if the proposal is accepted or `MinDeposit` was not reached before the +end of the `MaximumDepositPeriod`, then Atom holders can send +`TxGovClaimDeposit` transaction to claim their deposits. + +```go + type TxGovClaimDeposit struct { + ProposalID int64 + } +``` + +**State modifications:** +If conditions are met, reimburse the deposit, i.e. +* Increase `AtomBalance` of sender by `deposit` +* Set `deposit` of sender in `DepositorsList` to 0 + +And the associated pseudocode. + +```go + // PSEUDOCODE // + /* Check if TxGovClaimDeposit is valid. If vote never started and MaxDepositPeriod is reached or if vote started and proposal was accepted, return deposit */ + + upon receiving txGovClaimDeposit from sender do + // check if proposal is correctly formatted. Includes fee payment. + + if !correctlyFormatted(txGovClaimDeposit) then + throw + + else + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + + throw + + else + deposit = load(store, Deposits, :) + + if (deposit == nil) + // sender has not deposited on this proposal + + throw + + else + if (deposit <= 0) + // deposit has already been claimed + + throw + + else + if (proposal.VotingStartBlock <= 0) + // Vote never started + + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) + // MaxDepositPeriod is not reached + + throw + + else + // MaxDepositPeriod is reached + // Set sender's deposit to 0 and refund + + store(Deposits, :, 0) + sender.AtomBalance += deposit + + else + // Vote started + + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) + + if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then + + // Proposal was accepted either because + // Proposal was urgent and special condition was met + // Voting period ended and vote satisfies threshold + + store(Deposits, :, 0) + sender.AtomBalance += deposit +``` + +### Vote + +Once `ActiveProcedure.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 + type TxGovVote struct { + ProposalID int64 // proposalID of the proposal + Option string // option from OptionSet chosen by the voter + ValidatorPubKey crypto.PubKey // PubKey of the validator voter wants to tie its vote to + } +``` + +**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 + `proposal.Votes['validatorOption']` by sender's `voting power` +* If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` + by sender's `voting power` +* If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by + validator's `InitialVotingPower - minus` (`minus` can be equal to 0) + +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 GovernancePubKey as `ValidatorPubKey` + +Next is a pseudocode proposal of the way `TxGovVote` transactions can be +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) then + throw + + else + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + + throw + + else + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened + validator = load(store, Validators, txGovVote.ValidatorPubKey) + + if !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 + // ValidatorPubKey is not the GovPubKey of a current validator + + throw + + else + option = load(store, Options, ::) + + if (option != nil) + // sender has already voted with the Atoms bonded to ValidatorPubKey + + throw + + else + if (proposal.VotingStartBlock < 0) OR + (CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR + (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR + (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR + (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then + + // Throws if + // Vote has not started OR if + // Vote had ended OR if + // sender bonded Atoms to ValidatorPubKey after start of vote OR if + // sender unbonded Atoms from ValidatorPubKey after start of vote OR if + // proposal is urgent and special condition is met, i.e. proposal is accepted and closed + + throw + + else + validatorGovInfo = load(store, ValidatorGovInfos, :) + + if (validatorGovInfo == nil) + // validator became validator after proposal entered voting period + + throw + + else + // sender can vote, check if sender == validator and store sender's option in Options + + store(Options, ::, txGovVote.Option) + + if (sender != validator.GovPubKey) + // Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey + + if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then + // check in Staking module + + throw + + else + validatorOption = load(store, Options, :::, validatorGovInfo) + + else + // Validator has already voted + // Reduce votes of option chosen by validator by sender's bonded Amount + + proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey) + + // increase votes of option chosen by sender by bonded Amount + proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey) + + else + // sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey + // i.e. sender == validator + + proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) +``` \ No newline at end of file From 275abe56e764288c539df2ef45bfa1ed516b6fb7 Mon Sep 17 00:00:00 2001 From: gamarin Date: Mon, 26 Feb 2018 16:36:09 +0100 Subject: [PATCH 006/182] Split in multiple files --- docs/spec/governance/.DS_Store | Bin 6148 -> 6148 bytes docs/spec/governance/governance.md | 806 +++++++++++++++++++++++++++++ 2 files changed, 806 insertions(+) create mode 100644 docs/spec/governance/governance.md diff --git a/docs/spec/governance/.DS_Store b/docs/spec/governance/.DS_Store index 245302bad866a4420ac4f260272976d306216901..a476a2ac22b8d2a9200a714d51077fe1cb73965d 100644 GIT binary patch delta 347 zcmZoMXfc=|#>B!ku~2NHo+2a1#(>?7iyfGm7&#{MF!|T>GNd!)Gn6r;G88f7F(d+U zGLWsukjs$bnUkNKl#`#tz`!5?#4CYV<3AVxSquz#bQY8(`IHuy6aiIPFoZG`gH11H z&>1wdht)%rj(AIQ%Ile$1w3Q%4jNah1&K!$+S zrUKa@<1n4BPta+8P^W=BAdYDO*zcPon5HvrX6NAN00!M=L+0B)qu~2NHo+2ab#(>?7jI5J+So}8^vrb{!*dW2YnVo~51E^%PAjfy+ V$^0UY91K9f$iTp|IYML&GXOvB4_W{K diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md new file mode 100644 index 000000000..155a20ac2 --- /dev/null +++ b/docs/spec/governance/governance.md @@ -0,0 +1,806 @@ +# Governance documentation + +*Disclaimer: This is work in progress. Mechanisms are susceptible to change.* + +This document describes the high-level architecture of the governance module. +The governance module allows bonded Atom holders to vote on proposals on a 1 +bonded Atom 1 vote basis. + +## Design overview + +The governance process is divided in a few steps that are outlined below: + +* **Proposal submission:** Proposal is submitted to the blockchain with a + deposit. +* **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is + confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` + transactions to vote on the proposal. +* If the proposal involves a software upgrade: + * **Signal:** Validators start signaling that they are ready to switch to the + new version. + * **Switch:** Once more than 75% of validators have signaled that they are + ready to switch, their software automatically flips to the new version. + +## Proposal submission + +### Right to submit a proposal + +Any Atom holder, whether bonded or unbonded, can submit proposals by sending a +`TxGovProposal` transaction. Once a proposal is submitted, it is identified by +its unique `proposalID`. + +### Proposal filter (minimum deposit) + +To prevent spam, proposals must be submitted with a deposit in Atoms. Voting +period will not start as long as the proposal's deposit is smaller than the +minimum deposit `MinDeposit`. + +When a proposal is submitted, it has to be accompanied by a deposit that must +be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter +need not pay for the entire deposit on its own. If a proposal's deposit is +strictly inferior to `MinDeposit`, other Atom holders can increase the +proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposal's +deposit reaches `MinDeposit`, it enters voting period. + +### Deposit refund + +There are two instances where Atom holders that deposited can claim back their +deposit: +* If the proposal is accepted. +* If the proposal's deposit does not reach `MinDeposit` for a period longer + than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is + considered closed and nobody can deposit on it anymore. + +In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` +transaction to retrieve their share of the deposit. + +### Proposal types + +In the initial version of the governance module, there are two types of +proposal: +* `PlainTextProposal` All the proposals that do not involve a modification of + the source code go under this type. For example, an opinion poll would use a + proposal of type `PlainTextProposal`. +* `SoftwareUpgradeProposal`. If accepted, validators are expected to update + their software in accordance with the proposal. They must do so by following + a 2-steps process described in the [Software Upgrade](#software-upgrade) + section below. Software upgrade roadmap may be discussed and agreed on via + `PlainTextProposals`, but actual software upgrades must be performed via + `SoftwareUpgradeProposals`. + +### Proposal categories + +There are two categories of proposal: +* `Regular` +* `Urgent` + +These two categories are strictly identical except that `Urgent` proposals can +be accepted faster if a certain condition is met. For more information, see +[Threshold](#threshold) section. + +## Vote + +### Participants + +*Participants* are users that have the right to vote on proposals. On the +Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and +other users do not get the right to participate in governance. However, they +can submit and deposit on proposals. + +Note that some *participants* can be forbidden to vote on a proposal under a +certain validator if: +* *participant* bonded or unbonded Atoms to said validator after proposal + entered voting period. +* *participant* became validator after proposal entered voting period. + +This does not prevent *participant* to vote with Atoms bonded to other +validators. For example, if a *participant* bonded some Atoms to validator A +before a proposal entered voting period and other Atoms to validator B after +proposal entered voting period, only the vote under validator B will be +forbidden. + +### Voting period + +Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We +define `Voting period` as the interval between the moment the vote opens and +the moment the vote closes. `Voting period` should always be shorter than +`Unbonding period` to prevent double voting. The initial value of +`Voting period` is 2 weeks. + +### Option set + +The option set of a proposal refers to the set of choices a participant can +choose from when casting its vote. + +The initial option set includes the following options: +- `Yes` +- `No` +- `NoWithVeto` +- `Abstain` + +`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option +allows voters to signal that they do not intend to vote in favor or against the +proposal but accept the result of the vote. + +*Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ +option that casts a `NoWithVeto` vote.* + +### Quorum + +Quorum is defined as the minimum percentage of voting power that needs to be +casted on a proposal for the result to be valid. + +In the initial version of the governance module, there will be no quorum +enforced by the protocol. Participation is ensured via the combination of +inheritance and validator's punishment for non-voting. + +### Threshold + +Threshold is defined as the minimum proportion of `Yes` votes (excluding +`Abstain` votes) for the proposal to be accepted. + +Initially, the threshold is set at 50% with a possibility to veto if more than +1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means +that proposals are accepted if the proportion of `Yes` votes (excluding +`Abstain` votes) at the end of the voting period is superior to 50% and if the +proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` +votes). + +`Urgent` proposals also work with the aforementioned threshold, except there is +another condition that can accelerate the acceptance of the proposal. Namely, +if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, +`UrgentProposal` will be immediately accepted, even if the `Voting period` is +not finished. `InitTotalVotingPower` is the total voting power of all bonded +Atom holders at the moment when the vote opens. + +### Inheritance + +If a delegator does not vote, it will inherit its validator vote. + +* If the delegator votes before its validator, it will not inherit from the + validator's vote. +* If the delegator votes after its validator, it will override its validator + vote with its own. If the proposal is a `Urgent` proposal, it is possible + that the vote will close before delegators have a chance to react and + override their validator's vote. This is not a problem, as `Urgent` proposals + require more than 2/3rd of the total voting power to pass before the end of + the voting period. If more than 2/3rd of validators collude, they can censor + the votes of delegators anyway. + +### Validator’s punishment for non-voting + +Validators are required to vote on all proposals to ensure that results have +legitimacy. Voting is part of validators' directives and failure to do it will +result in a penalty. + +If a validator’s address is not in the list of addresses that voted on a +proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting +period` is over), then the validator will automatically be partially slashed by +`GovernancePenalty`. + +*Note: Need to define values for `GovernancePenalty`* + +**Exception:** If a proposal is an `Urgent` proposal and is accepted via the +special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` +that exceeds 2:3, validators cannot be punished for not having voted on it. +That is because the proposal will close as soon as the ratio exceeds 2:3, +making it mechanically impossible for some validators to vote on it. + +### Governance key and governance address + +Validators can make use of a slot where they can designate a +`Governance PubKey`. By default, a validator's `Governance PubKey` will be the +same as its main PubKey. Validators can change this `Governance PubKey` by +sending a `Change Governance PubKey` transaction signed by their main +`Consensus PrivKey`. From there, they will be able to sign votes using the +`Governance PrivKey` associated with their `Governance PubKey`. The +`Governance PubKey` can be changed at any moment. + +## Software Upgrade + +If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade +their software to the new version that was voted. This process is divided in +two steps. + +### Signal + +After a `SoftwareUpgradeProposal` is accepted, validators are expected to +download and install the new version of the software while continuing to run +the previous version. Once a validator has downloaded and installed the +upgrade, it will start signaling to the network that it is ready to switch by +including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation +that we want it in the precommit?*) + +Note: There is only one signal slot per *precommit*. If several +`SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will +form and they will be implemented one after the other in the order that they +were accepted. + +### Switch + +Once a block contains more than 2/3rd *precommits* where a common +`SoftwareUpgradeProposal` is signaled, all the nodes (including validator +nodes, non-validating full nodes and light-nodes) are expected to switch to the +new version of the software. + +*Note: Not clear how the flip is handled programatically* + +## Implementation + +*Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic +and implementation might widely differ* + +### State + +#### Procedures + +`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 Procedure struct { + VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks + MinDeposit int64 // Minimum deposit for a proposal to enter voting period. + OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} + ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} + 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 + + IsActive bool // If true, procedure is active. Only one procedure can have isActive true. +} +``` + +**Store**: +* `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their + `ProcedureNumber` +* `ActiveProcedureNumber`: returns current procedure number + +#### Proposals + +`Proposals` are item to be voted on. + +```go +type Proposal struct { + Title string // Title of the proposal + Description string // Description of the proposal + Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Category bool // false=regular, true=urgent + Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit + SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included + + 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) + InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1) + Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain) +} +``` + +We also introduce a type `ValidatorGovInfo` + +```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 +} +``` + +**Store:** + +* `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their + `proposalID` +* `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by + `:` as `[]byte`. Given a `proposalID` and a + `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the + proposal) +* `Options`: A mapping `map[[]byte]string` of options indexed by + `::` as `[]byte`. Given a + `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by + this `PubKey` for this validator (`nil` if `PubKey` has not voted under this + validator) +* `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's + governance infos indexed by `:`. Returns + `nil` if proposal has not entered voting period or if `PubKey` was not the + governance public key of a validator when proposal entered voting period. + + +#### 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 + InitProcedure.VotingPeriod`. If it is, + then the application checks if validators in `InitVotingPowerList` have voted + and, if not, applies `GovernancePenalty`. After that proposal is ejected from + `ProposalProcessingQueue` and the next element of the queue is evaluated. + Note that if a proposal is urgent and accepted under the special condition, + its `ProposalID` must be ejected from `ProposalProcessingQueue`. + +And the pseudocode for the `ProposalProcessingQueue`: + +```go + in BeginBlock do + + checkProposal() // First call of the recursive function + + + // Recursive function. First call in BeginBlock + func checkProposal() + if (ProposalProcessingQueue.Peek() == nil) + return + + else + proposalID = ProposalProcessingQueue.Peek() + proposal = load(store, Proposals, proposalID) + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) + + if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) + + // proposal was urgent and accepted under the special condition + // no punishment + + ProposalProcessingQueue.pop() + checkProposal() + + else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) + + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + + for each validator in CurrentBondedValidators + validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey) + + if (validatorGovInfo.InitVotingPower != nil) + // validator was bonded when vote started + + validatorOption = load(store, Options, validator.GovPubKey) + if (validatorOption == nil) + // validator did not vote + slash validator by activeProcedure.GovernancePenalty + + ProposalProcessingQueue.pop() + checkProposal() +``` + + +### Transactions + +#### Proposal Submission + +Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` +transaction. + +```go +type TxGovSubmitProposal struct { + Title string // Title of the proposal + Description string // Description of the proposal + Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Category bool // false=regular, true=urgent + InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. +} +``` + +**State modifications:** +* Generate new `proposalID` +* Create new `Proposal` +* Initialise `Proposals` attributes +* Store sender's deposit in `Deposits` +* Decrease balance of sender by `InitialDeposit` +* If `MinDeposit` is reached: + * Push `proposalID` in `ProposalProcessingQueueEnd` + * Store each validator's voting power in `ValidatorGovInfos` + +A `TxGovSubmitProposal` transaction can be handled according to the following +pseudocode. + +```go +// PSEUDOCODE // +// Check if TxGovSubmitProposal is valid. If it is, create proposal // + +upon receiving txGovSubmitProposal from sender do + + if !correctlyFormatted(txGovSubmitProposal) then + // check if proposal is correctly formatted. Includes fee payment. + + throw + + else + if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then + // InitialDeposit is negative or null OR sender has insufficient funds + + throw + + else + sender.AtomBalance -= txGovSubmitProposal.InitialDeposit + + proposalID = generate new proposalID + proposal = NewProposal() + + proposal.Title = txGovSubmitProposal.Title + proposal.Description = txGovSubmitProposal.Description + proposal.Type = txGovSubmitProposal.Type + proposal.Category = txGovSubmitProposal.Category + proposal.Deposit = txGovSubmitProposal.InitialDeposit + proposal.SubmitBlock = CurrentBlock + + store(Deposits, :, txGovSubmitProposal.InitialDeposit) + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + + if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then + // MinDeposit is not reached + + proposal.VotingStartBlock = -1 + proposal.InitTotalVotingPower = 0 + proposal.InitProcedureNumber = -1 + + else + // MinDeposit is reached + + proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedureNumber = ActiveProcedureNumber + + 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(proposalID) + + store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping + return proposalID +``` + +#### Deposit + +Once a proposal is submitted, if +`Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send +`TxGovDeposit` transactions to increase the proposal's deposit. + +```go +type TxGovDeposit struct { + ProposalID int64 // ID of the proposal + Deposit int64 // Number of Atoms to add to the proposal's deposit +} +``` + +**State modifications:** +* Decrease balance of sender by `deposit` +* Initialize or increase `deposit` of sender in `Deposits` +* Increase `proposal.Deposit` 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. + +```go +// PSEUDOCODE // +// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached + +upon receiving txGovDeposit from sender do + // check if proposal is correctly formatted. Includes fee payment. + + if !correctlyFormatted(txGovDeposit) then + throw + + else + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + + throw + + else + if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) + // deposit is negative or null OR sender has insufficient funds + + throw + + else + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + if (proposal.Deposit >= activeProcedure.MinDeposit) then + // MinDeposit was reached + + throw + + else + if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then + // Maximum deposit period reached + + throw + + else + // sender can deposit + + sender.AtomBalance -= txGovDeposit.Deposit + deposit = load(store, Deposits, :) + + if (deposit == nil) + // sender has never deposited on this proposal + + store(Deposits, :, deposit) + + else + // sender has already deposited on this proposal + + newDeposit = deposit + txGovDeposit.Deposit + store(Deposits, :, newDeposit) + + proposal.Deposit += txGovDeposit.Deposit + + if (proposal.Deposit >= activeProcedure.MinDeposit) then + // MinDeposit is reached, vote opens + + proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedureNumber = ActiveProcedureNumber + + 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) +``` + +#### Claim deposit + +Finally, if the proposal is accepted or `MinDeposit` was not reached before the +end of the `MaximumDepositPeriod`, then Atom holders can send +`TxGovClaimDeposit` transaction to claim their deposits. + +```go + type TxGovClaimDeposit struct { + ProposalID int64 + } +``` + +**State modifications:** +If conditions are met, reimburse the deposit, i.e. +* Increase `AtomBalance` of sender by `deposit` +* Set `deposit` of sender in `DepositorsList` to 0 + +And the associated pseudocode. + +```go + // PSEUDOCODE // + /* Check if TxGovClaimDeposit is valid. If vote never started and MaxDepositPeriod is reached or if vote started and proposal was accepted, return deposit */ + + upon receiving txGovClaimDeposit from sender do + // check if proposal is correctly formatted. Includes fee payment. + + if !correctlyFormatted(txGovClaimDeposit) then + throw + + else + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + + throw + + else + deposit = load(store, Deposits, :) + + if (deposit == nil) + // sender has not deposited on this proposal + + throw + + else + if (deposit <= 0) + // deposit has already been claimed + + throw + + else + if (proposal.VotingStartBlock <= 0) + // Vote never started + + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) + // MaxDepositPeriod is not reached + + throw + + else + // MaxDepositPeriod is reached + // Set sender's deposit to 0 and refund + + store(Deposits, :, 0) + sender.AtomBalance += deposit + + else + // Vote started + + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) + + if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then + + // Proposal was accepted either because + // Proposal was urgent and special condition was met + // Voting period ended and vote satisfies threshold + + store(Deposits, :, 0) + sender.AtomBalance += deposit +``` + +#### Vote + +Once `ActiveProcedure.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 + type TxGovVote struct { + ProposalID int64 // proposalID of the proposal + Option string // option from OptionSet chosen by the voter + ValidatorPubKey crypto.PubKey // PubKey of the validator voter wants to tie its vote to + } +``` + +**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 + `proposal.Votes['validatorOption']` by sender's `voting power` +* If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` + by sender's `voting power` +* If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by + validator's `InitialVotingPower - minus` (`minus` can be equal to 0) + +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 GovernancePubKey as `ValidatorPubKey` + +Next is a pseudocode proposal of the way `TxGovVote` transactions can be +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) then + throw + + else + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + + throw + + else + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened + validator = load(store, Validators, txGovVote.ValidatorPubKey) + + if !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 + // ValidatorPubKey is not the GovPubKey of a current validator + + throw + + else + option = load(store, Options, ::) + + if (option != nil) + // sender has already voted with the Atoms bonded to ValidatorPubKey + + throw + + else + if (proposal.VotingStartBlock < 0) OR + (CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR + (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR + (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR + (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then + + // Throws if + // Vote has not started OR if + // Vote had ended OR if + // sender bonded Atoms to ValidatorPubKey after start of vote OR if + // sender unbonded Atoms from ValidatorPubKey after start of vote OR if + // proposal is urgent and special condition is met, i.e. proposal is accepted and closed + + throw + + else + validatorGovInfo = load(store, ValidatorGovInfos, :) + + if (validatorGovInfo == nil) + // validator became validator after proposal entered voting period + + throw + + else + // sender can vote, check if sender == validator and store sender's option in Options + + store(Options, ::, txGovVote.Option) + + if (sender != validator.GovPubKey) + // Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey + + if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then + // check in Staking module + + throw + + else + validatorOption = load(store, Options, :::, validatorGovInfo) + + else + // Validator has already voted + // Reduce votes of option chosen by validator by sender's bonded Amount + + proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey) + + // increase votes of option chosen by sender by bonded Amount + proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey) + + else + // sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey + // i.e. sender == validator + + proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) +``` + +## Future improvements (not in scope for MVP) + +The current documentation only describes the minimum viable product for the +governance module. Future improvements may include: + +* **`BountyProposals`:** If accepted, a `BountyProposal` creates an open + bounty. The `BountyProposal` specifies how many Atoms will be given upon + completion. These Atoms will be taken from the `reserve pool`. After a + `BountyProposal` is accepted by governance, anybody can submit a + `SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a + `BountyProposal` is accepted, the corresponding funds in the `reserve pool` + are locked so that payment can always be honored. In order to link a + `SoftwareUpgradeProposal` to an open bounty, the submitter of the + `SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute. + If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by + governance, the funds that were reserved are automatically transferred to the + submitter. +* **Complex delegation:** Delegators could choose other representatives than + their validators. Ultimately, the chain of representatives would always end + up to a validator, but delegators could inherit the vote of their chosen + representative before they inherit the vote of their validator. In other + words, they would only inherit the vote of their validator if their other + appointed representative did not vote. +* **`ParameterProposals` and `WhitelistProposals`:** These proposals would + automatically change pre-defined parameters and whitelists. Upon acceptance, + these proposals would not require validators to do the signal and switch + process. +* **Better process for proposal review:** There would be two parts to + `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to + reward third party auditors. From 3071f05d0e47c99f3dd148b15d0c782af6d475da Mon Sep 17 00:00:00 2001 From: gamarin Date: Mon, 26 Feb 2018 16:40:25 +0100 Subject: [PATCH 007/182] remove governance.md --- docs/spec/governance/governance.md | 806 ----------------------------- 1 file changed, 806 deletions(-) delete mode 100644 docs/spec/governance/governance.md diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md deleted file mode 100644 index 155a20ac2..000000000 --- a/docs/spec/governance/governance.md +++ /dev/null @@ -1,806 +0,0 @@ -# Governance documentation - -*Disclaimer: This is work in progress. Mechanisms are susceptible to change.* - -This document describes the high-level architecture of the governance module. -The governance module allows bonded Atom holders to vote on proposals on a 1 -bonded Atom 1 vote basis. - -## Design overview - -The governance process is divided in a few steps that are outlined below: - -* **Proposal submission:** Proposal is submitted to the blockchain with a - deposit. -* **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is - confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` - transactions to vote on the proposal. -* If the proposal involves a software upgrade: - * **Signal:** Validators start signaling that they are ready to switch to the - new version. - * **Switch:** Once more than 75% of validators have signaled that they are - ready to switch, their software automatically flips to the new version. - -## Proposal submission - -### Right to submit a proposal - -Any Atom holder, whether bonded or unbonded, can submit proposals by sending a -`TxGovProposal` transaction. Once a proposal is submitted, it is identified by -its unique `proposalID`. - -### Proposal filter (minimum deposit) - -To prevent spam, proposals must be submitted with a deposit in Atoms. Voting -period will not start as long as the proposal's deposit is smaller than the -minimum deposit `MinDeposit`. - -When a proposal is submitted, it has to be accompanied by a deposit that must -be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter -need not pay for the entire deposit on its own. If a proposal's deposit is -strictly inferior to `MinDeposit`, other Atom holders can increase the -proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposal's -deposit reaches `MinDeposit`, it enters voting period. - -### Deposit refund - -There are two instances where Atom holders that deposited can claim back their -deposit: -* If the proposal is accepted. -* If the proposal's deposit does not reach `MinDeposit` for a period longer - than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is - considered closed and nobody can deposit on it anymore. - -In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` -transaction to retrieve their share of the deposit. - -### Proposal types - -In the initial version of the governance module, there are two types of -proposal: -* `PlainTextProposal` All the proposals that do not involve a modification of - the source code go under this type. For example, an opinion poll would use a - proposal of type `PlainTextProposal`. -* `SoftwareUpgradeProposal`. If accepted, validators are expected to update - their software in accordance with the proposal. They must do so by following - a 2-steps process described in the [Software Upgrade](#software-upgrade) - section below. Software upgrade roadmap may be discussed and agreed on via - `PlainTextProposals`, but actual software upgrades must be performed via - `SoftwareUpgradeProposals`. - -### Proposal categories - -There are two categories of proposal: -* `Regular` -* `Urgent` - -These two categories are strictly identical except that `Urgent` proposals can -be accepted faster if a certain condition is met. For more information, see -[Threshold](#threshold) section. - -## Vote - -### Participants - -*Participants* are users that have the right to vote on proposals. On the -Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and -other users do not get the right to participate in governance. However, they -can submit and deposit on proposals. - -Note that some *participants* can be forbidden to vote on a proposal under a -certain validator if: -* *participant* bonded or unbonded Atoms to said validator after proposal - entered voting period. -* *participant* became validator after proposal entered voting period. - -This does not prevent *participant* to vote with Atoms bonded to other -validators. For example, if a *participant* bonded some Atoms to validator A -before a proposal entered voting period and other Atoms to validator B after -proposal entered voting period, only the vote under validator B will be -forbidden. - -### Voting period - -Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We -define `Voting period` as the interval between the moment the vote opens and -the moment the vote closes. `Voting period` should always be shorter than -`Unbonding period` to prevent double voting. The initial value of -`Voting period` is 2 weeks. - -### Option set - -The option set of a proposal refers to the set of choices a participant can -choose from when casting its vote. - -The initial option set includes the following options: -- `Yes` -- `No` -- `NoWithVeto` -- `Abstain` - -`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option -allows voters to signal that they do not intend to vote in favor or against the -proposal but accept the result of the vote. - -*Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ -option that casts a `NoWithVeto` vote.* - -### Quorum - -Quorum is defined as the minimum percentage of voting power that needs to be -casted on a proposal for the result to be valid. - -In the initial version of the governance module, there will be no quorum -enforced by the protocol. Participation is ensured via the combination of -inheritance and validator's punishment for non-voting. - -### Threshold - -Threshold is defined as the minimum proportion of `Yes` votes (excluding -`Abstain` votes) for the proposal to be accepted. - -Initially, the threshold is set at 50% with a possibility to veto if more than -1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means -that proposals are accepted if the proportion of `Yes` votes (excluding -`Abstain` votes) at the end of the voting period is superior to 50% and if the -proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` -votes). - -`Urgent` proposals also work with the aforementioned threshold, except there is -another condition that can accelerate the acceptance of the proposal. Namely, -if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, -`UrgentProposal` will be immediately accepted, even if the `Voting period` is -not finished. `InitTotalVotingPower` is the total voting power of all bonded -Atom holders at the moment when the vote opens. - -### Inheritance - -If a delegator does not vote, it will inherit its validator vote. - -* If the delegator votes before its validator, it will not inherit from the - validator's vote. -* If the delegator votes after its validator, it will override its validator - vote with its own. If the proposal is a `Urgent` proposal, it is possible - that the vote will close before delegators have a chance to react and - override their validator's vote. This is not a problem, as `Urgent` proposals - require more than 2/3rd of the total voting power to pass before the end of - the voting period. If more than 2/3rd of validators collude, they can censor - the votes of delegators anyway. - -### Validator’s punishment for non-voting - -Validators are required to vote on all proposals to ensure that results have -legitimacy. Voting is part of validators' directives and failure to do it will -result in a penalty. - -If a validator’s address is not in the list of addresses that voted on a -proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting -period` is over), then the validator will automatically be partially slashed by -`GovernancePenalty`. - -*Note: Need to define values for `GovernancePenalty`* - -**Exception:** If a proposal is an `Urgent` proposal and is accepted via the -special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` -that exceeds 2:3, validators cannot be punished for not having voted on it. -That is because the proposal will close as soon as the ratio exceeds 2:3, -making it mechanically impossible for some validators to vote on it. - -### Governance key and governance address - -Validators can make use of a slot where they can designate a -`Governance PubKey`. By default, a validator's `Governance PubKey` will be the -same as its main PubKey. Validators can change this `Governance PubKey` by -sending a `Change Governance PubKey` transaction signed by their main -`Consensus PrivKey`. From there, they will be able to sign votes using the -`Governance PrivKey` associated with their `Governance PubKey`. The -`Governance PubKey` can be changed at any moment. - -## Software Upgrade - -If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade -their software to the new version that was voted. This process is divided in -two steps. - -### Signal - -After a `SoftwareUpgradeProposal` is accepted, validators are expected to -download and install the new version of the software while continuing to run -the previous version. Once a validator has downloaded and installed the -upgrade, it will start signaling to the network that it is ready to switch by -including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation -that we want it in the precommit?*) - -Note: There is only one signal slot per *precommit*. If several -`SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will -form and they will be implemented one after the other in the order that they -were accepted. - -### Switch - -Once a block contains more than 2/3rd *precommits* where a common -`SoftwareUpgradeProposal` is signaled, all the nodes (including validator -nodes, non-validating full nodes and light-nodes) are expected to switch to the -new version of the software. - -*Note: Not clear how the flip is handled programatically* - -## Implementation - -*Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic -and implementation might widely differ* - -### State - -#### Procedures - -`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 Procedure struct { - VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks - MinDeposit int64 // Minimum deposit for a proposal to enter voting period. - OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} - ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} - 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 - - IsActive bool // If true, procedure is active. Only one procedure can have isActive true. -} -``` - -**Store**: -* `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their - `ProcedureNumber` -* `ActiveProcedureNumber`: returns current procedure number - -#### Proposals - -`Proposals` are item to be voted on. - -```go -type Proposal struct { - Title string // Title of the proposal - Description string // Description of the proposal - Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Category bool // false=regular, true=urgent - Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit - SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included - - 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) - InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1) - Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain) -} -``` - -We also introduce a type `ValidatorGovInfo` - -```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 -} -``` - -**Store:** - -* `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their - `proposalID` -* `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by - `:` as `[]byte`. Given a `proposalID` and a - `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the - proposal) -* `Options`: A mapping `map[[]byte]string` of options indexed by - `::` as `[]byte`. Given a - `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by - this `PubKey` for this validator (`nil` if `PubKey` has not voted under this - validator) -* `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's - governance infos indexed by `:`. Returns - `nil` if proposal has not entered voting period or if `PubKey` was not the - governance public key of a validator when proposal entered voting period. - - -#### 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 + InitProcedure.VotingPeriod`. If it is, - then the application checks if validators in `InitVotingPowerList` have voted - and, if not, applies `GovernancePenalty`. After that proposal is ejected from - `ProposalProcessingQueue` and the next element of the queue is evaluated. - Note that if a proposal is urgent and accepted under the special condition, - its `ProposalID` must be ejected from `ProposalProcessingQueue`. - -And the pseudocode for the `ProposalProcessingQueue`: - -```go - in BeginBlock do - - checkProposal() // First call of the recursive function - - - // Recursive function. First call in BeginBlock - func checkProposal() - if (ProposalProcessingQueue.Peek() == nil) - return - - else - proposalID = ProposalProcessingQueue.Peek() - proposal = load(store, Proposals, proposalID) - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) - - if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) - - // proposal was urgent and accepted under the special condition - // no punishment - - ProposalProcessingQueue.pop() - checkProposal() - - else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) - - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - - for each validator in CurrentBondedValidators - validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey) - - if (validatorGovInfo.InitVotingPower != nil) - // validator was bonded when vote started - - validatorOption = load(store, Options, validator.GovPubKey) - if (validatorOption == nil) - // validator did not vote - slash validator by activeProcedure.GovernancePenalty - - ProposalProcessingQueue.pop() - checkProposal() -``` - - -### Transactions - -#### Proposal Submission - -Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` -transaction. - -```go -type TxGovSubmitProposal struct { - Title string // Title of the proposal - Description string // Description of the proposal - Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Category bool // false=regular, true=urgent - InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. -} -``` - -**State modifications:** -* Generate new `proposalID` -* Create new `Proposal` -* Initialise `Proposals` attributes -* Store sender's deposit in `Deposits` -* Decrease balance of sender by `InitialDeposit` -* If `MinDeposit` is reached: - * Push `proposalID` in `ProposalProcessingQueueEnd` - * Store each validator's voting power in `ValidatorGovInfos` - -A `TxGovSubmitProposal` transaction can be handled according to the following -pseudocode. - -```go -// PSEUDOCODE // -// Check if TxGovSubmitProposal is valid. If it is, create proposal // - -upon receiving txGovSubmitProposal from sender do - - if !correctlyFormatted(txGovSubmitProposal) then - // check if proposal is correctly formatted. Includes fee payment. - - throw - - else - if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then - // InitialDeposit is negative or null OR sender has insufficient funds - - throw - - else - sender.AtomBalance -= txGovSubmitProposal.InitialDeposit - - proposalID = generate new proposalID - proposal = NewProposal() - - proposal.Title = txGovSubmitProposal.Title - proposal.Description = txGovSubmitProposal.Description - proposal.Type = txGovSubmitProposal.Type - proposal.Category = txGovSubmitProposal.Category - proposal.Deposit = txGovSubmitProposal.InitialDeposit - proposal.SubmitBlock = CurrentBlock - - store(Deposits, :, txGovSubmitProposal.InitialDeposit) - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - - if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then - // MinDeposit is not reached - - proposal.VotingStartBlock = -1 - proposal.InitTotalVotingPower = 0 - proposal.InitProcedureNumber = -1 - - else - // MinDeposit is reached - - proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedureNumber = ActiveProcedureNumber - - 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(proposalID) - - store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping - return proposalID -``` - -#### Deposit - -Once a proposal is submitted, if -`Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send -`TxGovDeposit` transactions to increase the proposal's deposit. - -```go -type TxGovDeposit struct { - ProposalID int64 // ID of the proposal - Deposit int64 // Number of Atoms to add to the proposal's deposit -} -``` - -**State modifications:** -* Decrease balance of sender by `deposit` -* Initialize or increase `deposit` of sender in `Deposits` -* Increase `proposal.Deposit` 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. - -```go -// PSEUDOCODE // -// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached - -upon receiving txGovDeposit from sender do - // check if proposal is correctly formatted. Includes fee payment. - - if !correctlyFormatted(txGovDeposit) then - throw - - else - proposal = load(store, Proposals, txGovDeposit.ProposalID) - - if (proposal == nil) then - // There is no proposal for this proposalID - - throw - - else - if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) - // deposit is negative or null OR sender has insufficient funds - - throw - - else - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - if (proposal.Deposit >= activeProcedure.MinDeposit) then - // MinDeposit was reached - - throw - - else - if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then - // Maximum deposit period reached - - throw - - else - // sender can deposit - - sender.AtomBalance -= txGovDeposit.Deposit - deposit = load(store, Deposits, :) - - if (deposit == nil) - // sender has never deposited on this proposal - - store(Deposits, :, deposit) - - else - // sender has already deposited on this proposal - - newDeposit = deposit + txGovDeposit.Deposit - store(Deposits, :, newDeposit) - - proposal.Deposit += txGovDeposit.Deposit - - if (proposal.Deposit >= activeProcedure.MinDeposit) then - // MinDeposit is reached, vote opens - - proposal.VotingStartBlock = CurrentBlock - proposal.InitTotalVotingPower = TotalVotingPower - proposal.InitProcedureNumber = ActiveProcedureNumber - - 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) -``` - -#### Claim deposit - -Finally, if the proposal is accepted or `MinDeposit` was not reached before the -end of the `MaximumDepositPeriod`, then Atom holders can send -`TxGovClaimDeposit` transaction to claim their deposits. - -```go - type TxGovClaimDeposit struct { - ProposalID int64 - } -``` - -**State modifications:** -If conditions are met, reimburse the deposit, i.e. -* Increase `AtomBalance` of sender by `deposit` -* Set `deposit` of sender in `DepositorsList` to 0 - -And the associated pseudocode. - -```go - // PSEUDOCODE // - /* Check if TxGovClaimDeposit is valid. If vote never started and MaxDepositPeriod is reached or if vote started and proposal was accepted, return deposit */ - - upon receiving txGovClaimDeposit from sender do - // check if proposal is correctly formatted. Includes fee payment. - - if !correctlyFormatted(txGovClaimDeposit) then - throw - - else - proposal = load(store, Proposals, txGovDeposit.ProposalID) - - if (proposal == nil) then - // There is no proposal for this proposalID - - throw - - else - deposit = load(store, Deposits, :) - - if (deposit == nil) - // sender has not deposited on this proposal - - throw - - else - if (deposit <= 0) - // deposit has already been claimed - - throw - - else - if (proposal.VotingStartBlock <= 0) - // Vote never started - - activeProcedure = load(store, Procedures, ActiveProcedureNumber) - if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) - // MaxDepositPeriod is not reached - - throw - - else - // MaxDepositPeriod is reached - // Set sender's deposit to 0 and refund - - store(Deposits, :, 0) - sender.AtomBalance += deposit - - else - // Vote started - - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) - - if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then - - // Proposal was accepted either because - // Proposal was urgent and special condition was met - // Voting period ended and vote satisfies threshold - - store(Deposits, :, 0) - sender.AtomBalance += deposit -``` - -#### Vote - -Once `ActiveProcedure.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 - type TxGovVote struct { - ProposalID int64 // proposalID of the proposal - Option string // option from OptionSet chosen by the voter - ValidatorPubKey crypto.PubKey // PubKey of the validator voter wants to tie its vote to - } -``` - -**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 - `proposal.Votes['validatorOption']` by sender's `voting power` -* If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` - by sender's `voting power` -* If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by - validator's `InitialVotingPower - minus` (`minus` can be equal to 0) - -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 GovernancePubKey as `ValidatorPubKey` - -Next is a pseudocode proposal of the way `TxGovVote` transactions can be -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) then - throw - - else - proposal = load(store, Proposals, txGovDeposit.ProposalID) - - if (proposal == nil) then - // There is no proposal for this proposalID - - throw - - else - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened - validator = load(store, Validators, txGovVote.ValidatorPubKey) - - if !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 - // ValidatorPubKey is not the GovPubKey of a current validator - - throw - - else - option = load(store, Options, ::) - - if (option != nil) - // sender has already voted with the Atoms bonded to ValidatorPubKey - - throw - - else - if (proposal.VotingStartBlock < 0) OR - (CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR - (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR - (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR - (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then - - // Throws if - // Vote has not started OR if - // Vote had ended OR if - // sender bonded Atoms to ValidatorPubKey after start of vote OR if - // sender unbonded Atoms from ValidatorPubKey after start of vote OR if - // proposal is urgent and special condition is met, i.e. proposal is accepted and closed - - throw - - else - validatorGovInfo = load(store, ValidatorGovInfos, :) - - if (validatorGovInfo == nil) - // validator became validator after proposal entered voting period - - throw - - else - // sender can vote, check if sender == validator and store sender's option in Options - - store(Options, ::, txGovVote.Option) - - if (sender != validator.GovPubKey) - // Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey - - if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then - // check in Staking module - - throw - - else - validatorOption = load(store, Options, :::, validatorGovInfo) - - else - // Validator has already voted - // Reduce votes of option chosen by validator by sender's bonded Amount - - proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey) - - // increase votes of option chosen by sender by bonded Amount - proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey) - - else - // sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey - // i.e. sender == validator - - proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) -``` - -## Future improvements (not in scope for MVP) - -The current documentation only describes the minimum viable product for the -governance module. Future improvements may include: - -* **`BountyProposals`:** If accepted, a `BountyProposal` creates an open - bounty. The `BountyProposal` specifies how many Atoms will be given upon - completion. These Atoms will be taken from the `reserve pool`. After a - `BountyProposal` is accepted by governance, anybody can submit a - `SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a - `BountyProposal` is accepted, the corresponding funds in the `reserve pool` - are locked so that payment can always be honored. In order to link a - `SoftwareUpgradeProposal` to an open bounty, the submitter of the - `SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute. - If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by - governance, the funds that were reserved are automatically transferred to the - submitter. -* **Complex delegation:** Delegators could choose other representatives than - their validators. Ultimately, the chain of representatives would always end - up to a validator, but delegators could inherit the vote of their chosen - representative before they inherit the vote of their validator. In other - words, they would only inherit the vote of their validator if their other - appointed representative did not vote. -* **`ParameterProposals` and `WhitelistProposals`:** These proposals would - automatically change pre-defined parameters and whitelists. Upon acceptance, - these proposals would not require validators to do the signal and switch - process. -* **Better process for proposal review:** There would be two parts to - `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to - reward third party auditors. From aa19612dd0491bc6abb2d6d6f419cfe7228c6b79 Mon Sep 17 00:00:00 2001 From: gamarin Date: Mon, 26 Feb 2018 17:28:57 +0100 Subject: [PATCH 008/182] Improve store description --- docs/spec/governance/state.md | 25 ++++++++++++--------- docs/spec/governance/transactions.md | 33 +++++++++++++++------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index 97622645f..b0633ea83 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -24,10 +24,6 @@ type Procedure struct { } ``` -**Store**: -* `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their - `ProcedureNumber` -* `ActiveProcedureNumber`: returns current procedure number ### Proposals @@ -58,8 +54,12 @@ type ValidatorGovInfo struct { } ``` -**Store:** +### Stores +*Stores are KVStores in the multistore. The key to find the store is the first parameter in the list* + +* `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their + `ProcedureNumber`. First ever procedure is found at index '1'. Index '0' is reserved for parameter `ActiveProcedureNumber` which returns the number of the current procedure. * `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID` * `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by @@ -76,6 +76,10 @@ type ValidatorGovInfo struct { `nil` if proposal has not entered voting period or if `PubKey` was not the governance public key 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: + +* `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 @@ -105,8 +109,8 @@ And the pseudocode for the `ProposalProcessingQueue`: else proposalID = ProposalProcessingQueue.Peek() - proposal = load(store, Proposals, proposalID) - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) + proposal = load(Proposals, proposalID) + initProcedure = load(Procedures, proposal.InitProcedureNumber) if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) @@ -118,15 +122,16 @@ And the pseudocode for the `ProposalProcessingQueue`: else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) - activeProcedure = load(store, Procedures, ActiveProcedureNumber) + activeProcedureNumber = load(Procedures, '0') + activeProcedure = load(Procedures, activeProcedureNumber) for each validator in CurrentBondedValidators - validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey) + validatorGovInfo = load(multistore, ValidatorGovInfos, validator.GovPubKey) if (validatorGovInfo.InitVotingPower != nil) // validator was bonded when vote started - validatorOption = load(store, Options, validator.GovPubKey) + validatorOption = load(Options, validator.GovPubKey) if (validatorOption == nil) // validator did not vote slash validator by activeProcedure.GovernancePenalty diff --git a/docs/spec/governance/transactions.md b/docs/spec/governance/transactions.md index 4c58b0eb6..06e20ffd9 100644 --- a/docs/spec/governance/transactions.md +++ b/docs/spec/governance/transactions.md @@ -13,7 +13,7 @@ type TxGovSubmitProposal struct { Description string // Description of the proposal Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} Category bool // false=regular, true=urgent - InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. + InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. } ``` @@ -61,7 +61,8 @@ upon receiving txGovSubmitProposal from sender do proposal.SubmitBlock = CurrentBlock store(Deposits, :, txGovSubmitProposal.InitialDeposit) - activeProcedure = load(store, Procedures, ActiveProcedureNumber) + activeProcedureNumber = load(Procedures, '0') + activeProcedure = load(Procedures, activeProcedureNumber) if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then // MinDeposit is not reached @@ -127,7 +128,7 @@ upon receiving txGovDeposit from sender do throw else - proposal = load(store, Proposals, txGovDeposit.ProposalID) + proposal = load(Proposals, txGovDeposit.ProposalID) if (proposal == nil) then // There is no proposal for this proposalID @@ -141,7 +142,8 @@ upon receiving txGovDeposit from sender do throw else - activeProcedure = load(store, Procedures, ActiveProcedureNumber) + activeProcedureNumber = load(Procedures, '0') + activeProcedure = load(Procedures, activeProcedureNumber) if (proposal.Deposit >= activeProcedure.MinDeposit) then // MinDeposit was reached @@ -157,7 +159,7 @@ upon receiving txGovDeposit from sender do // sender can deposit sender.AtomBalance -= txGovDeposit.Deposit - deposit = load(store, Deposits, :) + deposit = load(Deposits, :) if (deposit == nil) // sender has never deposited on this proposal @@ -221,7 +223,7 @@ And the associated pseudocode. throw else - proposal = load(store, Proposals, txGovDeposit.ProposalID) + proposal = load(Proposals, txGovDeposit.ProposalID) if (proposal == nil) then // There is no proposal for this proposalID @@ -229,7 +231,7 @@ And the associated pseudocode. throw else - deposit = load(store, Deposits, :) + deposit = load(Deposits, :) if (deposit == nil) // sender has not deposited on this proposal @@ -246,7 +248,8 @@ And the associated pseudocode. if (proposal.VotingStartBlock <= 0) // Vote never started - activeProcedure = load(store, Procedures, ActiveProcedureNumber) + activeProcedureNumber = load(Procedures, '0') + activeProcedure = load(Procedures, ActiveProcedureNumber) if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) // MaxDepositPeriod is not reached @@ -262,7 +265,7 @@ And the associated pseudocode. else // Vote started - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) + initProcedure = load(Procedures, proposal.InitProcedureNumber) if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then @@ -318,7 +321,7 @@ handled: throw else - proposal = load(store, Proposals, txGovDeposit.ProposalID) + proposal = load(Proposals, txGovDeposit.ProposalID) if (proposal == nil) then // There is no proposal for this proposalID @@ -326,8 +329,8 @@ handled: throw else - initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened - validator = load(store, Validators, txGovVote.ValidatorPubKey) + initProcedure = load(Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened + validator = load(Validators, txGovVote.ValidatorPubKey) if !initProcedure.OptionSet.includes(txGovVote.Option) OR (validator == nil) then @@ -339,7 +342,7 @@ handled: throw else - option = load(store, Options, ::) + option = load(Options, ::) if (option != nil) // sender has already voted with the Atoms bonded to ValidatorPubKey @@ -363,7 +366,7 @@ handled: throw else - validatorGovInfo = load(store, ValidatorGovInfos, :) + validatorGovInfo = load(ValidatorGovInfos, :) if (validatorGovInfo == nil) // validator became validator after proposal entered voting period @@ -384,7 +387,7 @@ handled: throw else - validatorOption = load(store, Options, :::: Date: Mon, 26 Feb 2018 18:15:48 +0100 Subject: [PATCH 009/182] From two processes to one process (jae's pick) --- docs/spec/governance/overview.md | 28 +++++----------------------- docs/spec/governance/state.md | 3 +-- docs/spec/governance/transactions.md | 10 ++++------ 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/docs/spec/governance/overview.md b/docs/spec/governance/overview.md index 153b19740..93bb40dee 100644 --- a/docs/spec/governance/overview.md +++ b/docs/spec/governance/overview.md @@ -62,15 +62,6 @@ proposal: `PlainTextProposals`, but actual software upgrades must be performed via `SoftwareUpgradeProposals`. -### Proposal categories - -There are two categories of proposal: -* `Regular` -* `Urgent` - -These two categories are strictly identical except that `Urgent` proposals can -be accepted faster if a certain condition is met. For more information, see -[Threshold](#threshold) section. ## Vote @@ -140,12 +131,8 @@ that proposals are accepted if the proportion of `Yes` votes (excluding proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` votes). -`Urgent` proposals also work with the aforementioned threshold, except there is -another condition that can accelerate the acceptance of the proposal. Namely, -if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, -`UrgentProposal` will be immediately accepted, even if the `Voting period` is -not finished. `InitTotalVotingPower` is the total voting power of all bonded -Atom holders at the moment when the vote opens. +Proposals can be accepted before the end of the voting period if they meet a special condtion. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower`exceeds 2:3, the proposal will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens. +This condition exists so that the network can react quickly in case of urgency. ### Inheritance @@ -154,12 +141,9 @@ If a delegator does not vote, it will inherit its validator vote. * If the delegator votes before its validator, it will not inherit from the validator's vote. * If the delegator votes after its validator, it will override its validator - vote with its own. If the proposal is a `Urgent` proposal, it is possible + vote with its own. If the proposal is urgent, it is possible that the vote will close before delegators have a chance to react and - override their validator's vote. This is not a problem, as `Urgent` proposals - require more than 2/3rd of the total voting power to pass before the end of - the voting period. If more than 2/3rd of validators collude, they can censor - the votes of delegators anyway. + override their validator's vote. This is not a problem, as proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway. ### Validator’s punishment for non-voting @@ -174,9 +158,7 @@ period` is over), then the validator will automatically be partially slashed by *Note: Need to define values for `GovernancePenalty`* -**Exception:** If a proposal is an `Urgent` proposal and is accepted via the -special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` -that exceeds 2:3, validators cannot be punished for not having voted on it. +**Exception:** If a proposal is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2:3, making it mechanically impossible for some validators to vote on it. diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index b0633ea83..1fb2581c2 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -34,7 +34,6 @@ type Proposal struct { Title string // Title of the proposal Description string // Description of the proposal Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Category bool // false=regular, true=urgent Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included @@ -112,7 +111,7 @@ And the pseudocode for the `ProposalProcessingQueue`: proposal = load(Proposals, proposalID) initProcedure = load(Procedures, proposal.InitProcedureNumber) - if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) + if (proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) // proposal was urgent and accepted under the special condition // no punishment diff --git a/docs/spec/governance/transactions.md b/docs/spec/governance/transactions.md index 06e20ffd9..2288d3989 100644 --- a/docs/spec/governance/transactions.md +++ b/docs/spec/governance/transactions.md @@ -12,7 +12,6 @@ type TxGovSubmitProposal struct { Title string // Title of the proposal Description string // Description of the proposal Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} - Category bool // false=regular, true=urgent InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. } ``` @@ -56,7 +55,6 @@ upon receiving txGovSubmitProposal from sender do proposal.Title = txGovSubmitProposal.Title proposal.Description = txGovSubmitProposal.Description proposal.Type = txGovSubmitProposal.Type - proposal.Category = txGovSubmitProposal.Category proposal.Deposit = txGovSubmitProposal.InitialDeposit proposal.SubmitBlock = CurrentBlock @@ -267,10 +265,10 @@ And the associated pseudocode. initProcedure = load(Procedures, proposal.InitProcedureNumber) - if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then + if (proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then // Proposal was accepted either because - // Proposal was urgent and special condition was met + // pecial condition was met OR // Voting period ended and vote satisfies threshold store(Deposits, :, 0) @@ -354,14 +352,14 @@ handled: (CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR - (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then + (proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then // Throws if // Vote has not started OR if // Vote had ended OR if // sender bonded Atoms to ValidatorPubKey after start of vote OR if // sender unbonded Atoms from ValidatorPubKey after start of vote OR if - // proposal is urgent and special condition is met, i.e. proposal is accepted and closed + // special condition is met, i.e. proposal is accepted and closed throw From 38975ac6952ab1b8f6d74f30b7ddb6ecb94f1adc Mon Sep 17 00:00:00 2001 From: gamarin Date: Tue, 27 Feb 2018 16:04:44 +0100 Subject: [PATCH 010/182] Remove map from Proposal struct --- docs/spec/governance/state.md | 5 +++-- docs/spec/governance/transactions.md | 27 ++++++++++++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index 1fb2581c2..ecaadfbed 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -40,7 +40,6 @@ type Proposal struct { 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) InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1) - Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain) } ``` @@ -61,6 +60,7 @@ type ValidatorGovInfo struct { `ProcedureNumber`. First ever procedure is found at index '1'. Index '0' is reserved for parameter `ActiveProcedureNumber` which returns the number of the current procedure. * `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID` +* `Votes`: A mapping `map[[]byte]int64` of votes indexed by `: