Revamp gov spec

This commit is contained in:
gamarin 2018-06-04 17:20:07 +02:00
parent 6bb27c1d99
commit 5735075b05
2 changed files with 122 additions and 233 deletions

View File

@ -11,32 +11,38 @@ has to be created and the previous one rendered inactive.
```go
type VoteType byte
type Vote byte
const (
VoteTypeYes = 0x1
VoteTypeNo = 0x2
VoteTypeNoWithVeto = 0x3
VoteTypeAbstain = 0x4
VoteYes = 0x1
VoteNo = 0x2
VoteNoWithVeto = 0x3
VoteAbstain = 0x4
)
type ProposalType byte
const (
ProposalTypePlainText = 0x1
ProposalTypePlainText = 0x1
ProposalTypeSoftwareUpgrade = 0x2
)
type ProposalStatus byte
const (
ProposalStatusOpen = 0x1 // Proposal is submitted. Participants can deposit on it but not vote
ProposalStatusActive = 0x2 // MinDeposit is reachhed, participants can vote
ProposalStatusAccepted = 0x3 // Proposal has been accepted
ProposalStatusRejected = 0x4 // Proposal has been rejected
)
type Procedure struct {
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
MinDeposit int64 // Minimum deposit for a proposal to enter voting period.
VoteTypes []VoteType // Vote types available to voters.
ProposalTypes []ProposalType // Proposal types available to submitters.
MinDeposit sdk.Coins // Minimum deposit for a proposal to enter voting period.
Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
GovernancePenalty int64 // Penalty if validator does not vote
GovernancePenalty sdk.Rat // Penalty if validator does not vote
IsActive bool // If true, procedure is active. Only one procedure can have isActive true.
}
@ -53,18 +59,17 @@ The current active procedure is stored in a global `params` KVStore.
}
```
### Votes
### ValidatorGovInfo
This type is used in a temp map when tallying
```go
type Votes struct {
Yes int64
No int64
NoWithVeto int64
Abstain int64
type ValidatorGovInfo struct {
Minus sdk.Rat
Vote Vote
}
```
### Proposals
`Proposals` are an item to be voted on.
@ -77,37 +82,34 @@ type Proposal struct {
TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit
Deposits []Deposit // List of deposits on the proposal
SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included
Submitter crypto.Address // Address of the submitter
Submitter sdk.Address // Address of the submitter
VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0)
InitProcedure Procedure // Active Procedure when proposal enters voting period
CurrentStatus ProposalStatus // Current status of the proposal
Votes Votes // Total votes for each option
YesVotes sdk.Rat
NoVotes sdk.Rat
NoWithVetoVotes sdk.Rat
AbstainVotes sdk.Rat
}
```
We also introduce a type `ValidatorGovInfo`
We also mention a method to update the tally for a given proposal:
```go
type ValidatorGovInfo struct {
InitVotingPower int64 // Voting power of validator when proposal enters voting period
Minus int64 // Minus of validator, used to compute validator's voting power
}
func (proposal Proposal) updateTally(vote byte, amount sdk.Rat)
```
### Stores
*Stores are KVStores in the multistore. The key to find the store is the first parameter in the list*
*Stores are KVStores in the multistore. The key to find the store is the first parameter in the list*`
We will use one KVStore `Governance` to store two mappings:
* A mapping from `proposalID` to `Proposal`
* A mapping from `proposalID:addresses:address` to `Vote`. This mapping allows us to query all addresses that voted on the proposal along with their vote by doing a range query on `proposalID:addresses`
* `Proposals: int64 => Proposal` maps `proposalID` to the `Proposal`
`proposalID`
* `Options: <proposalID | voterAddress | validatorAddress> => VoteType`: maps to the vote of the `voterAddress` for `proposalID` re its delegation to `validatorAddress`.
Returns 0x0 If `voterAddress` has not voted under this validator.
* `ValidatorGovInfos: <proposalID | validatorAddress> => ValidatorGovInfo`: maps to the gov info for the `validatorAddress` and `proposalID`.
Returns `nil` if proposal has not entered voting period or if `address` was not the
address of a validator when proposal entered voting period.
For pseudocode purposes, here are the two function we will use to read or write in stores:
@ -121,16 +123,14 @@ For pseudocode purposes, here are the two function we will use to read or write
`ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest
element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if
`CurrentBlock == VotingStartBlock + InitProcedure.VotingPeriod`. If it is,
then the application checks if validators in `InitVotingPowerList` have voted
then the application tallies the votes, compute the votes of each validator and checks if every validator in the valdiator set have voted
and, if not, applies `GovernancePenalty`. If the proposal is accepted, deposits are refunded.
After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated.
Note that if a proposal is accepted under the special condition,
its `ProposalID` must be ejected from `ProposalProcessingQueue`.
And the pseudocode for the `ProposalProcessingQueue`:
```go
in BeginBlock do
in EndBlock do
checkProposal() // First call of the recursive function
@ -141,58 +141,55 @@ And the pseudocode for the `ProposalProcessingQueue`:
if (proposalID == nil)
return
proposal = load(Proposals, proposalID)
proposal = load(Governance, proposalID)
if (proposal.Votes.YesVotes/proposal.InitTotalVotingPower > 2/3)
if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod && proposal.CurrentStatus == ProposalStatusActive)
// proposal accepted early by super-majority
// no punishments; refund deposits
// End of voting period, tally
ProposalProcessingQueue.pop()
var newDeposits []Deposits
validators = stakeKeeper.getAllValidators()
tmpValMap := map(sdk.Address)ValidatorGovInfo
// XXX: why do we need to reset deposits? cant we just clear it ?
for each (amount, depositer) in proposal.Deposits
newDeposits.append[{0, depositer}]
depositer.AtomBalance += amount
// Initiate mapping at 0. Validators that remain at 0 at the end of tally will be punished
for each validator in validators
tmpValMap(validator).Minus = 0
voterIterator = rangeQuery(Governance, <proposalID|addresses>) //return all the addresses that voted on the proposal
proposal.Deposits = newDeposits
store(Proposals, proposalID, proposal)
// Tally
for each (voterAddress, vote) in voterIterator
delegations = stakeKeeper.getDelegations(voterAddress) // get all delegations for current voter
checkProposal()
for each delegation in delegations
tmpValMap(delegation.ValidatorAddr).Minus += delegation.Shares
proposal.updateTally(vote, delegation.Shares)
else if (CurrentBlock == proposal.VotingStartBlock + proposal.Procedure.VotingPeriod)
_, isVal = stakeKeeper.getValidator(voterAddress)
if (isVal)
tmpValMap(voterAddress).Vote = vote
ProposalProcessingQueue.pop()
activeProcedure = load(params, 'ActiveProcedure')
for each validator in CurrentBondedValidators
validatorGovInfo = load(ValidatorGovInfos, <proposalID | validator.Address>)
if (validatorGovInfo.InitVotingPower != nil)
// validator was bonded when vote started
validatorOption = load(Options, <proposalID | validator.Address>)
if (validatorOption == nil)
// validator did not vote
slash validator by activeProcedure.GovernancePenalty
totalNonAbstain = proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes
if( proposal.Votes.YesVotes/totalNonAbstain > 0.5 AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < 1/3)
// Slash validators that did not vote, or update tally if they voted
for each validator in validators
if (!tmpValMap(validator).HasVoted)
slash validator by proposal.Procedure.GovernancePenalty
else
proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus))
// Check if proposal is accepted or rejected
totalNonAbstain := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes
if (proposal.Votes.YesVotes/totalNonAbstain > proposal.InitProcedure.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < proposal.InitProcedure.Veto)
// proposal was accepted at the end of the voting period
// refund deposits (non-voters already punished)
var newDeposits []Deposits
proposal.CurrentStatus = ProposalStatusAccepted
for each (amount, depositer) in proposal.Deposits
newDeposits.append[{0, depositer}]
depositer.AtomBalance += amount
proposal.Deposits = newDeposits
store(Proposals, proposalID, proposal)
else
// proposal was rejected
proposal.CurrentStatus = ProposalStatusRejected
checkProposal()
store(Governance, proposalID, proposal)
checkProposal()
```

View File

@ -12,7 +12,7 @@ type TxGovSubmitProposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type ProposalType // Type of proposal
InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive.
InitialDeposit sdk.Coins // Initial deposit paid by sender. Must be strictly positive.
}
```
@ -22,8 +22,7 @@ type TxGovSubmitProposal struct {
* Initialise `Proposals` attributes
* Decrease balance of sender by `InitialDeposit`
* If `MinDeposit` is reached:
* Push `proposalID` in `ProposalProcessingQueueEnd`
* Store each validator's voting power in `ValidatorGovInfos`
* Push `proposalID` in `ProposalProcessingQueue`
A `TxGovSubmitProposal` transaction can be handled according to the following
pseudocode.
@ -34,16 +33,16 @@ pseudocode.
upon receiving txGovSubmitProposal from sender do
if !correctlyFormatted(txGovSubmitProposal) then
if !correctlyFormatted(txGovSubmitProposal)
// check if proposal is correctly formatted. Includes fee payment.
throw
initialDeposit = txGovSubmitProposal.InitialDeposit
if (initialDeposit <= 0) OR (sender.AtomBalance < initialDeposit) then
if (initialDeposit.Atoms <= 0) OR (sender.AtomBalance < initialDeposit.Atoms)
// InitialDeposit is negative or null OR sender has insufficient funds
throw
sender.AtomBalance -= initialDeposit
sender.AtomBalance -= initialDeposit.Atoms
proposalID = generate new proposalID
proposal = NewProposal()
@ -55,35 +54,25 @@ upon receiving txGovSubmitProposal from sender do
proposal.SubmitBlock = CurrentBlock
proposal.Deposits.append({initialDeposit, sender})
proposal.Submitter = sender
proposal.Votes.Yes = 0
proposal.Votes.No = 0
proposal.Votes.NoWithVeto = 0
proposal.Votes.Abstain = 0
proposal.YesVotes = 0
proposal.NoVotes = 0
proposal.NoWithVetoVotes = 0
proposal.AbstainVotes = 0
activeProcedure = load(params, 'ActiveProcedure')
if (initialDeposit < activeProcedure.MinDeposit) then
if (initialDeposit < activeProcedure.MinDeposit)
// MinDeposit is not reached
proposal.CurrentStatus = ProposalStatusOpen
proposal.VotingStartBlock = -1
proposal.InitTotalVotingPower = 0
else
// MinDeposit is reached
proposal.CurrentStatus = ProposalStatusActive
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = new ValidatorGovInfo
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID | validator.Address>, validatorGovInfo)
ProposalProcessingQueue.push(proposalID)
store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping
@ -98,8 +87,8 @@ Once a proposal is submitted, if
```go
type TxGovDeposit struct {
ProposalID int64 // ID of the proposal
Deposit int64 // Number of Atoms to add to the proposal's deposit
ProposalID int64 // ID of the proposal
Deposit sdk.Coins // Number of Atoms to add to the proposal's deposit
}
```
@ -109,7 +98,6 @@ type TxGovDeposit struct {
* Increase `proposal.TotalDeposit` by sender's `deposit`
* If `MinDeposit` is reached:
* Push `proposalID` in `ProposalProcessingQueueEnd`
* Store each validator's voting power in `ValidatorGovInfos`
A `TxGovDeposit` transaction has to go through a number of checks to be valid.
These checks are outlined in the following pseudocode.
@ -121,57 +109,40 @@ These checks are outlined in the following pseudocode.
upon receiving txGovDeposit from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit) then
if !correctlyFormatted(txGovDeposit)
throw
proposal = load(Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
if (proposal == nil)
// There is no proposal for this proposalID
throw
if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit)
// deposit is negative or null OR sender has insufficient funds
throw
activeProcedure = load(params, 'ActiveProcedure')
if (txGovDeposit.Deposit.Atoms <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit.Atoms) OR (proposal.TotalDeposit >= activeProcedure.MinDeposit) OR (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod)
// deposit is negative or null
// OR sender has insufficient funds
// OR minDeposit has already been reached
// OR Maximum deposit period reached
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
// MinDeposit was reached
// TODO: shouldnt we do something here ?
throw
else
if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then
// Maximum deposit period reached
throw
// sender can deposit
sender.AtomBalance -= txGovDeposit.Deposit.Atoms
proposal.Deposits.append({txGovVote.Deposit, sender})
proposal.TotalDeposit.Plus(txGovDeposit.Deposit)
if (proposal.TotalDeposit >= activeProcedure.MinDeposit)
// MinDeposit is reached, vote opens
// sender can deposit
sender.AtomBalance -= txGovDeposit.Deposit
proposal.VotingStartBlock = CurrentBlock
proposal.CurrentStatus = ProposalStatusActive
proposal.InitProcedure = activeProcedure
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
proposal.Deposits.append({txGovVote.Deposit, sender})
proposal.TotalDeposit += txGovDeposit.Deposit
if (proposal.TotalDeposit >= activeProcedure.MinDeposit) then
// MinDeposit is reached, vote opens
proposal.VotingStartBlock = CurrentBlock
proposal.InitTotalVotingPower = TotalVotingPower
proposal.InitProcedure = activeProcedure
for each validator in CurrentBondedValidators
// Store voting power of each bonded validator
validatorGovInfo = NewValidatorGovInfo()
validatorGovInfo.InitVotingPower = validator.VotingPower
validatorGovInfo.Minus = 0
store(ValidatorGovInfos, <proposalID | validator.Address>, validatorGovInfo)
ProposalProcessingQueue.push(txGovDeposit.ProposalID)
store(Proposals, txGovVote.ProposalID, proposal)
store(Proposals, txGovVote.ProposalID, proposal)
```
### Vote
@ -183,26 +154,14 @@ vote on the proposal.
```go
type TxGovVote struct {
ProposalID int64 // proposalID of the proposal
Option string // option from OptionSet chosen by the voter
ValidatorAddress crypto.address // Address of the validator voter wants to tie its vote to
Vote byte // option from OptionSet chosen by the voter
}
```
**State modifications:**
* If sender is not a validator and validator has not voted, initialize or
increase minus of validator by sender's `voting power`
* If sender is not a validator and validator has voted, decrease
votes of `validatorOption` by sender's `voting power`
* If sender is not a validator, increase votes of `txGovVote.Option`
by sender's `voting power`
* If sender is a validator, increase votes of `txGovVote.Option` by
validator's `InitVotingPower - minus` (`minus` can be equal to 0)
* Record `Vote` of sender
Votes need to be tied to a validator in order to compute validator's voting
power. If a delegator is bonded to multiple validators, it will have to send
one transaction per validator (the UI should facilitate this so that multiple
transactions can be sent in one "vote flow"). If the sender is the validator
itself, then it will input its own address as `ValidatorAddress`
*Note: Gas cost for this message has to take into account the future tallying of the vote in EndBlocker*
Next is a pseudocode proposal of the way `TxGovVote` transactions are
handled:
@ -214,91 +173,24 @@ handled:
upon receiving txGovVote from sender do
// check if proposal is correctly formatted. Includes fee payment.
if !correctlyFormatted(txGovDeposit) then
if !correctlyFormatted(txGovDeposit)
throw
proposal = load(Proposals, txGovDeposit.ProposalID)
if (proposal == nil) then
if (proposal == nil)
// There is no proposal for this proposalID
throw
validator = load(CurrentValidators, txGovVote.ValidatorAddress)
if (proposal.VotingStartBlock >= 0) AND
(CurrentBlock <= proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod)
// Sender can vote if
// Vote has started AND if
// Vote had notended
store(Governance, <txGovVote.ProposalID|addresses|sender>, txGovVote.Vote) // Voters can vote multiple times. Re-voting overrides previous vote. This is ok because tallying is done once at the end.
if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR
(validator == nil) then
// Throws if
// Option is not in Option Set of procedure that was active when vote opened OR if
// ValidatorAddress is not the address of a current validator
throw
option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
if (option != nil)
// sender has already voted with the Atoms bonded to ValidatorAddress
throw
if (proposal.VotingStartBlock < 0) OR
(CurrentBlock > proposal.VotingStartBlock + proposal.InitProcedure.VotingPeriod) OR
(proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorAddress) OR
(proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.Address) OR
(proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3) then
// Throws if
// Vote has not started OR if
// Vote had ended OR if
// sender bonded Atoms to ValidatorAddress after start of vote OR if
// sender unbonded Atoms from ValidatorAddress after start of vote OR if
// special condition is met, i.e. proposal is accepted and closed
throw
validatorGovInfo = load(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>)
if (validatorGovInfo == nil)
// validator became validator after proposal entered voting period
throw
// sender can vote, check if sender == validator and store sender's option in Options
store(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>, txGovVote.Option)
if (sender != validator.address)
// Here, sender is not the Address of the validator whose Address is txGovVote.ValidatorAddress
if sender does not have bonded Atoms to txGovVote.ValidatorAddress then
// check in Staking module
throw
validatorOption = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
if (validatorOption == nil)
// Validator has not voted already
validatorGovInfo.Minus += sender.bondedAmounTo(txGovVote.ValidatorAddress)
store(ValidatorGovInfos, <txGovVote.ProposalID>:<validator.ValidatorAddress>, validatorGovInfo)
else
// Validator has already voted
// Reduce votes of option chosen by validator by sender's bonded Amount
proposal.Votes.validatorOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
// increase votes of option chosen by sender by bonded Amount
senderOption = txGovVote.Option
propoal.Votes.senderOption -= sender.bondedAmountTo(txGovVote.ValidatorAddress)
store(Proposals, txGovVote.ProposalID, proposal)
else
// sender is the address of the validator whose main Address is txGovVote.ValidatorAddress
// i.e. sender == validator
proposal.Votes.validatorOption += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus)
store(Proposals, txGovVote.ProposalID, proposal)
```